/
Model.scala
286 lines (254 loc) · 9.06 KB
/
Model.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
/**
* Various common, "dumb" data-structures that represent common things that
* are passed around inside Ammonite
*/
package ammonite.util
import java.io.PrintStream
import ammonite.ops.Path
import scala.reflect.NameTransformer
import scala.reflect.runtime.universe.TypeTag
/**
* Information about a particular predef file or snippet. [[hardcoded]]
* represents whether or not we cache the snippet forever regardless of
* classpath, which is true for many "internal" predefs which only do
* imports from Ammonite's own packages and don't rely on external code
*/
case class PredefInfo(name: Name, code: String, hardcoded: Boolean, path: Option[Path])
/**
* Exception for reporting script compilation failures
*/
class CompilationError(message: String) extends Exception(message)
/**
* A unique key for a piece of code that gets run in a particular environment;
* contains the hash of the code and the hash of the environment stored
* separately, so you can e.g. compare the [[env]] hash even if you don't have
* the code available
*/
case class Tag(code: String, env: String){
def combined = code + env
}
object Tag{
implicit def rw: upickle.default.ReadWriter[Tag] = upickle.default.macroRW
}
/**
* Represents a single identifier in Scala source code, e.g. "scala" or
* "println" or "`Hello-World`".
*
* Holds the value "raw", with all special characters intact, e.g.
* "Hello-World". Can be used [[backticked]] e.g. "`Hello-World`", useful for
* embedding in Scala source code, or [[encoded]] e.g. "Hello$minusWorld",
* useful for accessing names as-seen-from the Java/JVM side of thigns
*/
case class Name(raw: String){
assert(
NameTransformer.decode(raw) == raw,
"Name() must be created with un-encoded text"
)
assert(raw.charAt(0) != '`', "Cannot create already-backticked identifiers")
override def toString = s"Name($backticked)"
def encoded = NameTransformer.encode(raw)
def backticked = Name.backtickWrap(raw)
}
object Name{
/**
* Read/write [[Name]]s as unboxed strings, in order to save verbosity
* in the JSON cache files as well as improving performance of
* reading/writing since we read/write [[Name]]s a *lot*.
*/
implicit val nameRW: upickle.default.ReadWriter[Name] =
upickle.default.readwriter[String].bimap[Name](
name => name.raw,
raw => Name(raw)
)
val alphaKeywords = Set(
"abstract", "case", "catch", "class", "def", "do", "else",
"extends", "false", "finally", "final", "finally", "forSome",
"for", "if", "implicit", "import", "lazy", "match", "new",
"null", "object", "override", "package", "private", "protected",
"return", "sealed", "super", "this", "throw", "trait", "try",
"true", "type", "val", "var", "while", "with", "yield", "_", "macro"
)
val symbolKeywords = Set(
":", ";", "=>", "=", "<-", "<:", "<%", ">:", "#", "@", "\u21d2", "\u2190"
)
val blockCommentStart = "/*"
val lineCommentStart = "//"
/**
* Custom implementation of ID parsing, instead of using the ScalaParse
* version. This lets us avoid loading FastParse and ScalaParse entirely if
* we're running a cached script, which shaves off 200-300ms of startup time.
*/
def backtickWrap(s: String) = {
if (s.isEmpty) "``"
else if (s(0) == '`' && s.last == '`') s
else {
val chunks = s.split("_", -1)
def validOperator(c: Char) = {
c.getType == Character.MATH_SYMBOL ||
c.getType == Character.OTHER_SYMBOL ||
"!#%&*+-/:<=>?@\\^|~".contains(c)
}
val validChunks = chunks.zipWithIndex.forall { case (chunk, index) =>
chunk.forall(c => c.isLetter || c.isDigit || c == '$') ||
(
chunk.forall(validOperator) &&
// operators can only come last
index == chunks.length - 1 &&
// but cannot be preceded by only a _
!(chunks.lift(index - 1).exists(_ == "") && index - 1== 0))
}
val firstLetterValid = s(0).isLetter || s(0) == '_' || s(0) == '$' || validOperator(s(0))
val valid =
validChunks &&
firstLetterValid &&
!alphaKeywords.contains(s) &&
!symbolKeywords.contains(s) &&
!s.contains(blockCommentStart) &&
!s.contains(lineCommentStart)
if (valid) s else '`' + s + '`'
}
}
}
/**
* Encapsulates a read-write cell that can be passed around
*/
trait StableRef[T]{
/**
* Get the current value of the this [[StableRef]] at this instant in time
*/
def apply(): T
/**
* Set the value of this [[StableRef]] to always be the value `t`
*/
def update(t: T): Unit
}
trait Ref[T] extends StableRef[T]{
/**
* Return a function that can be used to get the value of this [[Ref]]
* at any point in time
*/
def live(): () => T
/**
* Set the value of this [[Ref]] to always be the value of the by-name
* argument `t`, at any point in time
*/
def bind(t: => T): Unit
}
object Ref{
implicit def refer[T](t: T): Ref[T] = Ref(t)
def live[T](value0: () => T) = new Ref[T]{
var value: () => T = value0
def live() = value
def apply() = value()
def update(t: T) = value = () => t
def bind(t: => T): Unit = value = () => t
override def toString = s"Ref($value)"
}
def apply[T](value0: T) = live(() => value0)
}
/**
* Nice pattern matching for chained exceptions
*/
object Ex{
def unapplySeq(t: Throwable): Option[Seq[Throwable]] = {
def rec(t: Throwable): List[Throwable] = {
t match {
case null => Nil
case t => t :: rec(t.getCause)
}
}
Some(rec(t))
}
}
trait CodeColors{
def `type`: fansi.Attrs
def literal: fansi.Attrs
def comment: fansi.Attrs
def keyword: fansi.Attrs
}
/**
* A set of colors used to highlight the miscellanious bits of the REPL.
* Re-used all over the place in PPrint, TPrint, syntax highlighting,
* command-echoes, etc. in order to keep things consistent
*
* @param prompt The command prompt
* @param ident Definition of top-level identifiers
* @param `type` Definition of types
* @param literal Strings, integers and other literal expressions
* @param prefix The Seq/Foo when printing a Seq(...) or case class Foo(...)
* @param selected The color of text selected in the line-editor
* @param error The color used to print error messages of all kinds
*/
case class Colors(prompt: Ref[fansi.Attrs],
ident: Ref[fansi.Attrs],
`type`: Ref[fansi.Attrs],
literal: Ref[fansi.Attrs],
prefix: Ref[fansi.Attrs],
comment: Ref[fansi.Attrs],
keyword: Ref[fansi.Attrs],
selected: Ref[fansi.Attrs],
error: Ref[fansi.Attrs],
warning: Ref[fansi.Attrs],
info: Ref[fansi.Attrs])
object Colors{
def Default = Colors(
fansi.Color.Magenta,
fansi.Color.Cyan,
fansi.Color.Green,
fansi.Color.Green,
fansi.Color.Yellow,
fansi.Color.Blue,
fansi.Color.Yellow,
fansi.Reversed.On,
fansi.Color.Red,
fansi.Color.Yellow,
fansi.Color.Blue
)
def BlackWhite = Colors(
fansi.Attrs.Empty, fansi.Attrs.Empty, fansi.Attrs.Empty, fansi.Attrs.Empty,
fansi.Attrs.Empty, fansi.Attrs.Empty, fansi.Attrs.Empty, fansi.Attrs.Empty,
fansi.Attrs.Empty, fansi.Attrs.Empty, fansi.Attrs.Empty
)
}
/**
* Models a binding of a value to a typed name, and is passed into the
* REPL so it can re-create the bindings inside the REPL's scope
*/
case class Bind[T](name: String, value: T)
(implicit val typeTag: scala.reflect.runtime.universe.TypeTag[T])
object Bind{
implicit def ammoniteReplArrowBinder[T](t: (String, T))(implicit typeTag: TypeTag[T]) = {
Bind(t._1, t._2)(typeTag)
}
}
/**
* Encapsulates the ways the Ammonite REPL prints things. Does not print
* a trailing newline by default; you have to add one yourself.
*
* @param outStream Direct access to print to stdout
* @param errStream Direct access to print to stderr
* @param resultStream Direct access to print the result of the entered code
* @param warning How you want it to print a compile warning
* @param error How you want it to print a compile error
* @param info How you want to print compile info logging. *Not* the same
* as `out`, which is used to print runtime output.
*/
case class Printer(outStream: PrintStream,
errStream: PrintStream,
resultStream: PrintStream,
warning: String => Unit,
error: String => Unit,
info: String => Unit)
case class ImportTree(prefix: Seq[String],
mappings: Option[ImportTree.ImportMapping],
start: Int,
end: Int)
object ImportTree{
implicit def rw: upickle.default.ReadWriter[ImportTree] = upickle.default.macroRW
type ImportMapping = Seq[(String, Option[String])]
}
case class PredefFailedToLoad(msg: String,
cause: Option[Throwable],
res: Res.Failing,
watchedFilePaths: Seq[(Path, Long)])
extends Exception(msg, cause.orNull)