From 3127ccf440ee7bcd14f879a7b2547de04dc63eb9 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 12 Dec 2024 16:13:17 +0800 Subject: [PATCH] Fix parsing bug and add Stack zipping with tests --- .../src/main/scala/hkmc2/syntax/Keyword.scala | 6 +- .../src/main/scala/hkmc2/syntax/Parser.scala | 11 +- .../src/test/mlscript-compile/Predef.mjs | 1 + .../src/test/mlscript-compile/Predef.mls | 2 + .../src/test/mlscript-compile/Stack.mjs | 120 ++++++++++++++++++ .../src/test/mlscript-compile/Stack.mls | 40 ++++++ .../src/test/mlscript/basics/OpBlocks.mls | 19 ++- .../src/test/mlscript/codegen/While.mls | 2 +- .../src/test/mlscript/std/StackTests.mls | 62 +++++++++ 9 files changed, 243 insertions(+), 20 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/std/StackTests.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala index d0757fdd9..56268a9f7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala @@ -31,7 +31,7 @@ class Keyword( def assumeRightPrec: Int = rightPrec.getOrElse(lastWords(s"$this does not have right precedence")) def leftPrecOrMin: Int = leftPrec.getOrElse(Int.MinValue) def rightPrecOrMin: Int = rightPrec.getOrElse(Int.MinValue) - // def rightPrecOrMax: Int = rightPrec.getOrElse(Int.MaxValue) + def rightPrecOrMax: Int = rightPrec.getOrElse(Int.MaxValue) override def toString: Str = s"keyword '$name'" object Keyword: @@ -108,8 +108,8 @@ object Keyword: val `abstract` = Keyword("abstract", N, N) val `constructor` = Keyword("constructor", N, N) val `virtual` = Keyword("virtual", N, N) - val `true` = Keyword("true", N, curPrec) - val `false` = Keyword("false", N, curPrec) + val `true` = Keyword("true", N, N) + val `false` = Keyword("false", N, N) val `public` = Keyword("public", N, N) val `private` = Keyword("private", N, N) val `return` = Keyword("return", N, curPrec) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index 7a6ba4326..65988105a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -351,9 +351,9 @@ abstract class Parser( yeetSpaces match case (tok @ BRACKETS(Indent | Curly, toks), loc) :: _ if subRule.blkAlt.isEmpty => consume - rec(toks, S(tok.innerLoc), tok.describe).concludeWith(_.parseRule(kw.assumeRightPrec, subRule)) + rec(toks, S(tok.innerLoc), tok.describe).concludeWith(_.parseRule(kw.rightPrecOrMax, subRule)) case _ => - parseRule(kw.assumeRightPrec, subRule) + parseRule(kw.rightPrecOrMax, subRule) case N => if verbose then printDbg(s"$$ cannot find a rule starting with: ${id.name}") rule.exprAlt match @@ -460,9 +460,10 @@ abstract class Parser( // TODO: rm `allowIndentedBlock`? Seems it can always be `true` def expr(prec: Int, allowIndentedBlock: Bool = true)(using Line): Tree = - parseRule(prec, - if allowIndentedBlock then prefixRulesAllowIndentedBlock else prefixRules - ).getOrElse(errExpr) // * a `None` result means an alread-reported error + val res = parseRule(prec, + if allowIndentedBlock then prefixRulesAllowIndentedBlock else prefixRules + ).getOrElse(errExpr) // * a `None` result means an alread-reported error + exprCont(res, prec, allowIndentedBlock) def simpleExpr(prec: Int)(using Line): Tree = wrap(prec)(simpleExprImpl(prec)) def simpleExprImpl(prec: Int): Tree = diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index b60f8dbc7..157e85e44 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -1,5 +1,6 @@ const Predef$class = class Predef { constructor() { + this.assert = console.assert; this.MatchResult = function MatchResult(captures1) { return new MatchResult.class(captures1); }; this.MatchResult.class = class MatchResult { constructor(captures) { diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 5ba9be84f..9d51324ab 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -14,6 +14,8 @@ fun (|>.) call(receiver, f)(...args) = f.call(receiver, ...args) fun print(x) = console.log(String(x)) +val assert = console.assert + fun tupleSlice(xs, i, j) = globalThis.Array.prototype.slice.call(xs, i, xs.length - j) diff --git a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs index 44352d39a..72f949c7d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs @@ -1,3 +1,4 @@ +import Predef from "./Predef.mjs"; const Stack$class = class Stack { constructor() { this.Cons = function Cons(head1, tail1) { return new Cons.class(head1, tail1); }; @@ -24,6 +25,125 @@ const Stack$class = class Stack { } else { return false; } + } + reverseAndAppend(xs1, tail) { + let param0, param1, h, t, tmp; + if (xs1 instanceof this.Cons.class) { + param0 = xs1.head; + param1 = xs1.tail; + h = param0; + t = param1; + tmp = this.Cons(h, tail); + return this.reverseAndAppend(t, tmp); + } else { + if (xs1 instanceof this.Nil.class) { + return tail; + } else { + throw new globalThis.Error("match error"); + } + } + } + reverse(xs2) { + return this.reverseAndAppend(xs2, this.Nil); + } + fromArray(arr) { + let ls, i, len, scrut, tmp, tmp1, tmp2, tmp3; + ls = this.Nil; + i = 0; + len = arr.length; + tmp4: while (true) { + scrut = i < len; + if (scrut) { + tmp = arr.at(i) ?? null; + tmp1 = this.Cons(tmp, ls); + ls = tmp1; + tmp2 = i + 1; + i = tmp2; + tmp3 = null; + continue tmp4; + } else { + tmp3 = null; + } + break; + } + return ls; + } + toReverseArray(xs3) { + let arr1, i, param0, param1, h, t, tmp, tmp1; + arr1 = []; + i = 0; + tmp2: while (true) { + if (xs3 instanceof this.Cons.class) { + param0 = xs3.head; + param1 = xs3.tail; + h = param0; + t = param1; + tmp = arr1.push(h) ?? null; + xs3 = t; + tmp1 = null; + continue tmp2; + } else { + tmp1 = null; + } + break; + } + return arr1; + } + zip(...xss) { + let tmp, tmp1; + + const this$Stack = this; + function go(heads, tails) { + return (caseScrut) => { + let param0, param1, h, t, param01, param11, h2, t2, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11; + if (caseScrut instanceof this$Stack.Cons.class) { + param0 = caseScrut.head; + param1 = caseScrut.tail; + h = param0; + t = param1; + if (h instanceof this$Stack.Cons.class) { + param01 = h.head; + param11 = h.tail; + h2 = param01; + t2 = param11; + tmp2 = this$Stack.Cons(h2, heads); + tmp3 = this$Stack.Cons(t2, tails); + tmp4 = go(tmp2, tmp3); + return tmp4(t) ?? null; + } else { + if (h instanceof this$Stack.Nil.class) { + tmp5 = go(heads, tails); + return tmp5(t) ?? null; + } else { + throw new globalThis.Error("match error"); + } + } + } else { + if (caseScrut instanceof this$Stack.Nil.class) { + if (heads instanceof this$Stack.Nil.class) { + if (tails instanceof this$Stack.Nil.class) { + tmp6 = true; + } else { + tmp6 = false; + } + tmp7 = Predef.assert(tmp6) ?? null; + return (tmp7 , this$Stack.Nil); + } else { + tmp8 = this$Stack.toReverseArray(heads); + tmp9 = go(this$Stack.Nil, this$Stack.Nil); + tmp10 = this$Stack.reverse(tails); + tmp11 = tmp9(tmp10) ?? null; + return this$Stack.Cons(tmp8, tmp11); + } + } else { + throw new globalThis.Error("match error"); + } + } + }; + } + tmp = go(this.Nil, this.Nil); + tmp1 = this.fromArray(xss); + return tmp(tmp1) ?? null; } toString() { return "Stack"; } }; const Stack = new Stack$class; diff --git a/hkmc2/shared/src/test/mlscript-compile/Stack.mls b/hkmc2/shared/src/test/mlscript-compile/Stack.mls index 08e20e679..f393f17d0 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Stack.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Stack.mls @@ -1,4 +1,8 @@ +import "./Predef.mls" +open Predef + + // TODO // type Stack[A] = Stack.Cons[A] | Stack.Nil @@ -9,4 +13,40 @@ object Nil fun isEmpty(xs) = xs is Nil +fun reverseAndAppend(xs, tail) = if xs is + h :: t then reverseAndAppend(t, h :: tail) + Nil then tail + +fun reverse(xs) = reverseAndAppend(xs, Nil) + +fun fromArray(arr) = + let + ls = Nil + i = 0 + len = arr.length + while i < len do + ls = arr.at(i) :: ls + set i += 1 + ls + +fun toReverseArray(xs) = + let + arr = [] + i = 0 + while xs is + h :: t do + arr.push(h) + set xs = t + arr + +fun zip(...xss) = + fun go(heads, tails) = case + h :: t and h is + h2 :: t2 then go(h2 :: heads, t2 :: tails)(t) + Nil then go(heads, tails)(t) + Nil and heads is + Nil then assert(tails is Nil); Nil + else heads toReverseArray() :: go(Nil, Nil)(tails reverse()) + go(Nil, Nil) of fromArray(xss) + diff --git a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls index a2e1ac257..8e99ece9b 100644 --- a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls +++ b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls @@ -67,10 +67,7 @@ fun f(x) = x { + 1, * 2 } //│ ╔══[PARSE ERROR] Unexpected comma in this position //│ ║ l.66: fun f(x) = x { + 1, * 2 } //│ ╙── ^ -//│ ╔══[PARSE ERROR] Unexpected operator here -//│ ║ l.66: fun f(x) = x { + 1, * 2 } -//│ ╙── ^ -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(+),IntLit(1)))) (of class hkmc2.syntax.Tree$OpBlock) +//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(+),App(Ident(*),Tup(List(IntLit(1), IntLit(2))))))) (of class hkmc2.syntax.Tree$OpBlock) :pt @@ -121,10 +118,10 @@ fun f(x) = if x > 0 then "a" is 0 then "b" //│ ╔══[PARSE ERROR] Expect an operator instead of 'is' keyword -//│ ║ l.122: is 0 then "b" +//│ ║ l.119: is 0 then "b" //│ ╙── ^^ //│ ╔══[PARSE ERROR] Unexpected 'is' keyword here -//│ ║ l.122: is 0 then "b" +//│ ║ l.119: is 0 then "b" //│ ╙── ^^ //│ ═══[ERROR] Unrecognized operator branch. @@ -136,11 +133,11 @@ fun f(x) = if x foo(A) then a bar(B) then b //│ ╔══[ERROR] Unrecognized term split (juxtaposition). -//│ ║ l.135: fun f(x) = if x +//│ ║ l.132: fun f(x) = if x //│ ║ ^ -//│ ║ l.136: foo(A) then a +//│ ║ l.133: foo(A) then a //│ ║ ^^^^^^^^^^^^^^^ -//│ ║ l.137: bar(B) then b +//│ ║ l.134: bar(B) then b //│ ╙── ^^^^^^^^^^^^^^^ @@ -149,10 +146,10 @@ fun f(x) = if x is 0 then "a" is 1 then "b" //│ ╔══[PARSE ERROR] Expected start of statement in this position; found 'is' keyword instead -//│ ║ l.150: is 1 then "b" +//│ ║ l.147: is 1 then "b" //│ ╙── ^^ //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.150: is 1 then "b" +//│ ║ l.147: is 1 then "b" //│ ╙── ^ //│ = [Function: f] diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index 10fc2b7bc..db712a154 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -253,7 +253,7 @@ f(Cons(1, Cons(2, Cons(3, 0)))) :fixme () => while true then 0 -//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(InfixApp(Tup(List()),keyword '=>',IfLike(keyword 'while',None,BoolLit(true))),keyword 'then',IntLit(0)) (of class hkmc2.syntax.Tree$InfixApp) +//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(IfLike(keyword 'while',None,BoolLit(true)),keyword 'then',IntLit(0)) (of class hkmc2.syntax.Tree$InfixApp) :fixme while log("Hello World"); false diff --git a/hkmc2/shared/src/test/mlscript/std/StackTests.mls b/hkmc2/shared/src/test/mlscript/std/StackTests.mls new file mode 100644 index 000000000..224fedd11 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/std/StackTests.mls @@ -0,0 +1,62 @@ +:js + +import "../../mlscript-compile/Stack.mls" + +open Stack + + +let + s1 = 1 :: 2 :: 3 :: Nil + s2 = "a" :: "b" :: "c" :: Nil + s3 = true :: false :: Nil +//│ > Cons { +//│ > head: 1, +//│ > tail: Cons { head: 2, tail: Cons { head: 3, tail: [Nil] } } +//│ s1 = } +//│ > Cons { +//│ > head: 'a', +//│ > tail: Cons { head: 'b', tail: Cons { head: 'c', tail: [Nil] } } +//│ s2 = } +//│ > Cons { +//│ > head: true, +//│ > tail: Cons { head: false, tail: Nil { class: [class Nil] } } +//│ s3 = } + + +// *** Zipping *** + +zip of Nil +//│ = Nil { class: [class Nil] } + +zip of s1 +//│ > Cons { +//│ > head: [ 1 ], +//│ > tail: Cons { head: [ 2 ], tail: Cons { head: [Array], tail: [Nil] } } +//│ = } + +zip of s1, s2 +//│ > Cons { +//│ > head: [ 1, 'a' ], +//│ > tail: Cons { head: [ 2, 'b' ], tail: Cons { head: [Array], tail: [Nil] } } +//│ = } + +zip of s1, s3 +//│ > Cons { +//│ > head: [ 1, true ], +//│ > tail: Cons { +//│ > head: [ 2, false ], +//│ > tail: Cons { head: [Array], tail: [Nil] } +//│ > } +//│ = } + +s1 zip(s2) +//│ > Cons { +//│ > head: [ 1, 'a' ], +//│ > tail: Cons { head: [ 2, 'b' ], tail: Cons { head: [Array], tail: [Nil] } } +//│ = } + +print of ... +s1 zip of s2, s3 +//│ > Cons(1,a,true, Cons(2,b,false, Cons(3,c, Nil))) + +