Skip to content

Commit 8ca4e93

Browse files
authored
Merge pull request #157 from chengluyu/ucs-paper
Improvements and fixes for UCS
2 parents 88df416 + 17d05de commit 8ca4e93

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+4479
-889
lines changed

.github/workflows/scala.yml

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ on:
44
push:
55
branches: [ mlscript ]
66
pull_request:
7-
branches: [ mlscript ]
87

98
jobs:
109
build:

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ metals.sbt
77
project/Dependencies.scala
88
project/metals.sbt
99
**.worksheet.sc
10+
.DS_Store

.vscode/settings.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"files.associations": {
3-
"*.fun": "typescript",
4-
"*.mls": "scala"
3+
"*.fun": "typescript"
54
},
65
"typescript.validate.enable": false,
76
"files.watcherExclude": {

js/src/main/scala/Main.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ object Main {
134134

135135
val vars: Map[Str, typer.SimpleType] = Map.empty
136136
val tpd = typer.typeTypingUnit(tu, topLevel = true)(ctx.nest, raise, vars)
137-
137+
138138
object SimplifyPipeline extends typer.SimplifyPipeline {
139139
def debugOutput(msg: => Str): Unit =
140140
// if (mode.dbgSimplif) output(msg)

shared/src/main/scala/mlscript/NewLexer.scala

+41-8
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,34 @@ class NewLexer(origin: Origin, raise: Diagnostic => Unit, dbg: Bool) {
5555
def takeWhile(i: Int, cur: Ls[Char] = Nil)(pred: Char => Bool): (Str, Int) =
5656
if (i < length && pred(bytes(i))) takeWhile(i + 1, bytes(i) :: cur)(pred)
5757
else (cur.reverseIterator.mkString, i)
58+
59+
@tailrec final
60+
def str(i: Int, escapeMode: Bool, cur: Ls[Char] = Nil): (Str, Int) =
61+
if (escapeMode)
62+
if (i < length)
63+
bytes(i) match {
64+
case '"' => str(i + 1, false, '"' :: cur)
65+
case 'n' => str(i + 1, false, '\n' :: cur)
66+
case 't' => str(i + 1, false, '\t' :: cur)
67+
case 'r' => str(i + 1, false, '\r' :: cur)
68+
case ch =>
69+
raise(WarningReport(msg"Found invalid escape character" -> S(loc(i, i + 1)) :: Nil, source = Lexing))
70+
str(i + 1, false, ch :: cur)
71+
}
72+
else {
73+
raise(ErrorReport(msg"Expect an escape character" -> S(loc(i, i + 1)) :: Nil, source = Lexing))
74+
(cur.reverseIterator.mkString, i)
75+
}
76+
else {
77+
if (i < length)
78+
bytes(i) match {
79+
case '\\' => str(i + 1, true, cur)
80+
case '"' | '\n' => (cur.reverseIterator.mkString, i)
81+
case ch => str(i + 1, false, ch :: cur)
82+
}
83+
else
84+
(cur.reverseIterator.mkString, i)
85+
}
5886

5987
def loc(start: Int, end: Int): Loc = Loc(start, end, origin)
6088

@@ -78,7 +106,7 @@ class NewLexer(origin: Origin, raise: Diagnostic => Unit, dbg: Bool) {
78106
lex(j, ind, next(j, COMMA))
79107
case '"' =>
80108
val j = i + 1
81-
val (chars, k) = takeWhile(j)(c => c =/= '"' && c =/= '\n')
109+
val (chars, k) = str(j, false)
82110
val k2 = if (bytes.lift(k) === Some('"')) k + 1 else {
83111
pe(msg"unclosed quotation mark")
84112
k
@@ -135,14 +163,19 @@ class NewLexer(origin: Origin, raise: Diagnostic => Unit, dbg: Bool) {
135163
// go(j, if (keywords.contains(n)) KEYWORD(n) else IDENT(n, isAlphaOp(n)))
136164
lex(j, ind, next(j, if (keywords.contains(n)) KEYWORD(n) else IDENT(n, isAlphaOp(n))))
137165
case _ if isOpChar(c) =>
138-
val (n, j) = takeWhile(i)(isOpChar)
139-
if (n === "." && j < length && isIdentFirstChar(bytes(j))) {
140-
val (name, k) = takeWhile(j)(isIdentChar)
141-
// go(k, SELECT(name))
142-
lex(k, ind, next(k, SELECT(name)))
166+
if (c === '-' && isDigit(bytes(i + 1))) {
167+
val (str, j) = takeWhile(i + 1)(isDigit)
168+
lex(j, ind, next(j, LITVAL(IntLit(-BigInt(str)))))
169+
} else {
170+
val (n, j) = takeWhile(i)(isOpChar)
171+
if (n === "." && j < length && isIdentFirstChar(bytes(j))) {
172+
val (name, k) = takeWhile(j)(isIdentChar)
173+
// go(k, SELECT(name))
174+
lex(k, ind, next(k, SELECT(name)))
175+
}
176+
// else go(j, if (isSymKeyword.contains(n)) KEYWORD(n) else IDENT(n, true))
177+
else lex(j, ind, next(j, if (isSymKeyword.contains(n)) KEYWORD(n) else IDENT(n, true)))
143178
}
144-
// else go(j, if (isSymKeyword.contains(n)) KEYWORD(n) else IDENT(n, true))
145-
else lex(j, ind, next(j, if (isSymKeyword.contains(n)) KEYWORD(n) else IDENT(n, true)))
146179
case _ if isDigit(c) =>
147180
val (str, j) = takeWhile(i)(isDigit)
148181
// go(j, LITVAL(IntLit(BigInt(str))))

shared/src/main/scala/mlscript/Typer.scala

+8-13
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool)
255255
val intBinOpTy = fun(singleTup(IntType), fun(singleTup(IntType), IntType)(noProv))(noProv)
256256
val numberBinOpTy = fun(singleTup(DecType), fun(singleTup(DecType), DecType)(noProv))(noProv)
257257
val numberBinPred = fun(singleTup(DecType), fun(singleTup(DecType), BoolType)(noProv))(noProv)
258+
val stringBinPred = fun(singleTup(StrType), fun(singleTup(StrType), BoolType)(noProv))(noProv)
258259
Map(
259260
"true" -> TrueType,
260261
"false" -> FalseType,
@@ -278,6 +279,10 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool)
278279
"le" -> numberBinPred,
279280
"gt" -> numberBinPred,
280281
"ge" -> numberBinPred,
282+
"slt" -> stringBinPred,
283+
"sle" -> stringBinPred,
284+
"sgt" -> stringBinPred,
285+
"sge" -> stringBinPred,
281286
"length" -> fun(singleTup(StrType), IntType)(noProv),
282287
"concat" -> fun(singleTup(StrType), fun(singleTup(StrType), StrType)(noProv))(noProv),
283288
"eq" -> {
@@ -1062,19 +1067,9 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool)
10621067
case ((a_ty, tv), req) => a_ty & tv | req & a_ty.neg()
10631068
}
10641069
con(s_ty, req, cs_ty)
1065-
case iff @ If(body, fallback) =>
1066-
import mlscript.ucs._
1067-
try {
1068-
val caseTree = MutCaseOf.build(desugarIf(body, fallback))
1069-
println("The mutable CaseOf tree")
1070-
MutCaseOf.show(caseTree).foreach(println(_))
1071-
checkExhaustive(caseTree, N)(summarizePatterns(caseTree), ctx, raise)
1072-
val desugared = MutCaseOf.toTerm(caseTree)
1073-
println(s"Desugared term: ${desugared.print(false)}")
1074-
iff.desugaredTerm = S(desugared)
1075-
typeTerm(desugared)
1076-
} catch {
1077-
case e: DesugaringException => err(e.messages)
1070+
case elf: If =>
1071+
try typeTerm(desugarIf(elf)) catch {
1072+
case e: ucs.DesugaringException => err(e.messages)
10781073
}
10791074
case New(S((nmedTy, trm)), TypingUnit(Nil)) =>
10801075
typeMonomorphicTerm(App(Var(nmedTy.base.name).withLocOf(nmedTy), trm))

shared/src/main/scala/mlscript/codegen/Polyfill.scala

+6
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ object Polyfill {
166166
buffer += BuiltinFunc("not", makeUnaryFunc("!"))
167167
buffer += BuiltinFunc("negate", makeUnaryFunc("-"))
168168
buffer += BuiltinFunc("eq", makeBinaryFunc("==="))
169+
buffer += BuiltinFunc("ne", makeBinaryFunc("!=="))
170+
buffer += BuiltinFunc("sgt", makeBinaryFunc(">"))
171+
buffer += BuiltinFunc("slt", makeBinaryFunc("<"))
172+
buffer += BuiltinFunc("sge", makeBinaryFunc(">="))
173+
buffer += BuiltinFunc("sle", makeBinaryFunc("<="))
174+
buffer += BuiltinFunc("eq", makeBinaryFunc("==="))
169175
buffer += BuiltinFunc("unit", makeUnaryFunc("undefined"))
170176
buffer += BuiltinFunc(
171177
"log", fn(_, param("x")) { `return` { id("console.info")(id("x")) } }

shared/src/main/scala/mlscript/codegen/Scope.scala

+16-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ class Scope(name: Str, enclosing: Opt[Scope]) {
4747
"div",
4848
"gt",
4949
"not",
50+
"ne",
51+
"eq",
52+
"sgt",
53+
"slt",
54+
"sge",
55+
"sle",
5056
"typeof",
5157
"toString",
5258
"negate",
@@ -69,11 +75,20 @@ class Scope(name: Str, enclosing: Opt[Scope]) {
6975
i <- (1 to Int.MaxValue).iterator
7076
c <- Scope.nameAlphabet.combinations(i)
7177
name = c.mkString
72-
if !runtimeSymbols.contains(name)
78+
if !hasRuntimeName(name)
7379
} yield {
7480
name
7581
}
7682

83+
/**
84+
* Check if a runtime name is used recursively.
85+
*
86+
* @param name the name
87+
* @return whether it's available or not
88+
*/
89+
private def hasRuntimeName(name: Str): Bool =
90+
runtimeSymbols.contains(name) || enclosing.exists(_.hasRuntimeName(name))
91+
7792
/**
7893
* Allocate a non-sense runtime name.
7994
*/

shared/src/main/scala/mlscript/helpers.scala

+7-3
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ trait TypeImpl extends Located { self: Type =>
218218
case _: Union | _: Function | _: Tuple | _: Recursive
219219
| _: Neg | _: Rem | _: Bounds | _: WithExtension | Top | Bot
220220
| _: Literal | _: TypeVar | _: AppliedType | _: TypeName
221-
| _: Constrained | _ : Splice | _: TypeTag | _: PolyType =>
221+
| _: Constrained | _ : Splice | _: TypeTag | _: PolyType | _: Selection =>
222222
Nil
223223
}
224224

@@ -232,7 +232,8 @@ trait TypeImpl extends Located { self: Type =>
232232
case Inter(lhs, rhs) => lhs.collectTypeNames ++ rhs.collectTypeNames
233233
case _: Union | _: Function | _: Record | _: Tuple | _: Recursive
234234
| _: Neg | _: Rem | _: Bounds | _: WithExtension | Top | Bot | _: PolyType
235-
| _: Literal | _: TypeVar | _: Constrained | _ : Splice | _: TypeTag =>
235+
| _: Literal | _: TypeVar | _: Constrained | _ : Splice | _: TypeTag
236+
| _: Selection =>
236237
Nil
237238
}
238239

@@ -245,7 +246,8 @@ trait TypeImpl extends Located { self: Type =>
245246
case Inter(ty1, ty2) => ty1.collectBodyFieldsAndTypes ++ ty2.collectBodyFieldsAndTypes
246247
case _: Union | _: Function | _: Tuple | _: Recursive
247248
| _: Neg | _: Rem | _: Bounds | _: WithExtension | Top | Bot | _: PolyType
248-
| _: Literal | _: TypeVar | _: AppliedType | _: TypeName | _: Constrained | _ : Splice | _: TypeTag =>
249+
| _: Literal | _: TypeVar | _: AppliedType | _: TypeName | _: Constrained | _ : Splice | _: TypeTag
250+
| _: Selection =>
249251
Nil
250252
}
251253
}
@@ -729,6 +731,8 @@ trait StatementImpl extends Located { self: Statement =>
729731
).withLocOf(hd) :: cs)
730732
case NuTypeDef(Nms, nme, tps, tup @ Tup(fs), sig, pars, sup, ths, unit) =>
731733
??? // TODO
734+
case NuTypeDef(Mxn, nme, tps, tup @ Tup(fs), sig, pars, sup, ths, unit) =>
735+
??? // TODO
732736
case NuTypeDef(k @ Als, nme, tps, tup @ Tup(fs), sig, pars, sup, ths, unit) =>
733737
// TODO properly check:
734738
require(fs.isEmpty, fs)

shared/src/main/scala/mlscript/ucs/Clause.scala

+41-35
Original file line numberDiff line numberDiff line change
@@ -9,64 +9,70 @@ import scala.collection.mutable.Buffer
99
* A `Clause` represents a minimal unit of logical predicate in the UCS.
1010
* There are three kinds of clauses: boolean test, class match, and tuple match.
1111
*/
12-
abstract class Clause {
12+
sealed abstract class Clause {
1313
/**
1414
* Local interleaved let bindings declared before this condition.
1515
*/
16-
var bindings: Ls[(Bool, Var, Term)] = Nil
16+
var bindings: Ls[LetBinding] = Nil
1717

1818
/**
1919
* Locations of terms that build this `Clause`.
2020
*
2121
* @return
2222
*/
2323
val locations: Ls[Loc]
24+
25+
protected final def bindingsToString: String =
26+
if (bindings.isEmpty) "" else " with " + (bindings match {
27+
case Nil => ""
28+
case bindings => bindings.map(_.name.name).mkString("(", ", ", ")")
29+
})
30+
}
31+
32+
sealed abstract class MatchClause extends Clause {
33+
val scrutinee: Scrutinee
2434
}
2535

2636
object Clause {
37+
final case class MatchLiteral(
38+
override val scrutinee: Scrutinee,
39+
literal: SimpleTerm
40+
)(override val locations: Ls[Loc]) extends MatchClause {
41+
override def toString: String = s"«$scrutinee is $literal" + bindingsToString
42+
}
43+
44+
final case class MatchAny(override val scrutinee: Scrutinee)(override val locations: Ls[Loc]) extends MatchClause {
45+
override def toString: String = s"«$scrutinee is any" + bindingsToString
46+
}
47+
2748
final case class MatchClass(
28-
scrutinee: Scrutinee,
49+
override val scrutinee: Scrutinee,
2950
className: Var,
3051
fields: Ls[Str -> Var]
31-
)(override val locations: Ls[Loc]) extends Clause
52+
)(override val locations: Ls[Loc]) extends MatchClause {
53+
override def toString: String = s"«$scrutinee is $className»" + bindingsToString
54+
}
3255

3356
final case class MatchTuple(
3457
scrutinee: Scrutinee,
3558
arity: Int,
3659
fields: Ls[Str -> Var]
37-
)(override val locations: Ls[Loc]) extends Clause
38-
39-
final case class BooleanTest(test: Term)(override val locations: Ls[Loc]) extends Clause
40-
41-
def showBindings(bindings: Ls[(Bool, Var, Term)]): Str =
42-
bindings match {
43-
case Nil => ""
44-
case bindings => bindings.map {
45-
case (_, Var(name), _) => name
46-
}.mkString("(", ", ", ")")
47-
}
48-
60+
)(override val locations: Ls[Loc]) extends Clause {
61+
override def toString: String = s"«$scrutinee is Tuple#$arity»" + bindingsToString
62+
}
4963

50-
def showClauses(clauses: Iterable[Clause]): Str = {
51-
clauses.iterator.map { clause =>
52-
(clause match {
53-
case Clause.BooleanTest(test) => s"«$test»"
54-
case Clause.MatchClass(scrutinee, Var(className), fields) =>
55-
s"«$scrutinee is $className»"
56-
case Clause.MatchTuple(scrutinee, arity, fields) =>
57-
s"«$scrutinee is Tuple#$arity»"
58-
}) + (if (clause.bindings.isEmpty) "" else " with " + showBindings(clause.bindings))
59-
}.mkString("", " and ", "")
64+
final case class BooleanTest(test: Term)(
65+
override val locations: Ls[Loc]
66+
) extends Clause {
67+
override def toString: String = s"«$test»" + bindingsToString
6068
}
6169

62-
def print(println: (=> Any) => Unit, conjunctions: Iterable[Conjunction -> Term]): Unit = {
63-
println("Flattened conjunctions")
64-
conjunctions.foreach { case Conjunction(clauses, trailingBindings) -> term =>
65-
println("+ " + showClauses(clauses) + {
66-
(if (trailingBindings.isEmpty) "" else " ") +
67-
showBindings(trailingBindings) +
68-
s" => $term"
69-
})
70-
}
70+
/**
71+
* @param isField whether this binding is extracting a class field
72+
*/
73+
final case class Binding(name: Var, term: Term, isField: Bool)(
74+
override val locations: Ls[Loc]
75+
) extends Clause {
76+
override def toString: String = s"«$name = $term»" + bindingsToString
7177
}
7278
}

0 commit comments

Comments
 (0)