Skip to content

Commit 76b129a

Browse files
authored
Add trace functionality (#244)
1 parent 9986302 commit 76b129a

File tree

13 files changed

+415
-9
lines changed

13 files changed

+415
-9
lines changed

hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker:
2222
val showRepl = NullaryCommand("showRepl")
2323
val silent = NullaryCommand("silent")
2424
val noSanityCheck = NullaryCommand("noSanityCheck")
25+
val traceJS = NullaryCommand("traceJS")
2526
val expect = Command("expect"): ln =>
2627
ln.trim
2728

@@ -50,7 +51,9 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker:
5051
super.processTerm(blk, inImport)
5152
if js.isSet then
5253
val low = ltl.givenIn:
53-
new codegen.Lowering with codegen.LoweringSelSanityChecks(noSanityCheck.isUnset)
54+
new codegen.Lowering
55+
with codegen.LoweringSelSanityChecks(noSanityCheck.isUnset)
56+
with codegen.LoweringTraceLog(traceJS.isSet)
5457
given Elaborator.Ctx = curCtx
5558
val jsb = new JSBuilder
5659
with JSBuilderArgNumSanityChecks(noSanityCheck.isUnset)
@@ -95,7 +98,11 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker:
9598
case _ => output(s"$prefix= ${content}")
9699
case ReplHost.Empty =>
97100
case ReplHost.Unexecuted(message) => ???
98-
case ReplHost.Error(isSyntaxError, message) =>
101+
case ReplHost.Error(isSyntaxError, message, otherOutputs) =>
102+
if otherOutputs.nonEmpty then
103+
otherOutputs.splitSane('\n').foreach: line =>
104+
output(s"> ${line}")
105+
99106
if (isSyntaxError) then
100107
// If there is a syntax error in the generated code,
101108
// it should be a code generation error.
@@ -107,8 +114,17 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker:
107114
source = Diagnostic.Source.Runtime))
108115
if stderr.nonEmpty then output(s"// Standard Error:\n${stderr}")
109116

117+
118+
if traceJS.isSet then
119+
host.execute(
120+
"globalThis.Predef.TraceLogger.enabled = true; " +
121+
"globalThis.Predef.TraceLogger.resetIndent(0)")
122+
110123
mkQuery("", jsStr)
111124

125+
if traceJS.isSet then
126+
host.execute("globalThis.Predef.TraceLogger.enabled = false")
127+
112128
import Elaborator.Ctx.*
113129
def definedValues = curCtx.env.iterator.flatMap:
114130
case (nme, e @ (_: RefElem | SelElem(RefElem(_: InnerSymbol), _, _))) =>

hkmc2/jvm/src/test/scala/hkmc2/ReplHost.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ class ReplHost(rootPath: Str)(using TL) {
4848
case None => reply
4949
case Some(uncaughtErrorLine) => {
5050
val message = uncaughtErrorLine.substring(ReplHost.uncaughtErrorHead.length)
51-
ReplHost.Error(false, message)
51+
ReplHost.Error(false, message, reply.take(reply.indexOf(uncaughtErrorLine)).trim())
5252
}
5353
}
5454
case Some(syntaxErrorLine) =>
5555
val message = syntaxErrorLine.substring(ReplHost.syntaxErrorHead.length)
56-
ReplHost.Error(true, message)
56+
ReplHost.Error(true, message, reply.take(reply.indexOf(syntaxErrorLine)).trim())
5757
}
5858
tl.log(s"REPL> Collected:\n${res}")
5959
res
@@ -85,7 +85,7 @@ class ReplHost(rootPath: Str)(using TL) {
8585
if begin >= 0 && end >= 0 then
8686
// `console.log` inserts a space between every two arguments,
8787
// so + 1 and - 1 is necessary to get correct length.
88-
ReplHost.Error(false, reply.substring(begin + 1, end))
88+
ReplHost.Error(false, reply.substring(begin + 1, end), reply.takeWhile(_ != 0x200b).trim())
8989
else reply
9090
case error: ReplHost.Error => error
9191
tl.log(s"REPL> Parsed:\n${parsed}")
@@ -194,7 +194,7 @@ object ReplHost {
194194
* runtime error
195195
* @param message the error message
196196
*/
197-
final case class Error(syntax: Bool, message: Str) extends Reply {
197+
final case class Error(syntax: Bool, message: Str, otherOutputs: Str) extends Reply {
198198
override def map(f: Str => Reply): Reply = this
199199
override def toString(): Str =
200200
if syntax then

hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ class Lowering(using TL, Raise, Elaborator.State):
110110
Define(ValDefn(td.owner, knd, td.sym, r),
111111
term(st.Blk(stats, res))(k)))
112112
case syntax.Fun =>
113-
Define(FunDefn(td.sym, td.params, returnedTerm(bod)),
113+
val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme))
114+
Define(FunDefn(td.sym, paramLists, bodyBlock),
114115
term(st.Blk(stats, res))(k))
115116
// case cls: ClassDef =>
116117
case cls: ClassLikeDef =>
@@ -126,7 +127,8 @@ class Lowering(using TL, Raise, Elaborator.State):
126127
Define(ClsLikeDefn(cls.sym, syntax.Cls,
127128
mtds.flatMap: td =>
128129
td.body.map: bod =>
129-
FunDefn(td.sym, td.params, term(bod)(Ret))
130+
val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme))
131+
FunDefn(td.sym, paramLists, bodyBlock)
130132
,
131133
privateFlds,
132134
publicFlds,
@@ -165,7 +167,8 @@ class Lowering(using TL, Raise, Elaborator.State):
165167
term(st.Blk(stats, res))(k)
166168

167169
case st.Lam(params, body) =>
168-
k(Value.Lam(params, returnedTerm(body)))
170+
val (paramLists, bodyBlock) = setupFunctionDef(params :: Nil, body, N)
171+
k(Value.Lam(paramLists.head, bodyBlock))
169172

170173
/*
171174
case t @ st.If(Split.Let(sym, trm, tail)) =>
@@ -330,6 +333,8 @@ class Lowering(using TL, Raise, Elaborator.State):
330333
subTerm(prefix): p =>
331334
k(Select(p, nme)(sym))
332335

336+
def setupFunctionDef(paramLists: List[ParamList], bodyTerm: Term, name: Option[Str])(using Subst): (List[ParamList], Block) =
337+
(paramLists, returnedTerm(bodyTerm))
333338

334339

335340
trait LoweringSelSanityChecks
@@ -357,3 +362,79 @@ trait LoweringSelSanityChecks
357362
super.setupSelection(prefix, nme, sym)(k)
358363

359364

365+
366+
trait LoweringTraceLog
367+
(instrument: Bool)(using TL, Raise, Elaborator.State)
368+
extends Lowering:
369+
370+
private def selFromGlobalThis(path: Str*): Path =
371+
path.foldLeft[Path](Value.Ref(State.globalThisSymbol)):
372+
(qual, name) => Select(qual, Tree.Ident(name))(N)
373+
374+
private def assignStmts(stmts: (Local, Result)*)(rest: Block) =
375+
stmts.foldRight(rest):
376+
case ((sym, res), acc) => Assign(sym, res, acc)
377+
378+
extension (k: Block => Block)
379+
def |>: (b: Block): Block = k(b)
380+
381+
private val traceLogFn = selFromGlobalThis("Predef", "TraceLogger", "log")
382+
private val traceLogIndentFn = selFromGlobalThis("Predef", "TraceLogger", "indent")
383+
private val traceLogResetFn = selFromGlobalThis("Predef", "TraceLogger", "resetIndent")
384+
private val strConcatFn = selFromGlobalThis("String", "prototype", "concat", "call")
385+
private val inspectFn = selFromGlobalThis("util", "inspect")
386+
387+
388+
override def setupFunctionDef(paramLists: List[ParamList], bodyTerm: st, name: Option[Str])(using Subst): (List[ParamList], Block) =
389+
if instrument then
390+
val (ps, bod) = handleMultipleParamLists(paramLists, bodyTerm)
391+
val instrumentedBody = setupFunctionBody(ps, bod, name)
392+
(ps :: Nil, instrumentedBody)
393+
else
394+
super.setupFunctionDef(paramLists, bodyTerm, name)
395+
396+
def handleMultipleParamLists(paramLists: List[ParamList], bod: Term) =
397+
def go(paramLists: List[ParamList], bod: Term): (ParamList, Term) =
398+
paramLists match
399+
case Nil => ???
400+
case h :: Nil => (h, bod)
401+
case h :: t => go(t, Term.Lam(h, bod))
402+
go(paramLists.reverse, bod)
403+
404+
def setupFunctionBody(params: ParamList, bod: Term, name: Option[Str])(using Subst): Block =
405+
val enterMsgSym = TempSymbol(N, dbgNme = "traceLogEnterMsg")
406+
val prevIndentLvlSym = TempSymbol(N, dbgNme = "traceLogPrevIndent")
407+
val resSym = TempSymbol(N, dbgNme = "traceLogRes")
408+
val retMsgSym = TempSymbol(N, dbgNme = "traceLogRetMsg")
409+
val psInspectedSyms = params.params.map(p => TempSymbol(N, dbgNme = s"traceLogParam_${p.sym.nme}") -> p.sym)
410+
val resInspectedSym = TempSymbol(N, dbgNme = "traceLogResInspected")
411+
412+
val psSymArgs = psInspectedSyms.zipWithIndex.foldRight[Ls[Arg]](Arg(false, Value.Lit(Tree.StrLit(")"))) :: Nil):
413+
case (((s, p), i), acc) => if i == psInspectedSyms.length - 1
414+
then Arg(false, Value.Ref(s)) :: acc
415+
else Arg(false, Value.Ref(s)) :: Arg(false, Value.Lit(Tree.StrLit(", "))) :: acc
416+
417+
assignStmts(psInspectedSyms.map: (pInspectedSym, pSym) =>
418+
pInspectedSym -> Call(inspectFn, Arg(false, Value.Ref(pSym)) :: Nil)
419+
*) |>:
420+
assignStmts(
421+
enterMsgSym -> Call(
422+
strConcatFn,
423+
Arg(false, Value.Lit(Tree.StrLit(s"CALL ${name.getOrElse("[arrow function]")}("))) :: psSymArgs
424+
),
425+
TempSymbol(N) -> Call(traceLogFn, Arg(false, Value.Ref(enterMsgSym)) :: Nil),
426+
prevIndentLvlSym -> Call(traceLogIndentFn, Nil)
427+
) |>:
428+
term(bod)(r =>
429+
assignStmts(
430+
resSym -> r,
431+
resInspectedSym -> Call(inspectFn, Arg(false, Value.Ref(resSym)) :: Nil),
432+
retMsgSym -> Call(
433+
strConcatFn,
434+
Arg(false, Value.Lit(Tree.StrLit("=> "))) :: Arg(false, Value.Ref(resInspectedSym)) :: Nil
435+
),
436+
TempSymbol(N) -> Call(traceLogResetFn, Arg(false, Value.Ref(prevIndentLvlSym)) :: Nil),
437+
TempSymbol(N) -> Call(traceLogFn, Arg(false, Value.Ref(retMsgSym)) :: Nil)
438+
) |>:
439+
Ret(Value.Ref(resSym))
440+
)

hkmc2/shared/src/test/mlscript-compile/Predef.mjs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,51 @@ const Predef$class = class Predef {
1616
}
1717
toString() { return "MatchFailure(" + this.errors + ")"; }
1818
};
19+
const TraceLogger$class = class TraceLogger {
20+
constructor() {
21+
this.enabled = false;
22+
this.indentLvl = 0;
23+
}
24+
indent() {
25+
let scrut, prev, tmp;
26+
scrut = this.enabled;
27+
if (scrut) {
28+
prev = this.indentLvl;
29+
tmp = prev + 1;
30+
this.indentLvl = tmp;
31+
return prev;
32+
} else {
33+
return undefined;
34+
}
35+
}
36+
resetIndent(n) {
37+
let scrut;
38+
scrut = this.enabled;
39+
if (scrut) {
40+
this.indentLvl = n;
41+
return undefined;
42+
} else {
43+
return undefined;
44+
}
45+
}
46+
log(msg) {
47+
let scrut, tmp, tmp1, tmp2, tmp3, tmp4;
48+
scrut = this.enabled;
49+
if (scrut) {
50+
tmp = "| ".repeat(this.indentLvl);
51+
tmp1 = " ".repeat(this.indentLvl);
52+
tmp2 = "\n" + tmp1;
53+
tmp3 = msg.replaceAll("\n", tmp2);
54+
tmp4 = tmp + tmp3;
55+
return console.log(tmp4);
56+
} else {
57+
return undefined;
58+
}
59+
}
60+
toString() { return "TraceLogger"; }
61+
};
62+
this.TraceLogger = new TraceLogger$class;
63+
this.TraceLogger.class = TraceLogger$class;
1964
this.Test = class Test {
2065
constructor() {
2166
this.y = 1;

hkmc2/shared/src/test/mlscript-compile/Predef.mls

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ fun checkArgs(functionName, expected, isUB, got) =
4040
// + " arguments but got " + got)
4141
else ()
4242

43+
module TraceLogger with
44+
mut val enabled = false
45+
mut val indentLvl = 0
46+
fun indent() =
47+
if enabled then
48+
let prev = indentLvl
49+
set indentLvl = prev + 1
50+
prev
51+
else ()
52+
fun resetIndent(n) =
53+
if enabled then
54+
set indentLvl = n
55+
else ()
56+
fun log(msg) =
57+
if enabled then
58+
console.log("| ".repeat(indentLvl) + msg.replaceAll("\n", "\n" + " ".repeat(indentLvl)))
59+
else ()
60+
4361
class Test with
4462
val y = 1
4563

hkmc2/shared/src/test/mlscript/basics/Classes.mls

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class Foo(x: Int) { log("Hello!") }
3535
//│ ╙── ^^^^^^^^^^^^^
3636
//│ JS:
3737
//│ this.<error> = class <error> { constructor() { } toString() { return "<error>"; } }; undefined
38+
//│ > try { this.<error> = class <error> { constructor() { } toString() { return "<error>"; } }; undefined } catch (e) { console.log('\u200B' + e + '\u200B'); }
39+
//│ > ^
3840
//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected token '<'
3941

4042

hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ module Test with
8888
let f = () => x
8989
let x = 2
9090
log(f())
91+
//│ > try { const Test$class = class Test { #x; #f; #x; constructor() { let tmp; this.#x = 1; this.#f = (...args) => { globalThis.Predef.checkArgs("", 0, true, args.length); return this.#x; }; this.#x = 2; tmp = this.#f() ?? null; globalThis.log(tmp) ?? null } toString() { return "Test"; } }; this.Test = new Test$class; this.Test.class = Test$class; undefined } catch (e) { console.log('\u200B' + e + '\u200B'); }
92+
//│ > ^
9193
//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Identifier '#x' has already been declared
9294

9395

hkmc2/shared/src/test/mlscript/codegen/Primes.mls

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ let x = 1
99
let x' = 1
1010
//│ JS:
1111
//│ this.x' = 1; undefined
12+
//│ > try { this.x' = 1; undefined } catch (e) { console.log('\u200B' + e + '\u200B'); }
13+
//│ > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1214
//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected string
15+
//│ > try { this.x' } catch (e) { console.log('\u200B' + e + '\u200B'); }
16+
//│ > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1317
//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected string
1418

1519

hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class Foo with
4444
print(x)
4545
print(f())
4646
print(g())
47+
//│ > try { this.Foo = class Foo { #x; #f; #x; constructor() { let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; this.#x = 1; this.#f = (...args) => { globalThis.Predef.checkArgs("", 0, true, args.length); return this.#x; }; tmp = this.#f() ?? null; tmp1 = Predef.print(tmp) ?? null; this.#x = 2; tmp2 = Predef.print(this.#x) ?? null; tmp3 = this.#f() ?? null; tmp4 = Predef.print(tmp3) ?? null; tmp5 = this.g() ?? null; Predef.print(tmp5) ?? null } g(...args) { globalThis.Predef.checkArgs("g", 0, true, args.length); return this.#x; } toString() { return "Foo"; } }; undefined } catch (e) { console.log('\u200B' + e + '\u200B'); }
48+
//│ > ^
4749
//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Identifier '#x' has already been declared
4850

4951

0 commit comments

Comments
 (0)