Skip to content

Commit 7158626

Browse files
committed
Support matching on builtins and improve builtin symbols
1 parent 11d66ad commit 7158626

File tree

19 files changed

+243
-78
lines changed

19 files changed

+243
-78
lines changed

hkmc2/jvm/src/test/scala/hkmc2/DiffTestRunner.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ abstract class CompileTestRunner(state: DiffTestRunner.State)
141141

142142
println(s"Compiling: $relativeName")
143143

144-
val preludePath = dir/"decls"/"Prelude.mls"
144+
val preludePath = dir/"mlscript"/"decls"/"Prelude.mls"
145145

146146
MLsCompiler(preludePath).compileModule(file)
147147

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import scala.collection.mutable
55
import mlscript.utils.*, shorthands.*
66
import utils.*
77

8+
import semantics.*
9+
import codegen.*
810
import codegen.js.{JSBuilder, JSBuilderArgNumSanityChecks, JSBuilderSelSanityChecks}
911
import document.*
1012
import codegen.Block
@@ -49,9 +51,10 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker:
4951
if js.isSet then
5052
val low = ltl.givenIn:
5153
new codegen.Lowering with codegen.LoweringSelSanityChecks(noSanityCheck.isUnset)
52-
val jsb = new JSBuilder with JSBuilderArgNumSanityChecks(noSanityCheck.isUnset) with JSBuilderSelSanityChecks(noSanityCheck.isUnset)
53-
import semantics.*
54-
import codegen.*
54+
given Elaborator.Ctx = curCtx
55+
val jsb = new JSBuilder
56+
with JSBuilderArgNumSanityChecks(noSanityCheck.isUnset)
57+
with JSBuilderSelSanityChecks(noSanityCheck.isUnset)
5558
val le = low.program(blk)
5659
if showLoweredTree.isSet then
5760
output(s"Lowered:")

hkmc2/jvm/src/test/scala/hkmc2/MLsDiffMaker.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ abstract class MLsDiffMaker extends DiffMaker:
5050
val showUCS = Command("ucs"): ln =>
5151
ln.split(" ").iterator.map(x => "ucs:" + x.trim).toSet
5252

53-
given Elaborator.State = new Elaborator.State
53+
given Elaborator.State = new Elaborator.State:
54+
override def dbg: Bool =
55+
dbgParsing.isSet
56+
|| dbgElab.isSet
57+
|| debug.isSet
5458

5559
val etl = new TraceLogger:
5660
override def doTrace = dbgElab.isSet || scope.exists:
@@ -63,11 +67,12 @@ abstract class MLsDiffMaker extends DiffMaker:
6367
if doTrace then super.trace(pre, post)(thunk)
6468
else thunk
6569

66-
var curCtx = Elaborator.State.init.nest(N)
70+
var curCtx = Elaborator.State.init
6771

6872

6973
override def run(): Unit =
7074
if file =/= preludeFile then importFile(preludeFile, verbose = false)
75+
curCtx = curCtx.nest(N)
7176
super.run()
7277

7378

hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,30 @@ import hkmc2.syntax.Keyword.`override`
1111
import semantics.Elaborator.State
1212

1313

14+
class ParserSetup(file: os.Path, dbgParsing: Bool)(using Elaborator.State, Raise):
15+
16+
val block = os.read(file)
17+
val fph = new FastParseHelpers(block)
18+
val origin = Origin(file.toString, 0, fph)
19+
20+
val lexer = new syntax.Lexer(origin, dbg = dbgParsing)
21+
val tokens = lexer.bracketedTokens
22+
23+
// if showParse.isSet || dbgParsing.isSet then
24+
// output(syntax.Lexer.printTokens(tokens))
25+
26+
val rules = syntax.ParseRules()
27+
val parser = new syntax.Parser(origin, tokens, rules, raise, dbg = dbgParsing):
28+
def doPrintDbg(msg: => Str): Unit =
29+
// if dbg then output(msg)
30+
if dbg then println(msg)
31+
32+
val result = parser.parseAll(parser.block(allowNewlines = true))
33+
34+
val resultBlk = new syntax.Tree.Block(result)
35+
36+
37+
1438
class MLsCompiler(preludeFile: os.Path):
1539

1640

@@ -29,40 +53,33 @@ class MLsCompiler(preludeFile: os.Path):
2953

3054
def compileModule(file: os.Path): Unit =
3155

32-
val block = os.read(file)
33-
val fph = new FastParseHelpers(block)
34-
val origin = Origin(file.toString, 0, fph)
35-
36-
val lexer = new syntax.Lexer(origin, dbg = dbgParsing)
37-
val tokens = lexer.bracketedTokens
56+
given Elaborator.State = new Elaborator.State
3857

39-
// if showParse.isSet || dbgParsing.isSet then
40-
// output(syntax.Lexer.printTokens(tokens))
58+
val preludeParse = ParserSetup(preludeFile, dbgParsing)
59+
val mainParse = ParserSetup(file, dbgParsing)
4160

42-
given Elaborator.State = new Elaborator.State
43-
val rules = syntax.ParseRules()
44-
val p = new syntax.Parser(origin, tokens, rules, raise, dbg = dbgParsing):
45-
def doPrintDbg(msg: => Str): Unit =
46-
// if dbg then output(msg)
47-
if dbg then println(msg)
48-
val res = p.parseAll(p.block(allowNewlines = true))
49-
given Elaborator.Ctx = State.init.nest(N)
5061
val wd = file / os.up
5162
val elab = Elaborator(etl, wd)
52-
val resBlk = new syntax.Tree.Block(res)
53-
val (blk, newCtx) = elab.importFrom(resBlk)
54-
val low = ltl.givenIn:
55-
codegen.Lowering()
56-
val jsb = codegen.js.JSBuilder()
57-
val le = low.program(blk)
58-
val baseScp: codegen.js.Scope =
59-
codegen.js.Scope.empty
60-
val nestedScp = baseScp.nest
61-
val je = nestedScp.givenIn:
62-
jsb.program(le, S(file.baseName), wd)
63-
val jsStr = je.stripBreaks.mkString(100)
64-
val out = file / os.up / (file.baseName + ".mjs")
65-
os.write.over(out, jsStr)
63+
64+
val initState = State.init.nest(N)
65+
66+
val (pblk, newCtx) = elab.importFrom(preludeParse.resultBlk)(using initState)
67+
68+
newCtx.nest(N).givenIn:
69+
70+
val (blk, newCtx) = elab.importFrom(mainParse.resultBlk)
71+
val low = ltl.givenIn:
72+
codegen.Lowering()
73+
val jsb = codegen.js.JSBuilder()
74+
val le = low.program(blk)
75+
val baseScp: codegen.js.Scope =
76+
codegen.js.Scope.empty
77+
val nestedScp = baseScp.nest
78+
val je = nestedScp.givenIn:
79+
jsb.program(le, S(file.baseName), wd)
80+
val jsStr = je.stripBreaks.mkString(100)
81+
val out = file / os.up / (file.baseName + ".mjs")
82+
os.write.over(out, jsStr)
6683

6784

6885
end MLsCompiler

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,7 @@ case class Define(defn: Defn, rest: Block) extends Block with ProductWithTail
8282
sealed abstract class Defn:
8383
val sym: MemberSymbol[?]
8484

85-
// final case class TermDefn(
86-
// k: syntax.TermDefKind,
87-
// // sym: TermSymbol,
88-
// sym: BlockMemberSymbol,
89-
// params: Ls[ParamList],
90-
// body: Block,
91-
// ) extends Defn
9285
final case class FunDefn(
93-
// k: syntax.TermDefKind,
94-
// sym: TermSymbol,
9586
sym: BlockMemberSymbol,
9687
params: Ls[ParamList],
9788
body: Block,
@@ -101,13 +92,10 @@ final case class ValDefn(
10192
owner: Opt[InnerSymbol],
10293
k: syntax.Val,
10394
sym: BlockMemberSymbol,
104-
// params: Ls[ParamList],
10595
rhs: Path,
10696
) extends Defn
10797

10898
final case class ClsLikeDefn(
109-
// sym: ClassSymbol,
110-
// sym: MemberSymbol[ClassLikeDef],
11199
sym: MemberSymbol[? <: ClassLikeDef],
112100
k: syntax.ClsLikeKind,
113101
methods: Ls[FunDefn],

hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ abstract class CodeBuilder:
2525
type Context
2626

2727

28-
class JSBuilder(using Elaborator.State) extends CodeBuilder:
28+
class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder:
2929

3030
val builtinOpsBase: Ls[Str] = Ls(
3131
"+", "-", "*", "/", "%",
@@ -238,10 +238,14 @@ class JSBuilder(using Elaborator.State) extends CodeBuilder:
238238
case N => doc""
239239
t :: e :: returningTerm(rest)
240240
case Match(scrut, Case.Cls(cls, pth) -> trm :: Nil, els, rest) =>
241+
val sd = result(scrut)
241242
val test = cls match
242243
// case _: semantics.ModuleSymbol => doc"=== ${result(pth)}"
243-
case _ => doc"instanceof ${result(pth)}"
244-
val t = doc" # if (${ result(scrut) } $test) { #{ ${
244+
case Elaborator.ctx.Builtins.Str => doc"typeof $sd === 'string'"
245+
case Elaborator.ctx.Builtins.Num => doc"typeof $sd === 'number'"
246+
case Elaborator.ctx.Builtins.Int => doc"globalThis.Number.isInteger($sd)"
247+
case _ => doc"$sd instanceof ${result(pth)}"
248+
val t = doc" # if ($test) { #{ ${
245249
returningTerm(trm)
246250
} #} # }"
247251
val e = els match

hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ object Elaborator:
3434
val reservedNames = binaryOps.toSet ++ aliasOps.keySet + "NaN" + "Infinity"
3535

3636
case class Ctx(outer: Opt[InnerSymbol], parent: Opt[Ctx], env: Map[Str, Ctx.Elem]):
37+
3738
def +(local: Str -> Symbol): Ctx = copy(outer, env = env + local.mapSecond(Ctx.RefElem(_)))
3839
def ++(locals: IterableOnce[Str -> Symbol]): Ctx =
3940
copy(outer, env = env ++ locals.mapValues(Ctx.RefElem(_)))
4041
def elem_++(locals: IterableOnce[Str -> Ctx.Elem]): Ctx =
4142
copy(outer, env = env ++ locals)
43+
4244
def withMembers(members: Iterable[Str -> MemberSymbol[?]], out: Opt[Symbol] = N): Ctx =
4345
copy(env = env ++ members.map:
4446
case (nme, sym) => nme -> (
@@ -47,14 +49,29 @@ object Elaborator:
4749
case N => sym: Ctx.Elem
4850
)
4951
)
52+
5053
def nest(outer: Opt[InnerSymbol]): Ctx = Ctx(outer, Some(this), Map.empty)
54+
5155
def get(name: Str): Opt[Ctx.Elem] =
5256
env.get(name).orElse(parent.flatMap(_.get(name)))
5357
def getOuter: Opt[InnerSymbol] = outer.orElse(parent.flatMap(_.getOuter))
54-
lazy val allMembers: Map[Str, Symbol] =
55-
parent.fold(Map.empty)(_.allMembers) ++ env.flatMap:
56-
case (n, re: Ctx.RefElem) => (n, re.sym) :: Nil
57-
case _ => Nil // FIXME?
58+
59+
// * Invariant: We expect that the top-level context only contain hard-coded symbols like `globalThis`
60+
// * and that built-in symbols like Int and Str be imported into another nested context on top of it.
61+
// * It should not be possible to shadow these built-in symbols, so user code should always be compiled
62+
// * in further nested contexts.
63+
// * Method `getBuiltin` is used to look up built-in symbols in the context of builtin symbols.
64+
def getBuiltin(nme: Str): Opt[Ctx.Elem] =
65+
parent.filter(_.parent.nonEmpty).fold(env.get(nme))(_.getBuiltin(nme))
66+
object Builtins:
67+
private def assumeBuiltinCls(nme: Str): ClassSymbol =
68+
getBuiltin(nme)
69+
.getOrElse(throw new NoSuchElementException(s"builtin $nme ${env.keySet} $parent"))
70+
.symbol.getOrElse(throw new NoSuchElementException(s"builtin symbol $nme"))
71+
.asCls.getOrElse(throw new NoSuchElementException(s"builtin class symbol $nme"))
72+
val Int = assumeBuiltinCls("Int")
73+
val Num = assumeBuiltinCls("Num")
74+
val Str = assumeBuiltinCls("Str")
5875

5976
object Ctx:
6077
abstract class Elem:
@@ -76,8 +93,11 @@ object Elaborator:
7693
def symbol = symOpt
7794
given Conversion[Symbol, Elem] = RefElem(_)
7895
val empty: Ctx = Ctx(N, N, Map.empty)
96+
7997
type Ctxl[A] = Ctx ?=> A
80-
def ctx: Ctxl[Ctx] = summon
98+
99+
transparent inline def ctx(using Ctx): Ctx = summon
100+
81101
class State:
82102
given State = this
83103
val suid = new Uid.Symbol.State
@@ -86,9 +106,16 @@ object Elaborator:
86106
def init(using State): Ctx = Ctx.empty.copy(env = Map(
87107
"globalThis" -> globalThisSymbol,
88108
))
109+
def dbg: Bool = false
110+
def dbgUid(uid: Uid[Symbol]): Str = if dbg then s"$uid" else ""
89111
transparent inline def State(using state: State): State = state
112+
113+
end Elaborator
114+
115+
90116
import Elaborator.*
91117

118+
92119
class Elaborator(val tl: TraceLogger, val wd: os.Path)
93120
(using val raise: Raise, val state: State)
94121
extends Importer:

hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ class FlowSymbol(label: Str)(using State) extends Symbol:
6161
val outFlows2: mutable.Buffer[Consumer] = mutable.Buffer.empty
6262
val inFlows: mutable.Buffer[ConcreteProd] = mutable.Buffer.empty
6363
override def toString: Str =
64-
label
65-
// s"$label@$uid"
64+
label + State.dbgUid(uid)
6665

6766

6867
sealed trait LocalSymbol extends Symbol
@@ -86,7 +85,7 @@ class BuiltinSymbol
8685
(val nme: Str, val binary: Bool, val unary: Bool, val nullary: Bool)(using State)
8786
extends Symbol:
8887
def toLoc: Option[Loc] = N
89-
override def toString: Str = s"builtin:$nme"
88+
override def toString: Str = s"builtin:$nme${State.dbgUid(uid)}"
9089

9190

9291
/** This is the outside-facing symbol associated to a possibly-overloaded
@@ -110,7 +109,8 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[Tree])(using State)
110109
lazy val hasLiftedClass: Bool =
111110
modTree.isDefined || trmTree.isDefined || clsTree.exists(_.paramLists.nonEmpty)
112111

113-
override def toString: Str = s"member:$nme"
112+
override def toString: Str =
113+
s"member:$nme${State.dbgUid(uid)}"
114114

115115
end BlockMemberSymbol
116116

@@ -157,25 +157,25 @@ class ClassSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State)
157157
extends MemberSymbol[ClassDef] with CtorSymbol with InnerSymbol:
158158
def nme = id.name
159159
def toLoc: Option[Loc] = id.toLoc // TODO track source tree of classe here
160-
override def toString: Str = s"class:$nme"
160+
override def toString: Str = s"class:$nme${State.dbgUid(uid)}"
161161
/** Compute the arity. */
162162
def arity: Int = tree.paramLists.headOption.fold(0)(_.fields.length)
163163

164164
class ModuleSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State)
165165
extends MemberSymbol[ModuleDef] with CtorSymbol with InnerSymbol:
166166
def nme = id.name
167167
def toLoc: Option[Loc] = id.toLoc // TODO track source tree of module here
168-
override def toString: Str = s"module:${id.name}"
168+
override def toString: Str = s"module:${id.name}${State.dbgUid(uid)}"
169169

170170
class TypeAliasSymbol(val id: Tree.Ident)(using State) extends MemberSymbol[TypeDef]:
171171
def nme = id.name
172172
def toLoc: Option[Loc] = id.toLoc // TODO track source tree of type alias here
173-
override def toString: Str = s"module:${id.name}"
173+
override def toString: Str = s"module:${id.name}${State.dbgUid(uid)}"
174174

175175
class TopLevelSymbol(blockNme: Str)(using State)
176176
extends MemberSymbol[ModuleDef] with InnerSymbol:
177177
def nme = blockNme
178178
def toLoc: Option[Loc] = N
179-
override def toString: Str = s"globalThis:$blockNme"
179+
override def toString: Str = s"globalThis:$blockNme${State.dbgUid(uid)}"
180180

181181

hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ enum Tree extends AutoLocated:
131131
case Jux(lhs, rhs) => "juxtaposition"
132132
case SynthSel(prefix, name) => "synthetic selection"
133133
case Sel(prefix, name) => "selection"
134-
case InfixApp(lhs, kw, rhs) => "infix application"
134+
case InfixApp(lhs, kw, rhs) => "infix operation"
135135
case New(body) => "new"
136136
case IfLike(Keyword.`if`, split) => "if expression"
137137
case IfLike(Keyword.`while`, split) => "while expression"

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@ const Example$class = class Example {
88
}
99
inc(x) {
1010
return x + 1;
11+
}
12+
test(x1) {
13+
if (globalThis.Number.isInteger(x1)) {
14+
return "int";
15+
} else {
16+
if (typeof x1 === 'number') {
17+
return "num";
18+
} else {
19+
if (typeof x1 === 'string') {
20+
return "str";
21+
} else {
22+
return "other";
23+
}
24+
}
25+
}
1126
}
1227
toString() { return "Example"; }
1328
}; const Example = new Example$class;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,10 @@ fun (/) funnySlash(f, arg) = f(arg)
1010

1111
fun inc(x) = x + 1
1212

13+
fun test(x) = if x is
14+
Int then "int"
15+
Num then "num"
16+
Str then "str"
17+
else "other"
18+
1319

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11

2-
declare val String
3-
declare val console
4-
52
module Predef with ...
63

74
fun id(x) = x

0 commit comments

Comments
 (0)