forked from hkust-taco/mlscript
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathMain.scala
244 lines (218 loc) · 8.66 KB
/
Main.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
import scala.util.Try
import scala.scalajs.js.annotation.JSExportTopLevel
import org.scalajs.dom
import org.scalajs.dom.document
import org.scalajs.dom.raw.{Event, TextEvent, UIEvent, HTMLTextAreaElement}
import mlscript.utils._
import mlscript._
import mlscript.utils.shorthands._
import scala.util.matching.Regex
import scala.scalajs.js
import scala.collection.immutable
object Main {
def main(args: Array[String]): Unit = {
val source = document.querySelector("#mlscript-input")
update(source.textContent)
source.addEventListener("input", typecheck)
}
@JSExportTopLevel("typecheck")
def typecheck(e: dom.UIEvent): Unit = {
e.target match {
case elt: dom.HTMLTextAreaElement =>
update(elt.value)
}
}
@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf"))
def update(str: String): Unit = {
// println(s"Input: $str")
val target = document.querySelector("#mlscript-output")
def underline(fragment: Str): Str =
s"<u style=\"text-decoration: #E74C3C dashed underline\">$fragment</u>"
var totalTypeErrors = 0
var totalWarnings = 0
var outputMarker = ""
val blockLineNum = 0
val showRelativeLineNums = false
def report(diag: Diagnostic): Str = {
var sb = new collection.mutable.StringBuilder
def output(s: Str): Unit = {
sb ++= outputMarker
sb ++= s
sb ++= htmlLineBreak
()
}
val sctx = Message.mkCtx(diag.allMsgs.iterator.map(_._1), "?")
val headStr = diag match {
case ErrorReport(msg, loco, src) =>
totalTypeErrors += 1
s"╔══ <strong style=\"color: #E74C3C\">[ERROR]</strong> "
case WarningReport(msg, loco, src) =>
totalWarnings += 1
s"╔══ <strong style=\"color: #F39C12\">[WARNING]</strong> "
}
val lastMsgNum = diag.allMsgs.size - 1
var globalLineNum =
blockLineNum // solely used for reporting useful test failure messages
diag.allMsgs.zipWithIndex.foreach { case ((msg, loco), msgNum) =>
val isLast = msgNum =:= lastMsgNum
val msgStr = msg.showIn(sctx)
if (msgNum =:= 0)
output(headStr + msgStr)
else
output(s"${if (isLast && loco.isEmpty) "╙──" else "╟──"} ${msgStr}")
if (loco.isEmpty && diag.allMsgs.size =:= 1) output("╙──")
loco.foreach { loc =>
val (startLineNum, startLineStr, startLineCol) =
loc.origin.fph.getLineColAt(loc.spanStart)
if (globalLineNum =:= 0) globalLineNum += startLineNum - 1
val (endLineNum, endLineStr, endLineCol) =
loc.origin.fph.getLineColAt(loc.spanEnd)
var l = startLineNum
var c = startLineCol // c starts from 1
while (l <= endLineNum) {
val globalLineNum = loc.origin.startLineNum + l - 1
val relativeLineNum = globalLineNum - blockLineNum + 1
val shownLineNum =
if (showRelativeLineNums && relativeLineNum > 0)
s"l.+$relativeLineNum"
else "l." + globalLineNum
val prepre = "║ "
val pre = s"$shownLineNum: " // Looks like l.\d+
val curLine = loc.origin.fph.lines(l - 1)
val lastCol =
if (l =:= endLineNum) endLineCol else curLine.length + 1
val front = curLine.slice(0, c - 1)
val middle = underline(curLine.slice(c - 1, lastCol - 1))
val back = curLine.slice(lastCol - 1, curLine.size)
output(s"$prepre$pre\t$front$middle$back")
c = 1
l += 1
if (isLast) output("╙──")
}
}
}
if (diag.allMsgs.isEmpty) output("╙──")
sb.toString
}
val tryRes = try {
import fastparse._
import fastparse.Parsed.{Success, Failure}
import mlscript.{NewParser, ErrorReport, Origin}
val lines = str.splitSane('\n').toIndexedSeq
val processedBlock = lines.mkString
val fph = new mlscript.FastParseHelpers(str, lines)
val origin = Origin("<input>", 1, fph)
val lexer = new NewLexer(origin, throw _, dbg = false)
val tokens = lexer.bracketedTokens
val parser = new NewParser(origin, tokens, throw _, dbg = false, N) {
def doPrintDbg(msg: => Str): Unit = if (dbg) println(msg)
}
parser.parseAll(parser.typingUnit) match {
case tu =>
val pgrm = Pgrm(tu.entities)
println(s"Parsed: $pgrm")
val typer = new mlscript.Typer(
dbg = false,
verbose = false,
explainErrors = false
) {
newDefs = true
}
import typer._
implicit val raise: Raise = throw _
implicit var ctx: Ctx = Ctx.init
implicit val extrCtx: Opt[typer.ExtrCtx] = N
val vars: Map[Str, typer.SimpleType] = Map.empty
val tpd = typer.typeTypingUnit(tu, topLevel = true)(ctx.nest, raise, vars)
object SimplifyPipeline extends typer.SimplifyPipeline {
def debugOutput(msg: => Str): Unit =
// if (mode.dbgSimplif) output(msg)
println(msg)
}
val sim = SimplifyPipeline(tpd, all = false)(ctx)
val exp = typer.expandType(sim)(ctx)
val expStr = exp.showIn(ShowCtx.mk(exp :: Nil), 0).stripSuffix("\n")
.replaceAll(" ", " ")
.replaceAll("\n", "<br/>")
// TODO format HTML better
val typingStr = """<div><table width="100%">
| <tr>
| <td colspan="2"><h4><i>Typing Results:</i></h4></td>
| </tr>
|""".stripMargin +
s"""<tr>
| ${s"<td colspan=\"2\">${expStr}</td>"}
|</tr>
|""".stripMargin
val backend = new JSWebBackend()
val (lines, resNames) = backend(pgrm, true)
val code = lines.mkString("\n")
// TODO: add a toggle button to show js code
// val jsStr = ("\n\n=====================JavaScript Code=====================\n" + code)
// .stripSuffix("\n")
// .replaceAll(" ", " ")
// .replaceAll("\n", "<br/>")
val exe = executeCode(code) match {
case Left(err) => err
case Right(lines) => generateResultTable(resNames.zip(lines))
}
val resStr = ("""<tr>
| <td colspan="2"><h4><i>Execution Results:</i></h4></td>
|</tr>
|""".stripMargin + exe + "</table>")
typingStr + resStr
}
} catch {
// case err: ErrorReport =>
case err: Diagnostic =>
report(err)
case err: Throwable =>
s"""
<font color="Red">
Unexpected error: ${err}${
err.printStackTrace
// err.getStackTrace().map(s"$htmlLineBreak$htmlWhiteSpace$htmlWhiteSpace at " + _).mkString
""
}</font>"""
}
target.innerHTML = tryRes
}
// Execute the generated code.
// We extract this function because there is some boilerplate code.
// It returns a tuple of three items:
// 1. results of definitions;
// 2. results of expressions;
// 3. error message (if has).
@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf"))
private def executeCode(code: Str): Either[Str, Ls[Str]] = {
try {
R(js.eval(code).asInstanceOf[js.Array[Str]].toList)
} catch {
case e: Throwable =>
val errorBuilder = new StringBuilder()
errorBuilder ++= "<font color=\"red\">Runtime error occurred:</font>"
errorBuilder ++= htmlLineBreak + e.getMessage
errorBuilder ++= htmlLineBreak
errorBuilder ++= htmlLineBreak
L(errorBuilder.toString)
}
}
private def generateResultTable(res: Ls[(Str, Str)]): Str = {
val htmlBuilder = new StringBuilder
htmlBuilder ++= """<tr>
| <td>Name</td>
| <td>Value</td>
|</tr>
|""".stripMargin
res.foreach(value => {
htmlBuilder ++= s"""<tr>
| <td class="name">${value._1.replaceAll(" ", " ").replaceAll("\n", "<br/>")}</td>
| ${s"<td>${value._2.replaceAll(" ", " ").replaceAll("\n", "<br/>")}</td>"}
|</tr>
|""".stripMargin
})
htmlBuilder.toString
}
private val htmlLineBreak = "<br />"
private val htmlWhiteSpace = " "
}