diff --git a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala index dd7f56ae3c..73338db554 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala @@ -4,6 +4,7 @@ package bbml import scala.collection.mutable.{HashSet, HashMap, ListBuffer} import scala.annotation.tailrec +import sourcecode.{FileName, Line, Name} import mlscript.utils.*, shorthands.* import utils.* @@ -73,7 +74,7 @@ object BbCtx: end BbCtx -class BBTyper(using elState: Elaborator.State, tl: TL): +class BBTyper(using elState: Elaborator.State, tl: TL)(using Ctx): import tl.{trace, log} private val infVarState = new InfVarUid.State() @@ -100,7 +101,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL): state.lowerBounds = ctx.getRegEnv :: Nil InfVar(ctx.lvl, infVarState.nextUid, state, false)(sym, "") - private def error(msg: Ls[Message -> Opt[Loc]])(using BbCtx) = + private def error(using Line, FileName, Name, Raise)(msg: Ls[Message -> Opt[Loc]])(using BbCtx) = raise(ErrorReport(msg)) Bot // TODO: error type? @@ -258,7 +259,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL): val res = freshVar(new TempSymbol(S(blk), "ctx"))(using ctx) constrain(bodyCtx, sk | res) (bodyTy, rhsCtx | res, rhsEff | bodyEff) - case Term.IfLike(Keyword.`if`, Split.Let(_, cond, Split.Cons(Branch(_, FlatPattern.Lit(BoolLit(true)), Split.Else(cons)), Split.Else(alts)))) => + case Term.IfLike(Keyword.`if`, SimpleSplit.IfThenElse(cond, cons, alts)) => val (condTy, condCtx, condEff) = typeCode(cond) val (consTy, consCtx, consEff) = typeCode(cons) val (altsTy, altsCtx, altsEff) = typeCode(alts) @@ -363,8 +364,8 @@ class BBTyper(using elState: Elaborator.State, tl: TL): given BbCtx = nextCtx constrain(ascribe(term, skolemize(pt))._2, Bot) // * never generalize terms with effects (pt, Bot) - case (Term.IfLike(Keyword.`if`, branches), ty) => // * propagate - typeSplit(branches, S(ty)) + case (Term.IfLike(Keyword.`if`, split), ty) => // * propagate + typeSplit(split.getExpandedSplit, S(ty)) case (Term.Asc(term, ty), rhs) => ascribe(term, typeType(ty)) ascribe(term, rhs) @@ -550,7 +551,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL): case Term.Asc(term, ty) => val res = typeType(ty)(using ctx) ascribe(term, res) - case Term.IfLike(Keyword.`if`, branches) => typeSplit(branches, N) + case Term.IfLike(Keyword.`if`, split) => typeSplit(split.getExpandedSplit, N) case reg @ Term.Region(sym, body) => val sk = freshReg(sym)(using ctx) val nestCtx = ctx.nestReg(sk) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index c8b9ccd103..111d41b148 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -47,7 +47,7 @@ sealed abstract class Block extends Product: // Note that the handler's LHS and body are not part of the current block, so we do not consider them here. case HandleBlock(lhs, res, par, args, cls, hdr, bod, rst) => rst.definedVars + res case TryBlock(sub, fin, rst) => sub.definedVars ++ fin.definedVars ++ rst.definedVars - case Label(lbl, bod, rst) => bod.definedVars ++ rst.definedVars + case Label(lbl, _, bod, rst) => bod.definedVars ++ rst.definedVars lazy val size: Int = this match case _: Return | _: Throw | _: End | _: Break | _: Continue => 1 @@ -59,7 +59,7 @@ sealed abstract class Block extends Product: 1 + arms.map(_._2.size).sum + dflt.map(_.size).getOrElse(0) + rst.size case Define(_, rst) => 1 + rst.size case TryBlock(sub, fin, rst) => 1 + sub.size + fin.size + rst.size - case Label(_, bod, rst) => 1 + bod.size + rst.size + case Label(_, _, bod, rst) => 1 + bod.size + rst.size case HandleBlock(lhs, res, par, args, cls, handlers, bdy, rst) => 1 + handlers.map(_.body.size).sum + bdy.size + rst.size // TODO conserve if no changes @@ -74,7 +74,7 @@ sealed abstract class Block extends Product: Match(scrut, arms.map(_ -> _.mapTail(f)), dflt.map(_.mapTail(f)), rst) case Match(scrut, arms, dflt, rst) => Match(scrut, arms, dflt, rst.mapTail(f)) - case Label(label, body, rest) => Label(label, body, rest.mapTail(f)) + case Label(label, loop, body, rest) => Label(label, loop, body.mapTail(f), rest.mapTail(f)) case af @ AssignField(lhs, nme, rhs, rest) => AssignField(lhs, nme, rhs, rest.mapTail(f))(af.symbol) case adf @ AssignDynField(lhs, fld, arrayIdx, rhs, rest) => @@ -89,7 +89,7 @@ sealed abstract class Block extends Product: (pat, arm) => arm.freeVars -- pat.freeVars case Return(res, implct) => res.freeVars case Throw(exc) => exc.freeVars - case Label(label, body, rest) => (body.freeVars - label) ++ rest.freeVars + case Label(label, _, body, rest) => (body.freeVars - label) ++ rest.freeVars case Break(label) => Set(label) case Continue(label) => Set(label) case Begin(sub, rest) => sub.freeVars ++ rest.freeVars @@ -109,7 +109,7 @@ sealed abstract class Block extends Product: (pat, arm) => arm.freeVarsLLIR -- pat.freeVarsLLIR case Return(res, implct) => res.freeVarsLLIR case Throw(exc) => exc.freeVarsLLIR - case Label(label, body, rest) => (body.freeVarsLLIR - label) ++ rest.freeVarsLLIR + case Label(label, _, body, rest) => (body.freeVarsLLIR - label) ++ rest.freeVarsLLIR case Break(label) => Set.empty case Continue(label) => Set.empty case Begin(sub, rest) => sub.freeVarsLLIR ++ rest.freeVarsLLIR @@ -131,7 +131,7 @@ sealed abstract class Block extends Product: case AssignDynField(_, _, _, rhs, rest) => rhs.subBlocks ::: rest :: Nil case Define(d, rest) => d.subBlocks ::: rest :: Nil case HandleBlock(_, _, par, args, _, handlers, body, rest) => par.subBlocks ++ args.flatMap(_.subBlocks) ++ handlers.map(_.body) :+ body :+ rest - case Label(_, body, rest) => body :: rest :: Nil + case Label(_, _, body, rest) => body :: rest :: Nil // TODO rm Lam from values and thus the need for these cases case Return(r, _) => r.subBlocks @@ -176,12 +176,12 @@ sealed abstract class Block extends Product: then this else Match(scrut, newArms, newDflt, newRest) - case Label(label, body, rest) => + case Label(label, loop, body, rest) => val newBody = body.flattened val newRest = rest.flatten(k) if (newBody is body) && (newRest is rest) then this - else Label(label, newBody, newRest) + else Label(label, loop, newBody, newRest) case Begin(sub, rest) => sub.flatten(_ => rest.flatten(k)) @@ -266,7 +266,7 @@ case class Return(res: Result, implct: Bool) extends BlockTail case class Throw(exc: Result) extends BlockTail -case class Label(label: Local, body: Block, rest: Block) extends Block +case class Label(label: Local, loop: Bool, body: Block, rest: Block) extends Block case class Break(label: Local) extends BlockTail case class Continue(label: Local) extends BlockTail @@ -593,7 +593,7 @@ extension (k: Block => Block) def end = k.rest(End()) def ifthen(scrut: Path, cse: Case, trm: Block, els: Opt[Block] = N): Block => Block = k.chain(Match(scrut, cse -> trm :: Nil, els, _)) - def label(label: Local, body: Block) = k.chain(Label(label, body, _)) + def label(label: Local, loop: Bool, body: Block) = k.chain(Label(label, loop, body, _)) def ret(r: Result) = k.rest(Return(r, false)) def staticif(b: Boolean, f: (Block => Block) => (Block => Block)) = if b then k.transform(f) else k def foldLeft[A](xs: Iterable[A])(f: (Block => Block, A) => Block => Block) = xs.foldLeft(k)(f) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index af94cd69a9..b76723ae1f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -44,11 +44,11 @@ class BlockTransformer(subst: SymbolSubst): (arms2 is arms) && (dflt2 is dflt) && (rst2 is rst) then b else Match(scrut2, arms2, dflt2, rst2) - case Label(lbl, bod, rst) => + case Label(lbl, loop, bod, rst) => val lbl2 = applyLocal(lbl) val bod2 = applySubBlock(bod) val rst2 = applySubBlock(rst) - if (lbl2 is lbl) && (bod2 is bod) && (rst2 is rst) then b else Label(lbl2, bod2, rst2) + if (lbl2 is lbl) && (bod2 is bod) && (rst2 is rst) then b else Label(lbl2, loop, bod2, rst2) case Begin(sub, rst) => val sub2 = applySubBlock(sub) val rst2 = applySubBlock(rst) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala index 3729d1ed82..69ebdc9df7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala @@ -31,7 +31,7 @@ class BlockTraverser: applyCase(arm._1); applySubBlock(arm._2) dflt.foreach(applySubBlock) applySubBlock(rst) - case Label(lbl, bod, rst) => applyLocal(lbl); applySubBlock(bod); applySubBlock(rst) + case Label(lbl, loop, bod, rst) => applyLocal(lbl); applySubBlock(bod); applySubBlock(rst) case Begin(sub, rst) => applySubBlock(sub); applySubBlock(rst) case TryBlock(sub, fin, rst) => applySubBlock(sub); applySubBlock(fin); applySubBlock(rst) case Assign(l, r, rst) => applyLocal(l); applyResult(r); applySubBlock(rst) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 6971def4fa..1713e7432c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -227,7 +227,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, Match(scrut, newArms, dfltParts.map(_.head), StateTransition(restId)), BlockState(restId, restParts.head, N) :: states ) - case l @ Label(label, body, rest) => + case l @ Label(label, loop, body, rest) => val startId = freshId() // start of body val PartRet(restNew, restParts) = go(rest) @@ -586,7 +586,7 @@ class HandlerLowering(paths: HandlerPaths, opt: EffectHandlers)(using TL, Raise, End() ) - val lbl = blockBuilder.label(loopLbl, mainMatchBlk).rest(End()) + val lbl = blockBuilder.label(loopLbl, loop = true, mainMatchBlk).rest(End()) val resumedVal = VarSymbol(Tree.Ident("value$")) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 273c473ec6..edc30342d4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -66,7 +66,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): val lowerHandlers: Bool = config.effectHandlers.isDefined val lift: Bool = config.liftDefns.isDefined - private lazy val unreachableFn = + lazy val unreachableFn = Select(Value.Ref(State.runtimeSymbol), Tree.Ident("unreachable"))(N) def unit: Path = @@ -519,143 +519,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx): lamDef, k(lamSym |> Value.Ref.apply)) - /* - case t @ st.If(Split.Let(sym, trm, tail)) => - // term(st.Blk(semantics.LetDecl(sym) :: semantics.DefineVar(sym, trm) :: Nil, st.If(tail)(t.normalized)))(k) - term(trm): r => - Assign(sym, r, term(st.If(tail)(t.normalized))(k)) - // TODO rm - case st.If(Split.Cons( - Branch(scrut, Pattern.LitPat(tru @ Tree.BoolLit(true)), Split.Else(thn)), - restSplit - )) => - - val elseBranch = restSplit match - case Split.Else(els) => S(els) - case Split.Nil => N - - elseBranch match - case S(els) if k.isInstanceOf[Ret] => - subTerm(scrut): sr => - // Match(sr, Case.Lit(tru) -> term(thn)(k) :: Nil, - // Some(term(els)(k)), - // Unreachable - // ) - Match(sr, Case.Lit(tru) -> term(thn)(k) :: Nil, - N, - term(els)(k) - ) - case _ => - val l = new TempSymbol(S(t)) - subTerm(scrut): sr => - Match(sr, Case.Lit(tru) -> subTerm(thn)(r => Assign(l, r, End())) :: Nil, - elseBranch.map(els => subTerm(els)(r => Assign(l, r, End()))), - k(Value.Ref(l)) - ) - */ + case iftrm: st.IfLike => ucs.Normalization(this)(iftrm)(k) - case iftrm: st.IfLike => - - tl.log(s"${iftrm.kw} $iftrm") - - val isIf = iftrm.kw match - case syntax.Keyword.`if` => true - case syntax.Keyword.`while` => false - val isWhile = !isIf - - var usesResTmp = false - lazy val l = - usesResTmp = true - new TempSymbol(S(t)) - - lazy val lbl = - new TempSymbol(S(t)) - - def go(split: Split, topLevel: Bool)(using Subst): Block = split match - case Split.Let(sym, trm, tl) => - term_nonTail(trm): r => - Assign(sym, r, go(tl, topLevel)) - case Split.Cons(Branch(scrut, pat, tail), restSplit) => - subTerm_nonTail(scrut): sr => - tl.log(s"Binding scrut $scrut to $sr (${summon[Subst].map})") - // val cse = - def mkMatch(cse: Case -> Block) = Match(sr, cse :: Nil, - S(go(restSplit, topLevel = true)), - End() - ) - pat match - case FlatPattern.Lit(lit) => mkMatch(Case.Lit(lit) -> go(tail, topLevel = false)) - case FlatPattern.ClassLike(ctor, argsOpt, _mode, _refined) => - /** Make a continuation that creates the match. */ - def k(ctorSym: ClassLikeSymbol, clsParams: Ls[TermSymbol])(st: Path): Block = - val args = argsOpt.map(_.map(_.scrutinee)).getOrElse(Nil) - // Normalization should reject cases where the user provides - // more sub-patterns than there are actual class parameters. - assert(argsOpt.isEmpty || args.length <= clsParams.length, (argsOpt, clsParams)) - def mkArgs(args: Ls[TermSymbol -> BlockLocalSymbol])(using Subst): Case -> Block = args match - case Nil => - Case.Cls(ctorSym, st) -> go(tail, topLevel = false) - case (param, arg) :: args => - val (cse, blk) = mkArgs(args) - (cse, Assign(arg, Select(sr, new Tree.Ident(param.id.name).withLocOf(arg))(S(param)), blk)) - mkMatch(mkArgs(clsParams.iterator.zip(args).toList)) - ctor.symbol.flatMap(_.asClsOrMod) match - case S(cls: ClassSymbol) if ctx.builtins.virtualClasses contains cls => - // [invariant:0] Some classes (e.g., `Int`) from `Prelude` do - // not exist at runtime. If we do lowering on `trm`, backends - // (e.g., `JSBuilder`) will not be able to handle the corresponding selections. - // In this case the second parameter of `Case.Cls` will not be used. - // So we do not elaborate `ctor` when the `cls` is virtual - // and use it `Predef.unreachable` here. - k(cls, Nil)(unreachableFn) - case S(cls: ClassSymbol) => subTerm_nonTail(ctor)(k(cls, cls.tree.clsParams)) - case S(mod: ModuleOrObjectSymbol) => subTerm_nonTail(ctor)(k(mod, Nil)) - case N => - // Normalization have already checked the constructor - // resolves to a class or module. Branches with unresolved - // constructors should have been removed. - lastWords("Pattern.ClassLike: constructor is neither a class nor a module") - case FlatPattern.Tuple(len, inf) => mkMatch(Case.Tup(len, inf) -> go(tail, topLevel = false)) - case FlatPattern.Record(entries) => - val objectSym = ctx.builtins.Object - mkMatch( // checking that we have an object - Case.Cls(objectSym, Value.Ref(BuiltinSymbol(objectSym.nme, false, false, true, false))), - entries.foldRight(go(tail, topLevel = false)): - case ((fieldName, fieldSymbol), blk) => - mkMatch( - Case.Field(fieldName, safe = true), // we know we have an object, no need to check again - Assign(fieldSymbol, Select(sr, fieldName)(N), blk) - ) - ) - case Split.Else(els) => - if k.isInstanceOf[TailOp] && isIf then term_nonTail(els)(k) - else - term_nonTail(els): r => - Assign(l, r, - if isWhile && !topLevel then Continue(lbl) - else End() - ) - case Split.End => - Throw(Instantiate(mut = false, Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Error"))(N), - Value.Lit(syntax.Tree.StrLit("match error")).asArg :: Nil)) // TODO add failed-match scrutinee info - - val normalize = ucs.Normalization() - val normalized = tl.scoped("ucs:normalize"): - normalize(iftrm.desugared) - tl.scoped("ucs:normalized"): - tl.log(s"Normalized:\n${normalized.prettyPrint}") - - if k.isInstanceOf[TailOp] && isIf then go(normalized, topLevel = true) - else - val body = if isWhile - then Label(lbl, go(normalized, topLevel = true), End()) - else go(normalized, topLevel = true) - Begin( - body, - if usesResTmp then k(Value.Ref(l)) - else k(unit) // * it seems this currently never happens - ) + case iftrm: st.SynthIf => ucs.Normalization(this)(iftrm)(k) case sel @ Sel(prefix, nme) => setupSelection(prefix, nme, sel.sym)(k) @@ -889,7 +756,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): .assign(arrSym, Tuple(mut = false, (l4 :: l5 :: Nil).map(s => Value.Ref(s).asArg))) .rest(setupTerm("Blk", Value.Ref(arrSym) :: Value.Ref(l3) :: Nil)(k)) } - case IfLike(syntax.Keyword.`if`, split) => quoteSplit(split): r => + case IfLike(syntax.Keyword.`if`, split) => quoteSplit(split.getExpandedSplit): r => val l = new TempSymbol(N) Assign(l, r, setupTerm("IfLike", setupQuotedKeyword("If") :: Value.Ref(l) :: Nil)(k)) case Unquoted(body) => term(body)(k) @@ -921,18 +788,18 @@ class Lowering()(using Config, TL, Raise, State, Ctx): (mtds, publicFlds, privateFlds, ctor) /** Compile the pattern definition into `unapply` and `unapplyStringPrefix` - * methods using the `NaiveCompiler`, which transliterate the pattern into + * methods using the `SplitCompiler`, which transliterate the pattern into * UCS splits that backtrack without any optimizations. */ def compilePatternMethods(defn: PatternDef)(using Subst): // The return type is intended to be consistent with `gatherMembers` (Ls[FunDefn], Ls[BlockMemberSymbol -> TermSymbol], Ls[TermSymbol], Block) = - val compiler = new ups.NaiveCompiler + val compiler = new ups.SplitCompiler val methods = compiler.compilePattern(defn) - val mtds = methods - .flatMap: td => - td.body.map: bod => - val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) - FunDefn(td.owner, td.sym, paramLists, bodyBlock) + // We only need `owner`, `sym`, `params` and `body` + val mtds = methods.map: + case (sym, params, split) => + val (paramLists, bodyBlock) = setupFunctionDef(params :: Nil, split, S(sym.nme)) + FunDefn(N, sym, paramLists, bodyBlock) (mtds, Nil, Nil, End()) def args(elems: Ls[Elem])(k: Ls[Arg] => Block)(using Subst): Block = @@ -1050,6 +917,11 @@ class Lowering()(using Config, TL, Raise, State, Ctx): (using Subst): (List[ParamList], Block) = (paramLists, returnedTerm(bodyTerm)) + /** Same as the other overload, but expect the body to be a `Split`. */ + def setupFunctionDef(paramLists: List[ParamList], bodySplit: Split, name: Option[Str]) + (using Subst): (List[ParamList], Block) = + (paramLists, ucs.Normalization(this)(bodySplit)(Ret)) + def reportAnnotations(target: Statement, annotations: Ls[Annot]): Unit = annotations.foreach: case Annot.Untyped => () diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala index 20963e8629..5695dcf9f8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala @@ -37,9 +37,9 @@ object Printer: doc"match ${mkDocument(scrut)} #{ # ${docCases} # else #{ # ${docDefault} #} #} # in # ${mkDocument(rest)}" case Return(res, implct) => doc"return ${mkDocument(res)}" case Throw(exc) => doc"throw ${mkDocument(exc)}" - case Label(label, body, rest) => + case Label(label, loop, body, rest) => val l2 = summon[Scope].allocateName(label) - doc"label $l2 = ${mkDocument(body)} in # ${mkDocument(rest)}" + doc"labelled ${if loop then "loop" else "block"} $l2 = ${mkDocument(body)} in # ${mkDocument(rest)}" case Break(label) => doc"break ${getVar(label)}" case Continue(label) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala index bcad5f6ae7..e63d347ca5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala @@ -135,7 +135,7 @@ class UsedVarAnalyzer(b: Block, handlerPaths: Opt[HandlerPaths])(using State): accessed = accessed.addMutated(lhs) applyResult(rhs) applyBlock(rest) - case Label(label, body, rest) => + case Label(label, loop, body, rest) => accessed ++= blkAccessesShallow(body, S(label)) applyBlock(rest) case _ => super.applyBlock(b) @@ -304,7 +304,7 @@ class UsedVarAnalyzer(b: Block, handlerPaths: Opt[HandlerPaths])(using State): infos.map(merge) // IMPORTANT: rec all first, then merge, since each branch is mutually exclusive dfltInfo.map(merge) applyBlock(rest) - case Label(label, body, rest) => + case Label(label, loop, body, rest) => // for now, if the loop body mutates a variable and that variable is accessed or mutated by a defn, // or if it reads a variable that is later mutated by an instance inside the loop, // we put it in a capture. this preserves the current semantics of the IR (even though it's incorrect). diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala index 286f9857c3..d910283054 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -487,13 +487,13 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: case Continue(lbl) => doc" # continue ${getVar(lbl, lbl.toLoc)}${mkSemi}" - case Label(lbl, bod, rst) => + case Label(lbl, loop, bod, rst) => scope.allocateName(lbl) // [fixme:0] TODO check scope and allocate local variables here (see: https://github.com/hkust-taco/mlscript/pull/293#issuecomment-2792229849) - doc" # ${getVar(lbl, lbl.toLoc)}: while (true) " :: braced { - returningTerm(bod, endSemi = true) :/: doc"break;" + doc" # ${getVar(lbl, lbl.toLoc)}:${if loop then doc" while (true)" else ""} " :: braced { + returningTerm(bod, endSemi = true) :: (if loop then doc" # break;" else doc"") } :: returningTerm(rst, endSemi) case TryBlock(sub, fin, rst) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala index 290434f9df..1638fa98b3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala @@ -478,7 +478,7 @@ final class LlirBuilder(using Elaborator.State)(tl: TraceLogger, uid: FreshInt): Ls(Arg(N, Value.Lit(Tree.StrLit(e)))))) if ident.name === "Error" => Node.Panic(e) - case Label(label, body, rest) => TODO("Label not supported") + case Label(label, loop, body, rest) => TODO("Label not supported") case Break(label) => TODO("Break not supported") case Continue(label) => TODO("Continue not supported") case Begin(sub, rest) => @@ -546,7 +546,7 @@ final class LlirBuilder(using Elaborator.State)(tl: TraceLogger, uid: FreshInt): case Match(scrut, arms, dflt, rest) => applyBlock(rest) case Return(res, implct) => case Throw(exc) => - case Label(label, body, rest) => applyBlock(rest) + case Label(label, loop, body, rest) => applyBlock(rest) case Break(label) => case Continue(label) => case Begin(sub, rest) => applyBlock(rest) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala index 070cdeee1a..12d71cec82 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala @@ -24,7 +24,7 @@ trait BlockImpl(using Elaborator.State): td.name match case L(_) => head case R(name) => - InfixApp(head, syntax.Keyword.`extends`, name) + InfixApp(head, Keywrd(syntax.Keyword.`extends`), name) , N ))) ) ::: desug(stmts) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index ce66d08ffe..5e8d25c854 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -286,7 +286,7 @@ import Elaborator.* class Elaborator(val tl: TraceLogger, val wd: os.Path, val prelude: Ctx) (using val raise: Raise, val state: State) -extends Importer: +extends Importer with ucs.SplitElaborator: import tl.* def mkLetBinding(kw: Tree.Keywrd[?], sym: LocalSymbol, rhs: Term, annotations: Ls[Annot]): Ls[Statement] = @@ -434,7 +434,7 @@ extends Importer: Term.WildcardTy(S(subterm(arg1)), S(subterm(arg2))) case arg => subterm(arg) })(N).withLocOf(tree) - case InfixApp(TyTup(tvs), Keyword.`->`, body) => + case InfixApp(TyTup(tvs), Keywrd(Keyword.`->`), body) => val boundVars = mutable.HashMap.empty[Str, VarSymbol] def genSym(id: Tree.Ident) = val sym = VarSymbol(id) @@ -443,9 +443,9 @@ extends Importer: sym val syms = (tvs.collect: case id: Tree.Ident => (genSym(id), N, N) - case InfixApp(id: Tree.Ident, Keyword.`extends`, ub) => (genSym(id), S(ub), N) - case InfixApp(id: Tree.Ident, Keyword.`restricts`, lb) => (genSym(id), N, S(lb)) - case InfixApp(InfixApp(id: Tree.Ident, Keyword.`extends`, ub), Keyword.`restricts`, lb) => (genSym(id), S(ub), S(lb)) + case InfixApp(id: Tree.Ident, Keywrd(Keyword.`extends`), ub) => (genSym(id), S(ub), N) + case InfixApp(id: Tree.Ident, Keywrd(Keyword.`restricts`), lb) => (genSym(id), N, S(lb)) + case InfixApp(InfixApp(id: Tree.Ident, Keywrd(Keyword.`extends`), ub), Keywrd(Keyword.`restricts`), lb) => (genSym(id), S(ub), S(lb)) ) val outer = (tvs.collect: case Outer(S(name: Tree.Ident)) => genSym(name) @@ -466,26 +466,23 @@ extends Importer: case (sym, ub, lb) => QuantVar(sym, ub.map(ub => subterm(ub)), lb.map(lb => subterm(lb))) Term.Forall(bds, outer, subterm(body)) - case InfixApp(lhs, Keyword.`->`, Effectful(eff, rhs)) => + case InfixApp(lhs, Keywrd(Keyword.`->`), Effectful(eff, rhs)) => Term.FunTy(subterm(lhs), subterm(rhs), S(subterm(eff))) - case InfixApp(lhs, Keyword.`->`, rhs) => + case InfixApp(lhs, Keywrd(Keyword.`->`), rhs) => Term.FunTy(subterm(lhs), subterm(rhs), N) - case InfixApp(lhs, Keyword.`=>`, rhs) => + case InfixApp(lhs, Keywrd(Keyword.`=>`), rhs) => ctx.nest(OuterCtx.LambdaOrHandlerBlock).givenIn: val (syms, nestCtx) = funParams(lhs) Term.Lam(syms, term(rhs)(using nestCtx)) - case InfixApp(lhs, Keyword.`as`, rhs) => + case InfixApp(lhs, Keywrd(Keyword.`as`), rhs) => Term.Asc(subterm(lhs), subterm(rhs)) - case InfixApp(lhs, Keyword.`:`, rhs) => + case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => block(tree :: Nil, hasResult = false)._1 case PrefixApp(kw @ Keywrd(Keyword.`not`), rhs) => Term.App(State.builtinOpsMap("!").ref(new Ident("not").withLocOf(kw)), Term.Tup( PlainFld(subterm(rhs, inAppPrefix = true)) :: Nil)(DummyTup))(DummyApp, N, FlowSymbol("not-app")) - case tree @ InfixApp(lhs, Keyword.`is` | Keyword.`and` | Keyword.`or`, rhs) => - val des = new ucs.Desugarer(this)(tree) - scoped("ucs:desugared"): - log(s"Desugared:\n${des.prettyPrint}") - Term.IfLike(Keyword.`if`, des) + case tree @ InfixApp(lhs, Keywrd(Keyword.`is` | Keyword.`and` | Keyword.`or`), rhs) => + Term.IfLike(Keyword.`if`, shorthandSplit(tree)) case InfixApp(lhs, kw, rhs) => raise: ErrorReport(msg"Unexpected infix use of keyword '${kw.name}' here" -> tree.toLoc :: Nil) @@ -634,21 +631,14 @@ extends Importer: // case _ => // raise(ErrorReport(msg"Illegal new expression." -> tree.toLoc :: Nil)) - case tree @ IfLike(kw, split) => - val desugared = new ucs.Desugarer(this)(tree) - scoped("ucs:desugared"): - log(s"Desugared:\n${desugared.prettyPrint}") - Term.IfLike(kw.kw, desugared) + case tree: IfLike => Term.IfLike(tree.kw.kw, split(tree)) case Quoted(body) => Term.Quoted(subterm(body)) case Unquoted(body) => Term.Unquoted(subterm(body)) - case tree @ Case(kw, branches) => + case tree @ Case(kw, _) => val scrut = VarSymbol(Ident("caseScrut")) - val des = new ucs.Desugarer(this)(tree, scrut) - scoped("ucs:desugared"): - log(s"Desugared:\n${des.prettyPrint}") - Term.Lam(PlainParamList( - Param(FldFlags.empty, scrut, N, Modulefulness.none) :: Nil - ), Term.IfLike(Keyword.`if`, des)).mkLocWith(kw) + val body = Term.IfLike(Keyword.`if`, caseSplit(scrut, tree)) + val params = Param(FldFlags.empty, scrut, N, Modulefulness.none) :: Nil + Term.Lam(PlainParamList(params), body).mkLocWith(kw) case PrefixApp(kw @ Keywrd(Keyword.`return`), body) => ctx.getRetHandler match case ReturnHandler.Required(sym) => @@ -787,9 +777,9 @@ extends Importer: case u: Under => subterm(tree) // Note: currently `f(a, _, c)` is treated the same as `f of a, _, c` case _ => term(tree) def fld(tree: Tree)(using UnderCtx): Ctxl[Elem] = tree match - case InfixApp(id: Ident, Keyword.`:`, rhs) => + case InfixApp(id: Ident, Keywrd(Keyword.`:`), rhs) => Fld(FldFlags.empty, Term.Lit(StrLit(id.name).withLocOf(id)), S(arg(rhs))) - case InfixApp(lhs, Keyword.`:`, rhs) => + case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) => Fld(FldFlags.empty, term(lhs), S(arg(rhs))) case Spread(Keywrd(Keyword.`..`), S(trm)) => Spd(false, arg(trm)) @@ -979,7 +969,7 @@ extends Importer: case Spread(Keywrd(Keyword.`...`), S(body)) :: sts => reportUnusedAnnotations go(sts, Nil, RcdSpread(term(body)) :: acc) - case InfixApp(lhs, Keyword.`:`, rhs) :: sts => + case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) :: sts => var newCtx = ctx val rhs_t = rhs match case _: Under => subterm(rhs) @@ -1015,7 +1005,7 @@ extends Importer: val newAcc = rhso match case S(rhs) => val rrhs = tups.foldRight(rhs): - InfixApp(_, Keyword.`=>`, _) + InfixApp(_, Keywrd(Keyword.`=>`), _) mkLetBinding(kw, sym, term(rrhs), annotations) reverse_::: acc case N => if tups.nonEmpty then @@ -1040,7 +1030,7 @@ extends Importer: raise(ErrorReport(msg"Name not found: ${id.name}" -> id.toLoc :: Nil)) go(sts, Nil, Term.Error :: acc) case App(base, args) => - go(Def(base, InfixApp(args, Keyword.`=>`, rhs)) :: sts, Nil, acc) + go(Def(base, InfixApp(args, Keywrd(Keyword.`=>`), rhs)) :: sts, Nil, acc) case _ => raise(ErrorReport(msg"Unrecognized definitional assignment left-hand side: ${lhs.describe}" -> lhs.toLoc :: Nil)) // TODO BE @@ -1251,11 +1241,7 @@ extends Importer: // Pattern definition should not have a body like class definition. assert(body.isEmpty) val ps = pss.headOption - // The following iteration filters out: - // 1. pattern parameters, e.g., `T` in `pattern Nullable(pattern T) = ...`; - // 2. extraction bindings, e.g., `value` in `pattern Middle(value) = ...`; and - // 3. the rest are reported as invalid parameters. - val (patternParams, extractionParams) = ps.fold((Nil, Nil)): + val allParams = ps.fold(Nil): _.params.flatMap: // Only `pat` flag is `true`. case p @ Param(flags = FldFlags(false, false, true, false)) => S(p) @@ -1264,7 +1250,11 @@ extends Importer: case Param(flags, sym, _, _) => raise(ErrorReport(msg"Unexpected pattern parameter ${sym.name} with modifiers: ${flags.show}" -> sym.toLoc :: Nil)) N - .partition(_.flags.pat) + // The following iteration filters out: + // 1. pattern parameters, e.g., `T` in `pattern Nullable(pattern T) = ...`; + // 2. extraction bindings, e.g., `value` in `pattern Middle(value) = ...`; and + // 3. the rest are reported as invalid parameters. + val (patternParams, extractionParams) = allParams.partition(_.flags.pat) log(s"`${patSym.nme}`'s pattern parameters: ${patternParams.mkString("[", ", ", "]")}") log(s"`${patSym.nme}`'s extraction parameters: ${extractionParams.mkString("[", ", ", "]")}") // Empty pattern body is considered as wildcard patterns. @@ -1287,7 +1277,7 @@ extends Importer: scoped("ucs:ups:tree")(log(s"elaborated pattern body: ${pat.showAsTree}")) // `paramsOpt` is set to `N` because we don't want parameters to // appear in the generated class's constructor. - val pd = PatternDef(owner, patSym, sym, tps, + val pd = PatternDef(owner, patSym, sym, tps, allParams, patternParams, extractionParams, pat, annotations) patSym.defn = S(pd) pd @@ -1428,46 +1418,95 @@ extends Importer: case N => N def pattern(t: Tree): Ctxl[Pattern] = - import ucs.Desugarer.{Ctor, unapply}, Keyword.*, Pattern.*, InvalidReason.* - import ups.NaiveCompiler.isInvalidStringBounds, ucs.extractors.to + import ucs.{Ctor, unapply, error}, ucs.extractors.*, Keyword.*, Pattern.*, InvalidReason.* given TraceLogger = tl + /** String range bounds must be single characters. */ + def isInvalidStringBounds(lo: StrLit, hi: StrLit)(using Raise): Bool = + val ds = collection.mutable.Buffer.empty[(Message, Option[Loc])] + if lo.value.length != 1 then + ds += msg"The lower bound of character ranges must be a single character." -> lo.toLoc + if hi.value.length != 1 then + ds += msg"The upper bound of character ranges must be a single character." -> hi.toLoc + if ds.nonEmpty then error(ds.toSeq*) + ds.nonEmpty + /** Resolve an identifier. We need to perform a very preliminary check to + * determine whether this identifier refers to a pattern, a class, an + * object, or creates a new binding. + * + * FIXME: This routine is insufficient to look up definitions defined + * later in the program. */ + def ident(id: Ident)(using Ctx): Ctxl[Opt[Term]] = scoped("ucs:pattern:resolution"): + log(s"resolve ${id}") + ctx.get(id.name) match + case S(elem) => + log("has elem!") + elem.symbol.flatMap: + case vs: VarSymbol => vs.decl match + case S(d) if d.isPatternConstructor => S(elem.ref(id)) + case S(_) | N => N + case sym: Symbol => sym.asCls.orElse(sym.asObj).orElse(sym.asPat) match + case S(_) => S(elem.ref(id)) + case N => N + case N => + state.builtinOpsMap.get(id.name) match + case S(bi) => S(bi.ref(id)) + case N => N /** Elaborate arrow patterns like `p => t`. Meanwhile, report all invalid * variables we found in `p`. */ def arrow(lhs: Tree, rhs: Tree): Ctxl[Pattern] = val pattern = go(lhs) - val termCtx = ctx ++ pattern.variables.allocate + // The symbol allocated here will be bound in `split` to the values + // destructed from the scrutinee. + val variables = pattern.variables.allocate + // The `VarSymbol` created here is used as the parameters of the + // lambda expression generated by extraction (such as `p => t`). + // To avoid repeating `t`, we generate a lambda function before the + // branch starts, which will be called if the `split` succeeds. + // - `contextEntries` will be added to the context + // - `correspondence` is mapping from symbols representing variables + // in the pattern to symbols for parameters to be used in the `term`. + val (contextEntries, correspondence) = variables.iterator.map: + case (name, symbol) => + // We create a symbol specifically for `Param` for each variable to + // avoid repetitively declare symbols in `Scope` during code + // generation, which triggers the assertion in `Scope.addToBindings`. + val parameterSymbol = VarSymbol(new Ident(symbol.name)) + (name -> parameterSymbol, symbol -> parameterSymbol) + .toList.unzip pattern.variables.report // Report all invalid variables we found in `pattern`. - Transform(pattern, term(rhs)(using termCtx)) + Transform(pattern, correspondence, term(rhs)(using ctx ++ contextEntries)) /** Elaborate tuple patterns like `[p1, p2, ...ps, pn]`. */ def tuple(ts: Ls[Tree]): Ctxl[Pattern.Tuple] = - // We are accumulating three components: the leading patterns, the spread - // pattern, and the trailing patterns. - val z = (Ls[Pattern](), N: Opt[Pattern], Ls[Pattern]()) - val (leading, spread, trailing) = ts.foldLeft(z): - case (acc @ (_, S(_), _), Spread(Keywrd(`...`), _)) => - // Found two `...p` in the same tuple pattern. Report an error. + // We are accumulating two components: the leading patterns, the spred + // part including the trailing patterns. + val z = (Ls[Pattern](), N: Opt[(SpreadKind, Pattern, Ls[Pattern])]) + val (leading, spread) = ts.foldLeft(z): + case (acc @ (_, S(_)), t: Spread) => + // Found two `...p`s in the same tuple pattern. Report an error. raise(ErrorReport(msg"Multiple spread patterns are not supported." -> t.toLoc :: Nil)) acc // Do not modify the accumulator and skip this `Spread`. - case ((leading, N, trailing), Spread(Keywrd(`...`), S(t))) => + case ((leading, N), Spread(ellipsis, S(t))) => // Found `...p`, elaborate `p` and assign it to the spread pattern. - (leading, S(go(t)), trailing) - case ((leading, N, trailing), Spread(Keywrd(`...`), N)) => + (leading, S(SpreadKind.fromKw(ellipsis), go(t), Nil)) + case ((leading, N), Spread(ellipsis, N)) => // Found `...` (no following patterns), which means the spread part // will not be further matched. Set the spread pattern to `Wildcard`. - (leading, S(Wildcard()), trailing) - case ((leading, N, trailing), t) => + (leading, S(SpreadKind.fromKw(ellipsis), Wildcard(), Nil)) + case ((leading, N), t) => // Found a tuple field while the spread pattern is not set. Add the // elaborated pattern to the leading patterns. - (go(t) :: leading, N, trailing) - case ((leading, spread @ S(_), trailing), t) => + (go(t) :: leading, N) + case ((leading, S((spreadKind, spread, trailing))), t) => // Found a tuple field while the spread pattern has been set. Add the // elaborated pattern to the trailing patterns. - (leading, spread, go(t) :: trailing) - Tuple(leading.reverse, spread, trailing.reverse) + (leading, S((spreadKind, spread, go(t) :: trailing))) + Tuple(leading.reverse, spread) /** Elaborate record patterns like `(a: p1, b: p2, ...pn)`. */ def record(ps: Ls[Tree]): Ctxl[Pattern.Record] = val entries = ps.foldLeft(List[(Ident, Pattern)]()): - case (acc, InfixApp(id: Ident, Keyword.`:`, p)) => (id, go(p)) :: acc + case (acc, InfixApp(id: Ident, Keywrd(Keyword.`:`), p)) => (id, go(p)) :: acc + case (acc, InfixApp(key: StrLit, Keywrd(Keyword.`:`), p)) => + ((Ident(key.value): Ident).withLocOf(key), go(p)) :: acc case (acc, Pun(false, p)) => (p, Variable(p)) :: acc case (acc, t) => raise(ErrorReport(msg"Unexpected record property pattern." -> t.toLoc :: Nil)) @@ -1478,8 +1517,11 @@ extends Importer: case TypeDef(syntax.Pat, body, N) => L(go(body)) case _ => R(go(t)) def go(t: Tree): Ctxl[Pattern] = t match + // Annotated patterns like `@compile P`. + case Tree.Annotated(annotation, target) => + go(target).annotate(term(annotation), t.toLoc) // Brackets. - case Bra(BracketKind.Round, t) => go(t) + case Bra(BracketKind.Round | BracketKind.Curly, t) => go(t) // Tuple patterns like `[p1, p2, ...ps, pn]`. case TyTup(ps) => tuple(ps) case Tup(ps) => tuple(ps) @@ -1496,14 +1538,13 @@ extends Importer: Composition(op === "|", go(lhs), go(rhs)) // Constructor patterns with pattern arguments and arguments. case App(ctor: Ctor, Tup(argTrees)) => - val (patArgs, args) = argTrees.partitionMap(arg(_)) - Constructor(term(ctor), patArgs, S(args)) + Constructor(term(ctor), S(argTrees.map(go(_)))) // `[p1, p2, ...ps, pn] => term`: All patterns are in the `TyTup`. case (lhs: TyTup) `=>` rhs => arrow(lhs, rhs) // `pattern => term`: Note that `pattern` is wrapped in a `Tup`. case Tup(lhs) `=>` rhs => lhs match case p @ Pun(false, _) :: Nil => record(p) - case p @ InfixApp(_: Ident, Keyword.`:`, _) :: Nil => record(p) + case p @ InfixApp(_: Ident, Keywrd(Keyword.`:`), _) :: Nil => record(p) case lhs :: Nil => arrow(lhs, rhs) case _ :: _ | Nil => ??? // TODO: When is this case reached? case p as q => q match @@ -1517,13 +1558,18 @@ extends Importer: case N => go(p) binds id // Fallback to alias. // `p as q` where `q` is not an identifier is elaborated into chain. case _: Tree => Chain(go(p), go(q)) - case Under() => Pattern.Wildcard() + case p where t => + val q = go(p) + Guarded(q, term(t)(using ctx ++ q.variables.allocate)) + case Under() => Pattern.Wildcard().withLocOf(t) + // Singleton blocks like `{1}`. + case Block(p :: Nil) => go(p) // Record patterns like `(a: p1, b: p2, ...pn)`. case Block(ps) => record(ps) // A single pun pattern is a record pattern. case p @ Pun(false, _) => record(p :: Nil) // A single record field is a record pattern. - case p @ InfixApp(_, Keyword.`:`, _) => record(p :: Nil) + case p @ InfixApp(_, Keywrd(Keyword.`:`), _) => record(p :: Nil) // Range patterns. We can also desugar them into disjunctions of all the // literals in the range. case (lower: StrLit) to (incl, upper: StrLit) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index 1c40d35d57..b9f419a61f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -3,7 +3,8 @@ package semantics import mlscript.utils.*, shorthands.* import collection.immutable.HashMap, collection.mutable.Buffer -import syntax.Tree, Tree.Ident, Elaborator.State, Message.MessageContext, ucs.error +import syntax.{Keyword, SpreadKind, Tree}, Tree.{Ident, StrLit} +import Elaborator.State, Message.MessageContext, ucs.error import scala.annotation.tailrec, util.chaining.* import utils.{TraceLogger, tl} @@ -24,14 +25,6 @@ object Pattern: import InvalidReason.* - extension (aliases: Ls[Pattern.Alias]) - /** Allocate the symbol for the variable. This should be called in the - * elaborator and before elaborating the term from `Transform`. */ - def allocate(id: Ident)(using State): VarSymbol = - val symbol = VarSymbol(id) - aliases.foreach(_.symbol = symbol) - symbol - /** A set of variables that present in a pattern. * * These variables are represented by `Tree.Ident` because not every identifier @@ -50,13 +43,23 @@ object Pattern: /** Allocate symbols for all variables. */ def allocate(using State, TraceLogger): Seq[(Str, VarSymbol)] = varMap.iterator.map: (name, aliases) => - tl.scoped("ucs:translation"): - tl.log(s"allocating symbols for variable: ${name}") - val symbol = VarSymbol(Ident(name)) // Location is lost. + // We need to assign the same symbol to the same name. But I realize + // that the following cases will break this constraint: `(x where x > 0) + // | (x where x < 0)`. In both alternatives, `x` needs to be allocated + // in advanced, but at the level of the disjunction, `x` needs to be + // assigned to the same variable. This represents an edge case which + // needs to be fixed later. + val symbols = aliases.iterator.flatMap(_.symbolOption).toSet + // TODO: The above edge case would fail the following assertion. + assert(symbols.size <= 1) + // If no symbol had been created before, create a new symbol now. + val symbol = symbols.headOption.getOrElse(VarSymbol(Ident(name))) aliases.foreach: alias => - tl.scoped("ucs:translation"): - tl.log(s"allocated symbol for alias: ${alias.showDbg}") - alias.symbol = symbol + // For guarded patterns (`p where t`), the variables in `p` have to be + // allocated before `t` is elaborated. In that case, we don't need to + // allocate the variables in `p` again when the variables of pattern + // containing `p` are allocated. + if alias.symbolOption.isEmpty then alias.symbol = symbol (name, symbol) .toSeq @@ -85,6 +88,12 @@ object Pattern: (name, aliases), invalidVars ::: that.invalidVars ::: duplicated.toList) + /** For debugging purpose only. */ + def display: Str = varMap.iterator.map: + case (key, aliases) => + key + " -> " + aliases.iterator.map(_.id.name).mkString(", ") + .mkString("{", "; ", "}") + /** Intersect two variable sets and move variables that are only present in * one side to the invalid variables. This method considers `this` as the * left side, and `that` as the right side. */ @@ -132,10 +141,6 @@ object Pattern: /** A shorthand for creating a variable pattern. */ def Variable = Pattern.Wildcard() binds (_: Ident) - object Constructor: - def apply(target: Term, arguments: Opt[Ls[Pattern]]): Constructor = - Constructor(target, Nil, arguments) - trait ConstructorImpl: self: Pattern.Constructor => @@ -164,15 +169,11 @@ import Pattern.*, InvalidReason.* enum Pattern extends AutoLocated: /** A pattern that matches a constructor and its arguments. * @param target The term representing the constructor. - * @param patternArguments Arguments of higher-order patterns. They are only - * meaningful when the `target` refers to a pattern symbol. They can't - * have any free variables. * @param arguments `None` if the pattern does not have a parameter list. The * patterns that are used to destruct the constructor's arguments. */ case Constructor( target: Term, - patternArguments: Ls[Pattern], arguments: Opt[Ls[Pattern]] ) extends Pattern with ConstructorImpl @@ -202,9 +203,16 @@ enum Pattern extends AutoLocated: case Concatenation(left: Pattern, right: Pattern) /** A pattern that matches a tuple. At most one `spread` pattern is allowed. - * When the `spread` pattern is absent, sub-patterns should be placed in the - * `leading` field. */ - case Tuple(leading: Ls[Pattern], spread: Opt[Pattern], trailing: Ls[Pattern]) + * When the `spread` pattern is absent, sub-patterns should be placed in the + * `leading` field. + * @param leading matches a fixed number of leading elements in the tuple. + * @param spread matches a variable number of elements in the tuple plus + * the trailing elements. + */ + case Tuple( + leading: Ls[Pattern], + spread: Opt[(SpreadKind, Pattern, Ls[Pattern])], + ) /** A pattern that matches a record consisting of a list of fields. Note that * the fields are not ordered semantically. */ @@ -222,17 +230,39 @@ enum Pattern extends AutoLocated: case Alias(pattern: Pattern, id: Ident) extends Pattern with AliasImpl /** A pattern that matches the same value as other pattern, with an additional - * function applied to the bound variables in the pattern. + * function applied to the bound variables in the pattern. + * + * @param pattern The pattern to be matched against. + * @param parameters A map from the variable symbols to parameter symbols. + * @param transform The term that should be applied to the extracted values. */ - case Transform(pattern: Pattern, transform: Term) + case Transform(pattern: Pattern, parameters: Ls[(VarSymbol, VarSymbol)], transform: Term) + + /** If the term is `Error`, we add `Opt[Loc]` to the list instead. */ + case Annotated(pattern: Pattern, annotations: Vector[Opt[Loc] \/ Term]) + + /** A pattern that comes with an extra condition. It works in a way similar to + * `and` in split. */ + case Guarded(pattern: Pattern, guard: Term) infix def binds(id: Ident): Pattern.Alias = Pattern.Alias(this, id) + /** Annotate the pattern using the given term. If the term is `Error`, then + * use the location of the original tree for error reporting. */ + inline def annotate(annotation: Term, treeLoc: Opt[Loc]): Pattern.Annotated = + val elem = if annotation is Term.Error then L(treeLoc) else R(annotation) + this match + case Annotated(pattern, annotations) => + Annotated(pattern, annotations :+ elem) + case _ => Annotated(this, Vector(elem)) + + inline def withGuard(guard: Term) = Pattern.Guarded(this, guard) + /** Collect all variables in the pattern. Meanwhile, list invalid variables, * which will be reported when constructing symbols for variables. We use a * map because we want to replace variables. */ lazy val variables: Variables = this match - case Constructor(_, _, arguments) => arguments.fold(Variables.empty)(_.variables) + case Constructor(_, arguments) => arguments.fold(Variables.empty)(_.variables) case Composition(false, left, right) => left.variables ++ right.variables case union @ Composition(true, left, right) => left.variables.intersect(right.variables, union) // If we only allow negation patterns to be used as the top-level pattern @@ -243,44 +273,52 @@ enum Pattern extends AutoLocated: case negation @ Negation(pattern) => pattern.variables.invalidated(Negated(negation)) case _: (Wildcard | Literal | Range | Transform) => Variables.empty case Concatenation(left, right) => left.variables ++ right.variables - case Tuple(leading, spread, trailing) => - leading.variables ++ spread.map(_.variables).getOrElse(Variables.empty) ++ trailing.variables + case Tuple(leading, spread) => leading.variables ++ spread.fold(Variables.empty): + case (_, middle, trailing) => middle.variables ++ trailing.variables case Record(fields) => fields.iterator.map(_._2).variables case alias @ Alias(pattern, _) => pattern.variables + alias case Chain(first, second) => first.variables ++ second.variables + case Annotated(pattern, _) => pattern.variables + case Guarded(pattern, _) => pattern.variables def children: Ls[Located] = this match - case Constructor(target, patternArguments, arguments) => - target :: patternArguments ::: arguments.getOrElse(Nil) + case Constructor(target, arguments) => target :: arguments.getOrElse(Nil) case Composition(polarity, left, right) => left :: right :: Nil case Negation(pattern) => pattern :: Nil case Wildcard() => Nil case Literal(literal) => literal :: Nil case Range(lower, upper, rightInclusive) => lower :: upper :: Nil case Concatenation(left, right) => left :: right :: Nil - case Tuple(leading, spread, trailing) => leading ::: spread.toList ::: trailing + case Tuple(leading, spread) => leading ::: spread.fold(Nil): + case (_, middle, trailing) => middle :: trailing case Record(fields) => fields.flatMap: case (name, pattern) => name :: pattern.children case Chain(first, second) => first :: second :: Nil case Alias(pattern, alias) => pattern :: alias :: Nil - case Transform(pattern, transform) => pattern :: transform :: Nil + case Transform(pattern, _, transform) => pattern :: transform :: Nil + case Annotated(pattern, annotations) => pattern :: + annotations.iterator.collect { case R(term) => term }.toList + case Guarded(pattern, guard) => pattern.children :+ guard def subTerms: Ls[Term] = this match - case Constructor(target, patternArguments, arguments) => - target :: patternArguments.flatMap(_.subTerms) ::: arguments.fold(Nil)(_.flatMap(_.subTerms)) + case Constructor(target, arguments) => + target :: arguments.fold(Nil)(_.flatMap(_.subTerms)) case Composition(_, left, right) => left.subTerms ::: right.subTerms case Negation(pattern) => pattern.subTerms case _: (Wildcard | Literal | Range) => Nil case Concatenation(left, right) => left.subTerms ::: right.subTerms - case Tuple(leading, spread, trailing) => leading.flatMap(_.subTerms) ::: - spread.fold(Nil)(_.subTerms) ::: trailing.flatMap(_.subTerms) + case Tuple(leading, spread) => leading.flatMap(_.subTerms) ::: spread.fold(Nil): + case (_, middle, trailing) => middle.subTerms ::: trailing.flatMap(_.subTerms) case Record(fields) => fields.flatMap(_._2.subTerms) case Chain(first, second) => first.subTerms ::: second.subTerms case Alias(pattern, _) => pattern.subTerms - case Transform(pattern, transform) => pattern.subTerms :+ transform + case Transform(pattern, _, transform) => pattern.subTerms :+ transform + case Annotated(pattern, annotations) => pattern.subTerms ::: + annotations.iterator.collect { case R(term) => term }.toList + case Guarded(pattern, guard) => pattern.subTerms :+ guard def describe: Str = this match - case Constructor(_, _, _) => "constructor" + case Constructor(_, _) => "constructor" case Composition(true, _, _) => "disjunction" case Composition(false, _, _) => "conjunction" case Negation(_) => "negation" @@ -288,28 +326,25 @@ enum Pattern extends AutoLocated: case Literal(_) => "literal" case Range(_, _, _) => "range" case Concatenation(_, _) => "concatenation" - case Tuple(_, _, _) => "tuple" + case Tuple(_, _) => "tuple" case Record(_) => "record" case Chain(_, _) => "chain" case Alias(_, _) => "alias" - case Transform(_, _) => "transform" + case Transform(_, _, _) => "transform" + case Annotated(_, _) => "annotated pattern" + case Guarded(_, _) => "guarded pattern" private def showDbgWithPar = val addPar = this match - case _: (Constructor | Wildcard | Literal | Tuple | Record | Negation) => false + case _: (Constructor | Wildcard | Literal | Tuple | Record | Negation | Annotated) => false case Alias(Wildcard(), _) => false - case _: (Alias | Composition | Transform | Range | Concatenation | Chain) => true + case _: (Alias | Composition | Transform | Range | Concatenation | Chain | Guarded) => true if addPar then s"(${showDbg})" else showDbg def showDbg: Str = this match - case Constructor(target, patternArguments, arguments) => - val targetText = target.symbol.fold(target.showDbg)(_.toString()) - val patternArgumentsText = if patternArguments.isEmpty then "" else - patternArguments.iterator.map(_.showDbg) - .map("pattern " + _).mkString("(", ", ", ")") - val argumentsText = arguments.fold(""): args => - s"(${args.map(_.showDbg).mkString(", ")})" - s"$targetText$patternArgumentsText$argumentsText" + case Constructor(target, arguments) => + target.symbol.fold(target.showDbg)(_.nme) + arguments.fold(""): + args => s"(${args.map(_.showDbg).mkString(", ")})" case Composition(true, left, right) => s"${left.showDbg} ∨ ${right.showDbg}" case Composition(false, left, right) => s"${left.showDbg} ∧ ${right.showDbg}" case Negation(pattern) => s"¬${pattern.showDbgWithPar}" @@ -318,12 +353,18 @@ enum Pattern extends AutoLocated: case Range(lower, upper, rightInclusive) => s"${lower.idStr} ${if rightInclusive then "to" else "until"} ${upper.idStr}" case Concatenation(left, right) => s"${left.showDbg} ~ ${right.showDbg}" - case Tuple(leading, spread, trailing) => - (leading.iterator.map(_.showDbg) ++ - spread.iterator.map(s => "..." + s.showDbg) ++ - trailing.iterator.map(_.showDbg)).mkString("[", ", ", "]") + case Tuple(leading, spread) => + (leading.iterator.map(_.showDbg) ++ spread.fold(Iterator.empty): + case (spreadKind, middle, trailing) => + Iterator.single(spreadKind.str + middle.showDbg) ++ + trailing.iterator.map(_.showDbg)).mkString("[", ", ", "]") case Record(fields) => s"{${fields.map((k, v) => s"${k.name}: ${v.showDbg}").mkString(", ")}}" case Chain(first, second) => s"${first.showDbgWithPar} as ${second.showDbgWithPar}" case Alias(Wildcard(), alias) => alias.name case Alias(pattern, alias) => s"${pattern.showDbgWithPar} as ${alias.name}" - case Transform(pattern, transform) => s"${pattern.showDbgWithPar} => ${transform.showDbg}" + case Transform(pattern, _, transform) => s"${pattern.showDbgWithPar} => ${transform.showDbg}" + case Annotated(pattern, annotations) => annotations.iterator.map: + case L(errorLoc) => "error" + case R(term) => term.showDbg + .mkString("@", " @", " ") + pattern.showDbgWithPar + case Guarded(pattern, guard) => pattern.showDbg + " where " + guard.showDbg diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index ea57cc40aa..ff37a905d8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -302,20 +302,20 @@ class Resolver(tl: TraceLogger) traverseStmts(stats) case t: Term.IfLike => - def split(s: Split): Unit = s match - case Split.Cons(head, tail) => + def simpleSplit(s: SimpleSplit): Unit = s match + case SimpleSplit.Cons(head: SimpleSplit.Head.Match, tail) => traverse(head.scrutinee, expect = NonModule(N)) head.pattern.subTerms.foreach(traverse(_, expect = NonModule(N))) - split(head.continuation) - split(tail) - case Split.Let(sym, term, tail) => + simpleSplit(head.consequent) + simpleSplit(tail) + case SimpleSplit.Cons(SimpleSplit.Head.Let(sym, term), tail) => traverse(term, expect = NonModule(N)) - split(tail) - case Split.Else(default) => + simpleSplit(tail) + case SimpleSplit.Else(default) => traverse(default, expect = Any) - case Split.End => - split(t.desugared) - + case SimpleSplit.End => + simpleSplit(t.split) + case Term.Handle(lhs, rhs, args, derivedClsSym, defs, body) => traverse(rhs, expect = Class(S("The 'handle' keyword requires a statically known class."))) args.foreach(traverse(_, expect = NonModule(N))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala new file mode 100644 index 0000000000..c48b37c250 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/SimpleSplit.scala @@ -0,0 +1,162 @@ +package hkmc2 +package semantics + +import mlscript.utils.*, shorthands.*, syntax.*, Tree.{BoolLit, Keywrd} +import Keyword.{`do`, `else`, `then`}, utils.TL, Elaborator.{Ctx, State} + +enum SimpleSplit extends AutoLocated with ProductWithTail: + import SimpleSplit.Head + + case Cons(branch: SimpleSplit.Head, tail: SimpleSplit) + /** + * The end of splits with a default term. + * + * @param default The default term. + * @param kw The keyword of `then` or `else`. It is `None` if the split is + * the topmost one. + */ + case Else(default: Term)(val kw: Opt[Keywrd[`else`.type | `then`.type | `do`.type]]) + case End + + inline def ~:(head: SimpleSplit.Head): Cons = Cons(head, this) + + def ~~:(front: SimpleSplit): SimpleSplit = + front match + case Cons(head, tail) => Cons(head, tail ~~: this) + case els: Else => els + case End => this + + protected def children: List[Located] = this match + case Cons(branch, tail) => List(branch, tail) + case els @ Else(default) => els.kw match + case N => default :: Nil + case S(kw) => kw :: default :: Nil + case End => Nil + + def subTerms: Ls[Term] = this match + case Cons(branch, tail) => branch.subTerms ::: tail.subTerms + case Else(default) => default :: Nil + case End => Nil + + def showDbg: Str = this match + case Cons(branch, tail) => s"${branch.showDbg}; ${tail.showDbg}" + case Else(default) => s"else ${default.showDbg}" + case End => "" + + def prettyPrint: Str = SimpleSplit.prettyPrint(this) + + /** Get the results of all branches. */ + def results: Ls[Term] = + def go(acc: Ls[Term], split: SimpleSplit): Ls[Term] = split match + case Cons(_: Head.Let, tail) => go(acc, tail) + case Cons(Head.Match(_, _, consequent), alternative) => + go(go(acc, consequent), alternative) + case Else(default) => default :: acc + case End => acc + go(Nil, this).reverse + + /** This field is designed to be compatible with bbML. */ + private var _expandedSplit: Opt[Split] = N + + /** */ + def getExpandedSplit(using TL, Ctx, State, Raise): Split = _expandedSplit.getOrElse: + val split = Split.from(this) + _expandedSplit = S(split) + split + +object SimpleSplit: + object IfThenElse: + def unapply(split: SimpleSplit): Opt[(Term, Term, Term)] = split match + case Cons( + Head.Let(binding, condition), + Cons(Head.Match( + scrutinee, + Pattern.Literal(BoolLit(true)), Else(consequent)), + Else(alternative)) + ) if scrutinee.sym === binding => S((condition, consequent, alternative)) + case _ => N + + /** Represents a single branch of a simple split. */ + enum Head extends AutoLocated: + case Match(scrutinee: Term.Ref, pattern: Pattern, consequent: SimpleSplit) + case Let(binding: BlockLocalSymbol, term: Term) + + def subTerms: Ls[Term] = this match + case Match(scrutinee, pattern, consequent) => + scrutinee :: pattern.subTerms ::: consequent.subTerms + case Let(_, term) => term :: Nil + + def showDbg: Str = this match + case Match(scrutinee, pattern, consequent) => + val consequentStr = consequent match + case Cons(_, _) => s"and ${consequent.showDbg}" + case Else(default) => s"then ${default.showDbg}" + case End => "then {}" + s"${scrutinee.showDbg} is ${pattern.showDbg} ${consequentStr}" + case Let(binding, term) => s"let ${binding.nme} = ${term.showDbg}" + + protected def children: List[Located] = this match + case Match(scrutinee, pattern, consequent) => + List(scrutinee, pattern, consequent) + case Let(binding, term) => List(binding, term) + + private[semantics] object prettyPrint: + /** Represents lines with indentations. */ + type Lines = Ls[(Int, Str)] + + extension (lines: Lines) + /** Increase the indentation of all lines by one. */ + def indent: Lines = lines.map: + case (n, line) => (n + 1, line) + + /** Make a multi-line string. */ + def toIndentedString: Str = lines.iterator.map: + case (n, line) => " " * n + line + .mkString("\n") + + extension (prefix: String) + /** + * If the first line does not have indentation and the remaining lines are + * indented, prepend the given string to the first line. Otherwise, prepend + * the given string to the first line and indent all remaining lines. + * + * When you want to amend the title of lines, you should use this function. + */ + def #:(lines: Lines): Lines = lines match + case all @ ((0, line) :: lines) if lines.forall(_._1 > 0) => + if prefix.isEmpty then all else (0, s"$prefix $line") :: lines + case lines => (0, prefix) :: lines.indent + + inline def apply(s: SimpleSplit): Str = showSplit("if", s) + + /** Show a split as a list of lines. + * @param isFirst whether this is the first and frontmost branch + * @param isTopLevel whether this is the top-level split + */ + private[semantics] def split(s: SimpleSplit, isFirst: Bool, isTopLevel: Bool): Lines = s match + case SimpleSplit.Cons(head: Head.Match, tail) => (branch(head, isTopLevel) match + case (n, line) :: tail => (n, line) :: tail + case Nil => Nil + ) ::: split(tail, false, isTopLevel) + case SimpleSplit.Cons(Head.Let(nme, rhs), tail) => + (0, s"let $nme = ${rhs.showDbg}") :: split(tail, false, true) + case SimpleSplit.Else(t) => + (if isFirst && !isTopLevel then "" else "else") #: term(t) + case SimpleSplit.End => Nil + + private def term(t: Statement): Lines = (0, t.showDbg) :: Nil + + private def branch(b: Head.Match, isTopLevel: Bool): Lines = + val Head.Match(scrutinee, pattern, consequent) = b + val lines = split(consequent, true, false) + val prefix = s"${scrutinee.sym} is ${pattern.showDbg}" + consequent match + case SimpleSplit.Else(_) => (prefix + " then") #: lines + case _ => (prefix + " and") #: lines + + private def showSplit(prefix: Str, s: SimpleSplit): Str = + val lines = split(s, true, true) + (if prefix.isEmpty then lines else prefix #: lines).toIndentedString + + end prettyPrint + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala index e30ee56ae4..695e40453b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala @@ -2,15 +2,21 @@ package hkmc2 package semantics import mlscript.utils.*, shorthands.* -import syntax.*, ucs.FlatPattern +import syntax.*, Elaborator.State, ucs.FlatPattern final case class Branch(scrutinee: Term.Ref, pattern: FlatPattern, continuation: Split) extends AutoLocated: + def mkClone(using State): Branch = + val scrutineeClone = new Term.Ref(scrutinee.sym) + (Tree.Ident(scrutinee.tree.name), scrutinee.refNum, scrutinee.typ) + Branch(scrutineeClone, pattern.mkClone, continuation.mkClone) + override def children: List[Located] = scrutinee :: pattern :: continuation :: Nil + def showDbg: String = s"${scrutinee.sym.nme} is ${pattern.showDbg} -> { ${continuation.showDbg} }" object Branch: def apply(scrutinee: Term.Ref, continuation: Split): Branch = - Branch(scrutinee, FlatPattern.Lit(Tree.BoolLit(true))(Nil), continuation) + Branch(scrutinee, FlatPattern.Lit(Tree.BoolLit(true)), continuation) enum Split extends AutoLocated with ProductWithTail: case Cons(head: Branch, tail: Split) @@ -20,6 +26,29 @@ enum Split extends AutoLocated with ProductWithTail: inline def ~:(head: Branch): Split = Split.Cons(head, this) + def mkClone(using State): Split = this match + case Cons(head, tail) => Cons(head.mkClone, tail.mkClone) + case Let(sym, term, tail) => Let(sym, term.mkClone, tail.mkClone) + case Else(default) => Else(default.mkClone) + case End => End + + /** Used to indicate whether the `Split` was duplicated during desugaring or + * normalization. */ + def duplicated: Bool = false + + private var _duplicated: Bool = false + + def setDuplicated: this.type = + if this != End then _duplicated = true + this + + def duplicate: Split = + (this match + case Cons(head, tail) => Cons(head, tail.duplicate) + case Let(name, term, tail) => Let(name, term, tail.duplicate) + case Else(default) => Else(default) + case End => End).setDuplicated + lazy val isFull: Bool = this match case Split.Cons(_, tail) => tail.isFull case Split.Let(_, _, tail) => tail.isFull @@ -75,6 +104,31 @@ extension (split: Split) object Split: def default(term: Term): Split = Split.Else(term) + + import SimpleSplit as SS + import Elaborator.{Ctx, State} + import utils.{tl, TL} + import collection.mutable.{Map as MutMap} + import ups.SplitCompiler, SplitCompiler.{MakeConsequent, Scrut, SymbolScrut} + import Term.Ref + + def from(rootSplit: SS)(using tl: TL)(using Ctx, Raise, State): Split = + val compiler = new SplitCompiler() + val scrutCache = MutMap.empty[Ref, Scrut] + def go(split: SS): Split = split match + case SS.Cons(branch, tail) => branch match + case SS.Head.Match(ref, pattern, consequent) => + lazy val alternative = go(tail) + val makeConsequent: MakeConsequent = (output, bindings) => + bindings.iterator.foldLeft(go(consequent)): + case (innerSplit, (symbol, mkTerm)) => Let(symbol, mkTerm(), innerSplit) + compiler.makeMatchSplit + (scrutCache.getOrElseUpdate(ref, Scrut.from(ref)), pattern) + (makeConsequent, alternative) + case SS.Head.Let(binding, term) => Let(binding, term, go(tail)) + case SS.Else(default) => Else(default) + case SS.End => End + go(rootSplit) private object prettyPrint: /** Represents lines with indentations. */ @@ -110,20 +164,28 @@ object Split: * @param isFirst whether this is the first and frontmost branch * @param isTopLevel whether this is the top-level split */ - def split(s: Split, isFirst: Bool, isTopLevel: Bool): Lines = s match - case Split.Cons(head, tail) => (branch(head, isTopLevel) match - case (n, line) :: tail => (n, line) :: tail - case Nil => Nil - ) ::: split(tail, false, isTopLevel) - case Split.Let(nme, rhs, tail) => - (0, s"let $nme = ${rhs.showDbg}") :: split(tail, false, true) - case Split.Else(t) => - (if isFirst && !isTopLevel then "" else "else") #: term(t) - case Split.End => Nil + def split(s: Split, isFirst: Bool, isTopLevel: Bool): Lines = + val lines = s match + case Split.Cons(head, tail) => (branch(head, isTopLevel) match + case (n, line) :: tail => (n, line) :: tail + case Nil => Nil + ) ::: split(tail, false, isTopLevel) + case Split.Let(nme, rhs, tail) => + (0, s"let $nme = ${rhs.showDbg}") :: split(tail, false, true) + case Split.Else(t) => + (if isFirst && !isTopLevel then "" else "else") #: term(t) + case Split.End => Nil + if s.duplicated then lines.map: + case (n, line) if !line.endsWith("// duplicated") => (n, s"$line // duplicated") + case other => other + else + lines def term(t: Statement): Lines = t match case Term.Blk(stmts, term) => stmts.iterator.concat(Iterator.single(term)).flatMap: - case DefineVar(sym, Term.IfLike(Keyword.`if`, splt)) => + case DefineVar(sym, Term.IfLike(kw, splt)) => + s"$sym = ${kw.name}" #: SimpleSplit.prettyPrint.split(splt, true, true) + case DefineVar(sym, Term.SynthIf(splt)) => s"$sym = if" #: split(splt, true, true) case stmt => (0, stmt.showDbg) :: Nil .toList diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 98c6a15298..007b7de9d4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -202,7 +202,12 @@ enum Term extends Statement: case Tup(fields: Ls[Elem])(val tree: Tree.Tup) case Mut(underlying: Tup | Rcd | New | DynNew) case CtxTup(fields: Ls[Elem])(val tree: Tree.Tup) - case IfLike(kw: Keyword.`if`.type | Keyword.`while`.type, desugared: Split) + case IfLike(kw: Keyword.`if`.type | Keyword.`while`.type, split: SimpleSplit) + /** `If` expressions synthesized by the pattern compiler. It should only be + * created and used in `Lowering`. One must make sure that all terms in the + * split are correctly resolved. In the future, we might look for a way to + * remove `SynthIf` by generating IR `Match` blocks directly. */ + case SynthIf(split: Split) case Lam(params: ParamList, body: Term) case FunTy(lhs: Term, rhs: Term, eff: Opt[Term]) case Forall(tvs: Ls[QuantVar], outer: Opt[VarSymbol], body: Term) @@ -305,7 +310,8 @@ enum Term extends Statement: case f: Fld => f.copy(term = f.term.mkClone, asc = f.asc.map(_.mkClone)) case s: Spd => s.copy(term = s.term.mkClone) })(term.tree) - case IfLike(kw, desugared) => IfLike(kw, desugared) // desugared is Split, which is immutable + case IfLike(kw, split) => IfLike(kw, split) + case SynthIf(split) => SynthIf(split.mkClone) case Lam(params, body) => Lam(params, body.mkClone) case FunTy(lhs, rhs, eff) => FunTy(lhs.mkClone, rhs.mkClone, eff.map(_.mkClone)) case Forall(tvs, outer, body) => Forall(tvs, outer, body.mkClone) @@ -370,8 +376,9 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case DynSel(o, f, _) => "dynamic selection" case Tup(fields) => "tuple literal" case CtxTup(fields) => "contextual tuple literal" - case IfLike(Keyword.`if`, body) => "`if` expression" - case IfLike(Keyword.`while`, body) => "`while` expression" + case IfLike(Keyword.`if`, _) => "`if` expression" + case IfLike(Keyword.`while`, _) => "`while` expression" + case SynthIf(split) => "`if` expression" case Lam(params, body) => "function literal" case FunTy(lhs, rhs, eff) => "function type" case Forall(tvs, outer, body) => "universal quantification" @@ -425,7 +432,8 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Tup(fields) => fields.flatMap(_.subTerms) case Mut(und) => und :: Nil case CtxTup(fields) => fields.flatMap(_.subTerms) - case IfLike(_, body) => body.subTerms + case IfLike(_, split) => split.subTerms + case SynthIf(split) => split.subTerms case Lam(params, body) => body :: Nil case Blk(stats, res) => stats.flatMap(_.subTerms) ::: res :: Nil case Rcd(mut, stats) => stats.flatMap(_.subTerms) @@ -475,7 +483,8 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case t: Tup => treeOrSubterms(t.tree) case l: Lam => l.params.paramSyms.map(_.id) ::: l.body :: Nil case t: App => treeOrSubterms(t.tree) - case IfLike(kw, desug) => desug :: Nil + case IfLike(_, split) => split :: Nil + case SynthIf(split) => split :: Nil case SynthSel(pre, nme) => pre :: nme :: Nil case Sel(pre, nme) => pre :: nme :: Nil case SelProj(prefix, cls, proj) => prefix :: cls :: proj :: Nil @@ -515,7 +524,8 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Sel(pre, nme) => s"${pre.showDbg}.${nme.name}" case SynthSel(pre, nme) => s"(${pre.showDbg}.)${nme.name}" case DynSel(pre, fld, _) => s"${pre.showDbg}[${fld.showDbg}]" - case IfLike(kw, body) => s"${kw.name} { ${body.showDbg} }" + case IfLike(kw, split) => s"${kw.name} { ${split.showDbg} }" + case SynthIf(split) => s"if { ${split.showDbg} }" case Lam(params, body) => s"λ${params.showDbg}. ${body.showDbg}" case Blk(stats, res) => (stats.map(_.showDbg + "; ") :+ (res match { case Lit(Tree.UnitLit(false)) => "" case x => x.showDbg + " " })) @@ -671,6 +681,14 @@ case class Import(sym: Symbol, file: Str) extends Statement sealed abstract class Declaration: val sym: Symbol + + /** Whether this declares a class, a pattern, an object, or a pattern + * parameter. Only they can be at the constructor position in patterns. */ + def isPatternConstructor: Bool = this match + case _: (TermDefinition | TypeDef | TyParam) => false + case d: ModuleOrObjectDef => d.kind isnt Mod + case _: (PatternDef | ClassDef) => true + case p: Param => p.flags.pat sealed abstract class Definition extends Declaration, Statement: val annotations: Ls[Annot] @@ -731,6 +749,8 @@ case class PatternDef( sym: PatternSymbol, bsym: BlockMemberSymbol, tparams: Ls[TyParam], + /** All parameters. */ + parameters: Ls[Param], /** The pattern parameters, for example, `T` in * `pattern Nullable(pattern T) = null | T`. */ patternParams: Ls[Param], diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala deleted file mode 100644 index 3c3913b49c..0000000000 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala +++ /dev/null @@ -1,688 +0,0 @@ -package hkmc2 -package semantics -package ucs - -import syntax.{BracketKind, Keyword, Literal, Tree}, Tree.* -import mlscript.utils.*, shorthands.* -import Message.MessageContext -import utils.TraceLogger -import Keyword.{`as`, `and`, `or`, `do`, `else`, is, let, `then`, where} -import collection.mutable.{Buffer, HashMap, SortedSet} -import Elaborator.{Ctx, Ctxl, State, UnderCtx, ctx} -import scala.annotation.targetName -import FlatPattern.{Argument, MatchMode} - -object Desugarer: - extension (op: Keyword.Infix) - infix def unapply(tree: Tree): Opt[(Tree, Tree)] = tree match - case InfixApp(lhs, `op`, rhs) => S((lhs, rhs)) - case _ => N - - type Ctor = SynthSel | Sel | Ident - - class ScrutineeData: - val subScrutinees: Buffer[BlockLocalSymbol] = Buffer.empty - val fields: HashMap[Ident, BlockLocalSymbol] = HashMap.empty - val tupleLead: HashMap[Int, BlockLocalSymbol] = HashMap.empty - val tupleLast: HashMap[Int, BlockLocalSymbol] = HashMap.empty -end Desugarer - -class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, Config, UnderCtx): - import Desugarer.*, elaborator.term, elaborator.subterm, elaborator.tl, tl.* - - // A few helper methods to select useful functions from the runtime. - private def selectTuple: Term.SynthSel = - Term.SynthSel(State.runtimeSymbol.ref(), Ident("Tuple"))(N, N) - private def tupleSlice = Term.SynthSel(selectTuple, Ident("slice"))(N, N) - private def tupleLazySlice = Term.SynthSel(selectTuple, Ident("lazySlice"))(N, N) - private def tupleGet = Term.SynthSel(selectTuple, Ident("get"))(N, N) - private def callTupleGet(t: Term, i: Int, s: FlowSymbol): Term = - val args = PlainFld(t) :: PlainFld(Term.Lit(IntLit(BigInt(i)))) :: Nil - Term.App(tupleGet, Term.Tup(args)(DummyTup))(DummyApp, N, s) - - given Ordering[Loc] = Ordering.by: loc => - (loc.spanStart, loc.spanEnd) - - /** Keep track of the locations where `do` and `then` are used as connectives. */ - private val kwLocSets = (SortedSet.empty[Loc], SortedSet.empty[Loc]) - - private def reportInconsistentConnectives(kw: Keywrd[?]): Unit = - log(kwLocSets) - (kwLocSets._1.headOption, kwLocSets._2.headOption) match - case (Some(doLoc), Some(thenLoc)) => - raise(ErrorReport( - msg"Mixed use of `do` and `then` in the `${kw.kw.name}` expression." -> kw.toLoc - :: msg"Keyword `then` is used here." -> S(thenLoc) - :: msg"Keyword `do` is used here." -> S(doLoc) :: Nil - )) - case _ => () - - private def topmostDefault: Split = - if kwLocSets._1.nonEmpty then Split.Else(Term.UnitVal()) else Split.End - - /** An extractor that accepts either `A and B`, `A then B`, and `A do B`. It - * also keeps track of the usage of `then` and `do`. - */ - object `~>`: - infix def unapply(tree: Tree): Opt[(Tree, Tree \/ Tree)] = tree match - case lhs `and` rhs => S((lhs, L(rhs))) - case lhs `then` rhs => kwLocSets._2 ++= tree.toLoc; S((lhs, R(rhs))) - case lhs `do` rhs => kwLocSets._1 ++= tree.toLoc; S((lhs, R(rhs))) - case _ => N - - // We're working on composing continuations in the UCS translation. - // The type of continuation is `Split => Ctx => Split`. - // The first parameter represents the "backup" split, which does not have - // access to the bindings in the current match. The second parameter - // represents the context with bindings in the current match. - - type Sequel = Ctx => Split - - - extension (sequel: Sequel) - @targetName("traceSequel") - def traced(pre: Str, post: Split => Str): Sequel = - if doTrace then ctx => trace(pre, post)(sequel(ctx)) else sequel - - extension (desugar: Split => Sequel) - @targetName("traceDesugar") - def traced(pre: Str, post: Split => Str): Split => Sequel = - if doTrace then - fallback => ctx => trace(pre, post)(desugar(fallback)(ctx)) - else - desugar - - extension (split: Split) - /** Concatenate two splits. */ - def ++(fallback: Split): Split = - if fallback == Split.End then - split - else if split.isFull then - split - else split match - case Split.Cons(head, tail) => Split.Cons(head, tail ++ fallback) - case Split.Let(name, term, tail) => Split.Let(name, term, tail ++ fallback) - case Split.Else(_) => split // Shouldn't actually happen because of the `split.isFull` check above - case Split.End => fallback - - private val subScrutineeMap = HashMap.empty[BlockLocalSymbol, ScrutineeData] - private val fieldScrutineeMap = HashMap.empty[BlockLocalSymbol, ScrutineeData] - - extension (symbol: BlockLocalSymbol) - def getSubScrutinees(count: Int): List[BlockLocalSymbol] = - val sd = subScrutineeMap.getOrElseUpdate(symbol, new ScrutineeData) - subScrutineeMap.sizeHint(count) - while sd.subScrutinees.size < count do - val scrutinee = TempSymbol(N, s"param${sd.subScrutinees.size}") - sd.subScrutinees += scrutinee - sd.subScrutinees.iterator.take(count).toList - def getTupleLeadSubScrutinee(index: Int): BlockLocalSymbol = - val data = subScrutineeMap.getOrElseUpdate(symbol, new ScrutineeData) - data.tupleLead.getOrElseUpdate(index, TempSymbol(N, s"first$index")) - def getTupleLastSubScrutinee(index: Int): BlockLocalSymbol = - val data = subScrutineeMap.getOrElseUpdate(symbol, new ScrutineeData) - data.tupleLast.getOrElseUpdate(index, TempSymbol(N, s"last$index")) - def getFieldScrutinee(fieldName: Ident): BlockLocalSymbol = - subScrutineeMap - .getOrElseUpdate(symbol, new ScrutineeData) - .fields - .getOrElseUpdate(fieldName, TempSymbol(N, s"field${fieldName.name}")) - - - def default: Split => Sequel = split => _ => split - - private def termSplitShorthands(tree: Tree, finish: Term => Term): Split => Sequel = tree match - case blk: Block => blk.desugStmts match - case Nil => lastWords("encountered empty block") - case branch :: rest => fallback => ctx => - if rest.nonEmpty then - raise(ErrorReport(msg"only one branch is supported in shorthands" -> tree.toLoc :: Nil)) - termSplitShorthands(branch, finish)(fallback)(ctx) - case coda is rhs => fallback => ctx => - nominate(ctx, finish(subterm(coda)(using ctx))): - patternSplitShorthands(rhs, _)(fallback) - case matches => fallback => - // There are N > 0 conjunct matches. We use `::[T]` instead of `List[T]`. - // Each match is represented by a pair of a _coda_ and a _pattern_ - // that is yet to be elaborated. - val (headCoda, headPattern) :: tail = disaggregate(matches) - // The `consequent` serves as the innermost split, based on which we - // expand from the N-th to the second match. - lazy val tailSplit = - val innermostSplit = Function.const(Split.default(Term.Lit(Tree.BoolLit(true)))): Sequel - tail.foldRight(innermostSplit): - case ((coda, pat), sequel) => ctx => trace( - pre = s"conjunct matches <<< $tail", - post = (res: Split) => s"conjunct matches >>> $res" - ): - nominate(ctx, term(coda)(using ctx)): - expandMatch(_, pat, sequel, Nil)(fallback) - // We apply `finish` to the first coda and expand the first match. - // Note that the scrutinee might be not an identifier. - headCoda match - case Under() => tailSplit - case _ => ctx => trace( - pre = s"shorthands <<< $matches", - post = (res: Split) => s"shorthands >>> $res" - ): - nominate(ctx, finish(term(headCoda)(using ctx))): - expandMatch(_, headPattern, tailSplit, Nil)(fallback) - - private def patternSplitShorthands(tree: Tree, scrutSymbol: BlockLocalSymbol): Split => Sequel = tree match - case blk: Block => blk.desugStmts match - case Nil => lastWords("encountered empty block") - case branch :: rest => fallback => ctx => - if rest.nonEmpty then - raise(ErrorReport(msg"only one pattern is supported in shorthands" -> tree.toLoc :: Nil)) - patternSplitShorthands(branch, scrutSymbol)(fallback)(ctx) - case patternAndMatches => fallback => - // TODO: Deduplicate with `patternSplit`. - // There are N > 0 conjunct matches. We use `::[T]` instead of `List[T]`. - // Each match is represented by a pair of a _coda_ and a _pattern_ - // that is yet to be elaborated. - val (headPattern, _) :: tail = disaggregate(patternAndMatches) - // The `consequent` serves as the innermost split, based on which we - // expand from the N-th to the second match. - val tailSplit = trace(s"conjunct matches <<< $tail"): - val innermostSplit = Function.const(Split.default(Term.Lit(Tree.BoolLit(true)))): Sequel - tail.foldRight(innermostSplit): - case ((coda, pat), sequel) => ctx => - nominate(ctx, term(coda)(using ctx)): - expandMatch(_, pat, sequel, Nil)(fallback) - expandMatch(scrutSymbol, headPattern, tailSplit, Nil)(fallback) - - def termSplit(trees: Ls[Tree], finish: Term => Term): Split => Sequel = - trees.foldRight(default): (t, elabFallback) => - t match - case LetLike(Keywrd(`let`), ident @ Ident(_), S(termTree), N) => fallback => ctx => trace( - pre = s"termSplit: let ${ident.name} = $termTree", - post = (res: Split) => s"termSplit: let >>> $res" - ): - val sym = VarSymbol(ident) - val fallbackCtx = ctx + (ident.name -> sym) - Split.Let(sym, term(termTree)(using ctx), elabFallback(fallback)(fallbackCtx)).withLocOf(t) - case PrefixApp(Keywrd(Keyword.`do`), computation) => fallback => ctx => trace( - pre = s"termSplit: do $computation", - post = (res: Split) => s"termSplit: else >>> $res" - ): - val sym = TempSymbol(N, "doTemp") - Split.Let(sym, term(computation)(using ctx), elabFallback(fallback)(ctx)).withLocOf(t) - case PrefixApp(Keywrd(Keyword.`else`), default) => fallback => ctx => trace( - pre = s"termSplit: else $default", - post = (res: Split) => s"termSplit: else >>> $res" - ): - // TODO: report `rest` as unreachable - Split.default(term(default)(using ctx)).withLocOf(t) - case branch => fallback => ctx => trace( - pre = s"termSplit: $branch", - post = (res: Split) => s"termSplit >>> $res" - ): - termSplit(branch, finish)(elabFallback(fallback)(ctx))(ctx) - - /** Desugar a _term split_ (TS) into a _split_ of core abstract syntax. - * @param tree the tree representing the term split. - * @param finish the accumulated partial term to be the LHS or the scrutinee - * @return a continuation that takes subsequent splits as fallbacks and then - * accepts a context with additional bindings from the enclosing - * matches and splits - */ - def termSplit(tree: Tree, finish: Term => Term): Split => Sequel = - log(s"termSplit: $tree") - tree match - case blk: Block => termSplit(blk.desugStmts, finish) - case coda is rhs => fallback => ctx => - nominate(ctx, finish(term(coda)(using ctx))): - patternSplit(rhs, _)(fallback) - case matches ~> consequent => fallback => - // There are N > 0 conjunct matches. We use `::[T]` instead of `List[T]`. - // Each match is represented by a pair of a _coda_ and a _pattern_ - // that is yet to be elaborated. - val (headCoda, headPattern) :: tail = disaggregate(matches) - // The `consequent` serves as the innermost split, based on which we - // expand from the N-th to the second match. - lazy val tailSplit = - val innermostSplit = consequent match - case L(tree) => termSplit(tree, identity)(Split.End) - case R(tree) => (ctx: Ctx) => Split.default(term(tree)(using ctx)) - tail.foldRight(innermostSplit): - case ((coda, pat), sequel) => ctx => trace( - pre = s"conjunct matches <<< $tail", - post = (res: Split) => s"conjunct matches >>> $res" - ): - nominate(ctx, term(coda)(using ctx)): - expandMatch(_, pat, sequel, Nil)(Split.End) - // We apply `finish` to the first coda and expand the first match. - // Note that the scrutinee might be not an identifier. - headCoda match - case Under() => tailSplit - case _ => ctx => trace( - pre = s"termBranch <<< $matches then $consequent", - post = (res: Split) => s"termBranch >>> $res" - ): - nominate(ctx, finish(term(headCoda)(using ctx))): - expandMatch(_, headPattern, tailSplit, Nil)(fallback) - // Handle binary operators. - case tree @ OpApp(lhs, opIdent @ Ident(opName), rhss) => fallback => ctx => trace( - pre = s"termSplit: after op <<< $opName", - post = (res: Split) => s"termSplit: after op >>> $res" - ): - // Resolve the operator. - val opRef = term(opIdent) - // Elaborate and finish the LHS. Nominate the LHS if necessary. - nominate(ctx, finish(term(lhs)(using ctx))): lhsSymbol => - // Compose a function that takes the RHS and finishes the application. - val finishInner = (rhsTerm: Term) => - val first = Fld(FldFlags.empty, lhsSymbol.ref(/* FIXME ident? */), N) - val second = Fld(FldFlags.empty, rhsTerm, N) - val arguments = Term.Tup(first :: second :: Nil)(Tree.DummyTup) - val joint = FlowSymbol("‹applied-result›") - Term.App(opRef, arguments)(Tree.DummyApp, N, joint) - termSplit(rhss, finishInner)(fallback) - // Handle operator splits. - case tree @ OpSplit(lhs, rhss) => fallback => ctx => - nominate(ctx, finish(term(lhs)(using ctx))): vs => - rhss.foldRight(Function.const(fallback): Sequel): (branch, elabFallback) => - branch match - case LetLike(Keywrd(`let`), pat, termTree, N) => ctx => - val ident = pat match // TODO handle patterns and rm special cases - case ident: Ident => ident - case und: Under => new Ident("_").withLocOf(und) - termTree match - case S(termTree) => - val sym = VarSymbol(ident) - val fallbackCtx = ctx + (ident.name -> sym) - Split.Let(sym, term(termTree)(using ctx), elabFallback(fallbackCtx)) - case PrefixApp(Keywrd(Keyword.`do`), computation) => ctx => trace( - pre = s"termSplit: do $computation", - post = (res: Split) => s"termSplit: do >>> $res" - ): - val sym = TempSymbol(N, "doTemp") - Split.Let(sym, term(computation)(using ctx), elabFallback(ctx)) - case PrefixApp(Keywrd(Keyword.`else`), default) => ctx => - // TODO: report `rest` as unreachable - Split.default(term(default)(using ctx)) - case rawRhs => ctx => - log(s"rawRhs: $rawRhs") - val rhs = rawRhs.splitOn(Trm(vs.ref(/* FIXME ident? */))) - log(s"rhs: $rhs") - termSplit(rhs, identity)(elabFallback(ctx))(ctx) - case _ => fallback => _ => - raise(ErrorReport(msg"Unrecognized term split (${tree.describe})." -> tree.toLoc :: Nil, - extraInfo = S(tree))) - fallback.withoutLoc // Hacky... a loc is always added for the result - - /** Given a elaborated scrutinee, give it a name and add it to the context. - * @param baseCtx the context to be extended with the new symbol - * @param scrutinee the elaborated scrutinee - * @param cont the continuation that needs the symbol and the context - */ - def nominate(baseCtx: Ctx, scrutinee: Term, nameHint: Str = "scrut") - (cont: BlockLocalSymbol => Sequel): Split = scrutinee match - case ref @ Term.Ref(symbol: VarSymbol) => - val innerCtx = baseCtx + (ref.tree.name -> symbol) - cont(symbol)(innerCtx) - case _ => - val symbol = TempSymbol(N, nameHint) - val innerCtx = baseCtx + (nameHint -> symbol) - Split.Let(symbol, scrutinee, cont(symbol)(innerCtx)) - - /** Decompose a `Tree` of conjunct matches. The tree is from the same line in - * the source code and followed by a `then`, or `and` with a continued line. - * A formal definition of the conjunction is: - * - * ```bnf - * conjunction ::= conjunction `and` conjunction # conjunction - * | term `is` pattern # pattern matching - * | term # Boolean condition - * ``` - * - * Each match is represented by a pair of a _coda_ and a _pattern_ that is - * yet to be elaborated. For boolean conditions, the pattern is a `BoolLit`. - * - * This function does not invoke elaboration and the implementation utilizes - * functional lists to avoid calling the `reverse` method on the output, - * which returns type `List[T]` instead of `::[T]`. See paper _A Novel - * Representation of Lists and Its Application to the Function_ for details. - * - * @param tree the tree to desugar - * @return a non-empty list of scrutinee and pattern pairs represented in - * type `::[T]` (instead of `List[T]`) so that the head element - * can be retrieved in a type-safe manner - */ - def disaggregate(tree: Tree): ::[(Tree, Tree)] = trace( - pre = s"disaggregate <<< $tree", - post = (ms: ::[(Tree, Tree)]) => - s"disaggregate >>> ${ms.mkString(", ")}" - ): - type TT = (Tree, Tree) - def go(tree: Tree, acc: TT => ::[TT]): () => ::[TT] = tree match - case lhs `and` rhs => go(lhs, ::(_, go(rhs, acc)())) - case lhs `or` rhs => - raise(ErrorReport( - msg"Logical `or` is not yet supported." -> tree.toLoc :: Nil)) - go(lhs, ::(_, go(rhs, acc)())) // FIXME: this is currently copy-pasted from the `and` case - case scrut `is` pat => () => acc((scrut, pat)) - case test => () => acc((test, Tree.BoolLit(true))) - go(tree, ::(_, Nil))() - - /** Desugar a _pattern split_ (PS) into a _split_ of core abstract syntax. - * The scrutinee has been already elaborated when this method is called. - * @param tree the `Tree` representing the pattern split - * @param scrutSymbol the symbol representing the elaborated scrutinee - */ - def patternSplit(tree: Tree, scrutSymbol: BlockLocalSymbol): Split => Sequel = - patternSplit(N, tree, scrutSymbol) - - /** Similar to `patternSplit`, but allows a transformation on the pattern head. - * @param transform the partial pattern before enter this pattern split - */ - def patternSplit( - finish: Opt[Tree => Tree], - tree: Tree, - scrutSymbol: BlockLocalSymbol - ): Split => Sequel = tree match - case blk: Block => blk.desugStmts.foldRight(default): (branch, elabFallback) => - // Terminology: _fallback_ refers to subsequent branches, _backup_ refers - // to the backup plan passed from the parent split. - branch.deparenthesized match - case LetLike(Keywrd(`let`), ident @ Ident(_), termTree, N) => backup => ctx => - termTree match - case S(termTree) => - val sym = VarSymbol(ident) - val fallbackCtx = ctx + (ident.name -> sym) - Split.Let(sym, term(termTree)(using ctx), elabFallback(backup)(fallbackCtx)) - case N => - raise(ErrorReport(msg"Pattern matching with `let` must have a term." -> branch.toLoc :: Nil)) - backup - case PrefixApp(Keywrd(Keyword.`do`), computation) => fallback => ctx => trace( - pre = s"patternSplit (do) <<< $computation", - post = (res: Split) => s"patternSplit: else >>> $res" - ): - val sym = TempSymbol(N, "doTemp") - Split.Let(sym, term(computation)(using ctx), elabFallback(fallback)(ctx)) - case PrefixApp(Keywrd(Keyword.`else`), body) => backup => ctx => trace( - pre = s"patternSplit (else) <<< $tree", - post = (res: Split) => s"patternSplit (else) >>> ${res.showDbg}" - ): - elabFallback(backup)(ctx) match - case Split.End => () - case _ => raise(ErrorReport(msg"Any following branches are unreachable." -> branch.toLoc :: Nil)) - Split.default(term(body)(using ctx)) - case branch => backup => ctx => trace( - pre = "patternSplit (alternative)", - post = (res: Split) => s"patternSplit (alternative) >>> ${res.showDbg}" - ): - patternSplit(finish, branch, scrutSymbol)(elabFallback(backup)(ctx))(ctx) - // For example, `Some of "A" then 0`, and - // ``` - // Some of - // "A" then 0 - // "B" then 1 - // ``` - // The precedence of `of` is higher than `then`. - case app @ App(_: (Ident | Sel | SynthSel), Tup(branches)) => - patternSplit(S(tree => app.copy(rhs = Tup(tree :: Nil))), Block(branches), scrutSymbol) - .traced(pre = s"patternSplit <<< partial pattern", post = (_) => s"patternSplit >>>") - case patternAndMatches ~> consequent => fallback => - // There are N > 0 conjunct matches. We use `::[T]` instead of `List[T]`. - // Each match is represented by a pair of a _coda_ and a _pattern_ - // that is yet to be elaborated. - val (headPattern, _) :: tail = disaggregate(patternAndMatches) - val realPattern = finish match - case N => headPattern - case S(f) => f(headPattern) - log(s"realPattern: $realPattern") - // The `consequent` serves as the innermost split, based on which we - // expand from the N-th to the second match. - val tailSplit = - val innermostSplit = consequent match - case L(tree) => termSplit(tree, identity)(Split.End) - case R(tree) => (ctx: Ctx) => Split.default(term(tree)(using ctx)) - tail.foldRight(innermostSplit): - case ((coda, pat), sequel) => ctx => - nominate(ctx, term(coda)(using ctx)): - expandMatch(_, pat, sequel, Nil)(Split.End) - .traced( - pre = s"conjunct matches <<< $tail", - post = (res: Split) => s"conjunct matches >>> $res") - expandMatch(scrutSymbol, realPattern, tailSplit, Nil)(fallback).traced( - pre = s"patternBranch <<< $patternAndMatches -> ${consequent.fold(_.showDbg, _.showDbg)}", - post = (res: Split) => s"patternBranch >>> ${res.showDbg}") - case _ => - raise(ErrorReport(msg"Unrecognized pattern split (${tree.describe})." -> tree.toLoc :: Nil)) - _ => _ => Split.default(Term.Error) - - /** Elaborate a single match (a scrutinee and a pattern) and forms a split - * with an innermost split as the sequel of the match. - * @param scrutSymbol the symbol representing the scrutinee - * @param pattern the un-elaborated pattern - * @param sequel the innermost split - * @return a function that takes the tail of the split and a context - */ - def expandMatch(scrutSymbol: BlockLocalSymbol, pattern: Tree, sequel: Sequel, output: Ls[VarSymbol]): Split => Sequel = - def ref = scrutSymbol.ref(/* FIXME ident? */) - def dealWithCtorCase(ctor: Ctor, mode: MatchMode)(fallback: Split): Sequel = ctx => - Branch(ref, FlatPattern.ClassLike(term(ctor), N, mode, false)(ctor, output), sequel(ctx)) ~: fallback - def dealWithAppCtorCase( - app: Tree, ctor: Ctor, args: Ls[Tree], mode: MatchMode - )(fallback: Split): Sequel = ctx => - val scrutinees = scrutSymbol.getSubScrutinees(args.size) - val matches = scrutinees.iterator.zip(args).map: - case (symbol, tree) => tree match - // We only elaborate arguments marked with `pattern` keyword. This is - // due to a technical limitation that the desugarer generates flat - // patterns on the fly and we don't know whether the argument should - // be interpreted as a sub-pattern or a pattern argument. This can be - // solved after we rewrite the desugarer using `Pattern`. - case TypeDef(syntax.Pat, body, _) => Argument(symbol, elaborator.pattern(body)) - case _: Tree => Argument(symbol, tree) - .toList - Branch( - ref, - FlatPattern.ClassLike(term(ctor), S(matches), mode, false)(app, output), // TODO: refined? - subMatches(matches, sequel)(Split.End)(ctx) - ) ~: fallback - pattern.deparenthesized.desugared match - // A single wildcard pattern. - case Under() => fallback => ctx => sequel(ctx) ++ fallback - // Alias pattern - case pat as (id: Ident) => fallback => - val aliasSymbol = VarSymbol(id) - val inner = (ctx: Ctx) => - val ctxWithAlias = ctx + (id.name -> aliasSymbol) - sequel(ctxWithAlias) - // Split.Let(aliasSymbol, ref, sequel(ctxWithAlias)) - expandMatch(scrutSymbol, pat, inner, aliasSymbol :: output)(fallback) - case id @ Ident(nme) if nme.headOption.forall(_.isLower) => fallback => ctx => - val aliasSymbol = VarSymbol(id) - val ctxWithAlias = ctx + (nme -> aliasSymbol) - // Not only bind the variable, but also bind the output symbols. - Split.Let(aliasSymbol, ref, output.foldRight(sequel(ctxWithAlias) ++ fallback): - case (symbol, innerSplit) => Split.Let(symbol, ref, innerSplit)) - case ctor: Ctor => dealWithCtorCase(ctor, MatchMode.Default) - case Annotated(annotation, ctor: Ctor) => - dealWithCtorCase(ctor, MatchMode.Annotated(term(annotation))) - case Tree.Tup(args) => fallback => ctx => trace( - pre = s"expandMatch <<< ${args.mkString(", ")}", - post = (r: Split) => s"expandMatch >>> ${r.showDbg}" - ): - // Break tuple into three parts: - // 1. A fixed number of leading patterns. - // 2. A variable number of middle patterns indicated by `..`. - // 3. A fixed number of trailing patterns. - val (lead, rest) = args.foldLeft[(Ls[Tree], Opt[(Keyword.Ellipsis, Opt[Tree], Ls[Tree])])]((Nil, N)): - case ((lead, N), Spread(kw, patOpt)) => (lead, S((kw.kw, patOpt, Nil))) - case ((lead, N), pat) => (lead :+ pat, N) - case ((lead, S((kw: Keyword.Ellipsis, rest, last))), pat) => (lead, S((kw, rest, last :+ pat))) - // `wrap`: add let bindings for tuple elements - // `matches`: pairs of patterns and symbols to be elaborated - val (wrapRest, restMatches) = rest match - case S((kw, rest, last)) => - val (wrapLast, reversedLastMatches) = last.reverseIterator.zipWithIndex - .foldLeft[(Split => Split, Ls[Argument.Term])]((identity, Nil)): - case ((wrapInner, matches), (pat, lastIndex)) => - val sym = scrutSymbol.getTupleLastSubScrutinee(lastIndex) - val wrap = (split: Split) => - Split.Let(sym, callTupleGet(ref, -1 - lastIndex, sym), wrapInner(split)) - (wrap, Argument(sym, pat) :: matches) - val lastMatches = reversedLastMatches.reverse - rest match - case N => (wrapLast, lastMatches) - case S(pat) => - val sym = TempSymbol(N, "rest") - val wrap = (split: Split) => - val arg0 = PlainFld(ref) - val arg1 = PlainFld(Term.Lit(IntLit(lead.length))) - val arg2 = PlainFld(Term.Lit(IntLit(BigInt(last.length)))) - val args = Term.Tup(arg0 :: arg1 :: arg2 :: Nil)(DummyTup) - val func = kw match - case Keyword.`..` => tupleLazySlice - case Keyword.`...` => tupleSlice - val call = Term.App(func, args)(DummyApp, N, TempSymbol(N, "slice")) - Split.Let(sym, call, wrapLast(split)) - (wrap, Argument(sym, pat) :: lastMatches) - case N => (identity: Split => Split, Nil) - val (wrap, arguments) = lead.zipWithIndex.foldRight((wrapRest, restMatches)): - case ((pat, i), (wrapInner, arguments)) => - val sym = scrutSymbol.getTupleLeadSubScrutinee(i) - val wrap = (split: Split) => - // TODO: Changing from the following line in #318 breaks some LLIR difftests (marked :todo) - // Split.Let(sym, Term.SynthSel(ref, Ident(s"$i"))(N), wrapInner(split)) - Split.Let(sym, callTupleGet(ref, i, sym), wrapInner(split)) - (wrap, Argument(sym, pat) :: arguments) - Branch( - ref, - FlatPattern.Tuple(lead.length + rest.fold(0)(_._3.length), rest.isDefined)(output), - // The outermost is a tuple, so pattern arguments are not possible. - wrap(subMatches(arguments, sequel)(Split.End)(ctx)) - ) ~: fallback - // Negative numeric literals - case App(Ident("-"), Tup(IntLit(value) :: Nil)) => fallback => ctx => - Branch(ref, FlatPattern.Lit(IntLit(-value))(output), sequel(ctx)) ~: fallback - case App(Ident("-"), Tup(DecLit(value) :: Nil)) => fallback => ctx => - Branch(ref, FlatPattern.Lit(DecLit(-value))(output), sequel(ctx)) ~: fallback - case OpApp(lhs, Ident("&"), rhs :: Nil) => fallback => ctx => - val newSequel = expandMatch(scrutSymbol, rhs, sequel, output)(fallback) - expandMatch(scrutSymbol, lhs, newSequel, output)(fallback)(ctx) - case OpApp(lhs, Ident("|"), rhs :: Nil) => fallback => ctx => - val newFallback = expandMatch(scrutSymbol, rhs, sequel, output)(fallback)(ctx) - expandMatch(scrutSymbol, lhs, sequel, output)(newFallback)(ctx) - // A single constructor pattern. - case Annotated(annotation, app @ App(ctor: Ctor, Tup(args))) => - dealWithAppCtorCase(app, ctor, args, MatchMode.Annotated(term(annotation))) - case app @ App(ctor: Ctor, Tup(args)) => - dealWithAppCtorCase(app, ctor, args, MatchMode.Default) - case app @ OpApp(lhs, ctor: Ctor, rhss) => - // TODO improve (eventually remove DummyApp) - dealWithAppCtorCase(app, ctor, lhs :: rhss, MatchMode.Default) - // A single literal pattern - case literal: Literal => fallback => ctx => trace( - pre = s"expandMatch: literal <<< $literal", - post = (r: Split) => s"expandMatch: literal >>> ${r.showDbg}" - ): - Branch(ref, FlatPattern.Lit(literal)(output), sequel(ctx)) ~: fallback - // A single pattern in conjunction with more conditions - case pattern and consequent => fallback => ctx => - val innerSplit = termSplit(consequent, identity)(Split.End) - expandMatch(scrutSymbol, pattern, innerSplit, output)(fallback)(ctx) - case pattern where condition => fallback => ctx => - val sym = TempSymbol(N, "conditionTemp") - val newSequel = expandMatch(sym, Tree.BoolLit(true), sequel, output)(fallback) - val newNewSequel = (ctx: Ctx) => Split.Let(sym, term(condition)(using ctx), newSequel(ctx)) - expandMatch(scrutSymbol, pattern, newNewSequel, output)(fallback)(ctx) - case Jux(Ident(".."), Ident(_)) => fallback => _ => - raise(ErrorReport(msg"Illegal rest pattern." -> pattern.toLoc :: Nil)) - fallback - case InfixApp(StrLit(fieldName), Keyword.`:`, pat) => fallback => ctx => - val fieldIdent = (Ident(fieldName): Ident).withLocOf(pattern) - val symbol = scrutSymbol.getFieldScrutinee(fieldIdent) - Branch( - ref, - FlatPattern.Record((fieldIdent, symbol) :: Nil)(output), - subMatches(Argument(symbol, pat) :: Nil, sequel)(Split.End)(ctx) - ) ~: fallback - case InfixApp(fieldName: Ident, Keyword.`:`, pat) => fallback => ctx => - val symbol = scrutSymbol.getFieldScrutinee(fieldName) - Branch( - ref, - FlatPattern.Record((fieldName, symbol) :: Nil)(output), - subMatches(Argument(symbol, pat) :: Nil, sequel)(Split.End)(ctx) - ) ~: fallback - case Pun(false, fieldName) => fallback => ctx => - val symbol = scrutSymbol.getFieldScrutinee(fieldName) - Branch( - ref, - FlatPattern.Record((fieldName, symbol) :: Nil)(output), - subMatches(Argument(symbol, fieldName) :: Nil, sequel)(Split.End)(ctx) - ) ~: fallback - case Block(st :: Nil) => fallback => ctx => - expandMatch(scrutSymbol, st, sequel, output)(fallback)(ctx) - case Block(sts) => fallback => ctx => // we assume this is a record - sts.foldRight[Option[List[(Tree.Ident, BlockLocalSymbol, Tree)]]](S(Nil)){ - // this collects the record parts, or fails if some statement does not correspond - // to a record field - case (_, N) => N // we only need to fail once to return N - case (p, S(tl)) => p match - case InfixApp(StrLit(fieldName), Keyword.`:`, pat) => - val fieldIdent = (Ident(fieldName): Ident).withLocOf(p) - S((fieldIdent, scrutSymbol.getFieldScrutinee(fieldIdent), pat) :: tl) - case InfixApp(fieldName: Ident, Keyword.`:`, pat) => - S((fieldName, scrutSymbol.getFieldScrutinee(fieldName), pat) :: tl) - case Pun(false, fieldName) => - S((fieldName, scrutSymbol.getFieldScrutinee(fieldName), fieldName) :: tl) - case p => - raise(ErrorReport(msg"invalid record field pattern" -> p.toLoc :: Nil)) - None - }.fold(fallback)(recordContent => - Branch( - ref, - FlatPattern.Record(recordContent.map((fieldName, symbol, _) => (fieldName, symbol)))(output), - subMatches(recordContent.map((_, symbol, pat) => Argument(symbol, pat)), sequel)(Split.End)(ctx) - ) ~: fallback - ) - case Bra(BracketKind.Curly | BracketKind.Round, inner) => fallback => ctx => - expandMatch(scrutSymbol, inner, sequel, output)(fallback)(ctx) - case pattern => fallback => _ => - // Raise an error and discard `sequel`. Use `fallback` instead. - raise(ErrorReport(msg"Unrecognized pattern (${pattern.describe})" -> pattern.toLoc :: Nil)) - fallback - - /** Desugar a list of sub-patterns (with their corresponding scrutinees). - * This is called when handling nested patterns. The caller is responsible - * for providing the symbols of scrutinees. - * - * @param matches a list of pairs consisting of a scrutinee and a pattern - * @param sequel the innermost split - */ - def subMatches(matches: Ls[Argument], - sequel: Sequel): Split => Sequel = matches match - case Nil => _ => ctx => trace( - pre = s"subMatches (done) <<< Nil", - post = (r: Split) => s"subMatches >>> ${r.showDbg}" - ): - sequel(ctx) - case (Argument.Term(_, Under()) | Argument.Pattern(_, _)) :: rest => - subMatches(rest, sequel) // Skip pattern arguments and wildcards - case Argument.Term(scrutinee, tree) :: rest => fallback => trace( - pre = s"subMatches (nested) <<< $scrutinee is $tree", - post = (r: Sequel) => s"subMatches (nested) >>>" - ): - val innermostSplit = subMatches(rest, sequel)(fallback) - expandMatch(scrutinee, tree, innermostSplit, Nil)(fallback) - - /** Desugar `case` expressions. */ - def apply(tree: Case, scrut: VarSymbol)(using Ctx): Split = - val topmost = patternSplit(tree.branches, scrut)(Split.End)(ctx) - reportInconsistentConnectives(tree.kw) - topmost ++ topmostDefault - - /** Desugar `if` and `while` expressions. */ - def apply(tree: IfLike)(using Ctx): Split = - val topmost = termSplit(tree.split, identity)(Split.End)(ctx) - reportInconsistentConnectives(tree.kw) - topmost ++ topmostDefault - - /** Desugar `is` and `and` shorthands. */ - def apply(tree: InfixApp)(using Ctx): Split = - termSplitShorthands(tree, identity)(Split.default(Term.Lit(Tree.BoolLit(false))))(ctx) -end Desugarer diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala index b4a714b70a..20811a741d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -3,60 +3,61 @@ package semantics package ucs import mlscript.utils.*, shorthands.* -import syntax.*, Tree.Ident -import Elaborator.{Ctx, ctx, State} -import collection.mutable.Buffer +import syntax.*, Tree.Ident, Elaborator.State -import FlatPattern.* -import hkmc2.codegen.Block - -/** Flat patterns for pattern matching */ +/** + * Flattened patterns used in splits for `Normalization` and `Lowering`. All + * cases of patterns declared hereby can be matched in constant time and fixed + * number of comparisons. Non-trivial patterns (e.g., unions, intersections, + * transformations, etc) have been compiled to and represented by `Split`. + */ enum FlatPattern extends AutoLocated: - /** The symbol that binds the output of this pattern. */ - val output: Ls[BlockLocalSymbol] - - case Lit(literal: Literal)(val output: Ls[BlockLocalSymbol]) + case Lit(literal: Literal) - /** An individual argument is None when it is not matched, i.e. when an underscore is used there. - * The whole argument list is None when no argument list is being matched at all, as in `x is Some then ...`. */ + /** + * To match against a class or an object. + * + * @param constructor The term representing the class or the object. + * @param symbol The symbol resolved from `constructor`. + * @param arguments The sub-scrutinees and their locations. This field is + * `None` when no argument list is provided, i.e., `x is Some`. + * @param refined Whether the type of the scrutinee can be further refined by + * other patterns in nested splits. It is not used currently. + * @param tree The tree which this pattern is elaborated from. This is only + * used for error reporting and should not be copied in `mkClone`. + */ case ClassLike( val constructor: Term, - val arguments: Opt[Ls[Argument]], - val mode: MatchMode, + val symbol: ClassSymbol | ModuleOrObjectSymbol, + val arguments: Opt[Ls[(BlockLocalSymbol, Opt[Loc])]], var refined: Bool - )(val tree: Tree, val output: Ls[BlockLocalSymbol]) + )(val tree: Tree) - case Pattern( - val constructor: Term, - val patternArguments: Ls[Argument.Pattern], - val extractionArguments: Ls[Argument.Term], - )(val output: Ls[BlockLocalSymbol]) + case Tuple(size: Int, inf: Bool) - case Tuple(size: Int, inf: Bool)(val output: Ls[BlockLocalSymbol]) + case Record(entries: List[(Ident -> BlockLocalSymbol)]) - case Record(entries: List[(Ident -> BlockLocalSymbol)])(val output: Ls[BlockLocalSymbol]) + def mkClone(using State): FlatPattern = this match + case Lit(literal) => Lit(literal) + case pattern @ ClassLike(constructor, symbol, arguments, refined) => + ClassLike(constructor.mkClone, symbol, arguments, refined)(Tree.Dummy) + case Tuple(size, inf) => Tuple(size, inf) + case Record(entries) => Record(entries) def subTerms: Ls[Term] = this match - case p: ClassLike => p.constructor :: (p.mode match - case MatchMode.Default => p.arguments.fold(Nil): - _.iterator.flatMap: - case Argument.Term(_, _) => Nil - case Argument.Pattern(_, pattern) => pattern.subTerms - .toList - case _: MatchMode.StringPrefix => Nil - case MatchMode.Annotated(annotation) => annotation :: Nil) + case p: ClassLike => p.constructor :: Nil case _: (Lit | Tuple | Record) => Nil def children: Ls[Located] = this match case Lit(literal) => literal :: Nil - case ClassLike(ctor, scruts, _, _) => ctor :: scruts.fold(Nil)(_.map(_.scrutinee)) + case ClassLike(ctor, symbol, scruts, _) => ctor :: scruts.fold(Nil)(_.map(_._1)) case Tuple(fields, _) => Nil case Record(entries) => entries.flatMap { case (nme, als) => nme :: als :: Nil } def showDbg: Str = (this match case Lit(literal) => literal.idStr - case ClassLike(ctor, args, _, rfd) => + case ClassLike(ctor, symbol, args, rfd) => def showCtor(ctor: Term): Str = ctor match // This prints the symbol name without `refNum` and "member:" prefix. case Term.Ref(sym: BlockMemberSymbol) => sym.nme @@ -66,64 +67,8 @@ enum FlatPattern extends AutoLocated: case Term.SynthSel(p, i) => s"${showCtor(p)}.${i.name}" case _ => ctor.showDbg (if rfd then "refined " else "") + showCtor(ctor) + - args.fold("")(_.iterator.map(_.scrutinee.nme).mkString("(", ", ", ")")) + args.fold("")(_.iterator.map(_._1.nme).mkString("(", ", ", ")")) case Tuple(size, inf) => "[]" + (if inf then ">=" else "=") + size case Record(Nil) => "{}" case Record(entries) => - entries.iterator.map(_.name + ": " + _).mkString("{ ", ", ", " }")) + - output.iterator.map(s => s.nme).mkStringOr("as ", " as ", "", "") - -object FlatPattern: - /** Represent arguments in constructor patterns. - * - * @param scrutinee the symbol representing the scrutinee - * @param tree the original `Tree` for making error messages - * @param pattern is for the new pattern compilation and translation. - */ - enum Argument extends Located: - val scrutinee: BlockLocalSymbol - - case Term(val scrutinee: BlockLocalSymbol, tree: Tree) - case Pattern(val scrutinee: BlockLocalSymbol, pattern: semantics.Pattern) - - override def toLoc: Opt[Loc] = this match - case Term(_, tree) => tree.toLoc - case Argument.Pattern(_, pattern) => pattern.toLoc - - object Argument: - def apply(scrutinee: BlockLocalSymbol, tree: Tree): Argument.Term = - Argument.Term(scrutinee, tree) - def apply(scrutinee: BlockLocalSymbol, pattern: semantics.Pattern): Argument.Pattern = - Argument.Pattern(scrutinee, pattern) - - /** A class-like pattern whose symbol is resolved to a class. */ - object Class: - def unapply(p: FlatPattern): Opt[ClassSymbol] = p match - case p: FlatPattern.ClassLike => p.constructor.symbol.flatMap(_.asCls) - case _ => N - - /** A class-like pattern whose symbol is resolved to a module. */ - object Module: - def unapply(p: FlatPattern): Opt[ModuleOrObjectSymbol] = p match - case p: FlatPattern.ClassLike => p.constructor.symbol.flatMap(_.asModOrObj) - case _ => N - - enum MatchMode: - /** The default mode. If the constructor resolves to: - * - a `ClassSymbol`, then check if the scrutinee is an instance; - * - a `ModuleSymbol`, then check if the scrutinee is the object; - * - a `PatternSymbol`, then call `unapply` on the pattern. - */ - case Default - /** Call `unapplyStringPrefix` instead of `unapply`. */ - case StringPrefix(prefix: TempSymbol, postfix: TempSymbol) - /** The pattern is annotated. The normalization will intepret the pattern - * matching behavior based on the resolved symbol - */ - case Annotated(annotation: Term) - - object ClassLike: - def apply(constructor: Term, arguments: Opt[Ls[Argument]], output: Ls[BlockLocalSymbol]): ClassLike = - ClassLike(constructor, arguments, MatchMode.Default, false)(Tree.Dummy, output) - def apply(constructor: Term, symbols: Opt[Ls[BlockLocalSymbol]]): ClassLike = - ClassLike(constructor, symbols.map(_.map(Argument(_, Tree.Dummy))), MatchMode.Default, false)(Tree.Dummy, Nil) + entries.iterator.map(_.name + ": " + _).mkString("{ ", ", ", " }")) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala index dbebf00251..2a3857b3ee 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -3,16 +3,15 @@ package semantics package ucs import mlscript.utils.*, shorthands.* -import syntax.{Literal, Tree}, utils.TraceLogger +import syntax.{Literal, Tree}, utils.* import Message.MessageContext import Elaborator.{Ctx, State, ctx} -import utils.* -import FlatPattern.Argument -import ups.Instantiator -import hkmc2.semantics.ups.NaiveCompiler +import codegen.Lowering +import collection.mutable.{Map as MutMap} -class Normalization(using tl: TL)(using Raise, Ctx, State) extends TermSynthesizer: - import Normalization.*, Mode.*, FlatPattern.MatchMode + +class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State) extends TermSynthesizer: + import Normalization.*, Mode.* import tl.* def reportUnreachableCase[T <: Located](unreachable: Located, subsumedBy: T, when: Bool = true): T = @@ -38,21 +37,6 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends TermSynthesiz case Split.Cons(head, tail) => Split.Cons(head, tail ++ those) case Split.Let(name, term, tail) => Split.Let(name, term, tail ++ those) case Split.Else(_) /* impossible */ | Split.End => those) - - extension (lhs: FlatPattern.ClassLike) - /** Generate a term that really resolves to the class at runtime. */ - def selectClass: FlatPattern.ClassLike = - val constructor = lhs.constructor.symbol match - case S(cls: ClassSymbol) => lhs.constructor - case S(mem: BlockMemberSymbol) => - // If the class is declaration-only, we do not need to select the - // class. - if !mem.hasLiftedClass || mem.defn.exists(_.hasDeclareModifier.isDefined) then - lhs.constructor - else - Term.SynthSel(lhs.constructor, Tree.Ident("class"))(mem.clsTree.orElse(mem.modOrObjTree).map(_.symbol), N).resolve - case _ => lhs.constructor - lhs.copy(constructor)(lhs.tree, lhs.output) extension (lhs: FlatPattern) /** Checks if two patterns are the same. */ @@ -95,13 +79,13 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends TermSynthesiz case FlatPattern.Record(rhsEntries) => val filteredEntries = lhs.entries.filter: (fieldName1, _) => rhsEntries.forall { (fieldName2, _) => !(fieldName1 === fieldName2)} - FlatPattern.Record(filteredEntries)(lhs.output) + FlatPattern.Record(filteredEntries) case rhs: FlatPattern.ClassLike => rhs.constructor.symbol.flatMap(_.asCls) match case S(cls: ClassSymbol) => cls.defn match case S(ClassDef.Parameterized(params = paramList)) => val filteredEntries = lhs.entries.filter: (fieldName1, _) => paramList.params.forall { (param:Param) => !(fieldName1 === param.sym.id)} - FlatPattern.Record(filteredEntries)(lhs.output) + FlatPattern.Record(filteredEntries) case S(_) | N => lhs case S(_) | N => lhs case _ => lhs @@ -120,393 +104,23 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends TermSynthesiz ): normalizeImpl(split) - /** Bind the current scrutinee to a flat pattern's output symbols. */ - def aliasOutputSymbols(scrutinee: => Term.Ref, outputSymbols: Ls[BlockLocalSymbol], split: Split): Split = - outputSymbols.foldRight(split): - // Can we use `Subst` to transform the inner split? - case (symbol, innerSplit) => Split.Let(symbol, scrutinee, innerSplit) - def normalizeImpl(split: Split)(using vs: VarSet): Split = split match - case Split.Cons(Branch(scrutinee, pattern, consequent), alternative) => pattern match - case pattern: (FlatPattern.Lit | FlatPattern.Tuple | FlatPattern.Record) => - log(s"MATCH: ${scrutinee.showDbg} is ${pattern.showDbg}") - // TODO(ucs): deduplicate [1] - val whenTrue = aliasOutputSymbols(scrutinee, pattern.output, - normalize(specialize(consequent ++ alternative, +, scrutinee, pattern))) - val whenFalse = normalizeImpl(specialize(alternative, -, scrutinee, pattern).clearFallback) - Branch(scrutinee, pattern, whenTrue) ~: whenFalse - case pattern @ FlatPattern.ClassLike(ctor, argsOpt, mode, _) => - log(s"MATCH: ${scrutinee.showDbg} is ${pattern.showDbg}") - // Make sure that the pattern has correct arity and fields are accessible. - ctor.symbol.map(_.asClsLike) match - case N => // The constructor is not resolved. The error should have been reported. - normalizeImpl(alternative) - case S(N) => - // The constructor is not a class-like symbol. But it might be a - // `VarSymbol` referencing to a pattern parameter. - ctor.symbol match - case S(symbol: VarSymbol) => symbol.decl match - case S(param @ Param(flags = FldFlags(pat = true))) => - if argsOpt.fold(false)(_.nonEmpty) then - error(msg"Pattern parameters cannot be applied." -> ctor.toLoc) - mode match - case MatchMode.Default => - normalizeExtractorPatternParameter(scrutinee, ctor, pattern.output, consequent, alternative) - case sp: MatchMode.StringPrefix => - log(s"symbol name is ${symbol.nme}") - normalizeStringPrefixPattern(scrutinee, ctor, N, sp, pattern.output, consequent, alternative) - case MatchMode.Annotated(annotation) => - error(msg"Annotated pattern parameters are not supported here." -> annotation.toLoc) - normalizeImpl(alternative) - case S(_) | N => - error(msg"Cannot use this ${ctor.describe} as a pattern" -> ctor.toLoc) - normalizeImpl(alternative) - case S(_) | N => - error(msg"Cannot use this ${ctor.describe} as a pattern" -> ctor.toLoc) - normalizeImpl(alternative) - case S(S(cls: (ClassSymbol | ModuleOrObjectSymbol))) if mode.isInstanceOf[MatchMode.StringPrefix] => - // Match classes and modules are disallowed in the string mode. - normalizeImpl(alternative) - case S(S(cls: ClassSymbol)) => - validateMatchMode(ctor, cls, mode) - if validateClassPattern(ctor, cls, ensureArguments(argsOpt)) then // TODO(ucs): deduplicate [1] - val whenTrue = aliasOutputSymbols(scrutinee, pattern.output, - normalize(specialize(consequent ++ alternative, +, scrutinee, pattern))) - val whenFalse = normalizeImpl(specialize(alternative, -, scrutinee, pattern).clearFallback) - Branch(scrutinee, pattern.selectClass, whenTrue) ~: whenFalse - else // If any errors were raised, we skip the branch. - log("BROKEN"); normalizeImpl(alternative) - case S(S(mod: ModuleOrObjectSymbol)) => - validateMatchMode(ctor, mod, mode) - if validateObjectPattern(pattern, mod, argsOpt) then // TODO(ucs): deduplicate [1] - val whenTrue = aliasOutputSymbols(scrutinee, pattern.output, - normalize(specialize(consequent ++ alternative, +, scrutinee, pattern))) - val whenFalse = normalizeImpl(specialize(alternative, -, scrutinee, pattern).clearFallback) - Branch(scrutinee, pattern.selectClass, whenTrue) ~: whenFalse - else // If any errors were raised, we skip the branch. - log("BROKEN"); normalizeImpl(alternative) - case S(S(pat: PatternSymbol)) => mode match - // Note: `argsOpt` is supposed to be used in following cases, but - // the current implementation does not use it. The future version - // should properly handle the pattern arguments. - case MatchMode.Default => - normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, pattern.output, consequent, normalizeImpl(alternative)) - case sp: MatchMode.StringPrefix => - normalizeStringPrefixPattern(scrutinee, ctor, argsOpt, sp, pattern.output, consequent, normalizeImpl(alternative)) - case MatchMode.Annotated(annotation) => annotation.symbol match - case S(symbol) if symbol === ctx.builtins.annotations.compile => - normalizeCompiledPattern(scrutinee, pat, ctor, argsOpt, pattern.output, consequent, normalizeImpl(alternative)) - case S(_) => - warn(msg"This annotation is not supported here." -> annotation.toLoc, - msg"Note: Patterns (like `${pat.nme}`) only support the `@compile` annotation." -> N) - normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, pattern.output,consequent, normalizeImpl(alternative)) - case N => - // Name resolution should have already reported an error. We - // treat this as an extractor pattern. - normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, pattern.output, consequent, normalizeImpl(alternative)) + case Split.Cons(Branch(scrutinee, pattern, consequent), alternative) => + log(s"MATCH: ${scrutinee.showDbg} is ${pattern.showDbg}") + val whenTrue = normalize(specialize(consequent ++ alternative.duplicate, +, scrutinee, pattern)) + val whenFalse = normalizeImpl(specialize(alternative, -, scrutinee, pattern).clearFallback) + Branch(scrutinee, pattern, whenTrue) ~: whenFalse case Split.Let(v, _, tail) if vs has v => log(s"LET: SKIP already declared scrutinee $v") normalizeImpl(tail) case Split.Let(v, rhs, tail) => log(s"LET: $v") Split.Let(v, rhs, normalizeImpl(tail)(using vs + v)) - case Split.Else(default) => + case split @ Split.Else(default) => log(s"DFLT: ${default.showDbg}") - Split.Else(default) + split case Split.End => Split.End - /** Check whether the number of parameters in class-like patterns matches the - * number in their definition, and whether each parameter is accessible. - */ - private def validateClassPattern( - ctorTerm: Term, - ctorSymbol: ClassSymbol, - argsOpt: Opt[Ls[FlatPattern.Argument.Term]] - ): Bool = - // Obtain the `classHead` used for error reporting and the parameter list - // from the class definitions. - val (classHead, paramsOpt) = ctorSymbol.defn match - case N => lastWords(s"Class ${ctorSymbol.name} does not have a definition") - // Use the constructor pattern's location for error reporting. - case S(cd) => new Tree.Ident(ctorSymbol.name).withLoc(ctorTerm.toLoc) -> cd.paramsOpt - paramsOpt match - case S(paramList) => argsOpt match - case S(args) => - // Check the number of parameters is correct. - if args.size != paramList.params.size then - val loc = Loc(args) orElse ctorTerm.toLoc - error: - if paramList.params.isEmpty then - msg"The constructor does not take any arguments but found ${ - "argument" countBy args.size}." -> loc - else - msg"Expected ${"argument" countBy paramList.params.size - }, but found ${if args.size < paramList.params.size then "only " else "" - }${"argument" countBy args.size}." -> loc - // Check the fields are accessible. - paramList.params.iterator.zip(args).map: - case (_, Argument.Term(_, Tree.Under())) => true - case (Param(flags, sym, _, _), arg) if !flags.isVal => - error(msg"This pattern cannot be matched" -> arg.toLoc, // TODO: use correct location - msg"because the corresponding parameter `${sym.name}` is not publicly accessible" -> sym.toLoc, - msg"Suggestion: use a wildcard pattern `_` in this position" -> N, - msg"Suggestion: mark this parameter with `val` so it becomes accessible" -> N) - false - case _ => true - // If patterns are more than parameters, or one of parameters is - // incessible, we cannot make the branch. - .foldLeft(args.size <= paramList.params.size)(_ && _) - case N => argsOpt match - case S(args) => - error(msg"class ${ctorSymbol.name} does not have parameters" -> classHead.toLoc, - msg"but the pattern has ${"sub-pattern" countBy args.size}" -> Loc(args)) - false - case N => true // No parameters, no arguments. This is fine. - case N => - // The class doesn't have parameters. Check if scruts are empty. - argsOpt match - case S(Nil) => - error(msg"Class ${ctorSymbol.name} does not have a parameter list" -> ctorTerm.toLoc) - true - case S(args) => - error(msg"Class ${ctorSymbol.name} does not have a parameter list" -> ctorTerm.toLoc, - msg"but the pattern has ${"sub-pattern" countBy args.size}" -> Loc(args)) - false - case N => true - - /** Check whether the object pattern has an argument list. */ - private def validateObjectPattern(pattern: FlatPattern.ClassLike, mod: ModuleOrObjectSymbol, argsOpt: Opt[Ls[FlatPattern.Argument]]): Bool = argsOpt match - case S(Nil) => - // This means the pattern has an unnecessary parameter list. - error(msg"`${mod.name}` is an object." -> mod.id.toLoc, - msg"Its pattern cannot have an argument list." -> pattern.tree.toLoc) - true - case S(_ :: _) => - // This means the pattern is an object with parameters. - error(msg"`${mod.name}` is an object." -> mod.id.toLoc, - msg"Its pattern cannot have arguments." -> pattern.tree.toLoc) - false - case N => true - - /** Ensure that there are no pattern arguments. */ - private def ensureArguments( - arguments: Opt[Ls[FlatPattern.Argument]] - ): Opt[Ls[FlatPattern.Argument.Term]] = arguments.map: - _.flatMap: - case arg: FlatPattern.Argument.Term => S(arg) - case FlatPattern.Argument.Pattern(_, pattern) => - error(msg"This ${pattern.describe} pattern cannot be used as an argument here." -> pattern.toLoc); N - - /** Warn about inappropriate annotations used on class or object patterns. */ - private def validateMatchMode( - ctorTerm: Term, - ctorSymbol: ClassSymbol | ModuleOrObjectSymbol, - mode: MatchMode - ): Unit = mode match - case MatchMode.Default | _: MatchMode.StringPrefix => () - case MatchMode.Annotated(annotation) => annotation.symbol match - case S(symbol) if symbol === ctx.builtins.annotations.compile => - warn(msg"`@compile` cannot be used on ${ctorSymbol.tree.k.desc} instance patterns." -> annotation.toLoc, - msg"Note: The `@compile` annotation is for compiling pattern definitions." -> N) - case S(_) => - warn(msg"This annotation is not supported on ${ctorSymbol.tree.k.desc} instance patterns." -> annotation.toLoc) - case N => () // `Resolver` should have already reported an error. - - /** This function normalizes a pattern that resolves to a pattern parameter. - * We might be able to merge this function with `normalizeExtractorPattern`. - * The difference is that we don't have a way to check the arity of the - * referenced pattern argument. */ - private def normalizeExtractorPatternParameter( - scrutinee: Term.Ref, - ctorTerm: Term, - outputSymbols: Ls[BlockLocalSymbol], - consequent: Split, - alternative: Split, - )(using VarSet): Split = - val call = app(sel(ctorTerm, "unapply").resolve, tup(fld(scrutinee)), s"result of unapply") - val split = tempLet("patternParamMatchResult", call): resultSymbol => - if outputSymbols.isEmpty then - // No need to destruct the result. - Branch(resultSymbol.safeRef, matchResultPattern(N), consequent) ~: alternative - else - val outputSymbol = TempSymbol(N, "output") - val bindingsSymbol = TempSymbol(N, "bindings") // TODO: This is useless. - Branch(resultSymbol.safeRef, matchResultPattern(S(outputSymbol :: bindingsSymbol :: Nil)), - aliasOutputSymbols(outputSymbol.safeRef, outputSymbols, consequent) - ) ~: alternative - normalize(split) - - /** Create a split that binds the pattern arguments. */ - def buildPatternArguments( - patternArguments: List[(BlockLocalSymbol, Pattern)], - split: Split - ): Split = - val compiler = new NaiveCompiler - patternArguments.foldRight(split): - case ((symbol, pattern), innerSplit) => - scoped("ucs:translation"): - log(s"build anonymous pattern: ${pattern.showDbg} for symbol ${symbol.nme}") - val record = compiler.compileAnonymousPattern(Nil, Nil, pattern) - Split.Let(symbol, record, innerSplit) - - /** Normalize splits whose leading branch matches a pattern and does not have - * a `@compile` annotation. */ - private def normalizeExtractorPattern( - scrutinee: Term.Ref, - patternSymbol: PatternSymbol, - ctorTerm: Term, - allArgsOpt: Opt[Ls[FlatPattern.Argument]], - outputSymbols: Ls[BlockLocalSymbol], - consequent: Split, - alternative: Split, - )(using VarSet): Split = - scoped("ucs:np"): - log: - allArgsOpt.fold(Iterator.empty[Str]): - _.iterator.map: - case Argument.Term(scrutinee, _) => s"extraction: ${scrutinee.nme}" - case Argument.Pattern(scrutinee, pattern) => s"pattern: ${scrutinee.nme} = ${pattern.showDbg}" - .mkString("extractor pattern arguments:\n", "\n", "") - val defn = patternSymbol.defn.getOrElse: - lastWords(s"Pattern `${patternSymbol.nme}` has not been elaborated.") - // Partition the arguments into pattern arguments and bindings. - val (extractionArgsOpt, patternArguments) = allArgsOpt.fold((N: Opt[Ls[BlockLocalSymbol]], Nil)): args => - val (extractionArgs, patternArgs) = args.partitionMap: - case Argument.Term(scrutinee, _) => Left(scrutinee) - case Argument.Pattern(scrutinee, pattern) => Right((scrutinee, pattern)) - (if extractionArgs.isEmpty then N else S(extractionArgs), patternArgs) - // Place pattern arguments first, then the scrutinee. - val unapplyArgs = patternArguments.map(_._1.safeRef |> fld) :+ fld(scrutinee) - val unapplyCall = app(sel(ctorTerm, "unapply").resolve, tup(unapplyArgs*), s"result of unapply") - val split = buildPatternArguments(patternArguments, tempLet("matchResult", unapplyCall): resultSymbol => - extractionArgsOpt match - case N => - if outputSymbols.isEmpty then - // No need to destruct the result. - Branch(resultSymbol.safeRef, matchResultPattern(N), consequent) ~: alternative - else - val extractionSymbol = TempSymbol(N, "output") - val bindingsSymbol = TempSymbol(N, "bindings") // TODO: This is useless. - Branch(resultSymbol.safeRef, matchResultPattern(S(extractionSymbol :: bindingsSymbol :: Nil)), - aliasOutputSymbols(extractionSymbol.safeRef, outputSymbols, consequent) - ) ~: alternative - case S(extractionArgs) => - val extractionParams = defn.extractionParams - // TODO: Check if the number of arguments is correct. - // Note that if the pattern definition doesn't have any extraction - // parameters, we still allow there to be a single argument, which - // represents the entire output. - val extractionSymbol = TempSymbol(N, "tuple") - val bindingsSymbol = TempSymbol(N, "bindings") // TODO: This is useless. - if extractionArgs.size === extractionParams.size then - log(s"number of arguments is correct") - // If the number of arguments is the same as the number of extraction - // parameters, we destruct the `MatchResult`'s argument as a tuple - // with length equal to the number of extraction parameters. - // - // For example, with pattern `pattern Foo(x, y, z) = ...`, we are - // allowed to do `if input is Foo(x, y, z) then ...`. - Branch(resultSymbol.safeRef, matchResultPattern(S(extractionSymbol :: bindingsSymbol :: Nil)), - aliasOutputSymbols(extractionSymbol.safeRef, outputSymbols, - makeTupleBranch(extractionSymbol.safeRef, extractionArgs, consequent, Split.End)) - ) ~: alternative - else extractionArgs match - case arg :: Nil if extractionParams.isEmpty => - log(s"only one argument and no extraction params") - // If the pattern definition doesn't have any extraction parameters, - // we allow there to be a single argument, which represents the - // entire output of the pattern. - // - // For example, with pattern `pattern Foo = ...`, we are allowed to - // do `if input is Foo(output) then ...`, which is equivalent to - // `if input is Foo as output then ...`. - Branch(resultSymbol.safeRef, matchResultPattern(S(extractionSymbol :: bindingsSymbol :: Nil)), - aliasOutputSymbols(extractionSymbol.safeRef, outputSymbols, - Split.Let(arg, extractionSymbol.safeRef, consequent)) - ) ~: alternative - case _ => - log(s"number of arguments is incorrect") - // Otherwise, the number of arguments is incorrect. - error(msg"Expected ${"argument" countBy extractionParams.size - }, but found ${if extractionArgs.size < extractionParams.size then "only " else "" - }${"argument" countBy extractionArgs.size}." -> Loc(extractionArgs)) - // TODO: Improve the error message by checking the pattern definition - // and demonstrating how to correctly write the pattern. - normalizeImpl(alternative)) - normalize(split) - - private def normalizeStringPrefixPattern( - scrutinee: Term.Ref, - ctorTerm: Term, - allArgsOpt: Opt[Ls[FlatPattern.Argument]], - stringPrefix: MatchMode.StringPrefix, - outputSymbols: Ls[BlockLocalSymbol], - consequent: Split, - alternative: Split, - )(using VarSet): Split = trace( - pre = s"normalizeStringPrefixPattern <<< ${ctorTerm.showDbg}", - post = (r: Split) => s"normalizeStringPrefixPattern >>> ${r.prettyPrint}" - ): - val patternArguments = allArgsOpt.fold(Nil)(_.collect: - case Argument.Pattern(symbol, pattern) => symbol -> pattern) - val call = - val method = "unapplyStringPrefix" - val args = tup(patternArguments.map(_._1.safeRef) :+ scrutinee) - app(sel(ctorTerm, method), args, s"result of $method") - val split = tempLet("matchResult", call): resultSymbol => - // let `matchResult` be the return value - val outputSymbol = TempSymbol(N, "arg") - val bindingsSymbol = TempSymbol(N, "bindings") - Branch( - resultSymbol.safeRef, - matchResultPattern(S(outputSymbol :: bindingsSymbol :: Nil)), - aliasOutputSymbols(resultSymbol.safeRef, outputSymbols, - // Bind the `remaining` variable to the second element of the output - // of `matchResult`. - Split.Let(stringPrefix.prefix, callTupleGet(outputSymbol.safeRef, 0, "prefix"), - Split.Let(stringPrefix.postfix, callTupleGet(outputSymbol.safeRef, 1, "postfix"), consequent))) - ) ~: alternative - normalize(buildPatternArguments(patternArguments, split)) - - // Note: This function will be overhauled in the new pattern compilation scheme. - private def normalizeCompiledPattern( - scrutinee: Term.Ref, - symbol: PatternSymbol, - ctorTerm: Term, - argsOpt: Opt[Ls[FlatPattern.Argument]], - outputSymbols: Ls[BlockLocalSymbol], - consequent: Split, - alternative: Split, - )(using VarSet): Split = scoped("ucs:rp"): - import ups.* - - // Instantiate the pattern and all patterns used in it. - val instantiator = new Instantiator - val patternArguments = argsOpt.fold(Nil)(_.collect: - case Argument.Pattern(_, pattern) => pattern) - val (synonym, context) = instantiator(symbol, patternArguments, Loc(ctorTerm :: patternArguments)) - // Initate the compilation. - val compiler = new Compiler(using context) - val ((matcherSymbol, fieldName), implementations) = compiler.buildMatcher(synonym) - val innermostSplit = - // 1. Bind the call result to a variable. - val recordSymbol = TempSymbol(N, "matchRecord") - val recordTerm = app(matcherSymbol.safeRef, tup(fld(scrutinee)), "result of matcher function") - val f1 = Split.Let(recordSymbol, recordTerm, _) - // 2. Select the selection field to the result. - val matchResultSymbol = TempSymbol(N, "matchResult") - val matchResultTerm = sel(recordSymbol.safeRef, fieldName) - val f2 = Split.Let(matchResultSymbol, matchResultTerm, _) - // 3. Check if the field value is a `MatchResult` and bind the output. - val outputSymbol = TempSymbol(N, "patternOutput") - val bindingsSymbol = TempSymbol(N, "bindings") // TODO: This is useless. - val branch = Branch(matchResultSymbol.safeRef, matchResultPattern(S(outputSymbol :: bindingsSymbol :: Nil)), - aliasOutputSymbols(outputSymbol.safeRef, outputSymbols, consequent)) - f1(f2(branch ~: alternative)) - implementations.iterator.foldRight(innermostSplit): - case ((symbol, paramList, term), innerSplit) => - Split.Let(symbol, Term.Lam(paramList, term), innerSplit) - /** * Specialize `split` with the assumption that `scrutinee` matches `pattern`. * If `mode` is `+`, the function _keeps_ branches that agree on @@ -589,14 +203,218 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends TermSynthesiz rec(split)(using mode, summon) private def aliasBindings(p: FlatPattern, q: FlatPattern): Split => Split = (p, q) match - case (FlatPattern.ClassLike(_, S(ss1), _, _), FlatPattern.ClassLike(_, S(ss2), _, _)) => + case (FlatPattern.ClassLike(_, _, S(ss1), _), FlatPattern.ClassLike(_, _, S(ss2), _)) => ss1.iterator.zip(ss2.iterator).foldLeft(identity[Split]): - case (acc, (l, r)) if l.scrutinee === r.scrutinee => acc - case (acc, (l, r)) => innermost => Split.Let(r.scrutinee, l.scrutinee.safeRef, acc(innermost)) + case (acc, (l, r)) if l._1 === r._1 => acc + case (acc, (l, r)) => innermost => Split.Let(r._1, l._1.safeRef, acc(innermost)) case (_, _) => identity + + import codegen.*, lowering.{term_nonTail, subTerm_nonTail, unreachableFn} + + /** Collect terms that appear in multiple `Split.Else` branches. We will share + * the corresponding blocks to avoid code duplication. */ + private def createLabelsForDuplicatedBranches(split: Split): Labels = + val counts: MutMap[Term, (order: Int, count: Int)] = MutMap.empty + var throwCount = 0 + def rec(s: Split): Unit = s match + case Split.End => throwCount += 1 + case Split.Else(els) => counts.updateWith(els): + case S((n, count)) => S((n, count + 1)) + case N => S((counts.size + 1, 1)) + case Split.Let(_, _, tail) => rec(tail) + case Split.Cons(Branch(_, _, cons), tail) => rec(cons); rec(tail) + rec(split) + val consequents = + counts.iterator.filter(_._2.count > 1).toSeq.sortBy(_._2.order).zipWithIndex.map: + case ((term, _), i) => (term, TempSymbol(S(term), s"split_${i + 1}$$")) + .toList + val default = if throwCount > 1 then S(TempSymbol(N, s"split_default$$")) else N + Labels(consequents, default) + + private def lowerSplit( + split: Split, + cont: (Result => Block) \/ (Bool => Result => Block), + topLevel: Bool + )(using labels: Labels)(using Subst): Block = split match + case Split.Let(sym, trm, tl) => + term_nonTail(trm): r => + Assign(sym, r, lowerSplit(tl, cont, topLevel)) + case Split.Cons(Branch(scrut, pat, tail), restSplit) => + subTerm_nonTail(scrut): sr => + tl.log(s"Binding scrut $scrut to $sr (${summon[Subst].map})") + def mkMatch(cse: Case -> Block) = Match(sr, cse :: Nil, + S(lowerSplit(restSplit, cont, topLevel = true)), + End() + ) + pat match + case FlatPattern.Lit(lit) => mkMatch(Case.Lit(lit) -> lowerSplit(tail, cont, topLevel = false)) + case FlatPattern.ClassLike(ctor, symbol, argsOpt, _refined) => + /** Make a continuation that creates the match. */ + def k(ctorSym: ClassLikeSymbol, clsParams: Ls[TermSymbol])(st: Path): Block = + val args = argsOpt.map(_.map(_._1)).getOrElse(Nil) + // Normalization should reject cases where the user provides + // more sub-patterns than there are actual class parameters. + assert(argsOpt.isEmpty || args.length <= clsParams.length, (argsOpt, clsParams)) + def mkArgs(args: Ls[TermSymbol -> BlockLocalSymbol])(using Subst): Case -> Block = args match + case Nil => + Case.Cls(ctorSym, st) -> lowerSplit(tail, cont, topLevel = false) + case (param, arg) :: args => + val (cse, blk) = mkArgs(args) + (cse, Assign(arg, Select(sr, new Tree.Ident(param.id.name).withLocOf(arg))(S(param)), blk)) + mkMatch(mkArgs(clsParams.iterator.zip(args).toList)) + // Select the constructor's `.class` field. + lazy val ctorTerm = ctor.symbol match + case S(mem: BlockMemberSymbol) => + // If the class is declaration-only, we do not need to + // select the class. + if !mem.hasLiftedClass || mem.defn.exists(_.hasDeclareModifier.isDefined) then ctor + else Term.SynthSel(ctor, Tree.Ident("class"))(mem.clsTree.orElse(mem.modOrObjTree).map(_.symbol), N).resolve + case _ => ctor + symbol match + case cls: ClassSymbol if ctx.builtins.virtualClasses contains cls => + // [invariant:0] Some classes (e.g., `Int`) from `Prelude` do + // not exist at runtime. If we do lowering on `trm`, backends + // (e.g., `JSBuilder`) will not be able to handle the corresponding selections. + // In this case the second parameter of `Case.Cls` will not be used. + // So we do not elaborate `ctor` when the `cls` is virtual + // and use it `Predef.unreachable` here. + k(cls, Nil)(unreachableFn) + case cls: ClassSymbol => + subTerm_nonTail(ctorTerm)(k(cls, cls.tree.clsParams)) + case mod: ModuleOrObjectSymbol => + subTerm_nonTail(ctorTerm)(k(mod, Nil)) + case FlatPattern.Tuple(len, inf) => mkMatch(Case.Tup(len, inf) -> lowerSplit(tail, cont, topLevel = false)) + case FlatPattern.Record(entries) => + val objectSym = ctx.builtins.Object + mkMatch( // checking that we have an object + Case.Cls(objectSym, Value.Ref(BuiltinSymbol(objectSym.nme, false, false, true, false))), + entries.foldRight(lowerSplit(tail, cont, topLevel = false)): + case ((fieldName, fieldSymbol), blk) => + mkMatch( + Case.Field(fieldName, safe = true), // we know we have an object, no need to check again + Assign(fieldSymbol, Select(sr, fieldName)(N), blk) + ) + ) + case Split.Else(els) => labels.get(els) match + case S(label) => Break(label) + case N => term_nonTail(els)(cont.fold(identity, _(topLevel))) + case Split.End => labels.default.fold(throwMatchErrorBlock)(Break(_)) + + /** + * Make a block that throws the match error. We might add the information of + * match failure in the future. + */ + private def throwMatchErrorBlock = + Throw(Instantiate(mut = false, Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Error"))(N), + Value.Lit(syntax.Tree.StrLit("match error")).asArg :: Nil)) // TODO add failed-match scrutinee info + + import syntax.Keyword.{`if`, `while`} + + def apply(t: Term.IfLike)(k: Result => Block)(using config: Config)(using Subst): Block = + val newSplit = t.split.getExpandedSplit + scoped("ucs:desugared"): + log(s"Split with nested patterns:\n${t.split.prettyPrint}") + log(s"Expanded split with flattened patterns:\n${newSplit.prettyPrint}") + this(newSplit, t.kw, S(t), k) + + def apply(t: Term.SynthIf)(k: Result => Block)(using Config, Subst): Block = + this(t.split, `if`, S(t), k) + + def apply(split: Split)(k: Result => Block)(using Config, Subst): Block = + this(split, `if`, N, k) + + private def apply(inputSplit: Split, kw: `if`.type | `while`.type, t: Opt[Term], k: Result => Block)(using Config, Subst) = + var usesResTmp = false + // The symbol of the temporary variable for the result of the `if`-like term. + // It will be created in one of the following situations. + // 1. The continuation `k` is not a tail operation. + // 2. There are shared consequents in the `if`-like term. + // 3. The term is a `while` and the result is used. + lazy val l = + usesResTmp = true + new TempSymbol(t) + // The symbol for the loop label if the term is a `while`. + lazy val loopLabel = new TempSymbol(t) + val normalized = tl.scoped("ucs:normalize"): + normalize(inputSplit)(using VarSet()) + tl.scoped("ucs:normalized"): + tl.log(s"Normalized:\n${normalized.prettyPrint}") + // Collect consequents that are shared in more than one branch. + given labels: Labels = createLabelsForDuplicatedBranches(normalized) + lazy val rootBreakLabel = new TempSymbol(N, "split_root$") + lazy val breakRoot = (r: Result) => Assign(l, r, Break(rootBreakLabel)) + lazy val assignResult = (r: Result) => Assign(l, r, End()) + val cont = + if kw === `while` then + // If the term is a `while`, the action of `else` branches depends on + // whether the the enclosing split is at the top level or not. + R((topLevel: Bool) => (r: Result) => Assign(l, r, if topLevel then End() else Continue(loopLabel))) + else if labels.isEmpty then + if k.isInstanceOf[TailOp] then + // If there are no shared consequents and the continuation is a tail + // operation, we can call it directly. + L(k) + else + // Otherwise, if the continuation is not a tail operation, we should + // save the result in a temporary variable and call the continuation + // in the end. + L(assignResult) + else + // When there are shared consequents, we are forced to save the result + // in the temporary variable nevertheless. Note that `cont` only gets + // called for non-shared consequents, so we should break to the end of + // the entire split after the assignment. + L(breakRoot) + // The main block contains the lowered split, where each shared consequent + // is replaced with a `Break` to the corresponding label. + val mainBlock = + val innermostBlock = lowerSplit(normalized, cont, topLevel = true) + // Wrap the main block in a labelled block for each shared consequent. The + // `rest` of each `Label` is the lowered consequent plus a `Break` to the + // end of the entire `if` term. Otherwise, it will fall through to the outer + // consequent, which is the wrong semantics. + val innerBlock: Block = labels.consequents match + case Nil => innermostBlock + case all @ (head :: tail) => + def wrap(consequents: Ls[(Term, TempSymbol)]): Block = + consequents.foldRight(innermostBlock): + case ((term, label), innerBlock) => + Label(label, false, innerBlock, term_nonTail(term)(breakRoot)) + // There is no need to generate `break` for the outermost split. + if labels.default.isEmpty then + Label(head._2, false, wrap(tail), term_nonTail(head._1)(assignResult)) + else wrap(all) + labels.default match + case S(label) => Label(label, false, innerBlock, throwMatchErrorBlock) + case N => innerBlock + // If there are shared consequents, we need a wrap the entire block in a + // `Label` so that `Break`s in the shared consequents can jump to the end. + val body = if labels.isEmpty then mainBlock else + Label(rootBreakLabel, false, mainBlock, End()) + // Embed the `body` into `Label` if the term is a `while`. + lazy val rest = if usesResTmp then k(Value.Ref(l)) else k(lowering.unit) + val block = + if kw === `while` then + Begin(Label(loopLabel, true, body, End()), rest) + else if labels.isEmpty && k.isInstanceOf[TailOp] then + body + else + Begin(body, rest) + scoped("ucs:lowered"): + log(s"Lowered:\n${block.showAsTree}") + block end Normalization object Normalization: + /** This contains the labels for duplicated consequents and the default + * branch which throws match errors. */ + private class Labels(val consequents: Ls[(Term, TempSymbol)], val default: Opt[TempSymbol]): + private val map = consequents.toMap + + inline def isEmpty: Bool = consequents.isEmpty && default.isEmpty + + inline def get(term: Term): Opt[TempSymbol] = map.get(term) + /** * Hard-coded subtyping relations used in normalization and coverage checking. * TODO use base classes and also handle modules @@ -605,18 +423,18 @@ object Normalization: import FlatPattern.*, ctx.builtins as blt (lhs, rhs) match // `Object` is the supertype of all (non-virtual) classes and modules. - case (Class(cs: ClassSymbol), Class(blt.`Object`)) + case (ClassLike(_, cs: ClassSymbol, _, _), ClassLike(symbol = blt.`Object`)) if !ctx.builtins.virtualClasses.contains(cs) => true // Class and module are subtypes of `Object`. - case (Module(_), Class(blt.`Object`)) => true + case (ClassLike(_, cs: ModuleOrObjectSymbol, _, _), ClassLike(symbol = blt.`Object`)) => true case (Tuple(n1, false), Tuple(n2, false)) if n1 === n2 => true case (Tuple(n1, _), Tuple(n2, true)) if n2 <= n1 => true - case (Class(blt.`Int`), Class(blt.`Num`)) => true + case (ClassLike(symbol = blt.`Int`), ClassLike(symbol = blt.`Num`)) => true // case (s1: ClassSymbol, s2: ClassSymbol) => s1 <:< s2 // TODO: find a way to check inheritance - case (Lit(Tree.IntLit(_)), Class(blt.`Int` | blt.`Num`)) => true - case (Lit(Tree.StrLit(_)), Class(blt.`Str`)) => true - case (Lit(Tree.DecLit(_)), Class(blt.`Num`)) => true - case (Lit(Tree.BoolLit(_)), Class(blt.`Bool`)) => true + case (Lit(Tree.IntLit(_)), ClassLike(symbol = blt.`Int` | blt.`Num`)) => true + case (Lit(Tree.StrLit(_)), ClassLike(symbol = blt.`Str`)) => true + case (Lit(Tree.DecLit(_)), ClassLike(symbol = blt.`Num`)) => true + case (Lit(Tree.BoolLit(_)), ClassLike(symbol = blt.`Bool`)) => true case (Record(entries1), Record(entries2)) => entries1.forall { (fieldName1, _) => entries2.exists { (fieldName2, _) => fieldName1 === fieldName2 } } case (Record(entries), rhs: ClassLike) => @@ -628,7 +446,8 @@ object Normalization: entries.forall { (fieldName, _) => clsParams.exists { case Param(flags = FldFlags(isVal = isVal), sym = sym) => isVal && fieldName === sym.id }} - case (_: FlatPattern, _: FlatPattern) => false + // case (Class(cs1: ClassSymbol), Class(cs2: ClassSymbol)) => true + case (_: FlatPattern, _: FlatPattern) => false final case class VarSet(declared: Set[BlockLocalSymbol]): def +(nme: BlockLocalSymbol): VarSet = copy(declared + nme) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/SplitElaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/SplitElaborator.scala new file mode 100644 index 0000000000..166189798f --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/SplitElaborator.scala @@ -0,0 +1,287 @@ +package hkmc2 +package semantics +package ucs + +import mlscript.utils.*, shorthands.* +import syntax.{Keyword, Tree}, Tree.* +import Keyword.{`and`, `do`, `else`, `if`, `is`, `let`, `or`, `then`} +import Elaborator.{Ctx, Ctxl, UnderCtx, ctx}, SimpleSplit.* +import Message.MessageContext +import collection.mutable.SortedSet +import utils.TL +import scala.annotation.tailrec + +object SplitElaborator: + /** A scrutinee is a function that returns a reference to the symbol. */ + type Reference = () => Term.Ref + + type Connective = `do`.type | `then`.type +import SplitElaborator.* + +trait SplitElaborator: + self: Elaborator => + + import tl.* + + private given TL = tl + + private given Ordering[Loc] = Ordering.by(l => (l.spanStart, l.spanEnd)) + + /** Keep track of the locations where `do` and `then` are used as connectives. */ + private var kwLocSets = (SortedSet.empty[Loc], SortedSet.empty[Loc]) + + private def reportInconsistentConnectives(kw: Keywrd[?]): Unit = + (kwLocSets._1.headOption, kwLocSets._2.headOption) match + case (Some(doLoc), Some(thenLoc)) => + raise(ErrorReport( + msg"Mixed use of `do` and `then` in the `${kw.kw.name}` expression." -> kw.toLoc + :: msg"Keyword `then` is used here." -> S(thenLoc) + :: msg"Keyword `do` is used here." -> S(doLoc) :: Nil + )) + case _ => () + + private def topmostDefault: SimpleSplit = + if kwLocSets._1.nonEmpty then Else(Term.UnitVal())(N) else End + + private object `~>`: + infix def unapply(tree: Tree): Opt[(Tree, Tree \/ (Keywrd[Connective], Tree))] = tree match + case InfixApp(lhs, Keywrd(`and`), rhs) => S((lhs, L(rhs))) + case InfixApp(lhs, kw @ Keywrd[`then`.type](`then`), rhs) => + kwLocSets._2 ++= tree.toLoc + S((lhs, R((kw, rhs)))) + case InfixApp(lhs, kw @ Keywrd[`do`.type](`do`), rhs) => + kwLocSets._1 ++= tree.toLoc + S((lhs, R((kw, rhs)))) + case _ => N + + private def withScopedConnectives(kw: Keywrd[?])(evaluate: => SimpleSplit): SimpleSplit = + val savedKwLocSets = kwLocSets + kwLocSets = (SortedSet.empty[Loc], SortedSet.empty[Loc]) + val split = evaluate + val result = split ~~: topmostDefault + reportInconsistentConnectives(kw) + kwLocSets = savedKwLocSets + result + + /** Transform trees into a UCS split. */ + protected def split(t: IfLike): Ctxl[SimpleSplit] = + withScopedConnectives(t.kw): + t.split match + case block: Block => termSplit(block.desugStmts, identity) + case other: Tree => termSplit(Ls(other), identity) + + /** Elaborate `case` expressions */ + protected def caseSplit(scrut: VarSymbol, tree: Case): Ctxl[SimpleSplit] = + withScopedConnectives(tree.kw): + patternBranch(() => scrut.ref(), tree.branches, identity) + + /** Elaborate shorthand expressions. */ + protected def shorthandSplit(tree: Tree)(using UnderCtx): Ctxl[SimpleSplit] = + val affirmative = Else(Term.Lit(BoolLit(true)))(N) + val negative = Else(Term.Lit(BoolLit(false)))(N) + val (scrutinee, pattern) :: matches = disaggregate(tree) + subterm(scrutinee).reference: scrutinee => + lazy val innerSplit: Ctxl[SimpleSplit] = expandMatches(matches)(affirmative) + pattern match + case Block(Nil) => + val recordPattern = Pattern.Record(Nil).withLocOf(pattern) + Head.Match(scrutinee(), recordPattern, innerSplit) ~: negative + case Block(trees) => trees.foldRight(negative): + case (pattern, alternative) => + Head.Match(scrutinee(), self.pattern(pattern), innerSplit) ~: alternative + case _ => + val firstPattern = self.pattern(pattern) + firstPattern.variables.report + (ctx ++ firstPattern.variables.allocate).givenIn: + Head.Match(scrutinee(), firstPattern, innerSplit) ~: negative + + /** Desugar a list of trees as a term split. The returned function takes a + * function, which takes a `Ctx` and returns a `SimpleSplit` representing + * the _alternative_ split, and returns a `SimpleSplit` representing the + * split of the given trees. */ + private def termSplit(ts: Ls[Tree], mk: Term => Term): Ctxl[SimpleSplit] = + val (_, splits) = ts.foldLeft((ctx, Ls[SimpleSplit]())): + case ((curCtx, splits), t) => + termBranch(t, mk)(using curCtx).mapSecond(_ :: splits) + concatenate(splits) + + /** Concatenate a sequence of splits and report warning for splits that come + * after a split which ends with an `else` branch. */ + private def concatenate(splits: Ls[SimpleSplit]): SimpleSplit = + // The first element is a list of branches. The second element is + // - `N` if no `else` branch has been found; or + // - `S((default, unreachables))` if `default` is the first `else` branch + // in the split and all splits thereafter will be added to `unreachables`. + val z: (Ls[Head], Opt[(Else, Ls[SimpleSplit])]) = (Nil, N) + val (reachables, elseRest) = splits.reverseIterator.foldLeft(z): + // This is the case when we haven't found an `else` branch yet. + case ((branches, N), split) => + @tailrec + def go(acc: Ls[Head], split: SimpleSplit): (Ls[Head], Opt[Else]) = + split match + case Cons(branch, tail) => go(branch :: acc, tail) + case els: Else => (acc, S(els)) + case End => (acc, N) + go(branches, split).mapSecond(_.map(_ -> (Nil: Ls[SimpleSplit]))) + case ((branches, S((default, unreachables))), split) => + (branches, S((default, split :: unreachables))) + // Report unreachables splits. + elseRest match + case S((default, unreachables)) => + val messages = unreachables.reverseIterator.map: split => + msg"This branch is unreachable." -> split.toLoc + .toList + if messages.nonEmpty then + raise(WarningReport((msg"This else clause makes the following branches unreachable." -> default.toLoc :: messages))) + case N => () + // Reconstruct the split from the reachable `heads`. + reachables.foldLeft(elseRest.fold(SimpleSplit.End)(_._1)): + case (innerSplit, branch) => branch ~: innerSplit + + /** Handle the common cases of branches in splits. */ + private def branch(using Ctx): Cfg[PartialFunction[Tree, (Ctx, SimpleSplit)]] = + // Interleaved-`let` bindings like `{ x is A then 0; let x = 1; ... }`. + case LetLike(Keywrd(`let`), ident: Ident, S(rhsTree), N) => + val symbol = VarSymbol(ident) + val head = Head.Let(symbol, term(rhsTree)) + ((ctx + (ident.name -> symbol)), head ~: End) + // Interleaved-`do` statements like `{ x is A then 0; do log(1); ... }`. + case PrefixApp(Keywrd(`do`), rhsTree) => + (ctx, Head.Let(TempSymbol(N, "unused"), term(rhsTree)) ~: End) + // Although the `else`-clause marks the end of the split, we cannot + // stop and still have to elaborate the remaining trees. + case PrefixApp(kw: Keywrd[`else`.type], elseTree) => + (ctx, Else(term(elseTree))(S(kw))) + + private def expandMatches(matchesTree: Ls[TT])(consequent: Ctxl[SimpleSplit]): Ctxl[SimpleSplit] = + val z = (ctx, Ls[(Term, Pattern)]()) + // Elaborate the term and the pattern in each match. + val (innerCtx, matches) = matchesTree.foldLeft(z): + case ((curCtx, matches), (scrutineeTree, patternTree)) => + val scrutinee = term(scrutineeTree)(using curCtx) + val pattern = self.pattern(patternTree)(using curCtx) + pattern.variables.report + val resCtx = curCtx ++ pattern.variables.allocate + (resCtx, (scrutinee, pattern) :: matches) + // As `matches` is reversed, we should process it from the left. + val split = matches.foldLeft(consequent(using innerCtx)): + case (innerSplit, (scrutinee, pattern)) => + scrutinee.reference: scrutineeRef => + Head.Match(scrutineeRef(), pattern, innerSplit) ~: End + split + + private def termBranch(t: Tree, mk: Term => Term): Ctxl[(Ctx, SimpleSplit)] = branch.appOrElse(t): + case block: Block => (ctx, termSplit(block.desugStmts, mk)) + case lhs is rhs => (ctx, mk(term(lhs)).reference(patternBranch(_, rhs, identity))) + // Several matches followed by `and`, `do`, or `then`. + case matchesTree ~> consequent => + val (coda, patternTree) :: matches = disaggregate(matchesTree) + def innerSplit(using ctx: Ctx) = expandMatches(matches): + consequent match + case L(tree) => termSplit(Ls(tree), identity) + case R((kw, tree)) => Else(term(tree))(S(kw)) + val split = coda match + case Under() => innerSplit + case coda => mk(term(coda)).reference: scrutinee => + val pattern = self.pattern(patternTree) + val innerCtx = ctx ++ pattern.variables.allocate + Head.Match(scrutinee(), pattern, innerSplit(using innerCtx)) ~: End + (ctx, split) + // Handle splits on binary operators. + case OpApp(lhs, ident: Ident, rhss) => + val op = term(ident) + val split = term(lhs).reference: lhs => + val mk2 = (rhs: Term) => + val args = Term.Tup(PlainFld(lhs()) :: PlainFld(rhs) :: Nil)(DummyTup) + Term.App(op, args)(Tree.DummyApp, N, FlowSymbol("‹operator-split›")) + termSplit(rhss, mk2 andThen mk) + (ctx, split) + case OpSplit(lhs, rhss) => + val split = mk(term(lhs)).reference: lhs => + val (_, splits) = rhss.foldLeft((ctx, Ls[SimpleSplit]())): + case ((curCtx, splits), t) => + operatorBranch(lhs, t)(using curCtx).mapSecond(_ :: splits) + concatenate(splits) + (ctx, split) + // Unrecognized term split. + case _ => + error(msg"Unrecognized term split (${t.describe})" -> t.toLoc) + (ctx, End) + + private def operatorBranch(scrutinee: Reference, rhs: Tree): Ctxl[(Ctx, SimpleSplit)] = + branch.appOrElse(rhs): rhsTree => + termBranch(rhsTree.splitOn(Trm(scrutinee())), identity) + + private def patternBranch(scrutinee: Reference, t: Tree, mk: Tree => Tree): Ctxl[SimpleSplit] = t match + case block: Block => + val (_, splits) = block.desugStmts.foldLeft((ctx, Ls[SimpleSplit]())): + case ((curCtx, splits), t) => + branch(using curCtx).lift(t).getOrElse: + (curCtx, patternBranch(scrutinee, t, mk)(using curCtx)) + .mapSecond(_ :: splits) + concatenate(splits) + case App(ctor: Ctor, Tup(rhss)) => + val nl = (t: Tree) => mk(App(ctor, Tup(t :: Nil))) + patternBranch(scrutinee, Block(rhss), nl) + case Annotated(annotation, target) => + patternBranch(scrutinee, target, Annotated(annotation, _) |> mk) + case patternAndMatches ~> consequentTree => + val (firstPatternTree, _) :: matches = disaggregate(patternAndMatches) + val firstPattern = self.pattern(mk(firstPatternTree)) + firstPattern.variables.report + (ctx ++ firstPattern.variables.allocate).givenIn: + val split = expandMatches(matches): + consequentTree match + case L(tree) => + termSplit(Ls(tree), identity) + case R((kw, tree)) => Else(term(tree))(S(kw)) + Head.Match(scrutinee(), firstPattern, split) ~: End + case _ => + error(msg"Unrecognized pattern split (${t.describe})." -> t.toLoc) + Else(Term.Error)(N).withLocOf(t) // To inspect the source of errors. + + extension (term: Term) + private inline def reference(continuation: Reference => SimpleSplit): SimpleSplit = + term match + // If the term is already a reference, we can re-reference its symbol. + case Term.Ref(symbol) => continuation(() => symbol.ref()) + // Otherwise, we need to create a temporary symbol holding the term. + case term: Term => + val symbol = TempSymbol(N, "scrut") + Head.Let(symbol, term) ~: continuation(() => symbol.ref()) + + private type TT = (Tree, Tree) + + /** Decompose a `Tree` of conjunct matches. The tree is from the same line in + * the source code and followed by a `then`, or `and` with a continued line. + * A formal definition of the conjunction is: + * + * ```bnf + * conjunction ::= conjunction `and` conjunction # conjunction + * | term `is` pattern # pattern matching + * | term # Boolean condition + * ``` + * + * Each match is represented by a pair of a _coda_ and a _pattern_ that is + * yet to be elaborated. For boolean conditions, the pattern is a `BoolLit`. + * + * This function does not invoke elaboration and the implementation utilizes + * functional lists to avoid calling the `reverse` method on the output, + * which returns type `List[T]` instead of `::[T]`. See paper _A Novel + * Representation of Lists and Its Application to the Function_ for details. + * + * @param tree the tree to desugar + * @return a non-empty list of scrutinee and pattern pairs represented in + * type `::[T]` (instead of `List[T]`) so that the head element + * can be retrieved in a type-safe manner + */ + private def disaggregate(tree: Tree): ::[TT] = + def go(tree: Tree, acc: TT => ::[TT]): () => ::[TT] = tree match + case lhs `and` rhs => go(lhs, ::(_, go(rhs, acc)())) + case lhs `or` rhs => + error(msg"Logical `or` is not yet supported." -> tree.toLoc) + go(lhs, ::(_, go(rhs, acc)())) // FIXME: this is currently copy-pasted from the `and` case + case scrut `is` pat => () => acc((scrut, pat)) + case test => () => acc((test, Tree.BoolLit(true))) + go(tree, ::(_, Nil))() + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala index c2d013cda5..7c35911e76 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala @@ -3,13 +3,13 @@ package semantics package ucs import mlscript.utils.*, shorthands.* -import syntax.Tree.*, Elaborator.{Ctx, State, ctx} +import syntax.Tree, Tree.*, Elaborator.{Ctx, State, ctx} /** This trait includes some helpers for synthesizing `Term`s which look like * they have already been processed by the `Resolver`. Its methods should only * be called in stages after the `Resolver`. Currently, its derived classes are - * `Normalization`, `Compiler`, and `NaiveCompiler`. */ -trait TermSynthesizer(using Ctx, State): + * `Normalization`, `Compiler`, and `SplitCompiler`. */ +trait TermSynthesizer(using State): protected final def sel(p: Term, k: Ident): Term.SynthSel = (Term.SynthSel(p, k)(N, N): Term.SynthSel).resolve protected final def sel(p: Term, k: Ident, s: FieldSymbol): Term.SynthSel = @@ -41,16 +41,20 @@ trait TermSynthesizer(using Ctx, State): sel(runtimeRef, "MatchResult", State.matchResultClsSymbol) /** Make a pattern that looks like `runtime.MatchResult.class`. */ - protected def matchResultPattern(parameters: Opt[Ls[BlockLocalSymbol]]): FlatPattern.ClassLike = - FlatPattern.ClassLike(sel(matchResultClass, "class", State.matchResultClsSymbol), parameters) + protected def matchResultPattern(parametersOpt: Opt[Ls[BlockLocalSymbol]]): FlatPattern.ClassLike = + val constructor = sel(matchResultClass, "class", State.matchResultClsSymbol) + val parameters = parametersOpt.map(_.map(_ -> N)) + FlatPattern.ClassLike(constructor, State.matchResultClsSymbol, parameters, false)(Tree.Dummy) /** Make a term that looks like `runtime.MatchFailure` with its symbol. */ protected lazy val matchFailureClass = sel(runtimeRef, "MatchFailure", State.matchFailureClsSymbol) /** Make a pattern that looks like `runtime.MatchFailure.class`. */ - protected def matchFailurePattern(parameters: Opt[Ls[BlockLocalSymbol]]): FlatPattern.ClassLike = - FlatPattern.ClassLike(sel(matchFailureClass, "class", State.matchFailureClsSymbol), parameters) + protected def matchFailurePattern(parametersOpt: Opt[Ls[BlockLocalSymbol]]): FlatPattern.ClassLike = + val constructor = sel(matchFailureClass, "class", State.matchFailureClsSymbol) + val parameters = parametersOpt.map(_.map(_ -> N)) + FlatPattern.ClassLike(constructor, State.matchFailureClsSymbol, parameters, false)(Tree.Dummy) protected lazy val tupleSlice = sel(sel(runtimeRef, "Tuple"), "slice") protected lazy val tupleLazySlice = sel(sel(runtimeRef, "Tuple"), "lazySlice") @@ -124,7 +128,7 @@ trait TermSynthesizer(using Ctx, State): consequent: => Split, alternative: Split ): Split = - Branch(scrut, FlatPattern.Tuple(subScrutinees.size, false)(Nil), + Branch(scrut, FlatPattern.Tuple(subScrutinees.size, false), subScrutinees.iterator.zipWithIndex.foldRight(consequent): case ((arg, index), innerSplit) => val label = s"the $index-th element of the match result" @@ -150,4 +154,4 @@ trait TermSynthesizer(using Ctx, State): case ((arg, index), innerSplit) => val label = s"the first ${index + 1}-th element of the tuple" Split.Let(arg, callTupleGet(scrut, index, label), innerSplit) - Branch(scrut, FlatPattern.Tuple(leading.size + trailing.size, true)(Nil), split2) ~: alternative + Branch(scrut, FlatPattern.Tuple(leading.size + trailing.size, true), split2) ~: alternative diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/package.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/package.scala index 2f342a4973..d76ce27df2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/package.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/package.scala @@ -2,6 +2,8 @@ package hkmc2 package semantics import sourcecode.{FileName, Line, Name} +import syntax.{Keyword, Tree}, Tree.{Ident, InfixApp, Keywrd, Sel, SynthSel} +import mlscript.utils.*, shorthands.* package object ucs: def error(using Line, FileName, Name, Raise)(msgs: (Message, Option[Loc])*): Unit = @@ -20,22 +22,17 @@ package object ucs: */ def safeRef: Term.Ref = symbol.ref().resolve + extension (op: Keyword.Infix) + infix def unapply(tree: Tree): Opt[(Tree, Tree)] = tree match + case InfixApp(lhs, Keywrd(`op`), rhs) => S((lhs, rhs)) + case _ => N + + type Ctor = SynthSel | Sel | Ident + /** A helper extractor for matching the tree of `x | y`. */ object extractors: - import syntax.Tree, Tree.* - import mlscript.utils.*, shorthands.* - - object or: - infix def unapply(tree: Tree): Opt[(Tree, Tree)] = tree match - case OpApp(lhs, Ident("|"), rhs :: Nil) => S(lhs, rhs) - case _ => N + import Tree.OpApp - /** A helper extractor for matching the tree of `x a y`.*/ - object and: - infix def unapply(tree: App): Opt[(Tree, Tree)] = tree match - case App(Ident("&"), Tup(lhs :: rhs :: Nil)) => S(lhs, rhs) - case _ => N - /** A helper extractor for matching the tree of `x ..= y` and `x ..< y`. * The Boolean value indicates whether the range is inclusive. */ @@ -44,9 +41,4 @@ package object ucs: case OpApp(lhs, Ident("..="), rhs :: Nil) => S(lhs, (true, rhs)) case OpApp(lhs, Ident("..<"), rhs :: Nil) => S(lhs, (false, rhs)) case _ => N - - object `~`: - infix def unapply(tree: Tree): Opt[(Tree, Tree)] = tree match - case OpApp(lhs, Ident("~"), rhs :: Nil) => S(lhs, rhs) - case _ => N end ucs diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala index 8e6eb3c525..04227b8e4c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -6,7 +6,7 @@ package ups import mlscript.utils.*, shorthands.* import syntax.{Keyword, LetBind, Tree}, Tree.{DecLit, Ident, IntLit, StrLit, UnitLit} -import Term.{Blk, IfLike, Rcd, Ref, SynthSel} +import Term.{Blk, Rcd, Ref, SynthIf, SynthSel} import Pattern.{Instantiation, Head} import Elaborator.{Ctx, State, ctx}, utils.TL import ucs.{TermSynthesizer, FlatPattern, safeRef} @@ -42,9 +42,10 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Ter extension (head: Head) /** Create a flat pattern that can be used in the UCS expressions. */ def toFlatPattern: FlatPattern = head match - case lit: syntax.Literal => FlatPattern.Lit(lit)(Nil) + case lit: syntax.Literal => FlatPattern.Lit(lit) case sym: (ClassSymbol | ModuleOrObjectSymbol) => - FlatPattern.ClassLike(reference(sym, head.toLoc).getOrElse(Term.Error), N, Nil) + val constructor = reference(sym, head.toLoc).getOrElse(Term.Error) + FlatPattern.ClassLike(constructor, sym, N, false)(Tree.Dummy) def showDbg: Str = head match case lit: syntax.Literal => lit.idStr case sym: ClassLikeSymbol => sym.nme @@ -119,7 +120,7 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Ter Split.Else(multiMatcherBranch(specialized, scrutinee)) // Make a split that tries all branches in order. val topmostSplit = branches.foldRight(default)(_ ~: _) - val bodyTerm = IfLike(Keyword.`if`, topmostSplit) + val bodyTerm = SynthIf(topmostSplit) log(s"Multi-matcher body:\n${topmostSplit.prettyPrint}") (paramList(param(scrutinee)), bodyTerm) @@ -153,11 +154,11 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Ter // Check the presence of the field, and call the matcher if it exists. val fieldIdent: Ident = field.asIdent val fieldSymbol = TempSymbol(N, fieldIdent.name) - val fieldTest = FlatPattern.Record((fieldIdent -> fieldSymbol) :: Nil)(Nil) + val fieldTest = FlatPattern.Record((fieldIdent -> fieldSymbol) :: Nil) val consequent = Split.Else: app(subMatcherSymbol.safeRef, tup(fld(fieldSymbol.safeRef)), "result") val branch = Branch(scrutinee.safeRef, fieldTest, consequent) - IfLike(Keyword.`if`, branch ~: Split.Else(emptyRecordSymbol.safeRef)) + SynthIf(branch ~: Split.Else(emptyRecordSymbol.safeRef)) LetDecl(subScrutineeVar, Nil) :: DefineVar(subScrutineeVar, conditional) :: Nil .toList // If there are no bindings, we do not need to create the empty record. @@ -179,7 +180,7 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Ter // Here the string "topmost" is just to indicate the failure is // passed from the topmost split for the purpose of debugging. alternative = Split.Else(makeMatchFailure(str("topmost")))) - val test = IfLike(Keyword.`if`, split) + val test = SynthIf(split) // The corresponding record field should just take the result of the split. val field = RcdField(str(label.asFieldName), symbol.safeRef) (DefineVar(symbol, test) :: LetDecl(symbol, Nil) :: stmts, field :: fields) @@ -291,7 +292,7 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Ter ) ) ~: alternative)): MakeSplit makeMakeSplit(Nil, Nil) - case Tuple(leading, spread, trailing) => (_, _) => + case Tuple(leading, spread) => (_, _) => // TODO: Think about how to handle the spread pattern. error(msg"Tuple patterns are not supported yet." -> pattern.toLoc) Split.Else(makeMatchFailure(str("unsupported tuple pattern"))) @@ -347,17 +348,20 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Ter case Rename(pattern, name) => // We should add those fields to a context. completePattern(pattern, scrutinee, subScrutinees, name :: aliases) - case Extract(pattern, term) => + case Extract(pattern, correspondence, term) => // The symbol representing the transform function, which should be // declared at the outermost level. val transformSymbol = TempSymbol(N, "transform") // The transform function takes a single record as the argument. val bindingsSymbol = VarSymbol(Ident("args")) val params = paramList(param(bindingsSymbol)) - // We then bind the variables to fields of the record. + // Because we pass the extracted values using recoreds. We need to bind + // each property to its corresponding variable which is accessible from + // then `term`. val letBindings = pattern.symbols.flatMap: symbol => - LetDecl(symbol, Nil) :: - DefineVar(symbol, sel(bindingsSymbol.safeRef, symbol.name)) :: Nil + val termSymbol = correspondence(symbol) + LetDecl(termSymbol, Nil) :: + DefineVar(termSymbol, sel(bindingsSymbol.safeRef, termSymbol.name)) :: Nil val makeSplit = completePattern(pattern, scrutinee, subScrutinees, Nil) (makeConsequent, alternative) => Split.Let( sym = transformSymbol, diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala index ab64b836cd..459ef4c369 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala @@ -27,6 +27,13 @@ class Instantiator(using tl: TL)(using Ctx, State, Raise): * patterns that need to be instantiated. */ val thingsToDo: MutQueue[Instantiation] = MutQueue.empty + /** Instantiate an anonymous pattern. */ + def apply(pattern: SP): (Pat, Context) = scoped("ucs:instantiation"): + // TODO: We should not pass an empty map to the `instantiate` method if the + // caller is from the `SplitCompiler`. + val entryPoint = instantiate(pattern)(using Map.empty) + (entryPoint, runInstantiationLoop) + /** Instantiate a pattern and patterns used in it. */ def apply( symbol: PatternSymbol, @@ -35,6 +42,10 @@ class Instantiator(using tl: TL)(using Ctx, State, Raise): ): (Pat, Context) = scoped("ucs:instantiation"): val entryPoint = Instantiation(symbol, arguments.map(instantiate(_)(using Map.empty)))(useSiteLoc) val result = schedule(entryPoint) + (Synonym(result), runInstantiationLoop) + + /** Run the loop to recursively instantiate needed patterns. */ + private def runInstantiationLoop: Context = while thingsToDo.nonEmpty do val instantiation = thingsToDo.dequeue() val defn = instantiation.symbol.defn.get @@ -61,7 +72,7 @@ class Instantiator(using tl: TL)(using Ctx, State, Raise): val definitions = progress.view.mapValues: _.getOrElse(lastWords("The pattern is expected to be instantiated.")) .toMap - (Synonym(result), Context(definitions)) + new Context(definitions) /** Add the instantiation to the queue if it has not been instantiated yet. */ def schedule(instantiation: Instantiation): Instantiation = @@ -70,12 +81,12 @@ class Instantiator(using tl: TL)(using Ctx, State, Raise): thingsToDo.enqueue(instantiation) else log(s"Already instantiated ${instantiation.showDbg}") - progress(instantiation) + progress(instantiation) instantiation /** Instantiate the given pattern with a substitution map. */ def instantiate(pattern: SP)(using subst: Map[VarSymbol, Pat]): Pat = pattern match - case SP.Constructor(target, patternArguments, arguments) => target.symbol match + case SP.Constructor(target, arguments) => target.symbol match // Look up the corresponding pattern from the substitution. case S(symbol: VarSymbol) => subst(symbol) // Recursively instantiate the arguments of constructor patterns. @@ -111,8 +122,11 @@ class Instantiator(using tl: TL)(using Ctx, State, Raise): msg"`${symbol.nme}` is a module, thus it cannot have arguments." -> Loc(arguments)) ClassLike(symbol, N) case S(symbol: PatternSymbol) => - val arguments = patternArguments.map(instantiate(_)) - val instantiation = Instantiation(symbol, arguments)(pattern.toLoc) + // TODO(after we defined the semantics of pattern parameters): We need + // to partition the arguments into pattern arguments and extraction + // arguments here. + val patternArguments = arguments.getOrElse(Nil).map(instantiate(_)) + val instantiation = Instantiation(symbol, patternArguments)(pattern.toLoc) Synonym(schedule(instantiation)) case SP.Composition(true, left, right) => instantiate(left) or instantiate(right) case SP.Composition(false, left, right) => instantiate(left) and instantiate(right) @@ -134,8 +148,13 @@ class Instantiator(using tl: TL)(using Ctx, State, Raise): case SP.Concatenation(left, right) => error(msg"String concatenation is not supported in pattern compilation." -> pattern.toLoc) Never - case SP.Tuple(leading, spread, trailing) => Tuple(leading.map(instantiate(_)), spread.map(instantiate(_)), trailing.map(instantiate(_))) - case SP.Record(fields) => Record(fields.iterator.map((id, pattern) => (id, instantiate(pattern))).to(SeqMap)) + case SP.Tuple(leading, spread) => + val instantiatedSpread = spread.map: + case (spreadKind, middle, trailing) => + (spreadKind, instantiate(middle), trailing.map(instantiate(_))) + Tuple(leading.map(instantiate(_)), instantiatedSpread) + case SP.Record(fields) => Record: + fields.iterator.map((id, pattern) => (id, instantiate(pattern))).to(SeqMap) case SP.Chain(first, second) => error(msg"Pattern chaining is not supported in pattern compilation." -> pattern.toLoc) Never @@ -143,4 +162,22 @@ class Instantiator(using tl: TL)(using Ctx, State, Raise): case N => instantiate(pattern) case S(symbol) => Rename(instantiate(pattern), symbol) // Pattern arguments are accessible throughout the pattern. - case SP.Transform(pattern, transform) => Extract(instantiate(pattern), transform) + case SP.Transform(pattern, parameters, transform) => + Extract(instantiate(pattern), parameters.toMap, transform) + case SP.Annotated(pattern, annotations) => + // Currently, we only support `@compile` annotation, so here we only + // check whether this annotation exists, and report an error for all + // other annotations. + val shouldCompile = annotations.foldLeft(true): (acc, termOrLoc) => + val res = termOrLoc match + case R(term) => term.resolvedSym match + case S(symbol) if symbol is ctx.builtins.annotations.compile => N + case S(_) | N => S(term.toLoc) + case L(loc) => S(loc) + res match + case S(loc) => + warn(msg"This annotation is not supported here." -> loc, + msg"Note: Patterns only support the `@compile` annotation." -> pattern.toLoc) + acc + case N => true + instantiate(pattern) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala deleted file mode 100644 index c6cc44baeb..0000000000 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala +++ /dev/null @@ -1,589 +0,0 @@ -package hkmc2 -package semantics -package ups - -import mlscript.utils.*, shorthands.* -import Message.MessageContext -import ucs.{TermSynthesizer, FlatPattern, error, safeRef}, ucs.extractors.* -import syntax.{Fun, Keyword, Tree}, Tree.{Ident, StrLit}, Keyword.{`as`, `=>`} -import scala.collection.mutable.Buffer -import Elaborator.{Ctx, State, ctx}, utils.TL -import semantics.Pattern as SP // "SP" is short for "semantic patterns" - -object NaiveCompiler: - /** String range bounds must be single characters. */ - def isInvalidStringBounds(lo: StrLit, hi: StrLit)(using Raise): Bool = - val ds = Buffer.empty[(Message, Option[Loc])] - if lo.value.length != 1 then - ds += msg"The lower bound of character ranges must be a single character." -> lo.toLoc - if hi.value.length != 1 then - ds += msg"The upper bound of character ranges must be a single character." -> hi.toLoc - if ds.nonEmpty then error(ds.toSeq*) - ds.nonEmpty - - /** Each scrutinee is represented by a function that creates a reference to - * the scrutinee symbol. It is sufficient for current implementation. - */ - type Scrut = () => Term.Ref - - extension (symbol: BlockLocalSymbol) - def toScrut: Scrut = () => symbol.safeRef - - type BindingMap = Map[VarSymbol, Scrut] - - type SplitSequel[Output] = (makeConsequent: (Output, BindingMap) => Split, alternative: Split) => Split - - extension [Output](sequel: SplitSequel[Output]) - def map[Result](f: Output => Result): SplitSequel[Result] = (makeConsequent, alternative) => - sequel((output, bindings) => makeConsequent(f(output), bindings), alternative) - - type MakeConsequent = (output: Scrut, bindings: BindingMap) => Split - - /** The continuation function returned by `makeMatchSplit`. - * Note that `alternative` does not serve as the fallback split. It means the - * next split we should try when the current split fails. - * - * Note: I realized that it would make more sense to let `output` be a term. - * Because in some cases, the output is not used in the consequent split. - * For example, the output of the `negation` pattern is discarded. - * - * The `bindings` parameter is used to denote the variables that are bound - * during the matching process. The key is the pattern's variable symbol and - * the value is the local symbol that represents the matched values. - */ - type MakeSplit = (makeConsequent: MakeConsequent, alternative: Split) => Split - - extension (first: MakeSplit) - def | (second: MakeSplit): MakeSplit = (makeConsequent, alternative) => - first(makeConsequent, second(makeConsequent, alternative)) - def & (second: MakeSplit): SplitSequel[(Scrut, Scrut)] = (makeConsequent, alternative) => - first( - (firstOutput, firstBindings) => second( - (secondOutput, secondBindings) => makeConsequent( - (firstOutput, secondOutput), - firstBindings ++ secondBindings // Do we need to duplicate the bindings? - ), - alternative), - alternative) - - type MakePrefixConsequent = (consumedOutput: Scrut, remainingOutput: Scrut, bindings: BindingMap) => Split - - /** The continuation function returned by `makeStringPrefixMatchSplit`. */ - type MakePrefixSplit = (makeConsequent: MakePrefixConsequent, alternative: Split) => Split - - val rejectPrefixSplit: MakePrefixSplit = (_, alternative) => alternative - -import NaiveCompiler.* - -/** This class compiles a tree describing a pattern into functions that can - * perform pattern matching on terms described by the pattern. */ -class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends TermSynthesizer: - import tl.*, FlatPattern.MatchMode, SP.* - - private lazy val lteq = State.builtinOpsMap("<=") - private lazy val lt = State.builtinOpsMap("<") - - private def makeRangeTest(scrut: Scrut, lo: syntax.Literal, hi: syntax.Literal, rightInclusive: Bool, innerSplit: Split) = - def scrutFld = fld(scrut()) - val test1 = app(lteq.safeRef, tup(fld(Term.Lit(lo)), scrutFld), "isGreaterThanLower") - val upperOp = if rightInclusive then lteq else lt - val test2 = app(upperOp.safeRef, tup(scrutFld, fld(Term.Lit(hi))), "isLessThanUpper") - plainTest(test1, "isGreaterThanLower")(plainTest(test2, "isLessThanUpper")(innerSplit)) - - extension (patterns: Ls[SP]) - def folded(z: (Ls[TempSymbol], MakeConsequent))(makeSubScrutineeSymbol: Int => TempSymbol) = - patterns.iterator.zipWithIndex.foldRight(z): - case ((element, index), (subScrutinees, makeInnerSplit)) => - val subScrutinee = makeSubScrutineeSymbol(index) - val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(subScrutinee.toScrut, element)( - (elementOutput, elementBindings) => makeInnerSplit( - elementOutput, // TODO: Combine `outerOutput` and `elementOutput` - outerBindings ++ elementBindings), - Split.End) - (subScrutinee :: subScrutinees, makeThisSplit) - - /** Make a UCS split that matches the entire scrutinee against the pattern. - * Since each pattern has an output, the split is responsible for creating - * a binding that holds the output value and pass it to the continuation - * function that makes the conseuqent split. - */ - private def makeMatchSplit(scrutinee: Scrut, pattern: SP): MakeSplit = - pattern match - case Constructor(target, patternArguments, arguments) => (makeConsequent, alternative) => - // If we treat a constructor pattern as the intersection of constructor - // instance pattern and argument patterns, the output value should be - // a tuple made of the input value and values of each argument. - // This is the sub-scrutinees for arguments. - - // The pattern arguments for destructing the constructor's arguments. - val (arguments1, makeChainedConsequent) = arguments.fold((N, makeConsequent)): - _.iterator.zipWithIndex.foldRight(Nil: Ls[FlatPattern.Argument], makeConsequent): - case ((argument, index), (theArguments, makeInnerSplit)) => - val subScrutinee = TempSymbol(N, s"argument$index$$") - val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(subScrutinee.toScrut, argument)( - (argumentOutput, argumentBindings) => makeInnerSplit( - argumentOutput, // TODO: Combine `outerOutput` and `argumentOutput` - outerBindings ++ argumentBindings), - Split.End) - val theArgument = FlatPattern.Argument(subScrutinee, Tree.Empty().withLocOf(argument)) - (theArgument :: theArguments, makeThisSplit) - .mapFirst(S(_)) - // For pattern arguments for higher-order patterns, we generate the - // inline objects with `unapply` and `unapplyStringPrefix` methods. - val arguments0 = patternArguments.iterator.zipWithIndex.map: (pattern, index) => - FlatPattern.Argument(TempSymbol(N, s"patternArgument$index$$"), pattern) - .toList - val theArguments = arguments1.fold(if arguments0.isEmpty then N else S(arguments0)): - case arguments => S(arguments0 ::: arguments) - scoped("ucs:translation"): - log(s"the arguments of ${pattern.showDbg}:\n${theArguments.showAsTree}") - // Here, passing `scrutinee` as the output is not always correct. When - // `target` is a class or object, the output should be the scrutinee. - // When `target` is a pattern, the output should be the pattern's output. - // But it is until the normalization we can tell whether `target` is a - // pattern or not. - val outputSymbol = TempSymbol(N, "output") - val consequent = makeChainedConsequent(outputSymbol.toScrut, Map.empty) - Branch(scrutinee(), FlatPattern.ClassLike(target, theArguments, outputSymbol :: Nil), consequent) ~: alternative - case Composition(true, left, right) => - makeMatchSplit(scrutinee, left) | makeMatchSplit(scrutinee, right) - case Composition(false, left, right) => (makeConsequent, alternative) => - makeMatchSplit(scrutinee, left)( - (leftOutput, leftBindings) => makeMatchSplit(scrutinee, right)( - (rightOutput, rightBindings) => - val tupleIdent = Ident("tupledResults") - val tupleSymbol = TempSymbol(N, "tupledResults") - val tupleTerm = tup(leftOutput() |> fld, rightOutput() |> fld) - Split.Let(tupleSymbol, tupleTerm, makeConsequent(() => tupleSymbol.safeRef, leftBindings ++ rightBindings) ~~: alternative), - alternative), - alternative) - case Negation(pattern) => (makeConsequent, alternative) => - // Currently, the negation pattern produces the original value. In the - // future, we would include diagnostic information about why the pattern - // failed. Note that this feature requires the `alternative` parameter - // to be a function that takes a diagnostic information generation - // function. - val outputSymbol = TempSymbol(N, "negationOutput") - // The place where the diagnostic information should be stored. - val outputTerm = scrutinee() - makeMatchSplit(scrutinee, pattern)( - (_output, _bindings) => alternative, // The output and bindings are discarded. - Split.Let(outputSymbol, outputTerm, makeConsequent(() => outputSymbol.safeRef, Map.empty) ~~: alternative) - ) - // Because a wildcard pattern always matches, `alternative` is not used. - case Wildcard() => (makeConsequent, _) => makeConsequent(scrutinee, Map.empty) - case Literal(literal) => (makeConsequent, alternative) => - Branch(scrutinee(), FlatPattern.Lit(literal)(Nil), makeConsequent(scrutinee, Map.empty)) ~: alternative - case Range(lower, upper, rightInclusive) => (makeConsequent, alternative) => - makeRangeTest(scrutinee, lower, upper, rightInclusive, makeConsequent(scrutinee, Map.empty)) ~~: alternative - case Concatenation(left, right) => (makeConsequent, alternative) => - log(s"Concatenation") - makeStringPrefixMatchSplit(scrutinee, left)( - (consumedOutput, remainingOutput, bindingsFromConsumed) => - makeMatchSplit(remainingOutput, right)( - // Here we discard the postfix output because I still haven't - // figured out the semantics of string concatenation. - (_postfixOutput, bindingsFromRemaining) => makeConsequent( - scrutinee, bindingsFromConsumed ++ bindingsFromRemaining - ) ~~: alternative, - alternative - ), - alternative - ) - case Tuple(elements, N, _) => (makeConsequent, alternative) => - // Fixed-length tuple patterns are similar to constructor patterns. - val z = (Nil: Ls[TempSymbol], makeConsequent) - // TODO: Deduplicate the code with the `Constructor` case. - val (subScrutinees, makeChainedConsequent) = elements.iterator.zipWithIndex.foldRight(z): - case ((element, index), (subScrutinees, makeInnerSplit)) => - val subScrutinee = TempSymbol(N, s"element$index$$") - val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(subScrutinee.toScrut, element)( - (elementOutput, elementBindings) => makeInnerSplit( - elementOutput, // TODO: Combine `outerOutput` and `elementOutput` - outerBindings ++ elementBindings), - Split.End) - (subScrutinee :: subScrutinees, makeThisSplit) - // END TODO - makeTupleBranch(scrutinee(), subScrutinees, makeChainedConsequent(scrutinee, Map.empty), alternative) - case Tuple(leading, S(spread), trailing) => (makeConsequent, alternative) => - val (trailSubScrutinees, makeConsequent0) = trailing.folded((Nil, makeConsequent)): - index => TempSymbol(N, s"lastElement$index$$") - val spreadSubScrutinee = TempSymbol(N, "middleElements") - val makeConsequent1: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(spreadSubScrutinee.toScrut, spread)( - (spreadOutput, spreadBindings) => makeConsequent0( - spreadOutput, // TODO: Combine `outerOutput` and `spreadOutput` - outerBindings ++ spreadBindings), - Split.End) - val (leadingSubScrutinees, makeConsequent2) = leading.folded((Nil, makeConsequent1)): - index => TempSymbol(N, s"firstElement$index$$") - makeTupleBranch(scrutinee(), leadingSubScrutinees, spreadSubScrutinee, trailSubScrutinees, makeConsequent2(scrutinee, Map.empty), alternative) - case Record(fields) => (makeConsequent, alternative) => - // This case is similar to the `Constructor` case. - val z = (Nil: Ls[(Ident, TempSymbol)], makeConsequent) - // TODO: Deduplicate the code with the `Constructor` case. - val (entries, makeChainedConsequent) = fields.iterator.zipWithIndex.foldRight(z): - case (((key, pattern), index), (fields, makeInnerSplit)) => - val subScrutinee = TempSymbol(N, s"field_${key.name}$$") - val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(subScrutinee.toScrut, pattern)( - (fieldOutput, fieldBindings) => makeInnerSplit( - fieldOutput, // TODO: Combine `outerOutput` and `fieldOutput` - outerBindings ++ fieldBindings), - alternative) // We fill in the alternative here. This is - // different from the `Constructor` case. - ((key, subScrutinee) :: fields, makeThisSplit) - // END TODO - val consequent = makeChainedConsequent(scrutinee, Map.empty) - Branch(scrutinee(), FlatPattern.Record(entries)(Nil), consequent) ~: alternative - case Chain(first, second) => (makeConsequent, alternative) => - makeMatchSplit(scrutinee, first)( - (firstOutput, firstBindings) => makeMatchSplit(firstOutput, second)( - (secondOutput, secondBindings) => makeConsequent(secondOutput, firstBindings ++ secondBindings), - alternative), - alternative) - case alias @ Alias(pattern, id) => alias.symbolOption match - // Ignore those who don't have symbols. `Elaborator` should have - // reported errors. - case N => - log(s"pattern ${pattern.showDbg} doesn't have an alias symbol: ${id.name}") - makeMatchSplit(scrutinee, pattern) - case S(symbol) => (makeConsequent, alternative) => - makeMatchSplit(scrutinee, pattern)( - (output, bindings) => - makeConsequent(output, bindings + (symbol -> output)), - alternative) - case Transform(pattern, transform) => - // We should first create a local function that transforms the captured - // values. So far, `pattern`'s variables should be bound to symbols. - // Thus, we can make a parameter list from the symbols. Then, we make - // a lambda term from the parameter list and the transform term. Because - // `pattern` might be translated to many branches, making a lambda term - // in advance reduces code duplication. - val symbols = pattern.variables.symbols - val params = symbols.map: - Param(FldFlags.empty, _, N, Modulefulness.none) - val lambdaSymbol = new TempSymbol(N, "transform") - // Next, we need to elaborate the pattern into a split. Note that - // `makeMatchSplit` returns a function that takes a split as the - // consequence. `makeMatchSplit` also takes a list of symbols so that - // it needs to make sure that those bindings are available in the - // consequence split. - (makeConsequent, alternative) => Split.Let( - sym = lambdaSymbol, - term = Term.Lam(PlainParamList(params), transform), - // Declare the lambda function at the outermost level. Even if there - // are multiple disjunctions in the consequent, we will not need to - // repeat the `transform` term. - tail = makeMatchSplit(scrutinee, pattern)( - // Note that the output is not used. Semantically, the `transform` - // term can only access the matched values by bindings. - (_output, bindings) => - log(s"we are handling pattern ${pattern.showDbg}") - log(s"produced bindings are ${bindings.keys.map(_.nme).mkString(", ")}") - val arguments = symbols.iterator.map(bindings).map(_() |> fld).toSeq - val resultTerm = app(lambdaSymbol.safeRef, tup(arguments*), "the transform's result") - val resultSymbol = TempSymbol(N, "transformResult") - Split.Let(resultSymbol, resultTerm, makeConsequent(resultSymbol.toScrut, Map.empty)), - alternative)) - - /** Construct a UCS split to match the prefix of the given scrutinee. - * - * @return The return value is a function that builds the split. */ - private def makeStringPrefixMatchSplit( - scrutinee: Scrut, - pattern: SP, - )(using Raise): MakePrefixSplit = pattern match - case Constructor(target, patternArguments, arguments) => target.symbol match - // The case when the target refers to a pattern parameter. - case S(symbol: VarSymbol) => symbol.decl match - case S(param @ Param(flags = FldFlags(pat = true))) => - (makeConsequent, alternative) => - val outputSymbol = TempSymbol(N, "output") // Denotes the pattern's output. - val remainingSymbol = TempSymbol(N, "remaining") // Denotes the remaining value. - val mode = MatchMode.StringPrefix(outputSymbol, remainingSymbol) - val thePattern = FlatPattern.ClassLike(target, N, mode, false)(Tree.Dummy, Nil) - val consequent = makeConsequent(outputSymbol.toScrut, remainingSymbol.toScrut, Map.empty) - Branch(scrutinee(), thePattern, consequent) ~: alternative - case S(_) | N => rejectPrefixSplit - case S(symbol) => symbol.asPat match - // The case when the target refers to a pattern symbol. - case S(symbol: PatternSymbol) => - (makeConsequent, alternative) => - val defn = symbol.defn.getOrElse(die) - val outputSymbol = TempSymbol(N, "output") // Denotes the pattern's output. - val remainingSymbol = TempSymbol(N, "remaining") // Denotes the remaining value. - // Unfold extraction parameters and create symbols for sub-scrutinees. - val (theExtractionArguments, makeChainedConsequent) = arguments.fold((N, makeConsequent)): - _.iterator.zipWithIndex.foldRight(Nil: Ls[FlatPattern.Argument], makeConsequent): - case ((argument, index), (theArguments, makeInnerSplit)) => - val subScrutinee = TempSymbol(N, s"argument$index$$") - val makeThisSplit: MakePrefixConsequent = (outerConsumedOutput, outerRemainingOutput, outerBindings) => - makeStringPrefixMatchSplit(subScrutinee.toScrut, argument)( - (consumedOutput, remainingOutput, bindings) => makeInnerSplit( - // TODO: Combine `outerConsumedOutput` and `consumedOutput` - consumedOutput, - // TODO: Combine `outerRemainingOutput` and `remainingOutput` - remainingOutput, - outerBindings ++ bindings), - Split.End) - val theArgument = FlatPattern.Argument(subScrutinee, Tree.Empty().withLocOf(argument)) - (theArgument :: theArguments, makeThisSplit) - .mapFirst(S(_)) - val thePatternArguments = patternArguments.iterator.zipWithIndex.map: (pattern, index) => - FlatPattern.Argument(TempSymbol(N, s"patternArgument$index$$"), pattern) - .toList - val allArguments = theExtractionArguments.fold( - if thePatternArguments.isEmpty then N else S(thePatternArguments) - ): - case arguments => S(thePatternArguments ::: arguments) - val consequent = makeChainedConsequent(outputSymbol.toScrut, remainingSymbol.toScrut, Map.empty) - val mode = MatchMode.StringPrefix(outputSymbol, remainingSymbol) - val thePattern = FlatPattern.ClassLike(target, allArguments, mode, false)(Tree.Dummy, Nil) - Branch(scrutinee(), thePattern, consequent) ~: alternative - case N => rejectPrefixSplit - // The other possibilities do not match strings. - case S(_) | N => rejectPrefixSplit - case Composition(true, left, right) => - val makeLeft = makeStringPrefixMatchSplit(scrutinee, left) - val makeRight = makeStringPrefixMatchSplit(scrutinee, right) - (makeConsequent, alternative) => - makeLeft(makeConsequent, makeRight(makeConsequent, alternative)) - case Composition(false, left, right) => (makeConsequent, alternative) => - // This case is different, as the left pattern should be matched in prefix - // mode, but the `right` pattern should be matched in full mode. If the - // `right` pattern fails, we should check if `left` can match a different - // prefix and retry `right`. - // TODO: Implement the correct backtracking behavior. - makeStringPrefixMatchSplit(scrutinee, left)( - (leftOutput, leftRemains, leftBindings) => makeMatchSplit(scrutinee, right)( - (rightOutput, rightBindings) => - val productSymbol = TempSymbol(N, "product") - val productTerm = tup(leftOutput() |> fld, rightOutput() |> fld) - Split.Let(productSymbol, productTerm, makeConsequent( - productSymbol.toScrut, leftRemains, leftBindings ++ rightBindings)), - alternative), - alternative) - case Negation(pattern) => - // This case is tricky. The question is how many of characters should be - // left to the continuation? For example, to match string "match is over" - // against pattern `~"game" ~ " is over"`. The first step is to match - // pattern `"game"` as a prefix of the input. After we found it doesn't - // not match, how many characters should we consume in this step? From a - // global perspective, we know that we should consume the prefix - // `"match is "``, but with backtracking, we have to try every - // combinations before we can make a conclusion. - rejectPrefixSplit - case Wildcard() => (makeConsequent, alternative) => - // Because the wildcard pattern always matches, we can match the entire - // string and returns an empty string as the remaining value. - val emptyStringSymbol = TempSymbol(N, "emptyString") - makeConsequent(scrutinee, emptyStringSymbol.toScrut, Map.empty) - Branch(scrutinee(), FlatPattern.ClassLike(ctx.builtins.Str.safeRef, N, Nil), - Split.Let(emptyStringSymbol, str(""), - makeConsequent(scrutinee, emptyStringSymbol.toScrut, Map.empty)) - ) ~: alternative - case Literal(prefix: StrLit) => (makeConsequent, alternative) => - // Check if the scrutinee is the same as the literal. If so, we return - // an empty string as the remaining value. - val isLeadingSymbol = TempSymbol(N, "isLeading") - val isLeadingTerm = callStringStartsWith( - scrutinee(), Term.Lit(prefix), "the result of startsWith") - val outputSymbol = TempSymbol(N, "consumed") - val outputTerm = callStringTake(scrutinee(), prefix.value.length, "the consumed part of input") - val remainsSymbol = TempSymbol(N, "remains") - val remainsTerm = callStringDrop(scrutinee(), prefix.value.length, "the remaining input") - Split.Let(isLeadingSymbol, isLeadingTerm, - Branch(isLeadingSymbol.safeRef, - Split.Let(outputSymbol, outputTerm, - Split.Let(remainsSymbol, remainsTerm, - makeConsequent(outputSymbol.toScrut, remainsSymbol.toScrut, Map.empty))) - ) ~: alternative) - // Non-string literal patterns are directly discarded. - case Literal(_) => rejectPrefixSplit - case Range(lower: StrLit, upper: StrLit, rightInclusive) => (makeConsequent, alternative) => - // Check if the string is not empty. Then - val stringHeadSymbol = TempSymbol(N, "stringHead") - val stringTailSymbol = TempSymbol(N, "stringTail") - val nonEmptySymbol = TempSymbol(N, "nonEmpty") - val nonEmptyTerm = app(this.lt.safeRef, tup(fld(int(0)), fld(sel(scrutinee(), "length"))), "string is not empty") - Split.Let(nonEmptySymbol, nonEmptyTerm, // `0 < string.length` - Branch(nonEmptySymbol.safeRef, - Split.Let(stringHeadSymbol, callStringGet(scrutinee(), 0, "head"), - Split.Let(stringTailSymbol, callStringDrop(scrutinee(), 1, "tail"), - makeRangeTest(stringHeadSymbol.toScrut, lower, upper, rightInclusive, - makeConsequent(stringHeadSymbol.toScrut, stringTailSymbol.toScrut, Map.empty)))) - ) ~: alternative) - // Other range patterns cannot be string prefixes. - case Range(_, _, _) => rejectPrefixSplit - case Concatenation(left, right) => (makeConsequent, alternative) => - makeStringPrefixMatchSplit(scrutinee, left)( - (leftConsumedOutput, leftRemainingOutput, leftBindings) => - makeStringPrefixMatchSplit(leftRemainingOutput, right)( - (rightConsumedOutput, rightRemainingOutput, rightBindings) => - makeConsequent(leftConsumedOutput, rightRemainingOutput, leftBindings ++ rightBindings), - alternative), - alternative) - // Tuples and records cannot be string prefixes. - case Tuple(_, _, _) => rejectPrefixSplit - case Record(_) => rejectPrefixSplit - case Chain(first, second) => (makeConsequent, alternative) => - // This case is different because the first pattern might haven - // non-string output. So, we should apply `makeMatchSplit` to the second - // pattern, and finally pass the remains from the first pattern to the - // continuation. - makeStringPrefixMatchSplit(scrutinee, first)( - (firstOutput, firstRemains, firstBindings) => - makeMatchSplit(firstOutput, second)( - (secondOutput, secondBindings) => - makeConsequent(secondOutput, firstRemains, firstBindings ++ secondBindings), - alternative), - alternative) - case alias @ Alias(pattern, id) => - alias.symbolOption match - // Ignore those who don't have symbols. `Elaborator` should have - // reported errors. - case N => - log(s"pattern ${pattern.showDbg} doesn't have an alias symbol: ${id.name}") - makeStringPrefixMatchSplit(scrutinee, pattern) - case S(symbol) => (makeConsequent, alternative) => - makeStringPrefixMatchSplit(scrutinee, pattern)( - (output, remains, bindings) => - makeConsequent(output, remains, bindings + (symbol -> output)), - alternative) - case Transform(pattern, transform) => - // TODO: Duplicate code with the `Transform` case in `makeMatchSplit`. - val symbols = pattern.variables.symbols - val params = symbols.map(Param(FldFlags.empty, _, N, Modulefulness.none)) - val lambdaSymbol = new TempSymbol(N, "transform") - (makeConsequent, alternative) => Split.Let( - sym = lambdaSymbol, - term = Term.Lam(PlainParamList(params), transform), - // Declare the lambda function at the outermost level. Even if there - // are multiple disjunctions in the consequent, we will not need to - // repeat the `transform` term. - tail = makeStringPrefixMatchSplit(scrutinee, pattern)( - // Note that the output is not used. Semantically, the `transform` - // term can only access the matched values by bindings. - (_output, remains, bindings) => - val arguments = symbols.iterator.map(bindings).map(_() |> fld).toSeq - val resultTerm = app(lambdaSymbol.safeRef, tup(arguments*), "the transform's result") - val resultSymbol = TempSymbol(N, "transformResult") - Split.Let(resultSymbol, resultTerm, makeConsequent(resultSymbol.toScrut, remains, Map.empty)), - alternative)) - - /** Make a term like `MatchFailure(null)`. We will synthesize detailed - * error messages and pass them to the function. */ - private def failure: Split = Split.Else(makeMatchFailure()) - - /** Create a method from the given UCS splits. - * The function has a parameter list that contains the pattern parameters and - * a parameter that represents the input value. - */ - private def makeMethod( - owner: Opt[PatternSymbol], - name: Str, - patternParameters: List[Param], - scrut: VarSymbol, - topmost: Split - ): TermDefinition = - val sym = BlockMemberSymbol(name, Nil) - val tsym = TermSymbol(Fun, owner, Ident(name)) - // Pattern parameters are passed as objects. - val patternInputs = patternParameters.map(_.copy(flags = FldFlags.empty)) - // The last parameter is the scrutinee. - val scrutParam = Param(FldFlags.empty, scrut, N, Modulefulness.none) - val ps = PlainParamList(patternInputs :+ scrutParam) - TermDefinition(Fun, sym, tsym, ps :: Nil, N, N, - S(Term.IfLike(Keyword.`if`, topmost)), FlowSymbol(s"‹unapply-result›"), - TermDefFlags.empty, Modulefulness.none, Nil, N) - - /** Translate a list of extractor/matching functions for the given pattern. - * There are currently two functions: `unapply` and `unapplyStringPrefix`. - * - * - `unapply` is used for matching the entire scrutinee. It returns the - * captured/extracted values. - * - `unapplyStringPrefix` is used for matching the string prefix of the - * scrutinee. It returns the remaining string and the captured/extracted - * values. If the given tree does not represent a string pattern, this - * function will not be generated. - * - * @param pattern We will eventually generate methods from the omnipotent - * `Pattern` class. Now the new `pattern` parameter and the - * old `body` parameter are mixed. - */ - def compilePattern(pd: PatternDef): Ls[TermDefinition] = trace( - pre = s"compilePattern <<< ${pd.showDbg}", - post = (blk: Ls[TermDefinition]) => s"compilePattern >>> $blk" - ): - // TODO: Use `pd.extractionParams`. - val unapply = scoped("ucs:translation"): - val inputSymbol = VarSymbol(Ident("input")) - val topmost = makeMatchSplit(inputSymbol.toScrut, pd.pattern) - ((output, bindings) => Split.Else(makeMatchResult(output())), failure) - log(s"Translated `unapply`: ${topmost.prettyPrint}") - makeMethod(N, "unapply", pd.patternParams, inputSymbol, topmost) - // TODO: Use `pd.extractionParams`. - val unapplyStringPrefix = scoped("ucs:cp"): - // We don't report errors here because they have been already reported in - // the translation of `unapply` function. - given Raise = Function.const(()) - val inputSymbol = VarSymbol(Ident("input")) - val topmost = makeStringPrefixMatchSplit(inputSymbol.toScrut, pd.pattern) - ((consumedOutput, remainingOutput, bindings) => Split.Else: - makeMatchResult(tup(fld(consumedOutput()), fld(remainingOutput()))), failure) - log(s"Translated `unapplyStringPrefix`: ${topmost.prettyPrint}") - makeMethod(N, "unapplyStringPrefix", pd.patternParams, inputSymbol, topmost) - unapply :: unapplyStringPrefix :: Nil - - /** Generate the record statements of `unapply` methods that can be used in - * objects for anonymous patterns. */ - def makeUnapplyRecordStatements( - name: Str, - patternParameters: List[Param], - scrut: VarSymbol, - topmost: Split - ): Ls[Statement] = - val fieldSymbol = TempSymbol(N, name) - val decl = LetDecl(fieldSymbol, Nil) - val param = Param(FldFlags.empty, scrut, N, Modulefulness.none) - val paramList = PlainParamList(param :: Nil) - val lambda = Term.Lam(paramList, Term.IfLike(Keyword.`if`, topmost)) - val defineVar = DefineVar(fieldSymbol, lambda) - val field = RcdField(Term.Lit(StrLit(name)), fieldSymbol.safeRef) - decl :: defineVar :: field :: Nil - - /** Translate an anonymous pattern. They are usually pattern arguments. */ - def compileAnonymousPattern(patternParams: Ls[Param], params: Ls[Param], pattern: SP): Term = trace( - pre = s"compileAnonymousPattern <<< $pattern", - post = (blk: Term) => s"compileAnonymousPattern >>> $blk" - ): - // If the `target` refers to a pattern symbol, we can reference the pattern. - val term = pattern match - case Constructor(target, Nil, N) => - log(s"target.symbol: ${target.symbol}") - log(s"target.symbol.flatMap(_.asPat): ${target.symbol.flatMap(_.asPat)}") - target.symbol.flatMap(_.asPat).flatMap(Compiler.reference(_, target.toLoc)) - case _ => N - log(s"term: ${term}") - term.getOrElse: - val unapply = scoped("ucs:translation"): - val inputSymbol = VarSymbol(Ident("input")) - val topmost = makeMatchSplit(inputSymbol.toScrut, pattern) - ((output, bindings) => Split.Else(makeMatchResult(output())), failure) - log(s"Translated `unapply`: ${topmost.prettyPrint}") - makeUnapplyRecordStatements("unapply", patternParams, inputSymbol, topmost) - val unapplyStringPrefix = scoped("ucs:cp"): - // We don't report errors here because they have been already reported in - // the translation of `unapply` function. - given Raise = Function.const(()) - val inputSymbol = VarSymbol(Ident("input")) - val topmost = makeStringPrefixMatchSplit(inputSymbol.toScrut, pattern) - ((consumedOutput, remainingOutput, bindings) => Split.Else: - makeMatchResult(tup(fld(consumedOutput()), fld(remainingOutput()))), failure) - log(s"Translated `unapplyStringPrefix`: ${topmost.prettyPrint}") - makeUnapplyRecordStatements("unapplyStringPrefix", patternParams, inputSymbol, topmost) - Term.Rcd(false, unapply ::: unapplyStringPrefix) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala index 569c3aa807..7e9c8324f6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala @@ -5,7 +5,7 @@ package ups import mlscript.utils.*, shorthands.* import semantics.Pattern as SP -import syntax.Tree, Tree.Ident +import syntax.{Tree, SpreadKind}, Tree.{Ident, StrLit} import Message.MessageContext import ucs.{error, warn} import Context.* @@ -53,12 +53,13 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: case ClassLike(sym, arguments) => arguments.fold(Nil): _.map((id, p) => p).toList case Record(entries) => entries.values.toList - case Tuple(leading, spread, trailing) => leading ++ spread.toList ++ trailing + case Tuple(leading, spread) => leading ::: spread.fold(Nil): + case (_, middle, trailing) => middle :: trailing case And(patterns) => patterns case Or(patterns) => patterns case Not(pattern) => pattern :: Nil case Rename(pattern, name) => pattern :: Nil - case Extract(pattern, term) => pattern :: term :: Nil + case Extract(pattern, _, term) => pattern :: term :: Nil case Synonym(pattern) => pattern.symbol :: pattern.arguments lazy val symbols: Ls[VarSymbol] = this match @@ -66,8 +67,8 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: case ClassLike(sym, arguments) => arguments.fold(Nil)(_.values.flatMap(_.symbols).toList) case Record(entries) => entries.values.flatMap(_.symbols).toList - case Tuple(leading, spread, trailing) => leading.flatMap(_.symbols) ::: - spread.fold(Nil)(_.symbols) ::: trailing.flatMap(_.symbols) + case Tuple(leading, spread) => leading.flatMap(_.symbols) ::: spread.fold(Nil): + case (_, middle, trailing) => middle.symbols ::: trailing.flatMap(_.symbols) case Synonym(_) => Nil case And(patterns) => patterns.flatMap(_.symbols) case Or(patterns) => @@ -76,7 +77,7 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: patterns.find(_ != Never).fold(Nil)(_.symbols) case Not(_) => Nil case Rename(pattern, name) => name :: pattern.symbols - case Extract(_, _) => Nil + case Extract(_, _, _) => Nil /** Apply a partial function to every node in the pattern tree. Replace each * node with the result of the partial function. If the partial function is @@ -89,7 +90,8 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: case Or(patterns) => Or[L](patterns.map(_.map(f))) case Not(pattern) => Not[L](pattern.map(f)) case Rename(pattern, name) => Rename[L](pattern.map(f), name) - case Extract(pattern, term) => Extract[L](pattern.map(f), term) + case Extract(pattern, correspondence, term) => + Extract[L](pattern.map(f), correspondence, term) /** A simplified reduce for ``Pattern``. * It is designed to be used when we want to @@ -105,16 +107,16 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: case Or(patterns) => merge(patterns.map(_.reduce(merge)(f))) case Not(pattern) => pattern.reduce(merge)(f) case Rename(pattern, _) => pattern.reduce(merge)(f) - case Extract(pattern, _) => pattern.reduce(merge)(f) + case Extract(pattern, _, _) => pattern.reduce(merge)(f) def heads: Set[Head] = reduce[Set[Head]](_.toSet.flatten): case Literal(lit) => Set(lit) case ClassLike(sym, _) => Set(sym) def fields: Set[Ident | Int] = reduce[Set[Ident | Int]](_.toSet.flatten): - case Record(entries) => entries.keys.toSet[Ident | Int] - case Tuple(leading, spread, trailing) => - val n = leading.size + trailing.size + case Record(entries) => entries.keys.toSet + case Tuple(leading, spread) => + val n = leading.size + spread.fold(0)(_._3.size) val subfields = Range(0, n).toSet[Ident | Int] // if this is strict, then a condition is imposed on field n if spread.isEmpty then subfields + n else subfields @@ -127,9 +129,9 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: case Some(arguments) => arguments.find((id1, _) => id === id).map((_, p) => p).toSet case Record(entries) => entries.get(id).toSet - case Tuple(leading, spread, trailing) => Set() - case n : Int => this.reduce[Set[Pat]](_.toSet.flatten): - case Tuple(leading, spread, trailing) => trailing.lift(n).toSet + case Tuple(leading, spread) => Set() + case n: Int => this.reduce[Set[Pat]](_.toSet.flatten): + case Tuple(leading, S(_, _, trailing)) => trailing.lift(n).toSet /** Simplify the pattern by removing `Never` patterns. */ def simplify: Pattern[K] = this match @@ -139,12 +141,16 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: case Record(entries) => val simplify = entries.map((id, p) => (id, p.simplify)) if simplify.exists((_, p) => p === Never) then Never else Record(simplify) - case Tuple(leading, spread, trailing) => + case Tuple(leading, N) => + val simplified = leading.map(_.simplify) + if simplified contains Never then Never else Tuple(simplified, N) + case Tuple(leading, S((spreadKind, middle, trailing))) => val leading2 = leading.map(_.simplify) - val spread2 = spread.map(_.simplify) + val middle2 = middle.simplify val trailing2 = trailing.map(_.simplify) - if leading2.contains(Never) || spread2.contains(Never) || trailing2.contains(Never) - then Never else Tuple(leading2, spread2, trailing2) + if leading2.contains(Never) || middle2 === Never || + trailing2.contains(Never) then Never + else Tuple(leading2, S((spreadKind, middle2, trailing2))) case And(patterns) => // TODO: Complete the simplification logic here. val simplified = patterns.foldRight(Nil: Ls[Pattern[K]]): @@ -174,9 +180,9 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: .foldSingleton(Or.apply)(identity) case Not(pattern) => Not(pattern.simplify) case Rename(pattern, name) => Rename(pattern.simplify, name) - case Extract(pattern, term) => pattern.simplify match + case Extract(pattern, correspondence, term) => pattern.simplify match case `Never` => Never // Lift up `Never`. Let the caller reduce it. - case simplified => Extract(simplified, term) + case simplified => Extract(simplified, correspondence, term) case Synonym(sym) => this /** Expand the pattern by replacing any top-level synonym with its body. @@ -214,11 +220,13 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: if entries.isEmpty then "{}" else entries.iterator.map: case (id, p) => s"${id.name}: ${p.showDbg}" .mkString("{ ", ", ", " }") - case Tuple(leading, spread, trailing) => - val leadingText = leading.map(_.showDbg).mkString(", ") - val spreadText = spread.map(_.showDbg).mkString(", ") - val trailingText = trailing.map(_.showDbg).mkString(", ") - List(leadingText, spreadText, trailingText).mkString("[", ", ", "]") + case Tuple(leading, N) => + leading.iterator.map(_.showDbg).mkString("[", ", ", "]") + case Tuple(leading, S(spreadKind, spread, trailing)) => + val leadingItems = leading.iterator.map(_.showDbg) + val spreadItem = Iterator.single(spreadKind.str + spread.showDbg) + val trailingItems = trailing.iterator.map(_.showDbg) + (leadingItems ++ spreadItem ++ trailingItems).mkString("[", ", ", "]") case And(Nil) => "⊤" case And(pattern :: Nil) => pattern.showDbg case And(patterns) => @@ -230,7 +238,7 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: case Not(pattern) => s"!${pattern.showDbg}" case Rename(Or(Nil), name) => name.name case Rename(pattern, name) => s"${pattern.showDbg} as $name" - case Extract(pattern, term) => s"${pattern.showDbg} => ${term.showDbg}" + case Extract(pattern, _, term) => s"${pattern.showDbg} => ${term.showDbg}" case Synonym(sym) => sym.showDbg object Pattern: @@ -268,8 +276,7 @@ object Pattern: */ final case class Tuple( leading: List[Pat], - spread: Opt[Pat], - trailing: List[Pat] + spread: Opt[(SpreadKind, Pat, List[Pat])], ) extends NonCompositional[Kind.Specialized] /** Represents a pattern synonym. @@ -282,7 +289,19 @@ object Pattern: final case class Or[+K <: Kind.Complete](patterns: List[Pattern[K]]) extends Pattern[K] final case class Not[+K <: Kind.Complete](pattern: Pattern[K]) extends Pattern[K] final case class Rename[+K <: Kind.Complete](pattern: Pattern[K], name: VarSymbol) extends Pattern[K] - final case class Extract[+K <: Kind.Complete](pattern: Pattern[K], term: Term) extends Pattern[K] + /** + * Similar to `Pattern.Transform`. + * + * @param pattern The pattern to be matched against. + * @param correspondence The correspondence between symbols in the pattern + * and symbols in the transformation term. + * @param term The term to be applied to the extracted values. + */ + final case class Extract[+K <: Kind.Complete]( + pattern: Pattern[K], + correspondence: Map[VarSymbol, VarSymbol], + term: Term + ) extends Pattern[K] val Wildcard = Or(Nil) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala new file mode 100644 index 0000000000..3b129d88d3 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/SplitCompiler.scala @@ -0,0 +1,1045 @@ +package hkmc2 +package semantics +package ups + +import mlscript.utils.*, shorthands.* +import Message.MessageContext +import ucs.{TermSynthesizer, FlatPattern, error, warn, safeRef}, ucs.extractors.* +import syntax.{Fun, Keyword, Tree}, Tree.{Ident, StrLit}, Keyword.{`as`, `=>`} +import collection.mutable.{Buffer, HashMap}, collection.immutable.SeqMap +import Elaborator.{Ctx, State, ctx}, utils.TL +import semantics.Pattern as SP // "SP" is short for "semantic patterns" +import Term.Ref + +object SplitCompiler: + /** A class that can generate `Ref` to the scrutinee. It also comes with a few + * mutable maps to reuse sub-scrutinees. Memoization of sub-scrutinees help + * the normalization to merge let bindings from different branches. + */ + sealed abstract class Scrut: + private val subScrutinees: Buffer[SymbolScrut[?]] = Buffer.empty + private val fields: HashMap[Ident, SymbolScrut[?]] = HashMap.empty + private val tupleLead: HashMap[Int, SymbolScrut[?]] = HashMap.empty + private val tupleLast: HashMap[Int, SymbolScrut[?]] = HashMap.empty + + def apply(): Term.Ref + def getSubScrutinee(index: Int)(using State): SymbolScrut[?] = + while subScrutinees.size <= index do + subScrutinees += TempSymbol(N, s"argument${subScrutinees.size}$$").toScrut + subScrutinees(index) + def getTupleLeadSubScrutinee(index: Int)(using State): SymbolScrut[?] = + tupleLead.getOrElseUpdate(index, TempSymbol(N, s"element$index$$").toScrut) + def getTupleLastSubScrutinee(index: Int)(using State): SymbolScrut[?] = + tupleLast.getOrElseUpdate(index, TempSymbol(N, s"lastElement$index$$").toScrut) + def getFieldScrutinee(fieldName: Ident)(using State): SymbolScrut[?] = + fields.getOrElseUpdate(fieldName, TempSymbol(N, s"field_${fieldName.name}$$").toScrut) + + object Scrut: + def from(ref: Term.Ref): RefScrut = RefScrut(() => ref) + + class RefScrut(make: () => Term.Ref) extends Scrut: + def apply(): Ref = make() + + class SymbolScrut[SymbolType <: BlockLocalSymbol](val symbol: SymbolType) extends Scrut: + def apply(): Ref = symbol.ref() + + extension [SymbolType <: BlockLocalSymbol](symbol: SymbolType) + def toScrut: SymbolScrut[SymbolType] = SymbolScrut(symbol) + + type BindingMap = SeqMap[VarSymbol, Scrut] + + type SplitSequel[Output] = (makeConsequent: (Output, BindingMap) => Split, alternative: Split) => Split + + extension [Output](sequel: SplitSequel[Output]) + def map[Result](f: Output => Result): SplitSequel[Result] = (makeConsequent, alternative) => + sequel((output, bindings) => makeConsequent(f(output), bindings), alternative) + + type MakeConsequent = (output: Scrut, bindings: BindingMap) => Split + + /** The continuation function returned by `makeMatchSplit`. + * Note that `alternative` does not serve as the fallback split. It means the + * next split we should try when the current split fails. + * + * Note: I realized that it would make more sense to let `output` be a term. + * Because in some cases, the output is not used in the consequent split. + * For example, the output of the `negation` pattern is discarded. + * + * The `bindings` parameter is used to denote the variables that are bound + * during the matching process. The key is the pattern's variable symbol and + * the value is the local symbol that represents the matched values. + */ + type MakeSplit = (makeConsequent: MakeConsequent, alternative: Split) => Split + + object RejectSplit extends ((MakeConsequent, Split) => Split): + def apply(makeConsequent: MakeConsequent, alternative: Split): Split = alternative + + extension (first: MakeSplit) + def | (second: MakeSplit): MakeSplit = (makeConsequent, alternative) => + first(makeConsequent, second(makeConsequent, alternative)) + def & (second: MakeSplit): SplitSequel[(Scrut, Scrut)] = (makeConsequent, alternative) => + first( + (firstOutput, firstBindings) => second( + (secondOutput, secondBindings) => makeConsequent( + (firstOutput, secondOutput), + firstBindings ++ secondBindings // Do we need to duplicate the bindings? + ), + alternative), + alternative) + + type MakePrefixConsequent = (consumedOutput: Scrut, remainingOutput: Scrut, bindings: BindingMap) => Split + + /** The continuation function returned by `makeStringPrefixMatchSplit`. It + * represents functions making splits that match any given string's prefix. + */ + type MakePrefixSplit = (makeConsequent: MakePrefixConsequent, alternative: Split) => Split + + object RejectPrefixSplit extends ((MakePrefixConsequent, Split) => Split): + def apply(makeConsequent: MakePrefixConsequent, alternative: Split): Split = alternative + + extension (makePrefixSplit: MakePrefixSplit) + /** Build another `MakePrefixSplit` if the function does not reject. */ + transparent inline def whenAccept(derivedFunction: => MakePrefixSplit): MakePrefixSplit = + if makePrefixSplit is RejectPrefixSplit then RejectPrefixSplit else derivedFunction + + /** A lazily created scrutinee. */ + class LazyScrut(nameHint: Opt[Str] = N)(using State) extends Scrut: + private var _hasBeenUsed = false + private lazy val symbol = + _hasBeenUsed = true + TempSymbol(N, nameHint.getOrElse("output")) + def apply(): Ref = symbol.safeRef + def toList: Ls[TempSymbol] = if _hasBeenUsed then symbol :: Nil else Nil + def toLet(term: => Term, tail: Split): Split = + if _hasBeenUsed then Split.Let(symbol, term, tail) else tail + +import SplitCompiler.* + +/** This class compiles a pattern to a split that matches the pattern. */ +class SplitCompiler(using tl: TL)(using State, Ctx, Raise) extends TermSynthesizer: + import tl.*, SP.* + + private lazy val lteq = State.builtinOpsMap("<=") + private lazy val lt = State.builtinOpsMap("<") + private lazy val add = State.builtinOpsMap("+") + + private def makeRangeTest(scrut: Scrut, lo: syntax.Literal, hi: syntax.Literal, rightInclusive: Bool, innerSplit: Split) = + def scrutFld = fld(scrut()) + val test1 = app(lteq.safeRef, tup(fld(Term.Lit(lo)), scrutFld), "isGreaterThanLower") + val upperOp = if rightInclusive then lteq else lt + val test2 = app(upperOp.safeRef, tup(scrutFld, fld(Term.Lit(hi))), "isLessThanUpper") + plainTest(test1, "isGreaterThanLower")(plainTest(test2, "isLessThanUpper")(innerSplit)) + + extension (patterns: Ls[SP]) + /** + * Folds over sub-patterns to create a consequent making function with + * accumulated sub-scrutinees. This function processes a list of patterns by + * creating a consequent making function that applies consequent making + * function of each sub-pattern from right to left. + * + * Mental model example: for patterns `[p1, p2, p3]` with sub-scrutinees + * `[s1, s2, s3]`, this function creates: + + * ``` + * s1 is p1 and s2 is p2 and s3 is p3 and + * {| z(outerOutput + bindings1 + bindings2 + bindings3) |} + * ``` + * + * @param z The initial consequent making function to start the fold with. + * @param getSubScrutinee To get a scrutinee for a given pattern index. + * @return A tuple containing: + * - A list of `(BlockLocalSymbol, Option[Loc])` pairs for each + * sub-scrutinee and its location. + * - A `MakeConsequent` function that creates the nested match split. + */ + def folded(z: MakeConsequent)(getSubScrutinee: Int => SymbolScrut[?]) + : (Ls[(BlockLocalSymbol, Opt[Loc])], MakeConsequent) = + patterns.iterator.zipWithIndex.foldRight((Nil, z)): + case ((subPattern, index), (accSubScrutinees, makeInnerSplit)) => + val subScrutinee = getSubScrutinee(index) + val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => + makeMatchSplit(subScrutinee, subPattern)( + // Note that the individual pattern's output is ignored because + // in real world it's hard to synthesized a valid object if the + // pattern carries some transformation. + (_elementOutput, elementBindings) => makeInnerSplit( + outerOutput, // TODO: Combine `outerOutput` and `elementOutput` + outerBindings ++ elementBindings), + Split.End) + ((subScrutinee.symbol -> subPattern.toLoc) :: accSubScrutinees, makeThisSplit) + + /** Same as the other overload, but for `MakePrefixConsequent`. */ + def folded(z: MakePrefixConsequent)(getSubScrutinee: Int => SymbolScrut[?]) + : (Ls[(BlockLocalSymbol, Opt[Loc])], MakePrefixConsequent) = + patterns.iterator.zipWithIndex.foldRight((Nil, z)): + case ((subPattern, index), (accSubScrutinees, makeInnerSplit)) => + val subScrutinee = getSubScrutinee(index) + val makeThisSplit: MakePrefixConsequent = (outerOutput, outerRemaining, outerBindings) => + makeStringPrefixMatchSplit(subScrutinee, subPattern)( + // Note that the individual pattern's output is ignored because + // in real world it's hard to synthesized a valid object if the + // pattern carries some transformation. + (_elementOutput, _elementRemaining, elementBindings) => makeInnerSplit( + outerOutput, // TODO: Combine `outerOutput` and `elementOutput` + outerRemaining, // TODO: Combine `outerRemaining` and `elementRemaining` + outerBindings ++ elementBindings), + Split.End) + ((subScrutinee.symbol -> subPattern.toLoc) :: accSubScrutinees, makeThisSplit) + + def makePatternBindings(using State): Ls[(TempSymbol, SP)] = + patterns.iterator.zipWithIndex.map: + case (pattern, index) => new TempSymbol(N, s"patternArgument${index}$$") -> pattern + .toList + + /** + * Check whether the number of parameters in class-like patterns matches the + * number in their definition, and whether each parameter is accessible. + * + * @param scrutinee the scrutinee + * @param classTerm the term that resolves to the class + * @param classSymbol the symbol for the class to be matched + * @param arguments sub-patterns + */ + private def makeMatchClassSplit( + scrutinee: Scrut, + classTerm: Term, + classSymbol: ClassSymbol, + arguments: Opt[Ls[SP]] + ): MakeSplit = + // Obtain the `classHead` used for error reporting and the parameter list + // from the class definitions. + val (classHead, paramsOpt) = classSymbol.defn match + case N => lastWords(s"Class ${classSymbol.name} does not have a definition") + // Use the constructor pattern's location for error reporting. + case S(cd) => new Tree.Ident(classSymbol.name).withLoc(classTerm.toLoc) -> cd.paramsOpt + // Check if the number of arguments matches the number of parameters. + val successful = paramsOpt match + case S(paramList) => arguments match + case S(args) => + // Check the number of parameters is correct. + if args.size != paramList.params.size then + val loc = Loc(args) orElse classTerm.toLoc + error: + if paramList.params.isEmpty then + msg"The constructor does not take any arguments but found ${ + "argument" countBy args.size}." -> loc + else + msg"Expected ${"argument" countBy paramList.params.size + }, but found ${if args.size < paramList.params.size then "only " else "" + }${"argument" countBy args.size}." -> loc + // Check the fields are accessible. + paramList.params.iterator.zip(args).map: + case (_, Wildcard()) => true + case (Param(flags, sym, _, _), arg) if !flags.isVal => + error(msg"This pattern cannot be matched" -> arg.toLoc, // TODO: use correct location + msg"because the corresponding parameter `${sym.name}` is not publicly accessible" -> sym.toLoc, + msg"Suggestion: use a wildcard pattern `_` in this position" -> N, + msg"Suggestion: mark this parameter with `val` so it becomes accessible" -> N) + false + case _ => true + // If the number of arguments are more than the number of parameters, + // or one of parameters is incessible, we cannot make the branch. + .foldLeft(args.size <= paramList.params.size)(_ && _) + case N => arguments match + case S(args) => + error(msg"class ${classSymbol.name} does not have parameters" -> classHead.toLoc, + msg"but the pattern has ${"sub-pattern" countBy args.size}" -> Loc(args)) + false + case N => true // No parameters, no arguments. This is fine. + case N => arguments match // No parameter lists. Check if scruts are empty. + case S(Nil) => + error(msg"Class ${classSymbol.name} does not have a parameter list" -> classTerm.toLoc) + true + case S(args) => + error(msg"Class ${classSymbol.name} does not have a parameter list" -> classTerm.toLoc, + msg"but the pattern has ${"sub-pattern" countBy args.size}" -> Loc(args)) + false + case N => true + if successful then (makeConsequent, alternative) => + // The pattern arguments for destructing the constructor's arguments. + val (theArguments, makeConsequentForArguments) = arguments.fold((N, makeConsequent)): + _.folded(makeConsequent)(scrutinee.getSubScrutinee).mapFirst(S(_)) + val outputSymbol = new LazyScrut() + val consequent = makeConsequentForArguments(outputSymbol, SeqMap.empty) + val pattern = FlatPattern.ClassLike(classTerm, classSymbol, theArguments, false)(Tree.Dummy) + Branch(scrutinee(), pattern, outputSymbol.toLet(scrutinee(), consequent)) ~: alternative + else RejectSplit + + /** + * Make a split that matches the given object. Meanwhile, check whether the + * object pattern has an argument list. + * + * @param scrutinee the scrutinee + * @param objectSymbol the symbol for the object + * @param arguments objects do not have parameters, thus the arguments are + * only used for error checking + */ + private def makeMatchObjectSplit( + patternLoc: Opt[Loc], + scrutinee: Scrut, + objectTerm: Term, + objectSymbol: ModuleOrObjectSymbol, + arguments: Opt[Ls[SP]] + ): MakeSplit = + val shouldReject = arguments match + case S(Nil) => + // The object pattern comes with an unnecessary parameter list, but it + // can still be compiled. + error(msg"`${objectSymbol.name}` is an object." -> objectSymbol.id.toLoc, + msg"Its pattern cannot have an argument list." -> patternLoc) + false + case S(_ :: _) => + // One or more parameters are provided, thus we cannot compile the + // following consequent split. + error(msg"`${objectSymbol.name}` is an object." -> objectSymbol.id.toLoc, + msg"Its pattern cannot have arguments." -> patternLoc) + true + case N => false + if shouldReject then RejectSplit else (makeConsequent, alternative) => + val outputSymbol = new LazyScrut() + val consequent = makeConsequent(outputSymbol, SeqMap.empty) + val pattern = FlatPattern.ClassLike(objectTerm, objectSymbol, N, false)(Tree.Dummy) + Branch(scrutinee(), pattern, outputSymbol.toLet(scrutinee(), consequent)) ~: alternative + + /** + * When a scrutinee is matched with a defined pattern `P`, this method checks + * whether the arguments provided by the user (both pattern arguments and + * extraction parameters) match the parameters in the definition of `P`. This + * part of the logic is used by both efficiently compiled patterns and + * naively compiled patterns to ensure the consistency of diagnostics. + * + * @param scrutinee the scrutinee of the match + * @param defn the pattern's definition + * @param arguments all arguments provided by the user + * @return Return a triple. The first element is the pattern arguments + * received when `P` is parameterized. The second element is the + * sub-patterns used to match the value returned after `P` is applied. + * The third element indicates whether expanding this branch should + * be abandoned due to invalid arguments. + */ + private def matchParametersWithArguments( + scrutinee: Scrut, + defn: PatternDef, + arguments: Opt[Ls[SP]] + ): (Ls[SP], Opt[Ls[SP]], Bool) = + val argumentCount = arguments.fold(0)(_.length) + val patternParameterCount = defn.patternParams.length + val loc = Loc(arguments.getOrElse(Nil)) + if argumentCount < patternParameterCount then + // Since pattern parameters are required, if the total number of arguments + // is less than this number, the pattern matching is definitely incorrect. + error: + msg"Expected ${"pattern argument" countBy patternParameterCount + }, but found only ${"pattern argument" countBy argumentCount}." -> loc + (Nil, N, true) + else arguments match + // Now that the number of pattern parameters can definitely be satisfied, + // what we need is to properly pair the parameters with the arguments. + case N => (Nil, N, false) // This implies `patternParameterCount` is 0. + case S(arguments) if argumentCount === patternParameterCount => + // All the arguments satisfy the pattern arguments, with none left over. + (arguments, N, false) + case S(arguments) => + val extractionParameterCount = defn.extractionParams.size + val extractionArgumentCount = argumentCount - patternParameterCount + if extractionArgumentCount === extractionParameterCount then + // Match arguments and parameters pairwisely. + val (patternArguments, extractionSubPatterns) = + arguments.iterator.zip(defn.parameters).toList.partitionMap: + case (patternArgument, Param(FldFlags(pat = true), _, _, _)) => + L(patternArgument) + case (subPattern, _) => R(subPattern) + (patternArguments, S(extractionSubPatterns), false) + else arguments match + // When the pattern does not have any parameters, the user can provide + // an argument as a shorthand of matching the output. For example, + // `x is P(Q) === x is P as Q` when `P`` is not parameterized. + case outputPattern :: Nil if defn.parameters.isEmpty => + (Nil, S(outputPattern :: Nil), false) + case _ :: _ | Nil => + error: + msg"Expected ${"extraction argument" countBy extractionParameterCount + }, but found ${if extractionArgumentCount < extractionParameterCount then "only " else "" + }${"argument" countBy extractionArgumentCount}." -> loc + (Nil, N, true) + + /** + * Create a split that naively compiles and then binds the pattern arguments. + * + * @param patternArguments the pattern arguments with their symbols + * @param split the inner split to be enclosed + */ + def buildPatternArguments( + patternArguments: List[(BlockLocalSymbol, SP)], + split: Split + ): Split = patternArguments.foldRight(split): + case ((symbol, pattern), innerSplit) => + Split.Let(symbol, compileAnonymousPattern(Nil, Nil, pattern), innerSplit) + + /** + * Make a split that matches the given pattern in the naive way. Note that + * the efficient pattern compilation without backtracking is handled in the + * case of `Annotated(_, _)`. + * + * @param scrutinee the scrutinee + * @param patternTerm the term that resolved to the pattern + * @param patternSymbol the symbol for the pattern + * @param arguments pattern arguments and extraction arguments + */ + private def makeMatchPatternSplit( + scrutinee: Scrut, + patternTerm: Term, + patternSymbol: PatternSymbol, + arguments: Opt[Ls[SP]] + ): MakeSplit = + val defn = patternSymbol.defn.getOrElse: + lastWords(s"Pattern `${patternSymbol.nme}` has not been elaborated.") + val (patternArguments, extractionMatches, shouldReject) = + matchParametersWithArguments(scrutinee, defn, arguments) + if shouldReject then RejectSplit else (makeConsequent, alternative) => + val (extractionArguments, makeConsequentForSubPatterns) = + val z = (N: Opt[Ls[(BlockLocalSymbol, Opt[Loc])]], makeConsequent) + extractionMatches.fold(z): + _.folded(makeConsequent)(scrutinee.getSubScrutinee).mapFirst(S(_)) + // First, we need to prepare the objects that performs the naive matching + // of pattern arguments which will be passed to the pattern. + val patternBindings = patternArguments.makePatternBindings + val wrapPatternArgumentDefinitions = buildPatternArguments(patternBindings, _) + // Then, we can call the `unapply` method. + val unapplyArguments = patternBindings.map(_._1.safeRef |> fld) :+ fld(scrutinee()) + val unapplyCall = app(sel(patternTerm, "unapply").resolve, tup(unapplyArguments*), s"result of unapply") + val unapplyResult = TempSymbol(N, "unapplyResult") + val wrapUnapply = Split.Let(unapplyResult, unapplyCall, _) + // Then, we need to destruct the produced `MatchResult`. + val outputSymbol = TempSymbol(N, "output").toScrut // TODO: We can use `LazyScrut` for this, but the match result pattern's parameters requires a symbol. + val bindingsSymbol = TempSymbol(N, "bindings") // TODO: This can be automatically removed when no transformation is used. + val wrapDestruction = (consequent: Split) => + val pattern = matchResultPattern(S(outputSymbol.symbol :: bindingsSymbol :: Nil)) + Branch(unapplyResult.safeRef, pattern, consequent) ~: alternative + // Check the number of extraction arguments. + val wrapExtractionArguments = extractionArguments match + case N | S(Nil) => identity[Split] + case S((single, _) :: Nil) => Split.Let(single, outputSymbol(), _) + case S(extractionArguments) => (split: Split) => + makeTupleBranch(scrutinee(), extractionArguments.map(_._1), split, Split.End) + // Finally, the inner split that does the work. + val consequent = makeConsequentForSubPatterns(outputSymbol, SeqMap.empty) + // Here we go! + wrapPatternArgumentDefinitions(wrapUnapply(wrapDestruction(wrapExtractionArguments(consequent)))) + + /** Report errors when pattern parameters are used incorrectly. */ + private def validatePatternParameter[A]( + parameterTerm: Term, + parameterSymbol: VarSymbol, + arguments: Opt[Ls[SP]], + patternLoc: Opt[Loc], + default: A + )(body: => A)(using Raise): A = parameterSymbol.decl match + case S(param @ Param(flags = FldFlags(pat = true))) => arguments match + case S(list) => + // The object pattern comes with an unnecessary parameter list, but it + // can still be compiled. + error(msg"`${parameterSymbol.name}` is a pattern parameter." -> param.toLoc, + msg"It cannot be applied${if list.isEmpty then "" else " to any arguments"}." -> patternLoc) + default + case N => body + case S(_) | N => + error(msg"Cannot use this ${parameterTerm.describe} as a pattern" -> parameterTerm.toLoc) + default + + private def makeMatchPatternParameterSplit( + scrutinee: Scrut, + parameterTerm: Term, + parameterSymbol: VarSymbol, + arguments: Opt[Ls[SP]], + patternLoc: Opt[Loc] + ): MakeSplit = + validatePatternParameter[MakeSplit]( + parameterTerm, parameterSymbol, arguments, patternLoc, RejectSplit + ): (makeConsequent, alternative) => + val unapplyTerm = sel(parameterTerm, "unapply").resolve + val unapplyCall = app(unapplyTerm, tup(fld(scrutinee())), s"result of unapply") + tempLet(s"matchResult_${parameterSymbol.name}", unapplyCall): matchResultSymbol => + val outputSymbol = TempSymbol(N, "output").toScrut + val bindingsSymbol = TempSymbol(N, "bindings").toScrut + val pattern = matchResultPattern(S(Ls(outputSymbol.symbol, bindingsSymbol.symbol))) + val consequent = makeConsequent(outputSymbol, SeqMap.empty) + Branch(matchResultSymbol.safeRef, pattern, consequent) ~: alternative + + /** Make a UCS split that matches the entire scrutinee against the pattern. + * Since each pattern has an output, the split is responsible for creating + * a binding that holds the output value and pass it to the continuation + * function that makes the conseuqent split. + */ + def makeMatchSplit(scrutinee: Scrut, pattern: SP): MakeSplit = + pattern match + case Constructor(target, arguments) => target.resolvedSym match + case S(symbol: VarSymbol) => + makeMatchPatternParameterSplit(scrutinee, target, symbol, arguments, pattern.toLoc) + case symbolOption => symbolOption.flatMap(_.asClsLike) match + case S(classSymbol: ClassSymbol) => + makeMatchClassSplit(scrutinee, target, classSymbol, arguments) + case S(objectSymbol: ModuleOrObjectSymbol) => + makeMatchObjectSplit(pattern.toLoc, scrutinee, target, objectSymbol, arguments) + case S(patternSymbol: PatternSymbol) => + makeMatchPatternSplit(scrutinee, target, patternSymbol, arguments) + case N => + error(msg"Cannot use this ${target.describe} as a pattern." -> target.toLoc) + RejectSplit + case Composition(true, left, right) => + makeMatchSplit(scrutinee, left) | makeMatchSplit(scrutinee, right) + case Composition(false, left, right) => (makeConsequent, alternative) => + makeMatchSplit(scrutinee, left)( + (leftOutput, leftBindings) => makeMatchSplit(scrutinee, right)( + (rightOutput, rightBindings) => + val outputScrut = new LazyScrut() + outputScrut.toLet( + tup(leftOutput() |> fld, rightOutput() |> fld), + makeConsequent(outputScrut, leftBindings ++ rightBindings) ~~: alternative), + alternative), + alternative) + case Negation(pattern) => (makeConsequent, alternative) => + // Currently, the negation pattern produces the original value. In the + // future, we would include diagnostic information about why the pattern + // failed. Note that this feature requires the `alternative` parameter + // to be a function that takes a diagnostic information generation + // function. + val outputSymbol = new LazyScrut() + makeMatchSplit(scrutinee, pattern)( + (_output, _bindings) => alternative, // The output and bindings are discarded. + // The place where the diagnostic information should be stored. + outputSymbol.toLet(scrutinee(), makeConsequent(outputSymbol, SeqMap.empty) ~~: alternative) + ) + // Note that we might duplicate the alternative split here. + case Wildcard() => (makeConsequent, alternative) => makeConsequent(scrutinee, SeqMap.empty) ~~: alternative + case Literal(literal) => (makeConsequent, alternative) => + Branch(scrutinee(), FlatPattern.Lit(literal), makeConsequent(scrutinee, SeqMap.empty)) ~: alternative + case Range(lower, upper, rightInclusive) => (makeConsequent, alternative) => + makeRangeTest(scrutinee, lower, upper, rightInclusive, makeConsequent(scrutinee, SeqMap.empty)) ~~: alternative + case Concatenation(left, right) => (makeConsequent, alternative) => + makeStringPrefixMatchSplit(scrutinee, left)( + (_consumedOutput, remainingOutput, bindingsFromConsumed) => + makeMatchSplit(remainingOutput, right)( + // Here we discard the postfix output because I still haven't + // figured out the semantics of string concatenation. + (_postfixOutput, bindingsFromRemaining) => makeConsequent( + scrutinee, bindingsFromConsumed ++ bindingsFromRemaining + ) ~~: alternative, + alternative + ), + alternative + ) + case Tuple(elements, N) => (makeConsequent, alternative) => + // Fixed-length tuple patterns are similar to constructor patterns. + val (subScrutinees, makeChainedConsequent) = + elements.folded(makeConsequent)(scrutinee.getTupleLeadSubScrutinee) + val consequent = makeChainedConsequent(scrutinee, SeqMap.empty) + makeTupleBranch(scrutinee(), subScrutinees.map(_._1), consequent, alternative) + case Tuple(leading, S((_, spread, trailing))) => (makeConsequent, alternative) => + val (trailSubScrutinees, makeConsequent0) = trailing.folded(makeConsequent): + index => scrutinee.getTupleLastSubScrutinee(index) + val spreadSubScrutinee = TempSymbol(N, "middleElements") + val makeConsequent1: MakeConsequent = (outerOutput, outerBindings) => + makeMatchSplit(spreadSubScrutinee.toScrut, spread)( + (spreadOutput, spreadBindings) => makeConsequent0( + spreadOutput, // TODO: Combine `outerOutput` and `spreadOutput` + outerBindings ++ spreadBindings), + Split.End) + val (leadingSubScrutinees, makeConsequent2) = leading.folded(makeConsequent1): + index => scrutinee.getTupleLeadSubScrutinee(index) + makeTupleBranch( + scrutinee(), + leadingSubScrutinees.map(_._1), + spreadSubScrutinee, + trailSubScrutinees.map(_._1), + makeConsequent2(scrutinee, SeqMap.empty), + alternative) + case Record(fields) => (makeConsequent, alternative) => + // This case is similar to the `Constructor` case. + val z = (Nil: Ls[(Ident, BlockLocalSymbol)], makeConsequent) + // TODO: Deduplicate the code with the `Constructor` case. + val (entries, makeChainedConsequent) = fields.iterator.zipWithIndex.foldRight(z): + case (((key, pattern), index), (fields, makeInnerSplit)) => + val subScrutinee = scrutinee.getFieldScrutinee(key) + val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => + makeMatchSplit(subScrutinee, pattern)( + (fieldOutput, fieldBindings) => makeInnerSplit( + fieldOutput, // TODO: Combine `outerOutput` and `fieldOutput` + outerBindings ++ fieldBindings), + alternative) // We fill in the alternative here. This is + // different from the `Constructor` case. + ((key, subScrutinee.symbol) :: fields, makeThisSplit) + // END TODO + val consequent = makeChainedConsequent(scrutinee, SeqMap.empty) + Branch(scrutinee(), FlatPattern.Record(entries), consequent) ~: alternative + case Chain(first, second) => (makeConsequent, alternative) => + makeMatchSplit(scrutinee, first)( + (firstOutput, firstBindings) => makeMatchSplit(firstOutput, second)( + (secondOutput, secondBindings) => makeConsequent(secondOutput, firstBindings ++ secondBindings), + alternative), + alternative) + case alias @ Alias(pattern, id) => alias.symbolOption match + // Ignore those who don't have symbols. `Elaborator` should have + // reported errors. + case N => makeMatchSplit(scrutinee, pattern) + case S(symbol) => (makeConsequent, alternative) => + makeMatchSplit(scrutinee, pattern)( + (output, bindings) => + makeConsequent(output, bindings + (symbol -> output)), + alternative) + case Transform(pattern, parameters, transform) => + // We should first create a local function that transforms the captured + // values. So far, `pattern`'s variables should be bound to symbols. + // Thus, we can make a parameter list from the symbols. Then, we make + // a lambda term from the parameter list and the transform term. Because + // `pattern` might be translated to many branches, making a lambda term + // in advance reduces code duplication. + val symbols = pattern.variables.symbols + val params = parameters.map: + case (_, parameterSymbol) => + Param(FldFlags.empty, parameterSymbol, N, Modulefulness.none) + val lambdaSymbol = new TempSymbol(N, "transform") + // Next, we need to elaborate the pattern into a split. Note that + // `makeMatchSplit` returns a function that takes a split as the + // consequence. `makeMatchSplit` also takes a list of symbols so that + // it needs to make sure that those bindings are available in the + // consequence split. + (makeConsequent, alternative) => Split.Let( + sym = lambdaSymbol, + term = Term.Lam(PlainParamList(params), transform.mkClone), + // Declare the lambda function at the outermost level. Even if there + // are multiple disjunctions in the consequent, we will not need to + // repeat the `transform` term. + tail = makeMatchSplit(scrutinee, pattern)( + // Note that the output is not used. Semantically, the `transform` + // term can only access the matched values by bindings. + (_output, bindings) => + val arguments = symbols.iterator.map(bindings).map(_() |> fld).toSeq + val resultTerm = app(lambdaSymbol.safeRef, tup(arguments*), "the transform's result") + val resultSymbol = TempSymbol(N, "transformResult") + Split.Let(resultSymbol, resultTerm, makeConsequent(resultSymbol.toScrut, SeqMap.empty)), + alternative)) + case Annotated(pattern, annotations) => + // Currently, we only support `@compile` annotation, so here we only + // check whether this annotation exists, and report an error for all + // other annotations. + val shouldCompile = annotations.foldLeft(true): (acc, termOrLoc) => + val res = termOrLoc match + case R(term) => term.resolvedSym match + case S(symbol) if symbol is ctx.builtins.annotations.compile => N + case S(_) | N => S(term.toLoc) + case L(loc) => S(loc) + res match + case S(loc) => + warn(msg"This annotation is not supported here." -> loc, + msg"Note: Patterns only support the `@compile` annotation." -> pattern.toLoc) + acc + case N => true + if shouldCompile then + compilePattern(scrutinee, pattern) + else + makeMatchSplit(scrutinee, pattern) + case Guarded(pattern, guard) => (makeConsequent, alternative) => + makeMatchSplit(scrutinee, pattern)( + (output, bindings) => + val guardSymbol = TempSymbol(N, "guardResult") + val branch = Branch(guardSymbol.ref(), makeConsequent(output, bindings)) + val innermost = Split.Let(guardSymbol, guard, branch ~: Split.End) + // The creation of bindings here is repeated with the creation of + // bindings during desugaring. We can add a piece of information + // to the `bindings` map. See tests in `where.mls`. + bindings.iterator.foldLeft(innermost): + case (innerSplit, (symbol, mkTerm)) => Split.Let(symbol, mkTerm(), innerSplit), + alternative) + + private def makeMatchPrefixPatternSplit( + scrutinee: Scrut, + patternTerm: Term, + patternSymbol: PatternSymbol, + arguments: Opt[Ls[SP]] + )(using Raise): MakePrefixSplit = + val defn = patternSymbol.defn.getOrElse(die) + val (patternArguments, extractionMatches, shouldReject) = + matchParametersWithArguments(scrutinee, defn, arguments) + if shouldReject then RejectPrefixSplit else (makeConsequent, alternative) => + val outputSymbol = TempSymbol(N, "output").toScrut + val remainingSymbol = TempSymbol(N, "remaining").toScrut + val (extractionArguments, makeConsequentForArguments) = + extractionMatches.fold((N, makeConsequent)): + _.folded(makeConsequent)(scrutinee.getSubScrutinee).mapFirst(S(_)) + val consequent = makeConsequentForArguments(outputSymbol, remainingSymbol, SeqMap.empty) + val patternBindings = patternArguments.makePatternBindings + val unapplyCall = + val method = "unapplyStringPrefix" + val args = tup(patternBindings.map(_._1.safeRef) :+ scrutinee()) + app(sel(patternTerm, method), args, s"result of $method") + val split = tempLet("unapplyResult", unapplyCall): matchResultSymbol => + val outputPairSymbol = TempSymbol(N, "outputPair") + val bindingsSymbol = TempSymbol(N, "bindings") + Branch( + matchResultSymbol.safeRef, + matchResultPattern(S(outputPairSymbol :: bindingsSymbol :: Nil)), + // Bind the `remaining` variable to the second element of the output + // of `matchResult`. + Split.Let(outputSymbol.symbol, callTupleGet(outputPairSymbol.safeRef, 0, "prefix"), + Split.Let(remainingSymbol.symbol, callTupleGet(outputPairSymbol.safeRef, 1, "postfix"), consequent)) + ) ~: alternative + buildPatternArguments(patternBindings, split) + + private def makeMatchPrefixPatternParameterSplit( + scrutinee: Scrut, + parameterTerm: Term, + parameterSymbol: VarSymbol, + arguments: Opt[Ls[SP]], + patternLoc: Opt[Loc] + )(using Raise): MakePrefixSplit = + validatePatternParameter[MakePrefixSplit]( + parameterTerm, parameterSymbol, arguments, patternLoc, RejectPrefixSplit + ): (makeConsequent, alternative) => + val unapplyTerm = sel(parameterTerm, "unapplyStringPrefix").resolve + val unapplyCall = app(unapplyTerm, tup(fld(scrutinee())), s"result of unapply") + tempLet(s"matchResult_${parameterSymbol.name}", unapplyCall): matchResultSymbol => + // Destruct the `MatchResult` produced by the `unapply` method. + val outputPairSymbol = TempSymbol(N, "outputPair").toScrut + val bindingsSymbol = TempSymbol(N, "bindings").toScrut + val pattern = matchResultPattern(S(Ls(outputPairSymbol.symbol, bindingsSymbol.symbol))) + // Destruct the first field of the `MatchResult` as a pair. + val outputSymbol = TempSymbol(N, "output").toScrut // Denotes the pattern's output. + val remainingSymbol = TempSymbol(N, "remaining").toScrut // Denotes the remaining value. + // Assemble the `Split`s in order from inside to outside. + val consequent1 = makeConsequent(outputSymbol, remainingSymbol, SeqMap.empty) + val consequent2 = makeTupleBranch(outputPairSymbol(), + Ls(outputSymbol.symbol, remainingSymbol.symbol), consequent1, Split.End) + Branch(matchResultSymbol.safeRef, pattern, consequent2) ~: alternative + + /** Construct a UCS split to match the prefix of the given scrutinee. + * + * @return The return value is a function that builds the split. */ + protected def makeStringPrefixMatchSplit( + scrutinee: Scrut, + pattern: SP, + )(using Raise): MakePrefixSplit = pattern match + case Constructor(target, arguments) => target.resolvedSym match + // The case when the target refers to a pattern parameter. + case S(symbol: VarSymbol) => + makeMatchPrefixPatternParameterSplit(scrutinee, target, symbol, arguments, pattern.toLoc) + case symbolOption => symbolOption.flatMap(_.asPat) match + // The case when the target refers to a pattern symbol. + case S(symbol: PatternSymbol) => + makeMatchPrefixPatternSplit(scrutinee, target, symbol, arguments) + // The other cases are not compatible with strings. + case S(_) | N => RejectPrefixSplit + case Composition(true, left, right) => + val makeLeft = makeStringPrefixMatchSplit(scrutinee, left) + val makeRight = makeStringPrefixMatchSplit(scrutinee, right) + (makeConsequent, alternative) => + makeLeft(makeConsequent, makeRight(makeConsequent, alternative)) + case Composition(false, left, right) => (makeConsequent, alternative) => + // This case is different, as the left pattern should be matched in prefix + // mode, but the `right` pattern should be matched in full mode. If the + // `right` pattern fails, we should check if `left` can match a different + // prefix and retry `right`. + // TODO: Implement the correct backtracking behavior. + makeStringPrefixMatchSplit(scrutinee, left)( + (leftOutput, leftRemains, leftBindings) => makeMatchSplit(scrutinee, right)( + (rightOutput, rightBindings) => + val productSymbol = TempSymbol(N, "product") + val productTerm = tup(leftOutput() |> fld, rightOutput() |> fld) + Split.Let(productSymbol, productTerm, makeConsequent( + productSymbol.toScrut, leftRemains, leftBindings ++ rightBindings)), + alternative), + alternative) + case Negation(pattern) => + // This case is tricky. The question is how many of characters should be + // left to the continuation? For example, to match string "match is over" + // against pattern `~"game" ~ " is over"`. The first step is to match + // pattern `"game"` as a prefix of the input. After we found it doesn't + // not match, how many characters should we consume in this step? From a + // global perspective, we know that we should consume the prefix + // `"match is "``, but with backtracking, we have to try every + // combinations before we can make a conclusion. + RejectPrefixSplit + case Wildcard() => (makeConsequent, alternative) => + // Because the wildcard pattern always matches, we can match the entire + // string and returns an empty string as the remaining value. + val emptyStringSymbol = TempSymbol(N, "emptyString") + makeConsequent(scrutinee, emptyStringSymbol.toScrut, SeqMap.empty) + Branch(scrutinee(), FlatPattern.ClassLike(ctx.builtins.Str.safeRef, ctx.builtins.Str, N, false)(Tree.Dummy), + Split.Let(emptyStringSymbol, str(""), + makeConsequent(scrutinee, emptyStringSymbol.toScrut, SeqMap.empty)) + ) ~: alternative + case Literal(prefix: StrLit) => (makeConsequent, alternative) => + // Check if the scrutinee is the same as the literal. If so, we return + // an empty string as the remaining value. + val isLeadingSymbol = TempSymbol(N, "isLeading") + val isLeadingTerm = callStringStartsWith( + scrutinee(), Term.Lit(prefix), "the result of startsWith") + val outputSymbol = TempSymbol(N, "consumed") + val outputTerm = callStringTake(scrutinee(), prefix.value.length, "the consumed part of input") + val remainsSymbol = TempSymbol(N, "remains") + val remainsTerm = callStringDrop(scrutinee(), prefix.value.length, "the remaining input") + Split.Let(isLeadingSymbol, isLeadingTerm, + Branch(isLeadingSymbol.safeRef, + Split.Let(outputSymbol, outputTerm, + Split.Let(remainsSymbol, remainsTerm, + makeConsequent(outputSymbol.toScrut, remainsSymbol.toScrut, SeqMap.empty))) + ) ~: alternative) + // Non-string literal patterns are directly discarded. + case Literal(_) => RejectPrefixSplit + case Range(lower: StrLit, upper: StrLit, rightInclusive) => (makeConsequent, alternative) => + // Check if the string is not empty. Then + val stringHeadSymbol = TempSymbol(N, "stringHead") + val stringTailSymbol = TempSymbol(N, "stringTail") + val nonEmptySymbol = TempSymbol(N, "nonEmpty") + val nonEmptyTerm = app(this.lt.safeRef, tup(fld(int(0)), fld(sel(scrutinee(), "length"))), "string is not empty") + Split.Let(nonEmptySymbol, nonEmptyTerm, // `0 < string.length` + Branch(nonEmptySymbol.safeRef, + Split.Let(stringHeadSymbol, callStringGet(scrutinee(), 0, "head"), + Split.Let(stringTailSymbol, callStringDrop(scrutinee(), 1, "tail"), + makeRangeTest(stringHeadSymbol.toScrut, lower, upper, rightInclusive, + makeConsequent(stringHeadSymbol.toScrut, stringTailSymbol.toScrut, SeqMap.empty)))) + ) ~: alternative) + // Other range patterns cannot be string prefixes. + case Range(_, _, _) => RejectPrefixSplit + case Concatenation(left, right) => (makeConsequent, alternative) => + makeStringPrefixMatchSplit(scrutinee, left)( + (leftConsumedOutput, leftRemainingOutput, leftBindings) => + makeStringPrefixMatchSplit(leftRemainingOutput, right)( + (rightConsumedOutput, rightRemainingOutput, rightBindings) => + val combinedOutputSymbol = LazyScrut(S("combinedOutput")) + combinedOutputSymbol.toLet( + app(add.ref(), tup(fld(leftConsumedOutput()), fld(rightConsumedOutput())), "combined output"), + makeConsequent(combinedOutputSymbol, rightRemainingOutput, leftBindings ++ rightBindings)), + alternative), + alternative) + // Tuples and records cannot be string prefixes. + case Tuple(_, _) => RejectPrefixSplit + case Record(_) => RejectPrefixSplit + case Chain(first, second) => (makeConsequent, alternative) => + // This case is different because the first pattern might haven + // non-string output. So, we should apply `makeMatchSplit` to the second + // pattern, and finally pass the remains from the first pattern to the + // continuation. + makeStringPrefixMatchSplit(scrutinee, first)( + (firstOutput, firstRemains, firstBindings) => + makeMatchSplit(firstOutput, second)( + (secondOutput, secondBindings) => + makeConsequent(secondOutput, firstRemains, firstBindings ++ secondBindings), + alternative), + alternative) + case alias @ Alias(pattern, id) => + alias.symbolOption match + // Ignore those who don't have symbols. `Elaborator` should have + // reported errors. + case N => makeStringPrefixMatchSplit(scrutinee, pattern) + case S(symbol) => + val make = makeStringPrefixMatchSplit(scrutinee, pattern) + make.whenAccept: + (makeConsequent, alternative) => make( + (output, remains, bindings) => + makeConsequent(output, remains, bindings + (symbol -> output)), + alternative) + case Transform(pattern, parameters, transform) => + val make = makeStringPrefixMatchSplit(scrutinee, pattern) + make.whenAccept: + // Declare the lambda function at the outermost level. Even if there are + // multiple disjunctions in the consequent, we will not need to repeat + // the `transform` term. + val symbols = pattern.variables.symbols + val params = parameters.map: + case (_, parameterSymbol) => + Param(FldFlags.empty, parameterSymbol, N, Modulefulness.none) + val lambdaSymbol = new TempSymbol(N, "transform") + (makeConsequent, alternative) => Split.Let( + sym = lambdaSymbol, + term = Term.Lam(PlainParamList(params), transform), + tail = make( + // Note that the output is not used. Semantically, the `transform` + // term can only access the matched values by bindings. + (_output, remains, bindings) => + val arguments = symbols.iterator.map(bindings).map(_() |> fld).toSeq + val resultTerm = app(lambdaSymbol.safeRef, tup(arguments*), "the transform's result") + val resultSymbol = TempSymbol(N, "transformResult") + Split.Let(resultSymbol, resultTerm, makeConsequent(resultSymbol.toScrut, remains, SeqMap.empty)), + alternative)) + case Guarded(pattern, guard) => + val make = makeStringPrefixMatchSplit(scrutinee, pattern) + make.whenAccept: + (makeConsequent, alternative) => make( + (output, remains, bindings) => + val guardSymbol = TempSymbol(N, "guardResult") + val branch = Branch(guardSymbol.ref(), makeConsequent(output, remains, bindings)) + val innermost = Split.Let(guardSymbol, guard, branch ~: Split.End) + // The creation of bindings here is repeated with the creation of + // bindings during desugaring. We can add a piece of information + // to the `bindings` map. See tests in `where.mls`. + bindings.iterator.foldLeft(innermost): + case (innerSplit, (symbol, mkTerm)) => Split.Let(symbol, mkTerm(), innerSplit), + alternative) + case Annotated(pattern, annotations) => + // Currently, we only support `@compile` annotation, so here we only + // check whether this annotation exists, and report an error for all + // other annotations. + val shouldCompile = annotations.foldLeft(true): (acc, termOrLoc) => + val res = termOrLoc match + case R(term) => term.resolvedSym match + case S(symbol) if symbol is ctx.builtins.annotations.compile => N + case S(_) | N => S(term.toLoc) + case L(loc) => S(loc) + res match + case S(loc) => + warn(msg"This annotation is not supported here." -> loc, + msg"Note: Patterns only support the `@compile` annotation." -> pattern.toLoc) + acc + case N => true + if shouldCompile then error: + msg"String patterns are not yet supported by efficient compilation." -> pattern.toLoc + makeStringPrefixMatchSplit(scrutinee, pattern) + + /** This method handles the efficient and non-backtracking pattern compilation. + * Note that we still have not supported accessing pattern parameters in the + * naive pattern declaration in the efficient pattern compilation. */ + def compilePattern(scrutinee: Scrut, pattern: SP): MakeSplit = + (makeConsequent, alternative) => scoped("ucs:ups:compilation"): + // Instantiate the pattern and all patterns used in it. + val instantiator = new Instantiator + val (synonym, context) = instantiator(pattern) + // Initate the compilation. + val compiler = new Compiler(using context) + val ((matcherSymbol, fieldName), implementations) = compiler.buildMatcher(synonym) + val innermostSplit = + // 1. Bind the call result to a variable. + val recordSymbol = TempSymbol(N, "matchRecord") + val recordTerm = app(matcherSymbol.safeRef, tup(fld(scrutinee())), "result of matcher function") + val f1 = Split.Let(recordSymbol, recordTerm, _) + // 2. Select the selection field to the result. + val matchResultSymbol = TempSymbol(N, "matchResult") + val matchResultTerm = sel(recordSymbol.safeRef, fieldName) + val f2 = Split.Let(matchResultSymbol, matchResultTerm, _) + // 3. Check if the field value is a `MatchResult` and bind the output. + val outputSymbol = TempSymbol(N, "patternOutput") + val bindingsSymbol = TempSymbol(N, "bindings") // TODO: This is useless. + // val consequent = aliasOutputSymbols(outputSymbol.safeRef, outputSymbols, consequent) + // TODO: How to forward the bindings from the pattern compilation to here? + val consequent = makeConsequent(outputSymbol.toScrut, SeqMap.empty) + val pattern = matchResultPattern(S(outputSymbol :: bindingsSymbol :: Nil)) + val branch = Branch(matchResultSymbol.safeRef, pattern, consequent) + f1(f2(branch ~: alternative)) + implementations.iterator.foldRight(innermostSplit): + case ((symbol, paramList, term), innerSplit) => + log(term.showDbg) + Split.Let(symbol, Term.Lam(paramList, term), innerSplit) + + /** Make a term like `MatchFailure(null)`. We will synthesize detailed + * error messages and pass them to the function. */ + private def failure: Split = Split.Else(makeMatchFailure()) + + /** + * Create a method from the given UCS splits. The function has a parameter + * list that contains the pattern parameters and a parameter that represents + * the input value. + * + * @param name the method's name + * @param patternParameters all pattern parameters + * @param scrut the symbol of the input value + * @param topmost the topmost split + */ + private def makeMethod( + name: Str, + patternParameters: List[Param], + scrut: VarSymbol, + topmost: Split + ): (BlockMemberSymbol, ParamList, Split) = + val sym = BlockMemberSymbol(name, Nil) + // Pattern parameters are passed as objects. + val patternInputs = patternParameters.map(_.copy(flags = FldFlags.empty)) + // The last parameter is the scrutinee. + val scrutParam = Param(FldFlags.empty, scrut, N, Modulefulness.none) + val ps = PlainParamList(patternInputs :+ scrutParam) + (sym, ps, topmost) + + /** Translate a list of extractor/matching functions for the given pattern. + * There are currently two functions: `unapply` and `unapplyStringPrefix`. + * + * - `unapply` is used for matching the entire scrutinee. It returns the + * captured/extracted values. + * - `unapplyStringPrefix` is used for matching the string prefix of the + * scrutinee. It returns the remaining string and the captured/extracted + * values. If the given tree does not represent a string pattern, this + * function will not be generated. + * + * @param pattern We will eventually generate methods from the omnipotent + * `Pattern` class. Now the new `pattern` parameter and the + * old `body` parameter are mixed. + */ + def compilePattern(pd: PatternDef): Ls[(BlockMemberSymbol, ParamList, Split)] = + trace( + pre = s"compilePattern <<< ${pd.showDbg}", + post = (blk: Ls[(BlockMemberSymbol, ParamList, Split)]) => + s"compilePattern >>> $blk" + ): + // TODO: Use `pd.extractionParams`. + val unapply = scoped("ucs:translation"): + val inputSymbol = VarSymbol(Ident("input")) + val topmost = makeMatchSplit(inputSymbol.toScrut, pd.pattern) + ((output, bindings) => Split.Else(makeMatchResult(output())), failure) + log(s"Translated `unapply`: ${topmost.prettyPrint}") + makeMethod("unapply", pd.patternParams, inputSymbol, topmost) + // TODO: Use `pd.extractionParams`. + val unapplyStringPrefix = scoped("ucs:cp"): + // We don't report errors here because they have been already reported in + // the translation of `unapply` function. + given Raise = Function.const(()) + val inputSymbol = VarSymbol(Ident("input")) + val topmost = makeStringPrefixMatchSplit(inputSymbol.toScrut, pd.pattern) + ((consumedOutput, remainingOutput, bindings) => Split.Else: + makeMatchResult(tup(fld(consumedOutput()), fld(remainingOutput()))), failure) + log(s"Translated `unapplyStringPrefix`: ${topmost.prettyPrint}") + makeMethod("unapplyStringPrefix", pd.patternParams, inputSymbol, topmost) + unapply :: unapplyStringPrefix :: Nil + + /** Generate the record statements of `unapply` methods that can be used in + * objects for anonymous patterns. */ + private def makeUnapplyRecordStatements( + name: Str, + patternParameters: List[Param], + scrut: VarSymbol, + topmost: Split + ): Ls[Statement] = + val fieldSymbol = TempSymbol(N, name) + val decl = LetDecl(fieldSymbol, Nil) + val param = Param(FldFlags.empty, scrut, N, Modulefulness.none) + val paramList = PlainParamList(param :: Nil) + val lambda = Term.Lam(paramList, Term.SynthIf(topmost)) + val defineVar = DefineVar(fieldSymbol, lambda) + val field = RcdField(str(name), fieldSymbol.safeRef) + decl :: defineVar :: field :: Nil + + /** Translate an anonymous pattern. They are usually pattern arguments. */ + private def compileAnonymousPattern(patternParams: Ls[Param], params: Ls[Param], pattern: SP): Term = trace( + pre = s"compileAnonymousPattern <<< $pattern", + post = (blk: Term) => s"compileAnonymousPattern >>> $blk" + ): + // If the `target` refers to a pattern symbol, we can reference the pattern. + val term = pattern match + case Constructor(target, N) => + target.symbol.flatMap(_.asPat).flatMap(Compiler.reference(_, target.toLoc)) + case _ => N + term.getOrElse: + val unapply = scoped("ucs:translation"): + val inputSymbol = VarSymbol(Ident("input")) + val topmost = makeMatchSplit(inputSymbol.toScrut, pattern) + ((output, bindings) => Split.Else(makeMatchResult(output())), failure) + log(s"Translated `unapply`: ${topmost.prettyPrint}") + makeUnapplyRecordStatements("unapply", patternParams, inputSymbol, topmost) + val unapplyStringPrefix = scoped("ucs:cp"): + // We don't report errors here because they have been already reported in + // the translation of `unapply` function. + given Raise = Function.const(()) + val inputSymbol = VarSymbol(Ident("input")) + val topmost = makeStringPrefixMatchSplit(inputSymbol.toScrut, pattern) + ((consumedOutput, remainingOutput, bindings) => Split.Else: + makeMatchResult(tup(fld(consumedOutput()), fld(remainingOutput()))), failure) + log(s"Translated `unapplyStringPrefix`: ${topmost.prettyPrint}") + makeUnapplyRecordStatements("unapplyStringPrefix", patternParams, inputSymbol, topmost) + Term.Rcd(false, unapply ::: unapplyStringPrefix) + diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala index 744226371e..e4f16d515a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala @@ -421,27 +421,33 @@ class ParseRules(using State): ) { case (rhs, _) => S(rhs) } ) - def genInfixRule[A](kw: Keyword, k: (Tree, Unit) => A): Alt[A] = - discardKw(kw): + def keywordThenTree[A, K <: Keyword](kw: K)(k: (Keywrd[kw.type], Tree) => A): Alt[A] = + keepKw(kw): ParseRule(s"'${kw}' operator")( - Expr(ParseRule(s"'${kw}' operator right-hand side")(end(())))(k) + Expr(ParseRule(s"'${kw}' operator right-hand side")(end(()))): + case (tree, ()) => tree // * Interestingly, this does not seem to change anything: // exprOrBlk(ParseRule(s"'${kw}' operator right-hand side")(End(())))(k)* ) + .map(k.tupled) + + def makeInfixRule[K <: Infix](kw: K): Alt[Tree => Tree] = + keywordThenTree(kw): + case (kw, rhs) => lhs => InfixApp(lhs, kw, rhs) val infixRules: ParseRule[Tree => Tree] = ParseRule("continuation of expression")( - genInfixRule(`and`, (rhs, _: Unit) => lhs => InfixApp(lhs, `and`, rhs)), - genInfixRule(`or`, (rhs, _: Unit) => lhs => InfixApp(lhs, `or`, rhs)), - genInfixRule(`is`, (rhs, _: Unit) => lhs => InfixApp(lhs, `is`, rhs)), - genInfixRule(`as`, (rhs, _: Unit) => lhs => InfixApp(lhs, `as`, rhs)), - genInfixRule(`then`, (rhs, _: Unit) => lhs => InfixApp(lhs, `then`, rhs)), - // genInfixRule(`else`, (rhs, _: Unit) => lhs => InfixApp(lhs, `else`, rhs)), - genInfixRule(`:`, (rhs, _: Unit) => lhs => InfixApp(lhs, `:`, rhs)), - genInfixRule(`extends`, (rhs, _: Unit) => lhs => InfixApp(lhs, `extends`, rhs)), - genInfixRule(`restricts`, (rhs, _: Unit) => lhs => InfixApp(lhs, `restricts`, rhs)), - genInfixRule(`do`, (rhs, _: Unit) => lhs => InfixApp(lhs, `do`, rhs)), - genInfixRule(`where`, (rhs, _: Unit) => lhs => InfixApp(lhs, `where`, rhs)), - genInfixRule(`with`, (rhs, _: Unit) => lhs => InfixApp(lhs, `with`, rhs)), + makeInfixRule(`and`), + makeInfixRule(`or`), + makeInfixRule(`is`), + makeInfixRule(`as`), + makeInfixRule(`then`), + // makeInfixRule(`else`), + makeInfixRule(`:`), + makeInfixRule(`extends`), + makeInfixRule(`restricts`), + makeInfixRule(`do`), + makeInfixRule(`where`), + makeInfixRule(`with`), ) end ParseRules diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index adba725662..3013e20576 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -603,7 +603,7 @@ abstract class Parser( case Curly => Bra(Curly, Block(ps)) case Square => TyTup(ps) exprCont( - Quoted(InfixApp(lhs, kw, Unquoted(rhs)).withLoc(S(loc))).withLoc(S(l ++ loc)), + Quoted(InfixApp(lhs, new Keywrd(kw).withLoc(S(l0)), Unquoted(rhs)).withLoc(S(loc))).withLoc(S(l ++ loc)), prec, allowNewlines = allowNewlines) case (KEYWORD(kw @ (Keyword.`=>` | Keyword.`->`)), l0) :: _ if kw.leftPrecOrMin > prec => @@ -613,7 +613,7 @@ abstract class Parser( case Round => Tup(ps) case Curly => ??? case Square => TyTup(ps) - val res = InfixApp(lhs, kw, rhs).withLoc(S(loc)) + val res = InfixApp(lhs, new Keywrd(kw).withLoc(S(l0)), rhs).withLoc(S(loc)) exprCont(res, prec, allowNewlines = allowNewlines) case _ => val sts = ps @@ -653,9 +653,9 @@ abstract class Parser( consume val ele = simpleExprImpl(prec, allowNewlines = false) term match - case InfixApp(lhs, Keyword.`then`, rhs) => + case InfixApp(lhs, kw @ Keywrd(Keyword.`then`), rhs) => Quoted(IfLike(new Keywrd(Keyword.`if`).withLoc(S(l0)), Block( - InfixApp(Unquoted(lhs), Keyword.`then`, Unquoted(rhs)) :: + InfixApp(Unquoted(lhs), kw, Unquoted(rhs)) :: PrefixApp(new Keywrd(Keyword.`else`).withLoc(S(l1)), Unquoted(ele)) :: Nil ))) case tk => @@ -772,7 +772,7 @@ abstract class Parser( e match case N => die case S(e) => - opSplitImpl(lhs, splittingOpLoc, prec, InfixApp(e, Keyword.`then`, rhs) :: acc) + opSplitImpl(lhs, splittingOpLoc, prec, InfixApp(e, new Keywrd(Keyword.`then`).withLoc(S(l0)), rhs) :: acc) case (IDENT(op, true), l0) :: rest => assert(opPrec(op)._1 <= prec) if rest.collectFirst{ case (_: NEWLINE_COMMA | IDENT("then", false), _) => }.isEmpty // TODO dedup @@ -801,7 +801,7 @@ abstract class Parser( consume consume val rhs = effectfulRhs(kw.rightPrecOrMin, allowNewlines = allowNewlines) - exprCont(Quoted(InfixApp(PlainTup(acc), kw, Unquoted(rhs))), prec, allowNewlines = allowNewlines) + exprCont(Quoted(InfixApp(PlainTup(acc), new Keywrd(kw).withLoc(S(l0)), Unquoted(rhs))), prec, allowNewlines = allowNewlines) case _ :: (br @ BRACKETS(Round, toks), loc) :: _ => consume consume @@ -849,7 +849,7 @@ abstract class Parser( consume val rhs = effectfulRhs(kw.rightPrecOrMin, allowNewlines = allowNewlines) val res = acc match - case _ => InfixApp(PlainTup(acc), kw, rhs) + case _ => InfixApp(PlainTup(acc), new Keywrd(kw).withLoc(S(l0)), rhs) exprCont(res, prec, allowNewlines = allowNewlines) case (IDENT("!", _), l0) :: (br @ BRACKETS(bk, toks), l1) :: _ => diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index ea41d2df03..c8343bb7c3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -89,7 +89,7 @@ enum Tree extends AutoLocated: case Sel(prefix: Tree, name: Ident) case MemberProj(cls: Tree, name: Ident) case PrefixApp(kw: Keywrd[Keyword.Prefix], rhs: Tree) - case InfixApp(lhs: Tree, kw: Keyword.Infix, rhs: Tree) + case InfixApp(lhs: Tree, kw: Keywrd[Keyword.Infix], rhs: Tree) case LexicalNew(body: Opt[Tree], rft: Opt[Block]) // * New as it is parsed, with its weird precedence – eg (new C)(123) case ProperNew(body: Opt[Tree], rft: Opt[Block]) // * A desugared version of New that sets it right – eg new(C(123)) case DynamicNew(cls: Tree) // * Dynamic version – eg new! C(123) @@ -137,7 +137,7 @@ enum Tree extends AutoLocated: case OpApp(lhs, op, rhss) => lhs :: op :: rhss case Jux(lhs, rhs) => Ls(lhs, rhs) case PrefixApp(kw, rhs) => kw :: rhs :: Nil - case InfixApp(lhs, _, rhs) => Ls(lhs, rhs) + case InfixApp(lhs, kw, rhs) => lhs :: kw :: rhs :: Nil case TermDef(k, head, rhs) => head :: rhs.toList case LexicalNew(body, rft) => body.toList ::: rft.toList case ProperNew(body, rft) => body.toList ::: rft.toList @@ -228,7 +228,7 @@ enum Tree extends AutoLocated: lazy val desugared: Tree = this match case Pun(false, id) => - InfixApp(id, Keyword.`:`, id) + InfixApp(id, Keywrd(Keyword.`:`), id) // TODO generalize to pattern-let and rm this special case case LetLike(kw, und @ Under(), r, b) => @@ -259,7 +259,7 @@ enum Tree extends AutoLocated: ProperNew(S(Apps(body, argss)), N).withLocOf(this) case LexicalNew(bodo, rfto) => ProperNew(bodo, rfto).withLocOf(this) - case InfixApp(Desugared(ProperNew(bodo, N)), Keyword.`with`, rhs: Block) => + case InfixApp(Desugared(ProperNew(bodo, N)), Keywrd(Keyword.`with`), rhs: Block) => ProperNew(bodo, S(rhs)).withLocOf(this) case _ => this @@ -279,7 +279,7 @@ enum Tree extends AutoLocated: case id: Ident if !inUsing => R(ParamTree(flags, id, N, N, modifiers)) // fun f(a: A) - case InfixApp(id: Ident, Keyword.`:`, sign) => + case InfixApp(id: Ident, Keywrd(Keyword.`:`), sign) => R(ParamTree(flags, id, S(sign), N, modifiers)) // fun f(..a) | fun f(...a) case SpreadParam(id, spd) => @@ -463,7 +463,7 @@ trait TypeOrTermDef extends Located: t match // use Foo as foo = ... - case InfixApp(typ, Keyword.`as`, id: Ident) if k == Ins => + case InfixApp(typ, Keywrd(Keyword.`as`), id: Ident) if k == Ins => (S(R(id)), R(id), Nil, N, S(typ)) // use Foo = ... @@ -473,7 +473,7 @@ trait TypeOrTermDef extends Located: (S(R(id)), R(id), Nil, N, S(typ)) - case InfixApp(tree, Keyword.`:`, ann) => + case InfixApp(tree, Keywrd(Keyword.`:`), ann) => rec(tree, symbName, S(ann)) // fun f @@ -518,11 +518,11 @@ trait TypeOrTermDef extends Located: val (baseHead, extension, withPart) = head match - case InfixApp(InfixApp(base, Keyword.`extends`, ext), Keyword.`with`, wp) => + case InfixApp(InfixApp(base, Keywrd(Keyword.`extends`), ext), Keywrd(Keyword.`with`), wp) => (base, S(ext), S(wp)) - case InfixApp(base, Keyword.`with`, wp) => + case InfixApp(base, Keywrd(Keyword.`with`), wp) => (base, N, S(wp)) - case InfixApp(base, Keyword.`extends`, ext) => + case InfixApp(base, Keywrd(Keyword.`extends`), ext) => (base, S(ext), N) case h => (h, N, N) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index d1a8d1fb79..a4ce900a78 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -124,17 +124,17 @@ globalThis.Object.freeze(class Predef { } static foldr(f) { return (first, ...rest) => { - let len, i, init, scrut, scrut1, tmp, tmp1, tmp2, tmp3; + let len, scrut, i, init, scrut1, tmp, tmp1, tmp2, tmp3; len = rest.length; - scrut1 = len == 0; - if (scrut1 === true) { + scrut = len == 0; + if (scrut === true) { return first } else { i = len - 1; init = runtime.safeCall(rest.at(i)); tmp4: while (true) { - scrut = i > 0; - if (scrut === true) { + scrut1 = i > 0; + if (scrut1 === true) { tmp = i - 1; i = tmp; tmp1 = runtime.safeCall(rest.at(i)); diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index 7b1c3e8013..a28beac994 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -517,22 +517,28 @@ globalThis.Object.freeze(class Runtime { tmp1 = loc; } loc1 = tmp1; - if (showLocals === true) { - scrut2 = curLocals.locals.length > 0; - if (scrut2 === true) { - lambda = (undefined, function (l) { - let tmp21, tmp22; - tmp21 = l.localName + "="; - tmp22 = Rendering.render(l.value); - return tmp21 + tmp22 - }); - tmp2 = runtime.safeCall(curLocals.locals.map(lambda)); - tmp3 = runtime.safeCall(tmp2.join(", ")); - tmp4 = " with locals: " + tmp3; - } else { - tmp4 = ""; + split_root$: { + split_1$: { + if (showLocals === true) { + scrut2 = curLocals.locals.length > 0; + if (scrut2 === true) { + lambda = (undefined, function (l) { + let tmp21, tmp22; + tmp21 = l.localName + "="; + tmp22 = Rendering.render(l.value); + return tmp21 + tmp22 + }); + tmp2 = runtime.safeCall(curLocals.locals.map(lambda)); + tmp3 = runtime.safeCall(tmp2.join(", ")); + tmp4 = " with locals: " + tmp3; + break split_root$ + } else { + break split_1$ + } + } else { + break split_1$ + } } - } else { tmp4 = ""; } localsMsg = tmp4; @@ -585,7 +591,7 @@ globalThis.Object.freeze(class Runtime { return msg } static showFunctionContChain(cont, hl, vis, reps) { - let scrut, result, scrut1, scrut2, tmp, lambda, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + let result, scrut, scrut1, scrut2, tmp, lambda, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; if (cont instanceof Runtime.FunctionContFrame.class) { tmp = cont.constructor.name + "(pc="; result = tmp + cont.pc; @@ -602,12 +608,12 @@ globalThis.Object.freeze(class Runtime { } }); tmp1 = runtime.safeCall(hl.forEach(lambda)); - scrut1 = runtime.safeCall(vis.has(cont)); - if (scrut1 === true) { + scrut = runtime.safeCall(vis.has(cont)); + if (scrut === true) { tmp2 = reps + 1; reps = tmp2; - scrut2 = reps > 10; - if (scrut2 === true) { + scrut1 = reps > 10; + if (scrut1 === true) { throw globalThis.Error("10 repeated continuation frame (loop?)") } else { tmp3 = runtime.Unit; @@ -622,8 +628,8 @@ globalThis.Object.freeze(class Runtime { tmp7 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp6 + tmp7 } else { - scrut = cont === null; - if (scrut === true) { + scrut2 = cont === null; + if (scrut2 === true) { return "(null)" } else { return "(NOT CONT)" @@ -631,7 +637,7 @@ globalThis.Object.freeze(class Runtime { } } static showHandlerContChain(cont, hl, vis, reps) { - let scrut, result, scrut1, scrut2, lambda, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + let result, scrut, scrut1, scrut2, lambda, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; if (cont instanceof Runtime.HandlerContFrame.class) { result = cont.handler.constructor.name; lambda = (undefined, function (m, marker) { @@ -647,12 +653,12 @@ globalThis.Object.freeze(class Runtime { } }); tmp = runtime.safeCall(hl.forEach(lambda)); - scrut1 = runtime.safeCall(vis.has(cont)); - if (scrut1 === true) { + scrut = runtime.safeCall(vis.has(cont)); + if (scrut === true) { tmp1 = reps + 1; reps = tmp1; - scrut2 = reps > 10; - if (scrut2 === true) { + scrut1 = reps > 10; + if (scrut1 === true) { throw globalThis.Error("10 repeated continuation frame (loop?)") } else { tmp2 = runtime.Unit; @@ -667,8 +673,8 @@ globalThis.Object.freeze(class Runtime { tmp6 = Runtime.showFunctionContChain(cont.next, hl, vis, reps); return tmp5 + tmp6 } else { - scrut = cont === null; - if (scrut === true) { + scrut2 = cont === null; + if (scrut2 === true) { return "(null)" } else { return "(NOT HANDLER CONT)" @@ -801,17 +807,22 @@ globalThis.Object.freeze(class Runtime { let prevHandlerFrame, scrut, scrut1, scrut2, handlerFrame, saved, scrut3, scrut4, tmp, tmp1, tmp2, tmp3, tmp4, tmp5; prevHandlerFrame = cur.contTrace; tmp6: while (true) { - scrut = prevHandlerFrame.nextHandler !== null; - if (scrut === true) { - scrut1 = prevHandlerFrame.nextHandler.handler !== cur.handler; - if (scrut1 === true) { - prevHandlerFrame = prevHandlerFrame.nextHandler; - tmp = runtime.Unit; - continue tmp6 - } else { - tmp = runtime.Unit; + split_root$: { + split_1$: { + scrut = prevHandlerFrame.nextHandler !== null; + if (scrut === true) { + scrut1 = prevHandlerFrame.nextHandler.handler !== cur.handler; + if (scrut1 === true) { + prevHandlerFrame = prevHandlerFrame.nextHandler; + tmp = runtime.Unit; + continue tmp6 + } else { + break split_1$ + } + } else { + break split_1$ + } } - } else { tmp = runtime.Unit; } break; diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index a7ae413a7a..6cf383b1f5 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -299,23 +299,26 @@ Foo(1, 2, 3).args :todo if Foo(1, 2, 3) is Foo(...args) then args -//│ ╔══[ERROR] Unrecognized pattern (spread) +//│ ╔══[ERROR] Unrecognized pattern (spread). //│ ║ l.301: if Foo(1, 2, 3) is Foo(...args) then args //│ ╙── ^^^^^^^ +//│ ╔══[ERROR] Name not found: args +//│ ║ l.301: if Foo(1, 2, 3) is Foo(...args) then args +//│ ╙── ^^^^ //│ ╔══[ERROR] The constructor does not take any arguments but found one argument. //│ ║ l.301: if Foo(1, 2, 3) is Foo(...args) then args -//│ ╙── ^^^^^^^ +//│ ╙── ^^^ //│ ═══[RUNTIME ERROR] Error: match error if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c] //│ ╔══[ERROR] The constructor does not take any arguments but found three arguments. -//│ ║ l.310: if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c] +//│ ║ l.313: if Foo(1, 2, 3) is Foo(a, b, c) then [a, b, c] //│ ╙── ^^^^^^^ //│ ═══[RUNTIME ERROR] Error: match error if Foo(1, 2, 3) is Foo(arg) then arg //│ ╔══[ERROR] The constructor does not take any arguments but found one argument. -//│ ║ l.316: if Foo(1, 2, 3) is Foo(arg) then arg +//│ ║ l.319: if Foo(1, 2, 3) is Foo(arg) then arg //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -349,10 +352,10 @@ fun foo() = let bar(x: Str): Str = x + x bar("test") //│ ╔══[ERROR] Unsupported let binding shape -//│ ║ l.349: let bar(x: Str): Str = x + x +//│ ║ l.352: let bar(x: Str): Str = x + x //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[ERROR] Expected 0 arguments, got 1 -//│ ║ l.350: bar("test") +//│ ║ l.353: bar("test") //│ ╙── ^^^^^^^^ // ——— ——— ——— @@ -362,7 +365,7 @@ fun foo() = module Foo(x) //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' //│ ╟── which references the symbol introduced here -//│ ║ l.362: module Foo(x) +//│ ║ l.365: module Foo(x) //│ ╙── ^ //│ JS (unsanitized): //│ let Foo5; @@ -386,7 +389,7 @@ module Foo(x) module Foo(val x) //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' //│ ╟── which references the symbol introduced here -//│ ║ l.386: module Foo(val x) +//│ ║ l.389: module Foo(val x) //│ ╙── ^ //│ JS (unsanitized): //│ let Foo7; @@ -409,7 +412,7 @@ module Foo(val x) // TODO support syntax? data class Foo(mut x) //│ ╔══[ERROR] Expected a valid parameter, found 'mut'-modified identifier -//│ ║ l.410: data class Foo(mut x) +//│ ║ l.413: data class Foo(mut x) //│ ╙── ^ // ——— ——— ——— diff --git a/hkmc2/shared/src/test/mlscript/backlog/UCS.mls b/hkmc2/shared/src/test/mlscript/backlog/UCS.mls index 76200f6f65..380e60224f 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/UCS.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/UCS.mls @@ -31,7 +31,7 @@ if arrMatches !== null do () -//│ ╔══[ERROR] Unrecognized term split (null). +//│ ╔══[ERROR] Unrecognized term split (null) //│ ║ l.31: arrMatches !== null //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] Error: match error @@ -71,7 +71,7 @@ if //│ ║ ^ //│ ║ l.67: true do //│ ╙── ^^ -//│ ╔══[ERROR] Unrecognized term split (‹erroneous syntax›). +//│ ╔══[ERROR] Unrecognized term split (‹erroneous syntax›) //│ ║ l.66: and //│ ║ ^ //│ ║ l.67: true do @@ -98,6 +98,9 @@ if let x = 1 and true do print("ok") +//│ ╔══[ERROR] Unrecognized term split (true literal) +//│ ║ l.97: and true +//│ ╙── ^^^^ //│ ╔══[ERROR] Unexpected infix use of keyword 'do' here //│ ║ l.98: let x = 1 //│ ║ ^ @@ -105,9 +108,7 @@ if //│ ║ ^^^^^^^^^^^^^ //│ ║ l.100: print("ok") //│ ╙── ^^^^^^^^^^^^^^^ -//│ ╔══[ERROR] Unrecognized term split (true literal). -//│ ║ l.97: and true -//│ ╙── ^^^^ +//│ ═══[RUNTIME ERROR] Error: match error // ——— ——— ——— @@ -123,24 +124,24 @@ if 1 + 1 * 2 == 1 then "A" |> id is r then r //│ ╔══[PARSE ERROR] Operator cannot be used inside this operator split -//│ ║ l.122: * 2 == 4 then "X" +//│ ║ l.123: * 2 == 4 then "X" //│ ║ ^^ //│ ╟── as it has lower precedence than the splitting operator here -//│ ║ l.122: * 2 == 4 then "X" +//│ ║ l.123: * 2 == 4 then "X" //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected literal in this operator split inner position -//│ ║ l.122: * 2 == 4 then "X" +//│ ║ l.123: * 2 == 4 then "X" //│ ║ ^ //│ ╟── Note: the operator split starts here -//│ ║ l.122: * 2 == 4 then "X" +//│ ║ l.123: * 2 == 4 then "X" //│ ╙── ^ //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.122: * 2 == 4 then "X" +//│ ║ l.123: * 2 == 4 then "X" //│ ╙── ^ -//│ ═══[ERROR] Unrecognized term split (reference term). -//│ ╔══[ERROR] Unrecognized term split (integer literal). -//│ ║ l.122: * 2 == 4 then "X" +//│ ╔══[ERROR] Unrecognized term split (integer literal) +//│ ║ l.123: * 2 == 4 then "X" //│ ╙── ^ +//│ ═══[ERROR] Unrecognized term split (reference term) //│ ═══[RUNTIME ERROR] Error: match error // ——— ——— ——— @@ -149,10 +150,10 @@ class Some(val v) Some(1) is Some of [process, rest] do "hi" -//│ ╔══[ERROR] Unrecognized pattern (infix operator 'do') -//│ ║ l.151: Some of [process, rest] do "hi" +//│ ╔══[ERROR] Unrecognized pattern (infix operator 'do'). +//│ ║ l.152: Some of [process, rest] do "hi" //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ -//│ = false +//│ = true // ——— ——— ——— @@ -199,16 +200,18 @@ data class Tuple(a, b, c) let foo = case Tuple(...elements) then elements -//│ ╔══[ERROR] Unrecognized pattern (spread) -//│ ║ l.201: Tuple(...elements) then elements +//│ ╔══[ERROR] Unrecognized pattern (spread). +//│ ║ l.202: Tuple(...elements) then elements //│ ╙── ^^^^^^^^^^^ +//│ ╔══[ERROR] Name not found: elements +//│ ║ l.202: Tuple(...elements) then elements +//│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Expected three arguments, but found only one argument. -//│ ║ l.201: Tuple(...elements) then elements -//│ ╙── ^^^^^^^^^^^ +//│ ║ l.202: Tuple(...elements) then elements +//│ ╙── ^^^^^ //│ foo = fun foo foo(Tuple(1, 2, 3)) -//│ ═══[RUNTIME ERROR] Error: match error // ——— ——— ——— @@ -221,21 +224,21 @@ fun foo(x) = if x is Foo. Bar then "Bar" Foo then "Foo" //│ ╔══[PARSE ERROR] Expected end of input; found period instead -//│ ║ l.220: fun foo(x) = if x is Foo. +//│ ║ l.223: fun foo(x) = if x is Foo. //│ ╙── ^ //│ ╔══[ERROR] Unrecognized pattern split (identifier). -//│ ║ l.220: fun foo(x) = if x is Foo. +//│ ║ l.223: fun foo(x) = if x is Foo. //│ ╙── ^^^ fun foo(x) = if x is Foo .Bar then "Bar" .Foo then "Foo" //│ ╔══[ERROR] Unrecognized pattern split (operator split). -//│ ║ l.230: fun foo(x) = if x is Foo +//│ ║ l.233: fun foo(x) = if x is Foo //│ ║ ^^^ -//│ ║ l.231: .Bar then "Bar" +//│ ║ l.234: .Bar then "Bar" //│ ║ ^^^^^^^^^^^^^^^^^ -//│ ║ l.232: .Foo then "Foo" +//│ ║ l.235: .Foo then "Foo" //│ ╙── ^^^^^^^^^^^^^^^^^ // ——— ——— ——— diff --git a/hkmc2/shared/src/test/mlscript/basics/Indentation.mls b/hkmc2/shared/src/test/mlscript/basics/Indentation.mls index cb5051dd51..a7416693d4 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Indentation.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Indentation.mls @@ -177,7 +177,7 @@ P.x //│ k = Mod //│ head = InfixApp: //│ lhs = Ident of "P" -//│ kw = keyword 'with' +//│ kw = Keywrd of keyword 'with' //│ rhs = Block of Ls of //│ App: //│ lhs = Ident of "print" diff --git a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls index 8c4d69be32..26fb4b1450 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls @@ -52,16 +52,16 @@ fun sum2 = case //│ sum2 = function sum2() { //│ let lambda; //│ lambda = (undefined, function (caseScrut) { -//│ let last03, rest3, first0, x, xs, y, tmp5, tmp6, tmp7; +//│ let x, xs, y, lastElement0$3, middleElements3, element0$, tmp5, tmp6, tmp7; //│ if (runtime.Tuple.isArrayLike(caseScrut) && caseScrut.length === 0) { //│ return 0 //│ } else if (runtime.Tuple.isArrayLike(caseScrut) && caseScrut.length >= 2) { -//│ first0 = runtime.Tuple.get(caseScrut, 0); -//│ rest3 = runtime.safeCall(runtime.Tuple.lazySlice(caseScrut, 1, 1)); -//│ last03 = runtime.Tuple.get(caseScrut, -1); -//│ x = first0; -//│ xs = rest3; -//│ y = last03; +//│ element0$ = runtime.Tuple.get(caseScrut, 0); +//│ middleElements3 = runtime.safeCall(runtime.Tuple.slice(caseScrut, 1, 1)); +//│ lastElement0$3 = runtime.Tuple.get(caseScrut, -1); +//│ y = lastElement0$3; +//│ xs = middleElements3; +//│ x = element0$; //│ tmp5 = x + y; //│ tmp6 = sum2(); //│ tmp7 = tmp6(xs); diff --git a/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls b/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls index dceca00123..5025320ce4 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MultilineExpressions.mls @@ -77,7 +77,7 @@ if 1 + 2 //│ op = Ident of "*" //│ rhss = Ls of //│ IntLit of 3 -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 0 //│ JS (unsanitized): //│ let scrut, tmp17; @@ -105,7 +105,7 @@ if 1 * 2 //│ op = Ident of "+" //│ rhss = Ls of //│ IntLit of 3 -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 0 //│ ═══[RUNTIME ERROR] Error: match error @@ -158,7 +158,7 @@ if (1 + 2) //│ op = Ident of "*" //│ rhss = Ls of //│ IntLit of 3 -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 0 //│ InfixApp: //│ lhs = OpApp: @@ -166,7 +166,7 @@ if (1 + 2) //│ op = Ident of "+" //│ rhss = Ls of //│ IntLit of 4 -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 1 //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls index 6ae20227b8..4fcf753ef0 100644 --- a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls +++ b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls @@ -319,7 +319,7 @@ fun f(x) = if x //│ op = Ident of "==" //│ rhss = Ls of //│ IntLit of 0 -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = StrLit of "a" //│ InfixApp: //│ lhs = OpApp: @@ -327,7 +327,7 @@ fun f(x) = if x //│ op = Ident of ">" //│ rhss = Ls of //│ IntLit of 1 -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = StrLit of "b" f(0) @@ -358,7 +358,7 @@ f(1) fun f(x) = if x foo(A) then a bar(B) then b -//│ ╔══[ERROR] Unrecognized term split (juxtaposition). +//│ ╔══[ERROR] Unrecognized term split (juxtaposition) //│ ║ l.358: fun f(x) = if x //│ ║ ^ //│ ║ l.359: foo(A) then a diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbSyntax.mls b/hkmc2/shared/src/test/mlscript/bbml/bbSyntax.mls index e24d95ee2a..d538451237 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbSyntax.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbSyntax.mls @@ -9,11 +9,11 @@ data class Foo data class Bar(x: Int) //│ Parsed: -//│ Modified(Keywrd(keyword 'data'),TypeDef(Cls,App(Ident(Bar),Tup(List(InfixApp(Ident(x),keyword ':',Ident(Int))))),None)) +//│ Modified(Keywrd(keyword 'data'),TypeDef(Cls,App(Ident(Bar),Tup(List(InfixApp(Ident(x),Keywrd(keyword ':'),Ident(Int))))),None)) data class Bar2(x: Int, y: Int) //│ Parsed: -//│ Modified(Keywrd(keyword 'data'),TypeDef(Cls,App(Ident(Bar2),Tup(List(InfixApp(Ident(x),keyword ':',Ident(Int)), InfixApp(Ident(y),keyword ':',Ident(Int))))),None)) +//│ Modified(Keywrd(keyword 'data'),TypeDef(Cls,App(Ident(Bar2),Tup(List(InfixApp(Ident(x),Keywrd(keyword ':'),Ident(Int)), InfixApp(Ident(y),Keywrd(keyword ':'),Ident(Int))))),None)) data class Baz[A] //│ Parsed: @@ -21,7 +21,7 @@ data class Baz[A] data class BazBaz[A](f: A -> A) //│ Parsed: -//│ Modified(Keywrd(keyword 'data'),TypeDef(Cls,App(App(Ident(BazBaz),TyTup(List(Ident(A)))),Tup(List(InfixApp(Ident(f),keyword ':',InfixApp(Tup(List(Ident(A))),keyword '->',Ident(A)))))),None)) +//│ Modified(Keywrd(keyword 'data'),TypeDef(Cls,App(App(Ident(BazBaz),TyTup(List(Ident(A)))),Tup(List(InfixApp(Ident(f),Keywrd(keyword ':'),InfixApp(Tup(List(Ident(A))),Keywrd(keyword '->'),Ident(A)))))),None)) Baz[Int] //│ Parsed: @@ -61,15 +61,15 @@ fun id(x) = x x => x + 1 //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',OpApp(Ident(x),Ident(+),List(IntLit(1)))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),OpApp(Ident(x),Ident(+),List(IntLit(1)))) true and true //│ Parsed: -//│ InfixApp(BoolLit(true),keyword 'and',BoolLit(true)) +//│ InfixApp(BoolLit(true),Keywrd(keyword 'and'),BoolLit(true)) false: Bool //│ Parsed: -//│ InfixApp(BoolLit(false),keyword ':',Ident(Bool)) +//│ InfixApp(BoolLit(false),Keywrd(keyword ':'),Ident(Bool)) f of false //│ Parsed: @@ -93,62 +93,62 @@ new Bar2(114, 514) fun inc: Int -> Int //│ Parsed: -//│ TermDef(Fun,InfixApp(Ident(inc),keyword ':',InfixApp(Tup(List(Ident(Int))),keyword '->',Ident(Int))),None) +//│ TermDef(Fun,InfixApp(Ident(inc),Keywrd(keyword ':'),InfixApp(Tup(List(Ident(Int))),Keywrd(keyword '->'),Ident(Int))),None) [A] -> A -> A //│ Parsed: -//│ InfixApp(TyTup(List(Ident(A))),keyword '->',InfixApp(Tup(List(Ident(A))),keyword '->',Ident(A))) +//│ InfixApp(TyTup(List(Ident(A))),Keywrd(keyword '->'),InfixApp(Tup(List(Ident(A))),Keywrd(keyword '->'),Ident(A))) fun id: [A] -> A -> A //│ Parsed: -//│ TermDef(Fun,InfixApp(Ident(id),keyword ':',InfixApp(TyTup(List(Ident(A))),keyword '->',InfixApp(Tup(List(Ident(A))),keyword '->',Ident(A)))),None) +//│ TermDef(Fun,InfixApp(Ident(id),Keywrd(keyword ':'),InfixApp(TyTup(List(Ident(A))),Keywrd(keyword '->'),InfixApp(Tup(List(Ident(A))),Keywrd(keyword '->'),Ident(A)))),None) [A] => (x: A) => x //│ Parsed: -//│ InfixApp(TyTup(List(Ident(A))),keyword '=>',InfixApp(Tup(List(InfixApp(Ident(x),keyword ':',Ident(A)))),keyword '=>',Ident(x))) +//│ InfixApp(TyTup(List(Ident(A))),Keywrd(keyword '=>'),InfixApp(Tup(List(InfixApp(Ident(x),Keywrd(keyword ':'),Ident(A)))),Keywrd(keyword '=>'),Ident(x))) [A, B] -> A -> B //│ Parsed: -//│ InfixApp(TyTup(List(Ident(A), Ident(B))),keyword '->',InfixApp(Tup(List(Ident(A))),keyword '->',Ident(B))) +//│ InfixApp(TyTup(List(Ident(A), Ident(B))),Keywrd(keyword '->'),InfixApp(Tup(List(Ident(A))),Keywrd(keyword '->'),Ident(B))) [A, B, C] -> (A, B) -> C //│ Parsed: -//│ InfixApp(TyTup(List(Ident(A), Ident(B), Ident(C))),keyword '->',InfixApp(Tup(List(Ident(A), Ident(B))),keyword '->',Ident(C))) +//│ InfixApp(TyTup(List(Ident(A), Ident(B), Ident(C))),Keywrd(keyword '->'),InfixApp(Tup(List(Ident(A), Ident(B))),Keywrd(keyword '->'),Ident(C))) ([A] -> A -> A) -> Int //│ Parsed: -//│ InfixApp(Tup(List(InfixApp(TyTup(List(Ident(A))),keyword '->',InfixApp(Tup(List(Ident(A))),keyword '->',Ident(A))))),keyword '->',Ident(Int)) +//│ InfixApp(Tup(List(InfixApp(TyTup(List(Ident(A))),Keywrd(keyword '->'),InfixApp(Tup(List(Ident(A))),Keywrd(keyword '->'),Ident(A))))),Keywrd(keyword '->'),Ident(Int)) x => if x == 0 then 1 else x //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(OpApp(Ident(x),Ident(==),List(IntLit(0))),keyword 'then',IntLit(1)), PrefixApp(Keywrd(keyword 'else'),Ident(x)))))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(OpApp(Ident(x),Ident(==),List(IntLit(0))),Keywrd(keyword 'then'),IntLit(1)), PrefixApp(Keywrd(keyword 'else'),Ident(x)))))) if 1 < 2 then 1 else 0 //│ Parsed: -//│ IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(OpApp(IntLit(1),Ident(<),List(IntLit(2))),keyword 'then',IntLit(1)), PrefixApp(Keywrd(keyword 'else'),IntLit(0))))) +//│ IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(OpApp(IntLit(1),Ident(<),List(IntLit(2))),Keywrd(keyword 'then'),IntLit(1)), PrefixApp(Keywrd(keyword 'else'),IntLit(0))))) if false then 0 else 42 //│ Parsed: -//│ IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(BoolLit(false),keyword 'then',IntLit(0)), PrefixApp(Keywrd(keyword 'else'),IntLit(42))))) +//│ IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(BoolLit(false),Keywrd(keyword 'then'),IntLit(0)), PrefixApp(Keywrd(keyword 'else'),IntLit(42))))) if 24 then false else true //│ Parsed: -//│ IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(IntLit(24),keyword 'then',BoolLit(false)), PrefixApp(Keywrd(keyword 'else'),BoolLit(true))))) +//│ IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(IntLit(24),Keywrd(keyword 'then'),BoolLit(false)), PrefixApp(Keywrd(keyword 'else'),BoolLit(true))))) if x then true else false //│ Parsed: -//│ IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(Ident(x),keyword 'then',BoolLit(true)), PrefixApp(Keywrd(keyword 'else'),BoolLit(false))))) +//│ IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(Ident(x),Keywrd(keyword 'then'),BoolLit(true)), PrefixApp(Keywrd(keyword 'else'),BoolLit(false))))) if 1 is Int then 1 else 0 //│ Parsed: -//│ IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(InfixApp(IntLit(1),keyword 'is',Ident(Int)),keyword 'then',IntLit(1)), PrefixApp(Keywrd(keyword 'else'),IntLit(0))))) +//│ IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(InfixApp(IntLit(1),Keywrd(keyword 'is'),Ident(Int)),Keywrd(keyword 'then'),IntLit(1)), PrefixApp(Keywrd(keyword 'else'),IntLit(0))))) fun fact = case 0 then 1 n then n * fact(n - 1) //│ Parsed: -//│ TermDef(Fun,Ident(fact),Some(Case(Keywrd(keyword 'case'),Block(List(InfixApp(IntLit(0),keyword 'then',IntLit(1)), InfixApp(Ident(n),keyword 'then',OpApp(Ident(n),Ident(*),List(App(Ident(fact),Tup(List(OpApp(Ident(n),Ident(-),List(IntLit(1)))))))))))))) +//│ TermDef(Fun,Ident(fact),Some(Case(Keywrd(keyword 'case'),Block(List(InfixApp(IntLit(0),Keywrd(keyword 'then'),IntLit(1)), InfixApp(Ident(n),Keywrd(keyword 'then'),OpApp(Ident(n),Ident(*),List(App(Ident(fact),Tup(List(OpApp(Ident(n),Ident(-),List(IntLit(1)))))))))))))) `42 @@ -157,15 +157,15 @@ fun fact = case x `=> x //│ Parsed: -//│ Quoted(InfixApp(Tup(List(Ident(x))),keyword '=>',Unquoted(Ident(x)))) +//│ Quoted(InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),Unquoted(Ident(x)))) (x, y) `=> x `+ y //│ Parsed: -//│ Quoted(InfixApp(Tup(List(Ident(x), Ident(y))),keyword '=>',Unquoted(Quoted(App(Ident(+),Tup(List(Unquoted(Ident(x)), Unquoted(Ident(y))))))))) +//│ Quoted(InfixApp(Tup(List(Ident(x), Ident(y))),Keywrd(keyword '=>'),Unquoted(Quoted(App(Ident(+),Tup(List(Unquoted(Ident(x)), Unquoted(Ident(y))))))))) (x, y, z) `=> x `+ y `+ z //│ Parsed: -//│ Quoted(InfixApp(Tup(List(Ident(x), Ident(y), Ident(z))),keyword '=>',Unquoted(Quoted(App(Ident(+),Tup(List(Unquoted(Quoted(App(Ident(+),Tup(List(Unquoted(Ident(x)), Unquoted(Ident(y))))))), Unquoted(Ident(z))))))))) +//│ Quoted(InfixApp(Tup(List(Ident(x), Ident(y), Ident(z))),Keywrd(keyword '=>'),Unquoted(Quoted(App(Ident(+),Tup(List(Unquoted(Quoted(App(Ident(+),Tup(List(Unquoted(Ident(x)), Unquoted(Ident(y))))))), Unquoted(Ident(z))))))))) `1 `+ `1 //│ Parsed: @@ -187,12 +187,12 @@ g`(`1, `2) `if x `== `0.0 then `1.0 else x //│ Parsed: -//│ Quoted(IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(Unquoted(Quoted(App(Ident(==),Tup(List(Unquoted(Ident(x)), Unquoted(Quoted(DecLit(0.0)))))))),keyword 'then',Unquoted(Quoted(DecLit(1.0)))), PrefixApp(Keywrd(keyword 'else'),Unquoted(Ident(x))))))) +//│ Quoted(IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(Unquoted(Quoted(App(Ident(==),Tup(List(Unquoted(Ident(x)), Unquoted(Quoted(DecLit(0.0)))))))),Keywrd(keyword 'then'),Unquoted(Quoted(DecLit(1.0)))), PrefixApp(Keywrd(keyword 'else'),Unquoted(Ident(x))))))) x `=> if 0 == 0 then x else `0 //│ Parsed: -//│ Quoted(InfixApp(Tup(List(Ident(x))),keyword '=>',Unquoted(IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(OpApp(IntLit(0),Ident(==),List(IntLit(0))),keyword 'then',Ident(x)), PrefixApp(Keywrd(keyword 'else'),Quoted(IntLit(0))))))))) +//│ Quoted(InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),Unquoted(IfLike(Keywrd(keyword 'if'),Block(List(InfixApp(OpApp(IntLit(0),Ident(==),List(IntLit(0))),Keywrd(keyword 'then'),Ident(x)), PrefixApp(Keywrd(keyword 'else'),Quoted(IntLit(0))))))))) region x in 42 //│ Parsed: @@ -216,11 +216,11 @@ region x in Int ->{Any} Int //│ Parsed: -//│ InfixApp(Tup(List(Ident(Int))),keyword '->',Effectful(Ident(Any),Ident(Int))) +//│ InfixApp(Tup(List(Ident(Int))),Keywrd(keyword '->'),Effectful(Ident(Any),Ident(Int))) [A] -> Str ->{A} Str //│ Parsed: -//│ InfixApp(TyTup(List(Ident(A))),keyword '->',InfixApp(Tup(List(Ident(Str))),keyword '->',Effectful(Ident(A),Ident(Str)))) +//│ InfixApp(TyTup(List(Ident(A))),Keywrd(keyword '->'),InfixApp(Tup(List(Ident(Str))),Keywrd(keyword '->'),Effectful(Ident(A),Ident(Str)))) A | B //│ Parsed: @@ -228,20 +228,20 @@ A | B [A extends Int] -> A -> A //│ Parsed: -//│ InfixApp(TyTup(List(InfixApp(Ident(A),keyword 'extends',Ident(Int)))),keyword '->',InfixApp(Tup(List(Ident(A))),keyword '->',Ident(A))) +//│ InfixApp(TyTup(List(InfixApp(Ident(A),Keywrd(keyword 'extends'),Ident(Int)))),Keywrd(keyword '->'),InfixApp(Tup(List(Ident(A))),Keywrd(keyword '->'),Ident(A))) [A restricts Int] -> A -> A //│ Parsed: -//│ InfixApp(TyTup(List(InfixApp(Ident(A),keyword 'restricts',Ident(Int)))),keyword '->',InfixApp(Tup(List(Ident(A))),keyword '->',Ident(A))) +//│ InfixApp(TyTup(List(InfixApp(Ident(A),Keywrd(keyword 'restricts'),Ident(Int)))),Keywrd(keyword '->'),InfixApp(Tup(List(Ident(A))),Keywrd(keyword '->'),Ident(A))) [A extends Int restricts Int] -> A -> A //│ Parsed: -//│ InfixApp(TyTup(List(InfixApp(InfixApp(Ident(A),keyword 'extends',Ident(Int)),keyword 'restricts',Ident(Int)))),keyword '->',InfixApp(Tup(List(Ident(A))),keyword '->',Ident(A))) +//│ InfixApp(TyTup(List(InfixApp(InfixApp(Ident(A),Keywrd(keyword 'extends'),Ident(Int)),Keywrd(keyword 'restricts'),Ident(Int)))),Keywrd(keyword '->'),InfixApp(Tup(List(Ident(A))),Keywrd(keyword '->'),Ident(A))) [A extends Int, B restricts Int] -> A -> B //│ Parsed: -//│ InfixApp(TyTup(List(InfixApp(Ident(A),keyword 'extends',Ident(Int)), InfixApp(Ident(B),keyword 'restricts',Ident(Int)))),keyword '->',InfixApp(Tup(List(Ident(A))),keyword '->',Ident(B))) +//│ InfixApp(TyTup(List(InfixApp(Ident(A),Keywrd(keyword 'extends'),Ident(Int)), InfixApp(Ident(B),Keywrd(keyword 'restricts'),Ident(Int)))),Keywrd(keyword '->'),InfixApp(Tup(List(Ident(A))),Keywrd(keyword '->'),Ident(B))) [A extends Int restricts Int, B extends Int restricts Int] -> A -> B //│ Parsed: -//│ InfixApp(TyTup(List(InfixApp(InfixApp(Ident(A),keyword 'extends',Ident(Int)),keyword 'restricts',Ident(Int)), InfixApp(InfixApp(Ident(B),keyword 'extends',Ident(Int)),keyword 'restricts',Ident(Int)))),keyword '->',InfixApp(Tup(List(Ident(A))),keyword '->',Ident(B))) +//│ InfixApp(TyTup(List(InfixApp(InfixApp(Ident(A),Keywrd(keyword 'extends'),Ident(Int)),Keywrd(keyword 'restricts'),Ident(Int)), InfixApp(InfixApp(Ident(B),Keywrd(keyword 'extends'),Ident(Int)),Keywrd(keyword 'restricts'),Ident(Int)))),Keywrd(keyword '->'),InfixApp(Tup(List(Ident(A))),Keywrd(keyword '->'),Ident(B))) diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls b/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls index 3b0dfc9718..1ad2794627 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadWhile.mls @@ -6,7 +6,7 @@ while {} do() //│ ╔══[ERROR] Unexpected infix use of keyword 'do' here //│ ║ l.6: while {} do() -//│ ╙── ^^ +//│ ╙── ^^^^ //│ JS (unsanitized): //│ /* error */ diff --git a/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls b/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls index d8a18fff1b..22009ea6bb 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CaseOfCase.mls @@ -17,10 +17,10 @@ fun test(x) = //│ JS (unsanitized): //│ let test; //│ test = function test(x) { -//│ let param0, v, scrut, param01, v1, tmp, tmp1; +//│ let v, scrut, v1, argument0$, argument0$1, tmp, tmp1; //│ if (x instanceof Some1.class) { -//│ param0 = x.value; -//│ v = param0; +//│ argument0$1 = x.value; +//│ v = argument0$1; //│ tmp = v + 1; //│ tmp1 = Some1(tmp); //│ } else if (x instanceof None1.class) { @@ -30,8 +30,8 @@ fun test(x) = //│ } //│ scrut = tmp1; //│ if (scrut instanceof Some1.class) { -//│ param01 = scrut.value; -//│ v1 = param01; +//│ argument0$ = scrut.value; +//│ v1 = argument0$; //│ return Predef.print(v1) //│ } else if (scrut instanceof None1.class) { //│ return Predef.print("none") diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls index 0fe73cf074..55c80c83c3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls @@ -9,11 +9,11 @@ object None :sjs if Some(0) is Some(x) then x //│ JS (unsanitized): -//│ let scrut, param0, x; +//│ let scrut, x, argument0$; //│ scrut = Some1(0); //│ if (scrut instanceof Some1.class) { -//│ param0 = scrut.value; -//│ x = param0; +//│ argument0$ = scrut.value; +//│ x = argument0$; //│ x //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ = 0 @@ -26,10 +26,10 @@ let s = Some(0) if s is Some(x) then x //│ JS (unsanitized): -//│ let param01, x1; +//│ let x1, argument0$1; //│ if (s instanceof Some1.class) { -//│ param01 = s.value; -//│ x1 = param01; +//│ argument0$1 = s.value; +//│ x1 = argument0$1; //│ x1 //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ = 0 @@ -55,10 +55,10 @@ x => if x is Some(x) then x //│ JS (unsanitized): //│ let lambda; //│ lambda = (undefined, function (x3) { -//│ let param04, x4; +//│ let x4, argument0$4; //│ if (x3 instanceof Some1.class) { -//│ param04 = x3.value; -//│ x4 = param04; +//│ argument0$4 = x3.value; +//│ x4 = argument0$4; //│ return x4 //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ }); @@ -120,17 +120,27 @@ fun f(x) = if x is //│ JS (unsanitized): //│ let f4; //│ f4 = function f(x3) { -//│ let param04, x4, scrut2; -//│ if (x3 instanceof Some1.class) { -//│ param04 = x3.value; -//│ x4 = param04; -//│ scrut2 = x4 > 0; -//│ if (scrut2 === true) { -//│ return 42 -//│ } else { -//│ return Predef.print("oops") +//│ let x4, scrut1, argument0$4, tmp5; +//│ split_root$1: { +//│ split_1$: { +//│ if (x3 instanceof Some1.class) { +//│ argument0$4 = x3.value; +//│ x4 = argument0$4; +//│ scrut1 = x4 > 0; +//│ if (scrut1 === true) { +//│ tmp5 = 42; +//│ break split_root$1 +//│ } else { +//│ break split_1$ +//│ } +//│ } else if (x3 instanceof None1.class) { +//│ tmp5 = "ok"; +//│ break split_root$1 +//│ } else { break split_1$ } //│ } -//│ } else if (x3 instanceof None1.class) { return "ok" } else { return Predef.print("oops") } +//│ tmp5 = Predef.print("oops"); +//│ } +//│ return tmp5 //│ }; f(Some(0)) @@ -156,16 +166,16 @@ fun f(x) = if x is //│ JS (unsanitized): //│ let f5; //│ f5 = function f(x3) { -//│ let param04, param1, a, b, u; +//│ let u, a, b, argument0$4, argument1$; //│ if (x3 instanceof Some1.class) { -//│ param04 = x3.value; -//│ u = param04; +//│ argument0$4 = x3.value; +//│ u = argument0$4; //│ return u //│ } else if (x3 instanceof Pair1.class) { -//│ param04 = x3.fst; -//│ param1 = x3.snd; -//│ a = param04; -//│ b = param1; +//│ argument0$4 = x3.fst; +//│ argument1$ = x3.snd; +//│ b = argument1$; +//│ a = argument0$4; //│ return a + b //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ }; @@ -186,18 +196,25 @@ fun f(x) = print of if x is //│ JS (unsanitized): //│ let f6; //│ f6 = function f(x3) { -//│ let param04, tmp8; -//│ if (x3 instanceof Some1.class) { -//│ param04 = x3.value; -//│ if (param04 === 0) { -//│ tmp8 = "0"; -//│ } else { -//│ tmp8 = "oops"; +//│ let argument0$4, tmp9; +//│ split_root$1: { +//│ split_1$: { +//│ if (x3 instanceof Some1.class) { +//│ argument0$4 = x3.value; +//│ if (argument0$4 === 0) { +//│ tmp9 = "0"; +//│ break split_root$1 +//│ } else { +//│ break split_1$ +//│ } +//│ } else if (x3 instanceof None1.class) { +//│ tmp9 = "ok"; +//│ break split_root$1 +//│ } else { break split_1$ } //│ } -//│ } else if (x3 instanceof None1.class) { -//│ tmp8 = "ok"; -//│ } else { tmp8 = "oops"; } -//│ return Predef.print(tmp8) +//│ tmp9 = "oops"; +//│ } +//│ return Predef.print(tmp9) //│ }; f(Some(0)) diff --git a/hkmc2/shared/src/test/mlscript/codegen/Do.mls b/hkmc2/shared/src/test/mlscript/codegen/Do.mls index 5df2c9a790..0cefa9a886 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Do.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Do.mls @@ -36,13 +36,21 @@ val f = case let res = "other" do print(res) _ then res -//│ Desugared: +//│ Split with nested patterns: //│ > if //│ > caseScrut is 0 then "null" -//│ > let $doTemp = member:console.log("non-null") +//│ > let $unused = member:console.log("non-null") //│ > caseScrut is 1 then "unit" //│ > let res = "other" -//│ > let $doTemp = (member:Predef.)print‹member:print›(res) +//│ > let $unused = (member:Predef.)print‹member:print›(res) +//│ > caseScrut is _ then res +//│ Expanded split with flattened patterns: +//│ > if +//│ > caseScrut is 0 then "null" +//│ > let $unused = member:console.log("non-null") +//│ > caseScrut is 1 then "unit" +//│ > let res = "other" +//│ > let $unused = (member:Predef.)print‹member:print›(res) //│ > else res //│ f = fun diff --git a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls index 702afdc8d7..5699747d40 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls @@ -70,21 +70,16 @@ case //│ restParam = N //│ body = IfLike: //│ kw = keyword 'if' -//│ desugared = Cons: -//│ head = Branch: +//│ split = Cons: +//│ branch = Match: //│ scrutinee = Ref{sym=caseScrut} of caseScrut -//│ pattern = ClassLike: -//│ constructor = Ref{sym=member:Foo} of member:Foo +//│ pattern = Constructor: +//│ target = Ref{sym=member:Foo} of member:Foo //│ arguments = S of Ls of -//│ Term: -//│ scrutinee = $param0 -//│ tree = Ident of "a" -//│ mode = Default -//│ refined = false -//│ continuation = Let: -//│ sym = a -//│ term = Ref{sym=$param0} of $param0 -//│ tail = Else of Ref{sym=a} of a +//│ Alias: +//│ pattern = Wildcard +//│ id = Ident of "a" +//│ consequent = Else of Ref{sym=a} of a //│ tail = End //│ Lowered: //│ Program: @@ -113,13 +108,13 @@ case //│ qual = Ref of member:Foo //│ name = Ident of "class" //│ _2 = Assign: -//│ lhs = $param0 +//│ lhs = $argument0$ //│ rhs = Select{class:Foo.x}: //│ qual = Ref of caseScrut //│ name = Ident of "x" //│ rest = Assign: \ //│ lhs = a -//│ rhs = Ref of $param0 +//│ rhs = Ref of $argument0$ //│ rest = Return: \ //│ res = Ref of a //│ implct = false diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImpreativeConditionals.mls b/hkmc2/shared/src/test/mlscript/codegen/ImperativeConditionals.mls similarity index 76% rename from hkmc2/shared/src/test/mlscript/codegen/ImpreativeConditionals.mls rename to hkmc2/shared/src/test/mlscript/codegen/ImperativeConditionals.mls index 8b190c16c7..9106d566c2 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImpreativeConditionals.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImperativeConditionals.mls @@ -14,7 +14,7 @@ fun f(x) = //│ ╔══[PARSE ERROR] Unexpected 'return' keyword here //│ ║ l.12: if x < 0 return 0 //│ ╙── ^^^^^^ -//│ ╔══[ERROR] Unrecognized term split (integer literal). +//│ ╔══[ERROR] Unrecognized term split (integer literal) //│ ║ l.12: if x < 0 return 0 //│ ╙── ^ @@ -33,5 +33,11 @@ fun hasZeroElement(xs) = //│ ╔══[ERROR] Unrecognized pattern split (identifier). //│ ║ l.24: Cons(0, tl) return true //│ ╙── ^^ +//│ ╔══[WARNING] This else clause makes the following branches unreachable. +//│ ║ l.24: Cons(0, tl) return true +//│ ║ ^ +//│ ╟── This branch is unreachable. +//│ ║ l.24: Cons(0, tl) return true +//│ ╙── ^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls b/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls index bb1dc3e77b..bc312d91f9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls @@ -58,19 +58,34 @@ if a is B then 2 C then 3 //│ JS (unsanitized): -//│ if (a instanceof A1.class) { -//│ if (b instanceof A1.class) { -//│ 1 -//│ } else if (b instanceof B1.class) { -//│ 2 -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) +//│ let tmp; +//│ split_root$: { +//│ split_default$: { +//│ split_1$: { +//│ if (a instanceof A1.class) { +//│ if (b instanceof A1.class) { +//│ tmp = 1; +//│ break split_root$ +//│ } else if (b instanceof B1.class) { +//│ break split_1$ +//│ } else { +//│ break split_default$ +//│ } +//│ } else if (a instanceof B1.class) { +//│ break split_1$ +//│ } else if (a instanceof C1.class) { +//│ tmp = 3; +//│ break split_root$ +//│ } else { +//│ break split_default$ +//│ } +//│ } +//│ tmp = 2; +//│ break split_root$; //│ } -//│ } else if (a instanceof B1.class) { -//│ 2 -//│ } else if (a instanceof C1.class) { -//│ 3 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ } +//│ tmp //│ = 2 // The match arms here are still merged because @@ -92,31 +107,31 @@ let x = if a is print("done") print(x) //│ JS (unsanitized): -//│ let x, tmp, tmp1, tmp2, tmp3; -//│ tmp = 3; +//│ let x, tmp1, tmp2, tmp3, tmp4; +//│ tmp1 = 3; //│ if (a instanceof A1.class) { -//│ tmp1 = 1; +//│ tmp2 = 1; //│ } else if (a instanceof B1.class) { -//│ tmp1 = 2; +//│ tmp2 = 2; //│ } else if (a instanceof C1.class) { -//│ tmp1 = 3; +//│ tmp2 = 3; //│ } else if (a instanceof D1.class) { //│ if (a instanceof A1.class) { -//│ tmp2 = 1 + tmp; +//│ tmp3 = 1 + tmp1; //│ } else if (a instanceof B1.class) { -//│ tmp2 = 2 + tmp; +//│ tmp3 = 2 + tmp1; //│ } else { //│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ } -//│ tmp3 = tmp2; -//│ tmp1 = Predef.print("done"); +//│ tmp4 = tmp3; +//│ tmp2 = Predef.print("done"); //│ } else if (a instanceof E1.class) { -//│ tmp3 = 5; -//│ tmp1 = Predef.print("done"); +//│ tmp4 = 5; +//│ tmp2 = Predef.print("done"); //│ } else { //│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ } -//│ x = tmp1; +//│ x = tmp2; //│ Predef.print(x) //│ > 1 //│ x = 1 @@ -130,12 +145,12 @@ if a is let tmp = 2 B then 2 + tmp //│ JS (unsanitized): -//│ let tmp4; -//│ tmp4 = 2; +//│ let tmp5; +//│ tmp5 = 2; //│ if (a instanceof A1.class) { //│ 1 //│ } else if (a instanceof B1.class) { -//│ 2 + tmp4 +//│ 2 + tmp5 //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ = 1 @@ -152,13 +167,13 @@ if a is let tmp = printAndId(3) B then 2 + tmp //│ JS (unsanitized): -//│ let tmp5; +//│ let tmp6; //│ if (a instanceof A1.class) { //│ 1 //│ } else { -//│ tmp5 = printAndId(3); +//│ tmp6 = printAndId(3); //│ if (a instanceof B1.class) { -//│ 2 + tmp5 +//│ 2 + tmp6 //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ } //│ = 1 @@ -175,16 +190,16 @@ if a is C then 3 print(x) //│ JS (unsanitized): -//│ let x1, tmp6; +//│ let x1, tmp7; //│ if (a instanceof A1.class) { //│ 1 //│ } else if (a instanceof B1.class) { -//│ tmp6 = 2; -//│ x1 = tmp6; +//│ tmp7 = 2; +//│ x1 = tmp7; //│ Predef.print(x1) //│ } else if (a instanceof C1.class) { -//│ tmp6 = 3; -//│ x1 = tmp6; +//│ tmp7 = 3; +//│ x1 = tmp7; //│ Predef.print(x1) //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ = 1 @@ -204,23 +219,23 @@ if a is print(x + 1) print(x + 2) //│ JS (unsanitized): -//│ let x2, tmp7, tmp8, tmp9, tmp10, tmp11; +//│ let x2, tmp8, tmp9, tmp10, tmp11, tmp12; //│ if (a instanceof B1.class) { //│ 1 //│ } else { //│ if (a instanceof A1.class) { -//│ tmp7 = 2; +//│ tmp8 = 2; //│ } else if (a instanceof C1.class) { -//│ tmp7 = 3; +//│ tmp8 = 3; //│ } else { //│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ } -//│ x2 = tmp7; -//│ tmp8 = Predef.print(x2); -//│ tmp9 = x2 + 1; -//│ tmp10 = Predef.print(tmp9); -//│ tmp11 = x2 + 2; -//│ Predef.print(tmp11) +//│ x2 = tmp8; +//│ tmp9 = Predef.print(x2); +//│ tmp10 = x2 + 1; +//│ tmp11 = Predef.print(tmp10); +//│ tmp12 = x2 + 2; +//│ Predef.print(tmp12) //│ } //│ > 2 //│ > 3 diff --git a/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls b/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls index 3e710ba474..1cbc9194fb 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls @@ -33,9 +33,9 @@ val isDefined = case //│ JS (unsanitized): //│ let isDefined1, lambda; //│ lambda = (undefined, function (caseScrut) { -//│ let param0; +//│ let argument0$; //│ if (caseScrut instanceof Some1.class) { -//│ param0 = caseScrut.value; +//│ argument0$ = caseScrut.value; //│ return true //│ } else if (caseScrut instanceof None1.class) { //│ return false diff --git a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls index 2512e1c101..df9c2cd7ce 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls @@ -245,10 +245,28 @@ _ - _ of 1, 2 // `_` here is interpreted as a UCS default case... :sjs +:w let f = if _ then 1 else 0 +//│ ╔══[WARNING] This else clause makes the following branches unreachable. +//│ ║ l.249: let f = if _ then 1 else 0 +//│ ║ ^^^^^^ +//│ ╟── This branch is unreachable. +//│ ║ l.249: let f = if _ then 1 else 0 +//│ ╙── ^^^^^^ //│ JS (unsanitized): //│ let f15, tmp21; tmp21 = 1; f15 = tmp21; //│ f = 1 - +:sjs +fun f(x) = + if x == + 0 then 1 + _ then 2 +//│ JS (unsanitized): +//│ let f16; +//│ f16 = function f(x3) { +//│ let scrut; +//│ scrut = x3 == 0; +//│ if (scrut === true) { return 1 } else { return 2 } +//│ }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index cfec5a3a72..3c18631157 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -18,14 +18,14 @@ class Foo Foo is Foo //│ JS (unsanitized): -//│ let scrut; scrut = Foo1; if (scrut instanceof Foo1) { true } else { false } +//│ if (Foo1 instanceof Foo1) { true } else { false } //│ = false (new Foo) is Foo //│ JS (unsanitized): -//│ let scrut1; -//│ scrut1 = globalThis.Object.freeze(new Foo1()); -//│ if (scrut1 instanceof Foo1) { true } else { false } +//│ let scrut; +//│ scrut = globalThis.Object.freeze(new Foo1()); +//│ if (scrut instanceof Foo1) { true } else { false } //│ = true new Foo diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index 8ed5867acb..9ec87b064d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -197,13 +197,13 @@ fun f(ls) = //│ JS (unsanitized): //│ let f; //│ f = function f(ls) { -//│ let param0, param1, h, tl, tmp28; +//│ let tl, h, argument0$, argument1$, tmp28; //│ tmp29: while (true) { //│ if (ls instanceof Cons1.class) { -//│ param0 = ls.hd; -//│ param1 = ls.tl; -//│ h = param0; -//│ tl = param1; +//│ argument0$ = ls.hd; +//│ argument1$ = ls.tl; +//│ tl = argument1$; +//│ h = argument0$; //│ ls = tl; //│ tmp28 = Predef.print(h); //│ continue tmp29 @@ -250,10 +250,14 @@ while x is {} do() //│ JS (unsanitized): //│ let tmp37; //│ tmp38: while (true) { -//│ if (x3 instanceof Object) { +//│ split_root$: { +//│ split_1$: { +//│ if (x3 instanceof Object) { +//│ break split_1$ +//│ } else { break split_1$ } +//│ } //│ tmp37 = runtime.Unit; -//│ continue tmp38 -//│ } else { tmp37 = runtime.Unit; } +//│ } //│ break; //│ } //│ tmp37 @@ -270,10 +274,10 @@ while print("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.270: then 0(0) +//│ ║ l.274: then 0(0) //│ ╙── ^^^^ -//│ ╔══[ERROR] Unrecognized term split (false literal). -//│ ║ l.269: while print("Hello World"); false +//│ ╔══[ERROR] Unrecognized term split (false literal) +//│ ║ l.273: while print("Hello World"); false //│ ╙── ^^^^^ //│ > Hello World //│ ═══[RUNTIME ERROR] Error: match error @@ -283,12 +287,12 @@ while { print("Hello World"), false } then 0(0) else 1 //│ ╔══[ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.282: while { print("Hello World"), false } +//│ ║ l.286: while { print("Hello World"), false } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.283: then 0(0) +//│ ║ l.287: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.284: else 1 +//│ ║ l.288: else 1 //│ ╙── ^^^^ :fixme @@ -298,14 +302,14 @@ while then 0(0) else 1 //│ ╔══[ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.296: print("Hello World") +//│ ║ l.300: print("Hello World") //│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.297: false +//│ ║ l.301: false //│ ║ ^^^^^^^^^ -//│ ║ l.298: then 0(0) +//│ ║ l.302: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.299: else 1 +//│ ║ l.303: else 1 //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/llir/ControlFlow.mls b/hkmc2/shared/src/test/mlscript/llir/ControlFlow.mls index 381a4d5224..cda9ed4783 100644 --- a/hkmc2/shared/src/test/mlscript/llir/ControlFlow.mls +++ b/hkmc2/shared/src/test/mlscript/llir/ControlFlow.mls @@ -259,6 +259,7 @@ f(A(1)) :sllir :intl +:fixme // Started failing since split deduplication have been introduced. data class A(x) data class B(y) fun f(a) = @@ -270,45 +271,4 @@ fun f(a) = t f(A(1)) //│ = 1 -//│ LLIR: -//│ class A(x) -//│ class B(y) -//│ def f(a) = -//│ case a of -//│ Class(class:A) => -//│ let x = a. in -//│ case a of -//│ Class(class:A) => -//│ let x1 = a. in -//│ case x1 of -//│ Lit(IntLit(1)) => -//│ let x2 = 1 in -//│ jump j(x2) -//│ _ => -//│ panic "match error" -//│ Class(class:B) => -//│ let x3 = a. in -//│ let x4 = 2 in -//│ jump j1(x4) -//│ _ => -//│ panic "match error" -//│ Class(class:B) => -//│ let x5 = a. in -//│ let x6 = 3 in -//│ jump j2(x6) -//│ _ => -//│ panic "match error" -//│ def j(tmp) = -//│ jump j1(tmp) -//│ def j1(tmp) = -//│ jump j2(tmp) -//│ def j2(tmp1) = -//│ tmp1 -//│ def entry() = -//│ let x7 = A(1) in -//│ let* (x8) = f(x7) in -//│ x8 -//│ entry = entry -//│ -//│ Interpreted: -//│ 1 +//│ /!!!\ Uncaught error: scala.NotImplementedError: Label not supported (of class String) diff --git a/hkmc2/shared/src/test/mlscript/llir/nofib/scc.mls b/hkmc2/shared/src/test/mlscript/llir/nofib/scc.mls index b1c0deab22..afe735b35c 100644 --- a/hkmc2/shared/src/test/mlscript/llir/nofib/scc.mls +++ b/hkmc2/shared/src/test/mlscript/llir/nofib/scc.mls @@ -3,8 +3,7 @@ :import NofibPrelude.mls //│ Imported 104 member(s) -//│ Error: hkmc2.ErrorReport: Unsupported kind of Call Call(Select(Select(Ref($runtime),Ident(Tuple)),Ident(get)),List(Arg(None,Ref(t1)), Arg(None,Lit(IntLit(0))))) -//│ Stopped due to an error during the Llir generation +//│ /!!!\ Uncaught error: scala.NotImplementedError: Label not supported (of class String) fun dfs(r, vsns, xs) = if vsns is [vs, ns] and @@ -13,8 +12,7 @@ fun dfs(r, vsns, xs) = if vsns is [vs, ns] and x :: xs and inList(x, vs) then dfs(r, [vs, ns], xs) dfs(r, [x :: vs, Nil], r(x)) is [vs', ns'] then dfs(r, [vs', (x :: ns') +: ns], xs) -//│ ═══[COMPILATION ERROR] Unsupported kind of Call Call(Select(Select(Ref($runtime),Ident(Tuple)),Ident(get)),List(Arg(None,Ref(vsns)), Arg(None,Lit(IntLit(0))))) -//│ Stopped due to an error during the Llir generation +//│ /!!!\ Uncaught error: scala.NotImplementedError: Label not supported (of class String) fun stronglyConnComp(es, vs) = fun swap(a) = if a is [f, s] then [s, f] diff --git a/hkmc2/shared/src/test/mlscript/nofib/scc.mls b/hkmc2/shared/src/test/mlscript/nofib/scc.mls index ce3fc5f146..384426e0a3 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/scc.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/scc.mls @@ -12,8 +12,7 @@ fun dfs(r, vsns, xs) = if vsns is [vs, ns] and x :: xs and inList(x, vs) then dfs(r, [vs, ns], xs) dfs(r, [x :: vs, Nil], r(x)) is [vs', ns'] then dfs(r, [vs', (x :: ns') +: ns], xs) -//│ ═══[COMPILATION ERROR] Unsupported kind of Call Call(Select(Select(Ref($runtime),Ident(Tuple)),Ident(get)),List(Arg(None,Ref(vsns)), Arg(None,Lit(IntLit(0))))) -//│ Stopped due to an error during the Llir generation +//│ /!!!\ Uncaught error: scala.NotImplementedError: Label not supported (of class String) fun stronglyConnComp(es, vs) = fun swap(a) = if a is [f, s] then [s, f] diff --git a/hkmc2/shared/src/test/mlscript/parser/Block.mls b/hkmc2/shared/src/test/mlscript/parser/Block.mls index b8f1b6462d..2db1161df4 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Block.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Block.mls @@ -42,18 +42,18 @@ foo of x => x //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',Ident(x)) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),Ident(x)) x => x //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',Block(List(Ident(x)))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),Block(List(Ident(x)))) x => foo x //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',Block(List(Ident(foo), Ident(x)))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),Block(List(Ident(foo), Ident(x)))) let a = 1, 2 diff --git a/hkmc2/shared/src/test/mlscript/parser/BoolOps.mls b/hkmc2/shared/src/test/mlscript/parser/BoolOps.mls index bdc34bb1d2..81d7d30c36 100644 --- a/hkmc2/shared/src/test/mlscript/parser/BoolOps.mls +++ b/hkmc2/shared/src/test/mlscript/parser/BoolOps.mls @@ -3,26 +3,26 @@ a and b //│ Parsed: -//│ InfixApp(Ident(a),keyword 'and',Ident(b)) +//│ InfixApp(Ident(a),Keywrd(keyword 'and'),Ident(b)) a and b and c //│ Parsed: -//│ InfixApp(InfixApp(Ident(a),keyword 'and',Ident(b)),keyword 'and',Ident(c)) +//│ InfixApp(InfixApp(Ident(a),Keywrd(keyword 'and'),Ident(b)),Keywrd(keyword 'and'),Ident(c)) a and b or x and d //│ Parsed: -//│ InfixApp(InfixApp(Ident(a),keyword 'and',Ident(b)),keyword 'or',InfixApp(Ident(x),keyword 'and',Ident(d))) +//│ InfixApp(InfixApp(Ident(a),Keywrd(keyword 'and'),Ident(b)),Keywrd(keyword 'or'),InfixApp(Ident(x),Keywrd(keyword 'and'),Ident(d))) a or b and x or d //│ Parsed: -//│ InfixApp(InfixApp(Ident(a),keyword 'or',InfixApp(Ident(b),keyword 'and',Ident(x))),keyword 'or',Ident(d)) +//│ InfixApp(InfixApp(Ident(a),Keywrd(keyword 'or'),InfixApp(Ident(b),Keywrd(keyword 'and'),Ident(x))),Keywrd(keyword 'or'),Ident(d)) (a and b) or (x and d) //│ Parsed: -//│ InfixApp(Bra(Round,InfixApp(Ident(a),keyword 'and',Ident(b))),keyword 'or',Bra(Round,InfixApp(Ident(x),keyword 'and',Ident(d)))) +//│ InfixApp(Bra(Round,InfixApp(Ident(a),Keywrd(keyword 'and'),Ident(b))),Keywrd(keyword 'or'),Bra(Round,InfixApp(Ident(x),Keywrd(keyword 'and'),Ident(d)))) a and (b or x) and d //│ Parsed: -//│ InfixApp(InfixApp(Ident(a),keyword 'and',Bra(Round,InfixApp(Ident(b),keyword 'or',Ident(x)))),keyword 'and',Ident(d)) +//│ InfixApp(InfixApp(Ident(a),Keywrd(keyword 'and'),Bra(Round,InfixApp(Ident(b),Keywrd(keyword 'or'),Ident(x)))),Keywrd(keyword 'and'),Ident(d)) diff --git a/hkmc2/shared/src/test/mlscript/parser/Class.mls b/hkmc2/shared/src/test/mlscript/parser/Class.mls index 007cb287ee..03e3878030 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Class.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Class.mls @@ -19,11 +19,11 @@ class Foo extends //│ ║ l.17: class Foo extends //│ ╙── ^ //│ Parsed: -//│ TypeDef(Cls,InfixApp(Ident(Foo),keyword 'extends',Error()),None) +//│ TypeDef(Cls,InfixApp(Ident(Foo),Keywrd(keyword 'extends'),Error()),None) class Foo extends Bar //│ Parsed: -//│ TypeDef(Cls,InfixApp(Ident(Foo),keyword 'extends',Ident(Bar)),None) +//│ TypeDef(Cls,InfixApp(Ident(Foo),Keywrd(keyword 'extends'),Ident(Bar)),None) :pe class Foo extends Bar with @@ -31,21 +31,21 @@ class Foo extends Bar with //│ ║ l.29: class Foo extends Bar with //│ ╙── ^ //│ Parsed: -//│ TypeDef(Cls,InfixApp(InfixApp(Ident(Foo),keyword 'extends',Ident(Bar)),keyword 'with',Error()),None) +//│ TypeDef(Cls,InfixApp(InfixApp(Ident(Foo),Keywrd(keyword 'extends'),Ident(Bar)),Keywrd(keyword 'with'),Error()),None) class Foo extends Bar with val x //│ Parsed: -//│ TypeDef(Cls,InfixApp(InfixApp(Ident(Foo),keyword 'extends',Ident(Bar)),keyword 'with',TermDef(ImmutVal,Ident(x),None)),None) +//│ TypeDef(Cls,InfixApp(InfixApp(Ident(Foo),Keywrd(keyword 'extends'),Ident(Bar)),Keywrd(keyword 'with'),TermDef(ImmutVal,Ident(x),None)),None) class Foo extends Bar with val x: Int //│ Parsed: -//│ TypeDef(Cls,InfixApp(InfixApp(Ident(Foo),keyword 'extends',Ident(Bar)),keyword 'with',Block(List(TermDef(ImmutVal,InfixApp(Ident(x),keyword ':',Ident(Int)),None)))),None) +//│ TypeDef(Cls,InfixApp(InfixApp(Ident(Foo),Keywrd(keyword 'extends'),Ident(Bar)),Keywrd(keyword 'with'),Block(List(TermDef(ImmutVal,InfixApp(Ident(x),Keywrd(keyword ':'),Ident(Int)),None)))),None) class Foo with val x: Int //│ Parsed: -//│ TypeDef(Cls,InfixApp(Ident(Foo),keyword 'with',Block(List(TermDef(ImmutVal,InfixApp(Ident(x),keyword ':',Ident(Int)),None)))),None) +//│ TypeDef(Cls,InfixApp(Ident(Foo),Keywrd(keyword 'with'),Block(List(TermDef(ImmutVal,InfixApp(Ident(x),Keywrd(keyword ':'),Ident(Int)),None)))),None) :pe @@ -74,8 +74,8 @@ class Bar with x //│ Parsed: -//│ TypeDef(Cls,InfixApp(Ident(Foo),keyword 'extends',Ident(Bar)),None) -//│ TypeDef(Cls,InfixApp(Ident(Bar),keyword 'with',Block(List(Ident(x)))),None) +//│ TypeDef(Cls,InfixApp(Ident(Foo),Keywrd(keyword 'extends'),Ident(Bar)),None) +//│ TypeDef(Cls,InfixApp(Ident(Bar),Keywrd(keyword 'with'),Block(List(Ident(x)))),None) :pe @@ -108,7 +108,7 @@ class Foo extends Bar //│ Parsed: -//│ TypeDef(Cls,InfixApp(Ident(Foo),keyword 'extends',Ident(Bar)),None) +//│ TypeDef(Cls,InfixApp(Ident(Foo),Keywrd(keyword 'extends'),Ident(Bar)),None) class Foo @@ -116,18 +116,18 @@ class Bar extends Baz //│ Parsed: -//│ TypeDef(Cls,InfixApp(Ident(Foo),keyword 'extends',Ident(Bar)),None) -//│ TypeDef(Cls,InfixApp(Ident(Bar),keyword 'extends',Ident(Baz)),None) +//│ TypeDef(Cls,InfixApp(Ident(Foo),Keywrd(keyword 'extends'),Ident(Bar)),None) +//│ TypeDef(Cls,InfixApp(Ident(Bar),Keywrd(keyword 'extends'),Ident(Baz)),None) class Foo extends Bar //│ Parsed: -//│ TypeDef(Cls,InfixApp(Ident(Foo),keyword 'extends',Ident(Bar)),None) +//│ TypeDef(Cls,InfixApp(Ident(Foo),Keywrd(keyword 'extends'),Ident(Bar)),None) class Foo extends Bar //│ Parsed: -//│ TypeDef(Cls,InfixApp(Ident(Foo),keyword 'extends',Block(List(Ident(Bar)))),None) +//│ TypeDef(Cls,InfixApp(Ident(Foo),Keywrd(keyword 'extends'),Block(List(Ident(Bar)))),None) diff --git a/hkmc2/shared/src/test/mlscript/parser/Let.mls b/hkmc2/shared/src/test/mlscript/parser/Let.mls index eda7dce6b6..74e4096975 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Let.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Let.mls @@ -223,34 +223,34 @@ let x = 1 in x => let x = 1 in x //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',LetLike(Keywrd(keyword 'let'),Ident(x),Some(IntLit(1)),Some(Ident(x)))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),LetLike(Keywrd(keyword 'let'),Ident(x),Some(IntLit(1)),Some(Ident(x)))) x => let x = 1 in x //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',LetLike(Keywrd(keyword 'let'),Ident(x),Some(IntLit(1)),Some(Block(List(Ident(x)))))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),LetLike(Keywrd(keyword 'let'),Ident(x),Some(IntLit(1)),Some(Block(List(Ident(x)))))) x => let x = 1 in x //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',LetLike(Keywrd(keyword 'let'),Ident(x),Some(IntLit(1)),Some(Ident(x)))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),LetLike(Keywrd(keyword 'let'),Ident(x),Some(IntLit(1)),Some(Ident(x)))) x => let x = 1 in x //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',Block(List(LetLike(Keywrd(keyword 'let'),Ident(x),Some(IntLit(1)),Some(Ident(x)))))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),Block(List(LetLike(Keywrd(keyword 'let'),Ident(x),Some(IntLit(1)),Some(Ident(x)))))) x => let x = 1 x //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',Block(List(LetLike(Keywrd(keyword 'let'),Ident(x),Some(IntLit(1)),None), Ident(x)))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),Block(List(LetLike(Keywrd(keyword 'let'),Ident(x),Some(IntLit(1)),None), Ident(x)))) x => let x = 1 x //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',LetLike(Keywrd(keyword 'let'),Ident(x),Some(Jux(IntLit(1),Block(List(Ident(x))))),None)) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),LetLike(Keywrd(keyword 'let'),Ident(x),Some(Jux(IntLit(1),Block(List(Ident(x))))),None)) :pe diff --git a/hkmc2/shared/src/test/mlscript/parser/Operators.mls b/hkmc2/shared/src/test/mlscript/parser/Operators.mls index 1b12610d16..d691f95d90 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Operators.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Operators.mls @@ -35,27 +35,27 @@ 1 => 2 //│ Parsed: -//│ InfixApp(Tup(List(IntLit(1))),keyword '=>',IntLit(2)) +//│ InfixApp(Tup(List(IntLit(1))),Keywrd(keyword '=>'),IntLit(2)) 1 => 2 //│ Parsed: -//│ InfixApp(Tup(List(IntLit(1))),keyword '=>',Block(List(IntLit(2)))) +//│ InfixApp(Tup(List(IntLit(1))),Keywrd(keyword '=>'),Block(List(IntLit(2)))) x => 1 2 //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',Block(List(IntLit(1), IntLit(2)))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),Block(List(IntLit(1), IntLit(2)))) x => x : t //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',InfixApp(Ident(x),keyword ':',Ident(t))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),InfixApp(Ident(x),Keywrd(keyword ':'),Ident(t))) x => x : t //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',Block(List(InfixApp(Ident(x),keyword ':',Ident(t))))) +//│ InfixApp(Tup(List(Ident(x))),Keywrd(keyword '=>'),Block(List(InfixApp(Ident(x),Keywrd(keyword ':'),Ident(t))))) 1 + diff --git a/hkmc2/shared/src/test/mlscript/parser/Suspension.mls b/hkmc2/shared/src/test/mlscript/parser/Suspension.mls index cfb3ff4906..6b67ff3961 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Suspension.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Suspension.mls @@ -45,13 +45,13 @@ module A with //│ k = Mod //│ head = InfixApp: //│ lhs = Ident of "A" -//│ kw = keyword 'with' +//│ kw = Keywrd of keyword 'with' //│ rhs = Block of Ls of //│ TypeDef: //│ k = Mod //│ head = InfixApp: //│ lhs = Ident of "B" -//│ kw = keyword 'with' +//│ kw = Keywrd of keyword 'with' //│ rhs = Block of Ls of //│ TermDef: //│ k = ImmutVal @@ -100,7 +100,7 @@ let foo = 1 //│ ╙── ^^^ //│ ╔══[WARNING] Pure expression in statement position //│ ║ l.95: x => if false then 0 else... -//│ ╙── ^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ //│ foo = 1 diff --git a/hkmc2/shared/src/test/mlscript/parser/Then.mls b/hkmc2/shared/src/test/mlscript/parser/Then.mls index 9a434a403a..c125e7f420 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Then.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Then.mls @@ -3,22 +3,22 @@ 1 then 2 //│ Parsed: -//│ InfixApp(IntLit(1),keyword 'then',IntLit(2)) +//│ InfixApp(IntLit(1),Keywrd(keyword 'then'),IntLit(2)) 1 then 2 then 3 //│ Parsed: -//│ InfixApp(InfixApp(IntLit(1),keyword 'then',IntLit(2)),keyword 'then',IntLit(3)) +//│ InfixApp(InfixApp(IntLit(1),Keywrd(keyword 'then'),IntLit(2)),Keywrd(keyword 'then'),IntLit(3)) 1 and 2 then 3 //│ Parsed: -//│ InfixApp(InfixApp(IntLit(1),keyword 'and',IntLit(2)),keyword 'then',IntLit(3)) +//│ InfixApp(InfixApp(IntLit(1),Keywrd(keyword 'and'),IntLit(2)),Keywrd(keyword 'then'),IntLit(3)) 1 and 2 and 3 then 4 //│ Parsed: -//│ InfixApp(InfixApp(InfixApp(IntLit(1),keyword 'and',IntLit(2)),keyword 'and',IntLit(3)),keyword 'then',IntLit(4)) +//│ InfixApp(InfixApp(InfixApp(IntLit(1),Keywrd(keyword 'and'),IntLit(2)),Keywrd(keyword 'and'),IntLit(3)),Keywrd(keyword 'then'),IntLit(4)) 1 then 2 and 3 //│ Parsed: -//│ InfixApp(IntLit(1),keyword 'then',InfixApp(IntLit(2),keyword 'and',IntLit(3))) +//│ InfixApp(IntLit(1),Keywrd(keyword 'then'),InfixApp(IntLit(2),Keywrd(keyword 'and'),IntLit(3))) diff --git a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls index 72c787668d..01db6e89df 100644 --- a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls @@ -99,12 +99,12 @@ let xs = FingerTreeList.mk(1, 2, 3) if xs is [a, ...ls] then [a, ls] //│ JS (unsanitized): -//│ let rest, first0, a, ls; +//│ let ls, a, middleElements, element0$; //│ if (runtime.Tuple.isArrayLike(xs1) && xs1.length >= 1) { -//│ first0 = runtime.Tuple.get(xs1, 0); -//│ rest = runtime.safeCall(runtime.Tuple.slice(xs1, 1, 0)); -//│ a = first0; -//│ ls = rest; +//│ element0$ = runtime.Tuple.get(xs1, 0); +//│ middleElements = runtime.safeCall(runtime.Tuple.slice(xs1, 1, 0)); +//│ ls = middleElements; +//│ a = element0$; //│ globalThis.Object.freeze([ //│ a, //│ ls diff --git a/hkmc2/shared/src/test/mlscript/syntax/AbusiveSuspensions.mls b/hkmc2/shared/src/test/mlscript/syntax/AbusiveSuspensions.mls index 8a9fab6c6c..7814f9a634 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/AbusiveSuspensions.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/AbusiveSuspensions.mls @@ -66,7 +66,7 @@ fun foo(expr1) = //│ ╔══[PARSE ERROR] Unexpected identifier here //│ ║ l.60: is Error(b) then b //│ ╙── ^^^^^ -//│ ╔══[ERROR] Unrecognized term split (identifier). +//│ ╔══[ERROR] Unrecognized term split (identifier) //│ ║ l.59: if expr1 //│ ╙── ^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls b/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls index 632c44dc86..d54d99f531 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/WeirdBrackets.mls @@ -27,8 +27,14 @@ fun (\) huh(x) = x \(0) //│ = 0 - +:e +// Writing record patterns in this way is strange. In the previous `Desugarer`, +// the logic from two methods happened to work together and supported this +// syntax. In the new `Desugarer`, we chose not to support this syntax. fun f(x) = if x is {( Int then 1, Bool then 2 )} +//│ ╔══[ERROR] Unrecognized pattern split (parenthesis section). +//│ ║ l.34: fun f(x) = if x is {( Int then 1, Bool then 2 )} +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ // * Potential alternative syntaxes for UCS splits @@ -36,23 +42,44 @@ fun f(x) = if x is {( Int then 1, Bool then 2 )} :e fun f(x) = if x is ( Int then 1, Bool then 2 ) //│ ╔══[ERROR] Unrecognized pattern split (parenthesis section). -//│ ║ l.37: fun f(x) = if x is ( Int then 1, Bool then 2 ) +//│ ║ l.43: fun f(x) = if x is ( Int then 1, Bool then 2 ) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ :e fun f(x) = if x is \{ Int then 1, Bool then 2 } //│ ╔══[ERROR] Unrecognized pattern split (juxtaposition). -//│ ║ l.43: fun f(x) = if x is \{ Int then 1, Bool then 2 } +//│ ║ l.49: fun f(x) = if x is \{ Int then 1, Bool then 2 } //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :e fun f(x) = if x is \( Int then 1, Bool then 2 ) -//│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.49: fun f(x) = if x is \( Int then 1, Bool then 2 ) +//│ ╔══[ERROR] Cannot use this reference as a pattern. +//│ ║ l.55: fun f(x) = if x is \( Int then 1, Bool then 2 ) //│ ╙── ^ -//│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.49: fun f(x) = if x is \( Int then 1, Bool then 2 ) +//│ ╔══[ERROR] Cannot use this reference as a pattern. +//│ ║ l.55: fun f(x) = if x is \( Int then 1, Bool then 2 ) //│ ╙── ^ - - +class Box[T](val content: T) + +:ucs desugared +fun interesting(x) = + if x is Box( Int then 1, Bool then 2 ) +//│ Split with nested patterns: +//│ > if +//│ > x is Box(Int) then 1 +//│ > x is Box(Bool) then 2 +//│ Expanded split with flattened patterns: +//│ > if +//│ > x is Box(argument0$) and $argument0$ is Int then 1 +//│ > x is Box(argument0$) and $argument0$ is Bool then 2 + +interesting of Box(42) +//│ = 1 + +interesting of Box(true) +//│ = 2 + +:re +interesting of Box("str") +//│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls index 1335f64aaf..ff6a0d8960 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/Pattern.mls @@ -4,41 +4,42 @@ open annotations { compile } class Box() -:w +// Now the `@compile` annotation applies to all patterns, while before it could +// only be used for pattern symbols. Now it expands and compiles the following +// pattern. + fun foo(x) = if x is @compile Box then "yes" else "no" -//│ ╔══[WARNING] `@compile` cannot be used on class instance patterns. -//│ ║ l.8: fun foo(x) = if x is @compile Box then "yes" else "no" -//│ ║ ^^^^^^^ -//│ ╙── Note: The `@compile` annotation is for compiling pattern definitions. object Singleton -:w fun foo(x) = if x is @compile Singleton then "yes" else "no" -//│ ╔══[WARNING] `@compile` cannot be used on object instance patterns. -//│ ║ l.17: fun foo(x) = if x is @compile Singleton then "yes" else "no" -//│ ║ ^^^^^^^ -//│ ╙── Note: The `@compile` annotation is for compiling pattern definitions. -:e + fun foo(x) = if x is @compile 42 then "yes" else "no" -//│ ╔══[ERROR] Unrecognized pattern (annotated) -//│ ║ l.24: fun foo(x) = if x is @compile 42 then "yes" else "no" -//│ ╙── ^^^^^^^^^^ object do_not_compile :w fun foo(x) = if x is @do_not_compile Box then "yes" else "no" -//│ ╔══[WARNING] This annotation is not supported on class instance patterns. -//│ ║ l.32: fun foo(x) = if x is @do_not_compile Box then "yes" else "no" -//│ ╙── ^^^^^^^^^^^^^^ +//│ ╔══[WARNING] This annotation is not supported here. +//│ ║ l.23: fun foo(x) = if x is @do_not_compile Box then "yes" else "no" +//│ ║ ^^^^^^^^^^^^^^ +//│ ╟── Note: Patterns only support the `@compile` annotation. +//│ ║ l.23: fun foo(x) = if x is @do_not_compile Box then "yes" else "no" +//│ ╙── ^^^ +:w :e fun foo(x) = if x is @name_not_found Box then "yes" else "no" //│ ╔══[ERROR] Name not found: name_not_found -//│ ║ l.38: fun foo(x) = if x is @name_not_found Box then "yes" else "no" +//│ ║ l.33: fun foo(x) = if x is @name_not_found Box then "yes" else "no" //│ ╙── ^^^^^^^^^^^^^^ +//│ ╔══[WARNING] This annotation is not supported here. +//│ ║ l.33: fun foo(x) = if x is @name_not_found Box then "yes" else "no" +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── Note: Patterns only support the `@compile` annotation. +//│ ║ l.33: fun foo(x) = if x is @name_not_found Box then "yes" else "no" +//│ ╙── ^^^ pattern Box' = Box @@ -47,6 +48,9 @@ fun foo(x) = if x is @compile Box' then "yes" else "no" :w fun foo(x) = if x is @do_not_compile Box' then "yes" else "no" //│ ╔══[WARNING] This annotation is not supported here. -//│ ║ l.48: fun foo(x) = if x is @do_not_compile Box' then "yes" else "no" +//│ ║ l.49: fun foo(x) = if x is @do_not_compile Box' then "yes" else "no" //│ ║ ^^^^^^^^^^^^^^ -//│ ╙── Note: Patterns (like `Box'`) only support the `@compile` annotation. +//│ ╟── Note: Patterns only support the `@compile` annotation. +//│ ║ l.49: fun foo(x) = if x is @do_not_compile Box' then "yes" else "no" +//│ ╙── ^^^^ + diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls index d9a8095c9a..5f2f8e6268 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls @@ -1,4 +1,5 @@ :js + import "../../../mlscript-compile/Stack.mls" import "../../../mlscript-compile/Iter.mls" import "../../../mlscript-compile/StrOps.mls" diff --git a/hkmc2/shared/src/test/mlscript/ucs/future/AppSplits.mls b/hkmc2/shared/src/test/mlscript/ucs/future/AppSplits.mls index 5f88e4dcd1..a7ef4f7483 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/future/AppSplits.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/future/AppSplits.mls @@ -11,7 +11,7 @@ fun foo(x) = x > 1 if foo of 0 then "a" 1 then "b" -//│ ╔══[ERROR] Unrecognized term split (application). +//│ ╔══[ERROR] Unrecognized term split (application) //│ ║ l.11: if foo of //│ ║ ^^^^^^ //│ ║ l.12: 0 then "a" @@ -33,7 +33,7 @@ if foo of 1, //│ ╔══[PARSE ERROR] Unexpected literal here //│ ║ l.28: 0 then "a" //│ ╙── ^ -//│ ╔══[ERROR] Unrecognized term split (application). +//│ ╔══[ERROR] Unrecognized term split (application) //│ ║ l.27: if foo of 1, //│ ║ ^^^^^^^^^ //│ ║ l.28: 0 then "a" @@ -47,7 +47,7 @@ if foo of 1, if foo (0) then "a" (1) then "b" -//│ ╔══[ERROR] Unrecognized term split (juxtaposition). +//│ ╔══[ERROR] Unrecognized term split (juxtaposition) //│ ║ l.47: if foo //│ ║ ^^^ //│ ║ l.48: (0) then "a" diff --git a/hkmc2/shared/src/test/mlscript/ucs/future/Or.mls b/hkmc2/shared/src/test/mlscript/ucs/future/Or.mls index 51018ef8e7..e8d2cb7a9f 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/future/Or.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/future/Or.mls @@ -10,12 +10,15 @@ fun f(a, b) = if a is and b is Some(v') then v + v' or b is Some(v) then v else 0 -//│ ╔══[ERROR] Unrecognized pattern (infix operator 'then') +//│ ╔══[ERROR] Unrecognized pattern (infix operator 'then'). //│ ║ l.9: Some(v) //│ ║ ^^^^^^^ //│ ║ l.10: and b is Some(v') then v + v' //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.11: or b is Some(v) then v //│ ╙── ^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[ERROR] Name not found: v +//│ ║ l.11: or b is Some(v) then v +//│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls b/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls index b770c8d3af..7907a829f7 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls @@ -25,19 +25,28 @@ fun test(x) = :sjs true and test(42) //│ JS (unsanitized): -//│ let scrut6, scrut7; -//│ scrut6 = true; -//│ if (scrut6 === true) { -//│ scrut7 = test(42); -//│ if (scrut7 === true) { true } else { false } -//│ } else { false } +//│ let scrut6, scrut7, tmp4; +//│ split_root$3: { +//│ split_1$3: { +//│ scrut6 = true; +//│ if (scrut6 === true) { +//│ scrut7 = test(42); +//│ if (scrut7 === true) { +//│ tmp4 = true; +//│ break split_root$3 +//│ } else { break split_1$3 } +//│ } else { break split_1$3 } +//│ } +//│ tmp4 = false; +//│ } +//│ tmp4 //│ > 42 //│ = false :fixme true or test(42) //│ ╔══[ERROR] Logical `or` is not yet supported. -//│ ║ l.38: true or test(42) +//│ ║ l.47: true or test(42) //│ ╙── ^^^^^^^^^^^^^^^^ //│ > 42 //│ = false diff --git a/hkmc2/shared/src/test/mlscript/ucs/hygiene/CrossBranchCapture.mls b/hkmc2/shared/src/test/mlscript/ucs/hygiene/CrossBranchCapture.mls index 3bc3bace8c..b86f4c8d3d 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/hygiene/CrossBranchCapture.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/hygiene/CrossBranchCapture.mls @@ -44,12 +44,31 @@ fun process(e) = //│ ╔══[ERROR] Name not found: n //│ ║ l.41: Pair(Vec(xs), Vec(ys)) then n //│ ╙── ^ +//│ ╔══[ERROR] Duplicate pattern variable. +//│ ║ l.42: Pair(Vec(n), Numb(n)) then n +//│ ║ ^ +//│ ╟── The previous definition is as follows. +//│ ║ l.42: Pair(Vec(n), Numb(n)) then n +//│ ╙── ^ +//│ ╔══[ERROR] Duplicate pattern variable. +//│ ║ l.43: Pair(Numb(n), Vec(n)) then n +//│ ║ ^ +//│ ╟── The previous definition is as follows. +//│ ║ l.43: Pair(Numb(n), Vec(n)) then n +//│ ╙── ^ -// * FIXME should warn, be rejected, or compare both values for equality +// * TODO: we might want to accept this and compare the two values for equality +:e fun process(e) = if e is Pair(Numb(n), Numb(n)) then n +//│ ╔══[ERROR] Duplicate pattern variable. +//│ ║ l.65: Pair(Numb(n), Numb(n)) then n +//│ ║ ^ +//│ ╟── The previous definition is as follows. +//│ ║ l.65: Pair(Numb(n), Numb(n)) then n +//│ ╙── ^ process(Pair(Numb(1), Numb(2))) //│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls b/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls index 7de8d4ff67..24fa1a4c21 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/hygiene/HygienicBindings.mls @@ -40,16 +40,16 @@ fun h1(a) = a is None then 0 //│ Normalized: //│ > if -//│ > a is Option.Some.class(param0) and -//│ > let x = $param0 -//│ > x is Left.class(param0) and -//│ > let y = $param0 +//│ > a is Option.Some(argument0$) and +//│ > let x = $argument0$ +//│ > x is Left(argument0$) and +//│ > let y = $argument0$ //│ > else y -//│ > let y = $param0 -//│ > y is Right.class(param0) and -//│ > let z = $param0 +//│ > let y = $argument0$ +//│ > y is Right(argument0$) and +//│ > let z = $argument0$ //│ > else z -//│ > a is Option.None.class then 0 +//│ > a is Option.None then 0 h1(Some(Left(42))) //│ = 42 @@ -72,18 +72,18 @@ fun h2(a) = a is None then 0 //│ Normalized: //│ > if -//│ > a is Option.Some.class(param0) and -//│ > let x = $param0 +//│ > a is Option.Some(argument0$) and +//│ > let x = $argument0$ //│ > let x' = x -//│ > x' is Left.class(param0) and -//│ > let y = $param0 +//│ > x' is Left(argument0$) and +//│ > let y = $argument0$ //│ > else y -//│ > let y = $param0 +//│ > let y = $argument0$ //│ > let y' = y -//│ > y' is Right.class(param0) and -//│ > let z = $param0 +//│ > y' is Right(argument0$) and +//│ > let z = $argument0$ //│ > else z -//│ > a is Option.None.class then 0 +//│ > a is Option.None then 0 // :w fun h3(x, y, f, p) = diff --git a/hkmc2/shared/src/test/mlscript/ucs/hygiene/PatVars.mls b/hkmc2/shared/src/test/mlscript/ucs/hygiene/PatVars.mls index 9c0a139eb3..d5f5786da3 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/hygiene/PatVars.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/hygiene/PatVars.mls @@ -1,15 +1,10 @@ :js -:ucs desugared +:re class x if 0 is x then 12 -//│ Desugared: -//│ > if -//│ > let $scrut = 0 -//│ > let x = $scrut -//│ > else 12 -//│ = 12 +//│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls new file mode 100644 index 0000000000..4bba48a888 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls @@ -0,0 +1,471 @@ +:js + +let x = 0 +let y = 0 +let z = 0 +//│ x = 0 +//│ y = 0 +//│ z = 0 + +:sjs +if x === 0 then 1 else 2 +//│ JS (unsanitized): +//│ let scrut; scrut = x === 0; if (scrut === true) { 1 } else { 2 } +//│ = 1 + +:sjs +let a = if x === 0 then 1 else 2 +//│ JS (unsanitized): +//│ let a, scrut1, tmp; scrut1 = x === 0; if (scrut1 === true) { tmp = 1; } else { tmp = 2; } a = tmp; +//│ a = 1 + +:sjs +print of if x === 0 then 1 else 2 +//│ JS (unsanitized): +//│ let scrut2, tmp1; +//│ scrut2 = x === 0; +//│ if (scrut2 === true) { tmp1 = 1; } else { tmp1 = 2; } +//│ Predef.print(tmp1) +//│ > 1 + +:sjs +if x is + 0 and y is + 0 and z is + 0 then "000" + 1 then "001" + 1 then "01" + 1 then "1" + else "" +//│ JS (unsanitized): +//│ let tmp2; +//│ split_root$: { +//│ split_1$: { +//│ if (x === 0) { +//│ if (y === 0) { +//│ if (z === 0) { +//│ tmp2 = "000"; +//│ break split_root$ +//│ } else if (z === 1) { +//│ tmp2 = "001"; +//│ break split_root$ +//│ } else { +//│ break split_1$ +//│ } +//│ } else if (y === 1) { +//│ tmp2 = "01"; +//│ break split_root$ +//│ } else { +//│ break split_1$ +//│ } +//│ } else if (x === 1) { +//│ tmp2 = "1"; +//│ break split_root$ +//│ } else { break split_1$ } +//│ } +//│ tmp2 = ""; +//│ } +//│ tmp2 +//│ = "000" + +:sjs +let qqq = if x is + 0 and y is + 0 and z is + 0 then "000" + 1 then "001" + 1 then "01" + 1 then "1" + else "" +//│ JS (unsanitized): +//│ let qqq, tmp3; +//│ split_root$1: { +//│ split_1$1: { +//│ if (x === 0) { +//│ if (y === 0) { +//│ if (z === 0) { +//│ tmp3 = "000"; +//│ break split_root$1 +//│ } else if (z === 1) { +//│ tmp3 = "001"; +//│ break split_root$1 +//│ } else { +//│ break split_1$1 +//│ } +//│ } else if (y === 1) { +//│ tmp3 = "01"; +//│ break split_root$1 +//│ } else { +//│ break split_1$1 +//│ } +//│ } else if (x === 1) { +//│ tmp3 = "1"; +//│ break split_root$1 +//│ } else { break split_1$1 } +//│ } +//│ tmp3 = ""; +//│ } +//│ qqq = tmp3; +//│ qqq = "000" + +:sjs +print of if x is + 0 and y is + 0 and z is + 0 then "000" + 1 then "001" + 1 then "01" + 1 then "1" + else "" +//│ JS (unsanitized): +//│ let tmp4; +//│ split_root$2: { +//│ split_1$2: { +//│ if (x === 0) { +//│ if (y === 0) { +//│ if (z === 0) { +//│ tmp4 = "000"; +//│ break split_root$2 +//│ } else if (z === 1) { +//│ tmp4 = "001"; +//│ break split_root$2 +//│ } else { +//│ break split_1$2 +//│ } +//│ } else if (y === 1) { +//│ tmp4 = "01"; +//│ break split_root$2 +//│ } else { +//│ break split_1$2 +//│ } +//│ } else if (x === 1) { +//│ tmp4 = "1"; +//│ break split_root$2 +//│ } else { break split_1$2 } +//│ } +//│ tmp4 = ""; +//│ } +//│ Predef.print(tmp4) +//│ > 000 + +:sjs +fun foo(x, y, z) = + if x is + 0 and y is + 0 and z is + 0 then "000" + 1 then "001" + 1 then "01" + 1 then "1" + else "" +//│ JS (unsanitized): +//│ let foo; +//│ foo = function foo(x1, y1, z1) { +//│ let tmp5; +//│ split_root$3: { +//│ split_1$3: { +//│ if (x1 === 0) { +//│ if (y1 === 0) { +//│ if (z1 === 0) { +//│ tmp5 = "000"; +//│ break split_root$3 +//│ } else if (z1 === 1) { +//│ tmp5 = "001"; +//│ break split_root$3 +//│ } else { +//│ break split_1$3 +//│ } +//│ } else if (y1 === 1) { +//│ tmp5 = "01"; +//│ break split_root$3 +//│ } else { +//│ break split_1$3 +//│ } +//│ } else if (x1 === 1) { +//│ tmp5 = "1"; +//│ break split_root$3 +//│ } else { break split_1$3 } +//│ } +//│ tmp5 = ""; +//│ } +//│ return tmp5 +//│ }; + +foo(0, 0, 0) +//│ = "000" + +:sjs +print of if + x is 0 and + y is 0 and + z is 0 then "000" + y is 1 then "01_" + z is 1 then "0_1" + x is 1 then "1__" + y is 2 then "_2_" + else "___" +//│ JS (unsanitized): +//│ let tmp5; +//│ split_root$3: { +//│ split_1$3: { +//│ split_2$: { +//│ split_3$: { +//│ if (x === 0) { +//│ if (y === 0) { +//│ if (z === 0) { +//│ tmp5 = "000"; +//│ break split_root$3 +//│ } else if (z === 1) { +//│ break split_1$3 +//│ } else { +//│ break split_2$ +//│ } +//│ } else if (y === 1) { +//│ tmp5 = "01_"; +//│ break split_root$3 +//│ } else { +//│ if (z === 1) { +//│ break split_1$3 +//│ } else { +//│ if (y === 2) { +//│ break split_3$ +//│ } else { +//│ break split_2$ +//│ } +//│ } +//│ } +//│ } else if (x === 1) { +//│ tmp5 = "1__"; +//│ break split_root$3 +//│ } else { +//│ if (y === 2) { +//│ break split_3$ +//│ } else { +//│ break split_2$ +//│ } +//│ } +//│ } +//│ tmp5 = "_2_"; +//│ break split_root$3; +//│ } +//│ tmp5 = "___"; +//│ break split_root$3; +//│ } +//│ tmp5 = "0_1"; +//│ } +//│ Predef.print(tmp5) +//│ > 000 + +:sjs +fun foo(x, y, z) = if + x is 0 and + y is 0 and + z is 0 then "000" + y is 1 then "01_" + z is 1 then "0_1" + x is 1 then "1__" + y is 2 then "_2_" + else "___" +//│ JS (unsanitized): +//│ let foo1; +//│ foo1 = function foo(x1, y1, z1) { +//│ let tmp6; +//│ split_root$4: { +//│ split_1$4: { +//│ split_2$1: { +//│ split_3$1: { +//│ if (x1 === 0) { +//│ if (y1 === 0) { +//│ if (z1 === 0) { +//│ tmp6 = "000"; +//│ break split_root$4 +//│ } else if (z1 === 1) { +//│ break split_1$4 +//│ } else { +//│ break split_2$1 +//│ } +//│ } else if (y1 === 1) { +//│ tmp6 = "01_"; +//│ break split_root$4 +//│ } else { +//│ if (z1 === 1) { +//│ break split_1$4 +//│ } else { +//│ if (y1 === 2) { +//│ break split_3$1 +//│ } else { +//│ break split_2$1 +//│ } +//│ } +//│ } +//│ } else if (x1 === 1) { +//│ tmp6 = "1__"; +//│ break split_root$4 +//│ } else { +//│ if (y1 === 2) { +//│ break split_3$1 +//│ } else { +//│ break split_2$1 +//│ } +//│ } +//│ } +//│ tmp6 = "_2_"; +//│ break split_root$4; +//│ } +//│ tmp6 = "___"; +//│ break split_root$4; +//│ } +//│ tmp6 = "0_1"; +//│ } +//│ return tmp6 +//│ }; + +fun expensive_call(str) = Math.pow(2, str.length) + +:sjs +fun foo(x, y, z) = if x is + 0 and y is + 0 and z is + 0 then "000" + 1 then "001" + 1 then "01" + 1 then "1" + else + let value = "hello" + expensive_call(value) +//│ JS (unsanitized): +//│ let foo2; +//│ foo2 = function foo(x1, y1, z1) { +//│ let value, tmp6; +//│ split_root$4: { +//│ split_1$4: { +//│ if (x1 === 0) { +//│ if (y1 === 0) { +//│ if (z1 === 0) { +//│ tmp6 = "000"; +//│ break split_root$4 +//│ } else if (z1 === 1) { +//│ tmp6 = "001"; +//│ break split_root$4 +//│ } else { +//│ break split_1$4 +//│ } +//│ } else if (y1 === 1) { +//│ tmp6 = "01"; +//│ break split_root$4 +//│ } else { +//│ break split_1$4 +//│ } +//│ } else if (x1 === 1) { +//│ tmp6 = "1"; +//│ break split_root$4 +//│ } else { break split_1$4 } +//│ } +//│ value = "hello"; +//│ tmp6 = expensive_call(value); +//│ } +//│ return tmp6 +//│ }; + +object A +object B +object C + +:sjs +// Since no branch is duplicated, there is no label or `break`. +fun foo(x, y, z) = + if x is A then "Hello" + else "Goodbye" +//│ JS (unsanitized): +//│ let foo3; +//│ foo3 = function foo(x1, y1, z1) { +//│ if (x1 instanceof A1.class) { return "Hello" } else { return "Goodbye" } +//│ }; + +:sjs +// Before the deduplication is introduced: +// let foo; +// foo = function foo(x1, y, z) { +// if (x1 instanceof A1) { +// if (y instanceof B1) { +// if (z instanceof C1) { +// return "Hello" +// } else { return "Goodbye" } +// } else { return "Goodbye" } +// } else { return "Goodbye" } +// }; +fun foo(x, y, z) = + if x is A and y is B and z is C then "Hello" + else "Goodbye" +//│ JS (unsanitized): +//│ let foo4; +//│ foo4 = function foo(x1, y1, z1) { +//│ let tmp6; +//│ split_root$4: { +//│ split_1$4: { +//│ if (x1 instanceof A1.class) { +//│ if (y1 instanceof B1.class) { +//│ if (z1 instanceof C1.class) { +//│ tmp6 = "Hello"; +//│ break split_root$4 +//│ } else { +//│ break split_1$4 +//│ } +//│ } else { break split_1$4 } +//│ } else { break split_1$4 } +//│ } +//│ tmp6 = "Goodbye"; +//│ } +//│ return tmp6 +//│ }; + +:sjs +// The default branch which throws the match error should be also deduplicated. +fun foo(x, y, z) = + if x is A and y is B and z is C then "Hello" +//│ JS (unsanitized): +//│ let foo5; +//│ foo5 = function foo(x1, y1, z1) { +//│ let tmp6; +//│ split_root$4: { +//│ split_default$: { +//│ if (x1 instanceof A1.class) { +//│ if (y1 instanceof B1.class) { +//│ if (z1 instanceof C1.class) { +//│ tmp6 = "Hello"; +//│ break split_root$4 +//│ } else { +//│ break split_default$ +//│ } +//│ } else { +//│ break split_default$ +//│ } +//│ } else { +//│ break split_default$ +//│ } +//│ } +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ } +//│ return tmp6 +//│ }; + +:expect "Hello" +foo(A, B, C) +//│ = "Hello" + +:re +foo(A, A, A) +//│ ═══[RUNTIME ERROR] Error: match error + +:re +foo(A, B, B) +//│ ═══[RUNTIME ERROR] Error: match error + +:sjs +let y = if true then 1 else 2 +//│ JS (unsanitized): +//│ let y1, scrut3, tmp6; +//│ scrut3 = true; +//│ if (scrut3 === true) { tmp6 = 1; } else { tmp6 = 2; } +//│ y1 = tmp6; +//│ y = 1 diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls index 26ad26d50c..6b42b6676e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls @@ -151,7 +151,7 @@ fun f(arg) = if arg is Object then String(arg) //│ Normalized: //│ > if -//│ > arg is Foo.class then "just Foo" +//│ > arg is Foo then "just Foo" //│ > arg is Object then member:String(arg) fun f(arg) = if arg is diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls index 8f94810765..7a70cb4e1f 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls @@ -11,20 +11,29 @@ x => if x is Pair(A, B) then 1 //│ JS (unsanitized): //│ let lambda; //│ lambda = (undefined, function (x) { -//│ let param0, param1; -//│ if (x instanceof Pair1.class) { -//│ param0 = x.a; -//│ param1 = x.b; -//│ if (param0 instanceof A1) { -//│ if (param1 instanceof B1) { -//│ return 1 +//│ let argument0$, argument1$, tmp; +//│ split_root$: { +//│ split_default$: { +//│ if (x instanceof Pair1.class) { +//│ argument0$ = x.a; +//│ argument1$ = x.b; +//│ if (argument0$ instanceof A1) { +//│ if (argument1$ instanceof B1) { +//│ tmp = 1; +//│ break split_root$ +//│ } else { +//│ break split_default$ +//│ } +//│ } else { +//│ break split_default$ +//│ } //│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) +//│ break split_default$ //│ } -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ } -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ } +//│ return tmp //│ }); //│ lambda //│ = fun @@ -38,26 +47,36 @@ fun f(x) = if x is //│ JS (unsanitized): //│ let f; //│ f = function f(x) { -//│ let param0, param1; -//│ if (x instanceof Pair1.class) { -//│ param0 = x.a; -//│ param1 = x.b; -//│ if (param0 instanceof A1) { -//│ if (param1 instanceof A1) { -//│ return 1 +//│ let argument0$, argument1$, tmp; +//│ split_root$: { +//│ split_default$: { +//│ if (x instanceof Pair1.class) { +//│ argument0$ = x.a; +//│ argument1$ = x.b; +//│ if (argument0$ instanceof A1) { +//│ if (argument1$ instanceof A1) { +//│ tmp = 1; +//│ break split_root$ +//│ } else { +//│ break split_default$ +//│ } +//│ } else if (argument0$ instanceof B1) { +//│ if (argument1$ instanceof B1) { +//│ tmp = 2; +//│ break split_root$ +//│ } else { +//│ break split_default$ +//│ } +//│ } else { +//│ break split_default$ +//│ } //│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) +//│ break split_default$ //│ } -//│ } else if (param0 instanceof B1) { -//│ if (param1 instanceof B1) { -//│ return 2 -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) -//│ } -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ } -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ } +//│ return tmp //│ }; diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls index 46e71893b3..85ebdc5920 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls @@ -12,11 +12,9 @@ fun map(f) = case Some(x as n) then Some of f(n) None as n then n -:ucs desugared + fun foo = case Some(Some(a as b) as c) as d then [a, b, c, d] -//│ Desugared: -//│ > if caseScrut is Option.Some(param0) as d and $param0 is Option.Some(param0) as c and -//│ > let a = $param0 -//│ > let b = $param0 -//│ > else [a, b, c, d] + +foo of Some(Some(42)) +//│ = [42, 42, Some(42), Some(Some(42))] diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls new file mode 100644 index 0000000000..d17b630d39 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Compilation.mls @@ -0,0 +1,36 @@ +:js + +open annotations + +:e +fun trimStart(str) = + if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ╔══[ERROR] Unrecognized pattern (unit). +//│ ║ l.7: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[ERROR] Name not found: rest +//│ ║ l.7: if str is (@compile ((" " | "\t") ~ rest)) then trimStart(rest) else str +//│ ╙── ^^^^ + +:pe +:e +:w +// Note: `compile ((" " | "\t") ~ rest)` is parsed as an annotation. +fun trimStart(str) = + if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ╔══[PARSE ERROR] Unexpected keyword 'then' in this position +//│ ║ l.20: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ╙── ^^^^ +//│ ╔══[ERROR] Unrecognized pattern (‹erroneous syntax›). +//│ ║ l.20: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ╙── ^^^^ +//│ ╔══[ERROR] Name not found: rest +//│ ║ l.20: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ╙── ^^^^ +//│ ╔══[ERROR] Name not found: rest +//│ ║ l.20: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ╙── ^^^^ +//│ ╔══[WARNING] This annotation is not supported here. +//│ ║ l.20: if str is @compile ((" " | "\t") ~ rest) then trimStart(rest) else str +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Note: Patterns only support the `@compile` annotation. diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls index 003be9bb5b..4d40e82446 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls @@ -1,4 +1,5 @@ :js + import "../../../mlscript-compile/Stack.mls" open Stack @@ -8,6 +9,9 @@ object A object B data class C(a) +// Note: It is normalization that caused the following tests to generate +// incorrect code. Because the specialization rejects the intersection patterns. + if A is A & A & B then 0 B & A & B then 1 @@ -28,7 +32,19 @@ fun foo(v) = A & B then 1 else 0 //│ JS (unsanitized): -//│ let foo; foo = function foo(v) { if (v instanceof A1.class) { return 0 } else { return 0 } }; +//│ let foo; +//│ foo = function foo(v) { +//│ let tmp2; +//│ split_root$2: { +//│ split_1$2: { +//│ if (v instanceof A1.class) { +//│ break split_1$2 +//│ } else { break split_1$2 } +//│ } +//│ tmp2 = 0; +//│ } +//│ return tmp2 +//│ }; fun range(i, j) = if i > j then Nil diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls index 189766915f..78813c97ad 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/NamePattern.mls @@ -4,7 +4,11 @@ fun id(x) = x :ucs desugared if id(0) is t then t -//│ Desugared: +//│ Split with nested patterns: +//│ > if +//│ > let $scrut = member:id(0) +//│ > $scrut is t then t +//│ Expanded split with flattened patterns: //│ > if //│ > let $scrut = member:id(0) //│ > let t = $scrut diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls index c276c01854..70fd495f8e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls @@ -118,7 +118,7 @@ fun foo(x) = if x is A() then 0 //│ ║ ^ //│ ╟── Its pattern cannot have an argument list. //│ ║ l.115: fun foo(x) = if x is A() then 0 -//│ ╙── ^^^ +//│ ╙── ^ :e fun foo(x) = if x is A(y) then y @@ -127,7 +127,7 @@ fun foo(x) = if x is A(y) then y //│ ║ ^ //│ ╟── Its pattern cannot have arguments. //│ ║ l.124: fun foo(x) = if x is A(y) then y -//│ ╙── ^^^^ +//│ ╙── ^^^ 0 is Foo //│ = false diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/RecordPattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/RecordPattern.mls index a97c414cd8..a9e720dcc6 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/RecordPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/RecordPattern.mls @@ -56,12 +56,19 @@ let rcd = // * As that will make sense when we have dependent types, // * as in: `{ n: Int, v: Vec[Int, n] }` -:js let r = {x: 2, y : 4} +//│ r = {x: 2, y: 4} + +:e if r is {x:a, y:a} then a +//│ ╔══[ERROR] Duplicate pattern variable. +//│ ║ l.64: {x:a, y:a} then a +//│ ║ ^ +//│ ╟── The previous definition is as follows. +//│ ║ l.64: {x:a, y:a} then a +//│ ╙── ^ //│ = 4 -//│ r = {x: 2, y: 4} let rr = {x: {y:10, z:100}, t:1000} @@ -173,6 +180,16 @@ is_object([1, 2]) is_object(is_object) //│ = true -:todo fun is_record_alt(x) = x is {} -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: encountered empty block + +is_record_alt of [] +//│ = true + +is_record_alt of 0 +//│ = false + +is_object("") +//│ = false + +is_object(Nil) +//│ = true diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls index 981f0fe38e..82b15489c4 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls @@ -31,8 +31,8 @@ x => if x is refined(None) then x refined(Some) then x //│ ╔══[ERROR] Name not found: refined -//│ ║ l.32: refined(Some) then x +//│ ║ l.31: refined(None) then x //│ ╙── ^^^^^^^ //│ ╔══[ERROR] Name not found: refined -//│ ║ l.31: refined(None) then x +//│ ║ l.32: refined(Some) then x //│ ╙── ^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls index 2688a0e489..33339b6e3a 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls @@ -1,5 +1,6 @@ :js + :sjs fun nonsense(xs) = if xs is [..ys] then ys @@ -7,10 +8,10 @@ fun nonsense(xs) = if xs is //│ JS (unsanitized): //│ let nonsense; //│ nonsense = function nonsense(xs) { -//│ let rest, ys; +//│ let ys, middleElements; //│ if (runtime.Tuple.isArrayLike(xs) && xs.length >= 0) { -//│ rest = runtime.safeCall(runtime.Tuple.lazySlice(xs, 0, 0)); -//│ ys = rest; +//│ middleElements = runtime.safeCall(runtime.Tuple.slice(xs, 0, 0)); +//│ ys = middleElements; //│ return ys //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } //│ }; @@ -28,14 +29,14 @@ fun lead_and_last(xs) = if xs is //│ JS (unsanitized): //│ let lead_and_last; //│ lead_and_last = function lead_and_last(xs) { -//│ let last0, rest, first0, x, ys, y; +//│ let x, ys, y, lastElement0$, middleElements, element0$; //│ if (runtime.Tuple.isArrayLike(xs) && xs.length >= 2) { -//│ first0 = runtime.Tuple.get(xs, 0); -//│ rest = runtime.safeCall(runtime.Tuple.lazySlice(xs, 1, 1)); -//│ last0 = runtime.Tuple.get(xs, -1); -//│ x = first0; -//│ ys = rest; -//│ y = last0; +//│ element0$ = runtime.Tuple.get(xs, 0); +//│ middleElements = runtime.safeCall(runtime.Tuple.slice(xs, 1, 1)); +//│ lastElement0$ = runtime.Tuple.get(xs, -1); +//│ y = lastElement0$; +//│ ys = middleElements; +//│ x = element0$; //│ return x + y //│ } else if (runtime.Tuple.isArrayLike(xs) && xs.length === 0) { //│ return 0 @@ -62,29 +63,42 @@ fun nested_tuple_patterns(xs) = if xs is //│ JS (unsanitized): //│ let nested_tuple_patterns; //│ nested_tuple_patterns = function nested_tuple_patterns(xs) { -//│ let last0, rest, first0, x, first1, first01, y, z, w, tmp6, tmp7; -//│ if (runtime.Tuple.isArrayLike(xs) && xs.length >= 2) { -//│ first0 = runtime.Tuple.get(xs, 0); -//│ rest = runtime.safeCall(runtime.Tuple.lazySlice(xs, 1, 1)); -//│ last0 = runtime.Tuple.get(xs, -1); -//│ x = first0; -//│ if (runtime.Tuple.isArrayLike(rest) && rest.length === 2) { -//│ first01 = runtime.Tuple.get(rest, 0); -//│ first1 = runtime.Tuple.get(rest, 1); -//│ y = first01; -//│ z = first1; -//│ w = last0; -//│ tmp6 = x + y; -//│ tmp7 = tmp6 + z; -//│ return tmp7 + w -//│ } else { -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")) +//│ let x, y, w, z, lastElement0$, middleElements, element0$, element1$, element0$1, tmp6, tmp7, tmp8; +//│ split_root$: { +//│ split_default$: { +//│ if (runtime.Tuple.isArrayLike(xs) && xs.length >= 2) { +//│ element0$ = runtime.Tuple.get(xs, 0); +//│ middleElements = runtime.safeCall(runtime.Tuple.slice(xs, 1, 1)); +//│ lastElement0$ = runtime.Tuple.get(xs, -1); +//│ if (runtime.Tuple.isArrayLike(middleElements) && middleElements.length === 2) { +//│ element0$1 = runtime.Tuple.get(middleElements, 0); +//│ element1$ = runtime.Tuple.get(middleElements, 1); +//│ w = lastElement0$; +//│ z = element1$; +//│ y = element0$1; +//│ x = element0$; +//│ tmp6 = x + y; +//│ tmp7 = tmp6 + z; +//│ tmp8 = tmp7 + w; +//│ break split_root$ +//│ } else { +//│ break split_default$ +//│ } +//│ } else if (runtime.Tuple.isArrayLike(xs) && xs.length === 0) { +//│ tmp8 = 0; +//│ break split_root$ +//│ } else { +//│ break split_default$ +//│ } //│ } -//│ } else if (runtime.Tuple.isArrayLike(xs) && xs.length === 0) { -//│ return 0 -//│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ } +//│ return tmp8 //│ }; +nested_tuple_patterns of [1, 2, 3, 4] +//│ = 10 + fun hack(tupleSlice) = if tupleSlice is [..tupleGet, x] then x diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls index c6cb0adde5..5fdfcdd47b 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls @@ -1,7 +1,7 @@ :js -// We test the support for simple tuple patterns in this file. -// Splice tuple patterns will be implement in the future. + + fun sum(x, y) = x + y sum(1, 2) diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/String.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/String.mls new file mode 100644 index 0000000000..2768956a89 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/String.mls @@ -0,0 +1,37 @@ +:js + +fun parseIntegerSimple(str) = if str is + "0x" ~ body then parseInt(body, 16) + "0b" ~ body then parseInt(body, 2) + "0o" ~ body then parseInt(body, 8) + else parseInt(str, 10) + +:expect 65535 +parseIntegerSimple("0xFFFF") +//│ = 65535 + +:expect 255 +parseIntegerSimple("0b11111111") +//│ = 255 + +:expect 4095 +parseIntegerSimple("0o7777") +//│ = 4095 + +:expect 7777 +parseIntegerSimple("07777") +//│ = 7777 + +fun trimStart(str) = if str is (" " | "\t") ~ rest then trimStart(rest) else str + +:expect "up" +trimStart of " up" +//│ = "up" + +:expect "" +trimStart of "" +//│ = "" + +:expect "hello" +trimStart of "\thello" +//│ = "hello" diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/where.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/where.mls index 7e6b91e02e..7321044208 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/where.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/where.mls @@ -35,7 +35,7 @@ foo(Pair(0, 4)) foo(Pair(4, 0)) //│ = false - +// Should we report `a` and `b` as inconsistent variables? fun bar(p) = p is (Pair(a, b) where a > b) | Int @@ -58,3 +58,40 @@ fun baz(p) = p is //│ ║ l.53: (Pair(a, b) | Int) where a > b //│ ╙── ^ +pattern Positive = ((Int as x) where x > 0) => x + +0 is Positive +//│ = false + +1 is Positive +//│ = true + +pattern OrderedPair = (Pair(a, b) where a < b) => b - a + +:expect true +Pair(1, 2) is OrderedPair as 1 +//│ = true + +:expect false +Pair(3, 2) is OrderedPair +//│ = false + +:expect true +Pair(42, 96) is OrderedPair as 54 +//│ = true + +pattern OrderedPairLike = ((Pair(a, b) | [a, b]) where a < b) => b - a + +:expect [true, false, true] +tuple of + Pair(1, 2) is OrderedPairLike as 1 + Pair(3, 2) is OrderedPairLike + Pair(42, 96) is OrderedPairLike as 54 +//│ = [true, false, true] + +:expect [true, false, true] +tuple of + [1, 2] is OrderedPairLike as 1 + [3, 2] is OrderedPairLike + [42, 96] is OrderedPairLike as 54 +//│ = [true, false, true] diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls index 33927c2a3a..f8fc571134 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls @@ -6,7 +6,7 @@ x and y //│ Parsed tree: //│ InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = Ident of "y" x @@ -14,7 +14,7 @@ x //│ Parsed tree: //│ InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = Ident of "y" if x @@ -25,9 +25,9 @@ if x //│ split = InfixApp: //│ lhs = InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = Ident of "y" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "z" if x @@ -40,9 +40,9 @@ if x //│ InfixApp: //│ lhs = InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = Ident of "y" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "z" //│ PrefixApp: //│ kw = Keywrd of keyword 'else' @@ -55,14 +55,14 @@ if x is //│ kw = Keywrd of keyword 'if' //│ split = InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Block of Ls of //│ InfixApp: //│ lhs = InfixApp: //│ lhs = Ident of "A" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = Ident of "y" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "z" if x is @@ -74,7 +74,7 @@ if x is //│ kw = Keywrd of keyword 'if' //│ split = InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Block of Ls of //│ InfixApp: //│ lhs = InfixApp: @@ -84,13 +84,13 @@ if x is //│ Ident of "u" //│ Ident of "v" //│ Ident of "w" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = Ident of "y" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "z" //│ InfixApp: //│ lhs = Ident of "B" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "C" if x is @@ -103,7 +103,7 @@ if x is //│ kw = Keywrd of keyword 'if' //│ split = InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Block of Ls of //│ InfixApp: //│ lhs = App: @@ -112,18 +112,18 @@ if x is //│ Ident of "u" //│ Ident of "v" //│ Ident of "w" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = InfixApp: //│ lhs = Ident of "v" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Block of Ls of //│ InfixApp: //│ lhs = Ident of "B" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "z" //│ InfixApp: //│ lhs = Ident of "B" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "C" :todo @@ -142,7 +142,7 @@ if x is //│ kw = Keywrd of keyword 'if' //│ split = InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Block of Ls of //│ InfixApp: //│ lhs = App: @@ -151,18 +151,18 @@ if x is //│ Ident of "u" //│ Ident of "v" //│ Ident of "w" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = InfixApp: //│ lhs = Ident of "v" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Error //│ InfixApp: //│ lhs = Ident of "B" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "z" //│ InfixApp: //│ lhs = Ident of "B" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "C" if x is @@ -175,7 +175,7 @@ if x is //│ kw = Keywrd of keyword 'if' //│ split = InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Block of Ls of //│ InfixApp: //│ lhs = InfixApp: @@ -186,21 +186,21 @@ if x is //│ Ident of "u" //│ Ident of "v" //│ Ident of "w" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = InfixApp: //│ lhs = Ident of "v" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Ident of "B" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = InfixApp: //│ lhs = Ident of "w" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Ident of "B" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "z" //│ InfixApp: //│ lhs = Ident of "B" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "C" if x is @@ -213,7 +213,7 @@ if x is //│ kw = Keywrd of keyword 'if' //│ split = InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Block of Ls of //│ InfixApp: //│ lhs = InfixApp: @@ -224,24 +224,24 @@ if x is //│ Ident of "u" //│ Ident of "v" //│ Ident of "w" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = InfixApp: //│ lhs = Ident of "v" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Ident of "B" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = InfixApp: //│ lhs = Ident of "z" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = InfixApp: //│ lhs = Ident of "w" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Ident of "B" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "z" //│ InfixApp: //│ lhs = Ident of "B" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "C" diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/BadUCSSyntax.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/BadUCSSyntax.mls index 5f79a8439b..e88451dd2c 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/BadUCSSyntax.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/BadUCSSyntax.mls @@ -2,18 +2,18 @@ :e if x else z +//│ ╔══[ERROR] Unrecognized term split (identifier) +//│ ║ l.4: if x else z +//│ ╙── ^ //│ ╔══[ERROR] Name not found: z //│ ║ l.4: if x else z //│ ╙── ^ -//│ ╔══[ERROR] Unrecognized term split (identifier). -//│ ║ l.4: if x else z -//│ ╙── ^ :e fun f(x, z) = if x else z -//│ ╔══[ERROR] Unrecognized term split (identifier). +//│ ╔══[ERROR] Unrecognized term split (identifier) //│ ║ l.14: if x //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls index d342d5d98f..d10cc2012f 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls @@ -24,19 +24,19 @@ if //│ lhs = InfixApp: //│ lhs = InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Ident of "A" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = InfixApp: //│ lhs = Ident of "y" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Ident of "B" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = InfixApp: //│ lhs = Ident of "c" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Ident of "Z" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 1 // But since naughty users may add parentheses, @@ -51,20 +51,20 @@ if //│ lhs = InfixApp: //│ lhs = InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Ident of "A" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = Bra: //│ k = Round //│ inner = InfixApp: //│ lhs = InfixApp: //│ lhs = Ident of "y" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Ident of "B" -//│ kw = keyword 'and' +//│ kw = Keywrd of keyword 'and' //│ rhs = InfixApp: //│ lhs = Ident of "c" -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Ident of "Z" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 1 diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls index 9e525e9f4f..f53b3935e4 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls @@ -23,7 +23,7 @@ fun f(x, y, z) = if x then y else z //│ split = Block of Ls of //│ InfixApp: //│ lhs = Ident of "x" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = Ident of "y" //│ PrefixApp: //│ kw = Keywrd of keyword 'else' @@ -119,3 +119,22 @@ else //│ ║ l.116: 5 else 1 //│ ╙── ^^^^ //│ = 8 + +data class Box[A](value: A) + +:w +fun foo(x) = if x is + Box(0) then "The box contains zero." + Box(1) then "The box contains one." + else "I can't tell you what's inside the box." + else "I don't want to tell you what's inside the box." + else "I shouldn't tell you what's inside the box." +//│ ╔══[WARNING] This else clause makes the following branches unreachable. +//│ ║ l.129: else "I can't tell you what's inside the box." +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── This branch is unreachable. +//│ ║ l.130: else "I don't want to tell you what's inside the box." +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── This branch is unreachable. +//│ ║ l.131: else "I shouldn't tell you what's inside the box." +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Empty.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Empty.mls index 0553ce5074..7ad7a765f3 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Empty.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Empty.mls @@ -26,7 +26,7 @@ if :e if let x -//│ ╔══[ERROR] Unrecognized term split (let). +//│ ╔══[ERROR] Unrecognized term split (let) //│ ║ l.28: let x //│ ╙── ^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Is.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Is.mls index b2fc2dbb8b..ac01636309 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Is.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Is.mls @@ -4,13 +4,13 @@ x is A //│ Parsed: -//│ InfixApp(Ident(x),keyword 'is',Ident(A)) +//│ InfixApp(Ident(x),Keywrd(keyword 'is'),Ident(A)) x is A is B //│ Parsed: -//│ OpSplit(Ident(x),List(InfixApp(SplitPoint(),keyword 'is',Ident(A)), InfixApp(SplitPoint(),keyword 'is',Ident(B)))) +//│ OpSplit(Ident(x),List(InfixApp(SplitPoint(),Keywrd(keyword 'is'),Ident(A)), InfixApp(SplitPoint(),Keywrd(keyword 'is'),Ident(B)))) diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls index a0af53560c..c76ee71694 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls @@ -1,4 +1,4 @@ - +:js :ucs desugared // * Note that this always associates to the left @@ -7,10 +7,16 @@ fun f(x) = 1 + 2 then 0 _ then 1 -//│ Desugared: +//│ Split with nested patterns: +//│ > if +//│ > let $scrut = 1 +//│ > let $scrut = builtin:==(x, builtin:+($scrut, 2)) +//│ > $scrut is true then 0 +//│ > else 1 +//│ Expanded split with flattened patterns: //│ > if -//│ > let $scrut = builtin:==(x, 1) -//│ > let $scrut = builtin:+($scrut, 2) +//│ > let $scrut = 1 +//│ > let $scrut = builtin:==(x, builtin:+($scrut, 2)) //│ > $scrut is true then 0 //│ > else 1 diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Of.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Of.mls index c8ec7c4346..d3b79d9968 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Of.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Of.mls @@ -46,3 +46,24 @@ check(Some(42)) check(Some(77)) //│ = false + +data class Left(leftValue) +data class Right(rightValue) + +fun getBothOrElse(either, defaultValue) = if either is + None then defaultValue + Some of + Left of value then value + Right of value then value + +:expect 42 +getBothOrElse of None, 42 +//│ = 42 + +:expect 7 +getBothOrElse of Some(Left(7)), 42 +//│ = 7 + +:expect "noice" +getBothOrElse of Some(Left("noice")), 42 +//│ = "noice" diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls index 4538e414b7..d8c70bbbc7 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/PlainConditionals.mls @@ -1,64 +1,131 @@ +:js - -fun f: Any -> Bool -fun g: Any -> Bool -fun h: Any -> Bool +fun f(x) = x +fun g(x) = x +fun h(x) = x data class Pair[A, B](fst: A, snd: B) +// Simple conditionals in the form of `x is P`. +// ============================================ +:expect true Pair(0, 1) is Pair +//│ = true +:expect true Pair(0, 1) is Pair(a, b) +//│ = true +:expect true Pair(0, 1) is Pair(0, _) +//│ = true +:expect true if Pair(0, 1) is Pair(a, b) then true else false +//│ = true +// Based on the above, add an `and` at the end, in the form of `x is P and t`. +// =========================================================================== -fun foo(x) = x is Pair(a, b) - - +:expect false Pair(0, 1) is Pair(a, b) and a > b +//│ = false -if Pair(0, 1) is Pair(a, b) then a > b else false +:expect true +Pair(1, 0) is Pair(a, b) and a > b +//│ = true +fun foo(x) = f(x) and g(x) and h(x) -fun foo(x) = x is Pair(a, b) and a > b - -fun foo(x) = if x is Pair(a, b) then a > b else false +:expect [true, false] +[foo(true), foo(false)] +//│ = [true, false] -fun foo(x) = f(x) and g(x) and h(x) +// Test matching multiple patterns at once. +// ======================================== -:e fun foo(x) = x is Pair Int -//│ ╔══[ERROR] only one pattern is supported in shorthands -//│ ║ l.35: Pair -//│ ║ ^^^^ -//│ ║ l.36: Int -//│ ╙── ^^^^^ + +:expect [true, false, true] +[foo(2), foo(3.14159), foo(Pair(0, 1))] +//│ = [true, false, true] + +fun foo(x) = x is Pair | Int + +:expect [true, false, true] +[foo(2), foo(3.14159), foo(Pair(0, 1))] +//│ = [true, false, true] + +fun foo(x) = x is + Pair | Int + +:expect [true, false, true] +[foo(2), foo(3.14159), foo(Pair(0, 1))] +//│ = [true, false, true] + +fun foo(x) = x is { Pair, Int } + +:expect [true, false, true] +[foo(2), foo(3.14159), foo(Pair(0, 1))] +//│ = [true, false, true] :e fun foo(x) = x is Pair(a, b) and a > b Int -//│ ╔══[ERROR] only one pattern is supported in shorthands -//│ ║ l.45: Pair(a, b) and a > b -//│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.46: Int -//│ ╙── ^^^^^ +//│ ╔══[ERROR] Unrecognized pattern (infix operator 'and'). +//│ ║ l.77: Pair(a, b) and a > b +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ +:e fun foo(x) = x is Pair(a, b) | Int - -fun foo(x) = x is (Pair(a, b) where a > b) | Int +//│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. +//│ ║ l.85: fun foo(x) = x is Pair(a, b) | Int +//│ ║ ^ +//│ ╟── The variable is missing from this sub-pattern. +//│ ║ l.85: fun foo(x) = x is Pair(a, b) | Int +//│ ╙── ^^^ +//│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. +//│ ║ l.85: fun foo(x) = x is Pair(a, b) | Int +//│ ║ ^ +//│ ╟── The variable is missing from this sub-pattern. +//│ ║ l.85: fun foo(x) = x is Pair(a, b) | Int +//│ ╙── ^^^ + +// Test how shorthand conditionals work together with lambda functions. +// ==================================================================== data class A[T](arg: T) +let foo = x => x is A(_) +//│ foo = fun foo + +:expect [false, true] +[foo(0), foo(A(0))] +//│ = [false, true] + +let foo = _ is A +//│ foo = fun foo + +:expect [false, true] +[foo(0), foo(A(0))] +//│ = [false, true] + +let foo = _ is A(_) +//│ foo = fun foo -x => (x is A(_)) +:expect [false, true] +[foo(0), foo(A(0))] +//│ = [false, true] +let foo = _ is A(x) and x > 0 +//│ foo = fun +:expect [false, false, true] +[foo(0), foo(A(0)), foo(A(1))] +//│ = [false, false, true] diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/SimpleUCS.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/SimpleUCS.mls index 0aeaecbfbf..aaf0fcbb4e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/SimpleUCS.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/SimpleUCS.mls @@ -1,4 +1,4 @@ - +:js abstract class Option[A]: Some[A] | None data class Some[A](value: A) extends Option[A] @@ -164,9 +164,16 @@ fun g(a, b) = None then x g(5, None) +//│ = 5 + g(5, Some(7)) +//│ = 35 + g(0 - 5, None) +//│ = 25 + g(0 - 5, Some(9)) +//│ = 4 data class Var(name: Str) abstract class ValBase: (IntVal | BoolVal) @@ -192,9 +199,9 @@ fun f(x) = 0 :: Nil() then "oh" //│ ╔══[ERROR] Unrecognized pattern split (operator application). -//│ ║ l.192: 0 :: +//│ ║ l.199: 0 :: //│ ║ ^^^^ -//│ ║ l.193: Nil() then "oh" +//│ ║ l.200: Nil() then "oh" //│ ╙── ^^^^^^ @@ -225,10 +232,15 @@ fun test(x) = if x then 0 else "oops" test(true) -test(false) +//│ = 0 +test(false) +//│ = "oops" test(0) +//│ = "oops" + test(1) +//│ = "oops" diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls index dbfdf199d9..33e4d41bdd 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls @@ -34,11 +34,11 @@ if f(x) == //│ op = Ident of "+" //│ rhss = Ls of //│ IntLit of 1 -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 0 //│ InfixApp: //│ lhs = IntLit of 2 -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 3 if x + @@ -66,11 +66,11 @@ if x + //│ Block of Ls of //│ InfixApp: //│ lhs = IntLit of 2 -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 3 //│ InfixApp: //│ lhs = IntLit of 4 -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 5 //│ OpApp: //│ lhs = Ident of "y" @@ -79,22 +79,22 @@ if x + //│ Block of Ls of //│ InfixApp: //│ lhs = IntLit of 6 -//│ kw = keyword 'is' +//│ kw = Keywrd of keyword 'is' //│ rhs = Block of Ls of //│ InfixApp: //│ lhs = Ident of "A" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 7 //│ InfixApp: //│ lhs = Ident of "B" -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 9 //│ PrefixApp: //│ kw = Keywrd of keyword 'else' //│ rhs = IntLit of 42 //│ InfixApp: //│ lhs = IntLit of 8 -//│ kw = keyword 'then' +//│ kw = Keywrd of keyword 'then' //│ rhs = IntLit of 9 :todo diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls index 0e7683a662..d02c7c20c1 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls @@ -1,5 +1,4 @@ -// :parseOnly -// :pt +:js import "../../../mlscript-compile/Stack.mls" diff --git a/hkmc2/shared/src/test/mlscript/ups/Future.mls b/hkmc2/shared/src/test/mlscript/ups/Future.mls index 3af8229da1..eb9559b8f4 100644 --- a/hkmc2/shared/src/test/mlscript/ups/Future.mls +++ b/hkmc2/shared/src/test/mlscript/ups/Future.mls @@ -5,43 +5,49 @@ pattern Rep0[A] = "" | A ~ Rep0[A] //│ ╔══[ERROR] Unrecognized pattern (application). //│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] //│ ╙── ^^^^^^^ +//│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. +//│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] +//│ ║ ^ +//│ ╟── The variable is missing from this sub-pattern. +//│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] +//│ ╙── ^^ :todo pattern Rep0(pattern A, B, C)(head) = "" | (A as head) ~ Rep0[A] //│ ╔══[ERROR] Multiple parameter lists are not supported for this definition. -//│ ║ l.10: pattern Rep0(pattern A, B, C)(head) = +//│ ║ l.16: pattern Rep0(pattern A, B, C)(head) = //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.11: "" | (A as head) ~ Rep0[A] +//│ ║ l.17: "" | (A as head) ~ Rep0[A] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[ERROR] Unrecognized pattern (application). -//│ ║ l.11: "" | (A as head) ~ Rep0[A] +//│ ║ l.17: "" | (A as head) ~ Rep0[A] //│ ╙── ^^^^^^^ //│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. -//│ ║ l.11: "" | (A as head) ~ Rep0[A] +//│ ║ l.17: "" | (A as head) ~ Rep0[A] //│ ║ ^^^^ //│ ╟── The variable is missing from this sub-pattern. -//│ ║ l.11: "" | (A as head) ~ Rep0[A] +//│ ║ l.17: "" | (A as head) ~ Rep0[A] //│ ╙── ^^ :todo // Pattern extractions via aliases. pattern Email(name, domain) = (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╔══[ERROR] Duplicate pattern variable. -//│ ║ l.29: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.35: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ║ ^^^^^^^^^^ //│ ╟── The previous definition is as follows. -//│ ║ l.29: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.35: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^ //│ ╔══[WARNING] Useless pattern binding: Identifier. -//│ ║ l.29: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.35: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^ :todo // View patterns pattern GreaterThan(value) = case n and n > value then n //│ ╔══[ERROR] Unrecognized pattern (case). -//│ ║ l.42: n and n > value then n +//│ ║ l.48: n and n > value then n //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ :todo @@ -54,11 +60,11 @@ fun foo(x) = if x is Unit then .... Arrow(...) then .... //│ ╔══[ERROR] Unrecognized pattern split (infix operator 'as'). -//│ ║ l.53: view as +//│ ║ l.59: view as //│ ║ ^^^^^^^ -//│ ║ l.54: Unit then .... +//│ ║ l.60: Unit then .... //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ║ l.55: Arrow(...) then .... +//│ ║ l.61: Arrow(...) then .... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -76,29 +82,31 @@ pattern Star(pattern T) = "" | Many(T) pattern Email(name, domains) = Rep(Char | ".") as name ~ "@" ~ Rep(Rep(Char) ~ ) as domain //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.73: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ║ l.79: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^ //│ ╔══[ERROR] Unrecognized pattern (juxtaposition). -//│ ║ l.73: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ║ l.79: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^^^^ :todo pattern Digits = "0" to "9" ~ (Digits | "") //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.86: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.92: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^ //│ ╔══[ERROR] Unrecognized pattern (juxtaposition). -//│ ║ l.86: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.92: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^^^^ -:todo // Extraction parameters. pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.95: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╙── ^^^ -//│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.95: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╙── ^^^ + +"foobar" is Test +//│ = true + +:fixme +:expect MatchResult(["foo", "bar"], {}) +Test.unapply("foobar") +//│ ═══[RUNTIME ERROR] Expected: 'MatchResult(["foo", "bar"], {})', got: 'MatchResult("foobar", {})' +//│ = MatchResult("foobar", {}) :todo // This is tokenized into: diff --git a/hkmc2/shared/src/test/mlscript/ups/MatchResult.mls b/hkmc2/shared/src/test/mlscript/ups/MatchResult.mls index dfd34c40ca..cac45a7203 100644 --- a/hkmc2/shared/src/test/mlscript/ups/MatchResult.mls +++ b/hkmc2/shared/src/test/mlscript/ups/MatchResult.mls @@ -35,7 +35,11 @@ fun foo(x) = x is Cross //│ JS (unsanitized): //│ let foo; //│ foo = function foo(x2) { -//│ let matchResult; -//│ matchResult = runtime.safeCall(Cross1.unapply(x2)); -//│ if (matchResult instanceof runtime.MatchResult.class) { return true } else { return false } +//│ let unapplyResult, output, bindings; +//│ unapplyResult = runtime.safeCall(Cross1.unapply(x2)); +//│ if (unapplyResult instanceof runtime.MatchResult.class) { +//│ output = unapplyResult.output; +//│ bindings = unapplyResult.bindings; +//│ return true +//│ } else { return false } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/ups/RangePatterns.mls b/hkmc2/shared/src/test/mlscript/ups/RangePatterns.mls index 0e3b6e4c26..f321ece2a2 100644 --- a/hkmc2/shared/src/test/mlscript/ups/RangePatterns.mls +++ b/hkmc2/shared/src/test/mlscript/ups/RangePatterns.mls @@ -55,11 +55,12 @@ pattern UnsignedByte = 0..< 256 //│ ╔══[ERROR] Name not found: .< //│ ║ l.51: pattern UnsignedByte = 0..< 256 //│ ╙── ^^ +//│ ═══[ERROR] Cannot use this ‹error› as a pattern. :e pattern BadRange = "s"..=0 //│ ╔══[ERROR] The upper and lower bounds of range patterns should be literals of the same type. -//│ ║ l.60: pattern BadRange = "s"..=0 +//│ ║ l.61: pattern BadRange = "s"..=0 //│ ╙── ^^^^^^^ // It becomes an absurd pattern. @@ -69,14 +70,14 @@ pattern BadRange = "s"..=0 :e pattern BadRange = 0 ..= "s" //│ ╔══[ERROR] The upper and lower bounds of range patterns should be literals of the same type. -//│ ║ l.70: pattern BadRange = 0 ..= "s" +//│ ║ l.71: pattern BadRange = 0 ..= "s" //│ ╙── ^^^^^^^^^ :e pattern BadRange = "yolo" ..= "swag" //│ ╔══[ERROR] The lower bound of character ranges must be a single character. -//│ ║ l.76: pattern BadRange = "yolo" ..= "swag" +//│ ║ l.77: pattern BadRange = "yolo" ..= "swag" //│ ║ ^^^^^^ //│ ╟── The upper bound of character ranges must be a single character. -//│ ║ l.76: pattern BadRange = "yolo" ..= "swag" +//│ ║ l.77: pattern BadRange = "yolo" ..= "swag" //│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ups/SimpleTransform.mls b/hkmc2/shared/src/test/mlscript/ups/SimpleTransform.mls index 7a25a3d079..e2538b533d 100644 --- a/hkmc2/shared/src/test/mlscript/ups/SimpleTransform.mls +++ b/hkmc2/shared/src/test/mlscript/ups/SimpleTransform.mls @@ -8,7 +8,37 @@ open annotations data class Pair[A, B](first: A, second: B) +:sjs pattern SumPair = Pair(a, b) => a + b +//│ JS (unsanitized): +//│ let SumPair1; +//│ globalThis.Object.freeze(class SumPair { +//│ static { +//│ SumPair1 = globalThis.Object.freeze(new this) +//│ } +//│ constructor() {} +//│ unapply(input) { +//│ let transform, argument0$, argument1$, transformResult, tmp, lambda; +//│ lambda = (undefined, function (a, b) { +//│ return a + b +//│ }); +//│ transform = lambda; +//│ if (input instanceof Pair1.class) { +//│ argument0$ = input.first; +//│ argument1$ = input.second; +//│ transformResult = runtime.safeCall(transform(argument0$, argument1$)); +//│ tmp = globalThis.Object.freeze({}); +//│ return runtime.MatchResult(transformResult, tmp) +//│ } else { +//│ return runtime.MatchFailure(null) +//│ } +//│ } +//│ unapplyStringPrefix(input) { +//│ return runtime.MatchFailure(null) +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["pattern", "SumPair"]; +//│ }); fun foo(value) = if value is SumPair as sum then print("sum: " + sum) diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/DnfCnf.mls b/hkmc2/shared/src/test/mlscript/ups/examples/DnfCnf.mls index 1590e55188..8f80b027ce 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/DnfCnf.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/DnfCnf.mls @@ -102,11 +102,11 @@ print of cnf6 is Dnf //│ > false //│ > false -pattern Dnf'(pattern P) = Or(Dnf'(pattern P), Dnf'(pattern P)) | Atoms'(pattern P) -pattern Atoms'(pattern P) = And(Atoms'(pattern P), Atoms'(pattern P)) | Atom'(pattern P) +pattern Dnf'(pattern P) = Or(Dnf'(P), Dnf'(P)) | Atoms'(P) +pattern Atoms'(pattern P) = And(Atoms'(P), Atoms'(P)) | Atom'(P) pattern Atom'(pattern P) = Pred(_, P) | Var -pattern DeepDnf = Dnf'(pattern DeepDnf) +pattern DeepDnf = Dnf'(DeepDnf) fun isDeepDnf(f) = f is @compile DeepDnf @@ -134,5 +134,5 @@ print of isDeepDnf of cnf6 //│ > false //│ > false -pattern Cnf'(pattern P) = And(Cnf'(pattern P), Cnf'(pattern P)) -pattern DnfOrCnf = Dnf'(pattern DnfOrCnf) | Cnf'(pattern DnfOrCnf) +pattern Cnf'(pattern P) = And(Cnf'(P), Cnf'(P)) +pattern DnfOrCnf = Dnf'(DnfOrCnf) | Cnf'(DnfOrCnf) diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/DoubleOrSum.mls b/hkmc2/shared/src/test/mlscript/ups/examples/DoubleOrSum.mls index 081196aa03..6aed6bd087 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/DoubleOrSum.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/DoubleOrSum.mls @@ -138,9 +138,8 @@ pattern DoubleOrSum = DoubleList | SumList fun isDoubleOrSum__naive(xs) = if xs is DoubleOrSum as output then output else "noop" -:todo // Fix the return value of the translation. isDoubleOrSum__naive of _1234 -//│ = Nil +//│ = Cons(1, Cons(2, Cons(3, Cons(4, Nil)))) isDoubleOrSum__naive of list(1, 2, 3) //│ > 3 + 0 = 3 diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/Extraction.mls b/hkmc2/shared/src/test/mlscript/ups/examples/Extraction.mls index 47e9615f91..cee9ef9ba9 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/Extraction.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/Extraction.mls @@ -55,20 +55,22 @@ biasedOr1 of Some(Some(Some(Some(Some(Some(Some(Some(42)))))))) // Make it a bit more complicated: biased disjunction with transformations. -pattern BiasedOr2 = Some(Some(x) => x + 1) | Some(x => x + 2) +pattern BiasedOr2 = + (Some((Some(x) => x + 1) as res) => res) | + (Some((x => x + 2) as res) => res) fun biasedOr2__compiled(x) = if x is (@compile BiasedOr2) as output then output fun biasedOr2__naive(x) = if x is BiasedOr2(output) then output biasedOr2__compiled of Some(1) -//│ = {value: 3} +//│ = 3 biasedOr2__naive of Some(1) //│ = 3 biasedOr2__compiled of Some(Some(1)) -//│ = {value: 2} +//│ = 2 biasedOr2__naive of Some(Some(1)) //│ = 2 @@ -105,6 +107,10 @@ fun exactlyOneNone(x) = fun exactlyOneNone__naive(x) = if x is ExactlyOneNone(output) then output else "failed" +// What should the output of class pattern be? In the pattern compilation, we +// return plain objects without the class tag, as we can't synthesize plain +// objects with arbitrary class tags. + exactlyOneNone of Triplet(None, Some(1), Some(2)) //│ = {first: None, second: {value: 1}, third: {value: 2}} @@ -114,16 +120,16 @@ exactlyOneNone of Triplet(Some(1), None, Some(2)) exactlyOneNone of Triplet(Some(1), Some(2), None) //│ = {first: {value: 1}, second: {value: 2}, third: None} -// It seems that the return value of the translation is not correct. +// But in the naive compilation of patterns, we return the scrutinee directly. exactlyOneNone__naive of Triplet(None, Some(1), Some(100)) -//│ = 100 +//│ = Triplet(None, Some(1), Some(100)) exactlyOneNone__naive of Triplet(Some(1), None, Some(400)) -//│ = 400 +//│ = Triplet(Some(1), None, Some(400)) exactlyOneNone__naive of Triplet(Some(1), Some(2), None) -//│ = None +//│ = Triplet(Some(1), Some(2), None) :expect "failed" exactlyOneNone of Triplet(None, Some(1), None) diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/ListPredicates.mls b/hkmc2/shared/src/test/mlscript/ups/examples/ListPredicates.mls index f910f07903..168665c72f 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/ListPredicates.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/ListPredicates.mls @@ -24,14 +24,15 @@ list1 contains of 4 // each element is unique, and rejects the list otherwise. pattern Unique(pattern P) = Nil | ( - ((((P as hd) :: (Unique(pattern P) as tl)) => + ((((P as hd) :: (Unique(P) as tl)) => let there = tl contains(hd) [there, if there then tl else hd :: tl]) as [false, list]) => list) -list1 is Unique(pattern _) +list1 is Unique(_) //│ = true -list1 is Unique(pattern (Num as x) => x.toString().length) +:expect false +list1 is Unique((Num as x) => x.toString().length) //│ = false fun addProperty(obj, k, v) = (...obj, (k): v) diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/Record.mls b/hkmc2/shared/src/test/mlscript/ups/examples/Record.mls index 525c3370cf..e6ad7932f6 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/Record.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/Record.mls @@ -11,14 +11,12 @@ pattern CalculateBMI = ((:weight, :height)) => weight / (height * height) pattern Rounded = (Num as value) => Math.round(value) -:todo // This looks decent! Make this happen! -// Note: This needs refactoring the desugarer. (weight: 70, height: 1.75) is CalculateBMI as Rounded(22) -//│ ╔══[ERROR] Unrecognized pattern (infix operator 'as') -//│ ║ l.16: (weight: 70, height: 1.75) is CalculateBMI as Rounded(22) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = false +(weight: 70, height: 1.75) is CalculateBMI as Rounded(23) +//│ = true + pattern RoundedBMI = CalculateBMI as Rounded fun bmi(record) = if record is RoundedBMI as value then value @@ -34,21 +32,31 @@ bmi of weight: 70 // returns `Some` if the pattern matches, and `None` otherwise. pattern MatchOrNone(pattern P) = ((P as x) => Some(x)) | (_ => None) -fun bmi(record) = if record is MatchOrNone(pattern RoundedBMI) as value then value +fun bmi(record) = if record is MatchOrNone(RoundedBMI) as value then value :e // alternatively? fun bmi'(record) = if record is MatchOrNone[RoundedBMI] as value then value -//│ ╔══[ERROR] Unrecognized pattern (application) -//│ ║ l.41: fun bmi'(record) = if record is MatchOrNone[RoundedBMI] as value then value +//│ ╔══[ERROR] Unrecognized pattern (application). +//│ ║ l.39: fun bmi'(record) = if record is MatchOrNone[RoundedBMI] as value then value //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ +:expect Some(23) bmi of weight: 70, height: 1.75 //│ = Some(23) bmi of height: 1.75 //│ = None +:todo // Make it happen! +fun bmi'(record) = if record is @compile MatchOrNone(RoundedBMI) as value then value +//│ ╔══[ERROR] Pattern chaining is not supported in pattern compilation. +//│ ║ l.20: pattern RoundedBMI = CalculateBMI as Rounded +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'value' +//│ ║ l.52: fun bmi'(record) = if record is @compile MatchOrNone(RoundedBMI) as value then value +//│ ╙── ^^^^^ + // Calculate the area of different shapes pattern ShapeArea = (((:radius)) => Math.PI * radius * radius) | // Circle @@ -93,14 +101,19 @@ pattern NumberSystem = (((:digits, radix: "o")) => parseInt(digits, 8)) | (((:digits, radix: "b")) => parseInt(digits, 2)) -:todo // This looks decent! Make this happen! fun parseIntegerLiteral(value) = if value is "0" ~ (("x" | "o" | "b") as radix) ~ digits and (:radix, :digits) is NumberSystem as value then Some(value) else None -//│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.98: value is "0" ~ (("x" | "o" | "b") as radix) ~ digits and -//│ ╙── ^ + +parseIntegerLiteral of "0xFF" +//│ = Some(255) + +parseIntegerLiteral of "0b11111111" +//│ = Some(255) + +parseIntegerLiteral of "0o377" +//│ = Some(255) pattern IntegerLiteral = ("0" ~ (("x" | "o" | "b") as radix) ~ digits) => (:radix, :digits) diff --git a/hkmc2/shared/src/test/mlscript/ups/examples/TupleSpread.mls b/hkmc2/shared/src/test/mlscript/ups/examples/TupleSpread.mls index e7bf47013c..d9cf0667f2 100644 --- a/hkmc2/shared/src/test/mlscript/ups/examples/TupleSpread.mls +++ b/hkmc2/shared/src/test/mlscript/ups/examples/TupleSpread.mls @@ -70,16 +70,14 @@ pattern StackLike = ([] => Nil) | ([x, ...StackLike as xs] => x :: xs) [1] is StackLike(1 :: Nil) //│ = true +:expect true [1, 2, 3, 4, 5] is StackLike(1 :: 2 :: 3 :: 4 :: 5 :: Nil) //│ = true -:todo [1, 2, 3, 4, 5] is StackLike as (1 :: 2 :: 3 :: 4 :: 5 :: Nil) -//│ ╔══[ERROR] Unrecognized pattern (infix operator 'as') -//│ ║ l.77: [1, 2, 3, 4, 5] is StackLike as (1 :: 2 :: 3 :: 4 :: 5 :: Nil) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ = false +//│ = true +:expect false [1, 2, 3, 4, 5] is StackLike(1 :: 2 :: 3 :: 4 :: Nil) //│ = false diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls index 6fc7d7c04b..c859c0ed74 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls @@ -7,25 +7,25 @@ pattern Nullable(pattern T) = T | null pattern Zero = 0 :ucs normalized -0 is Nullable(pattern Zero) +0 is Nullable(Zero) //│ Normalized: //│ > if //│ > let $scrut = 0 -//│ > let $param0 = member:Zero -//│ > let $matchResult = (member:Nullable.)unapply($param0, $scrut) -//│ > $matchResult is $runtime.MatchResult.class then true +//│ > let $patternArgument0$ = member:Zero +//│ > let $unapplyResult = (member:Nullable.)unapply($patternArgument0$, $scrut) +//│ > $unapplyResult is $runtime.MatchResult.class(output, bindings) then true //│ > else false //│ = true import "../../../mlscript-compile/Char.mls" :ucs normalized -fun foo(x) = x is Nullable(pattern Char.Letter) +fun foo(x) = x is Nullable(Char.Letter) //│ Normalized: //│ > if -//│ > let $param0 = (member:Char.)Letter‹member:Letter› -//│ > let $matchResult = (member:Nullable.)unapply($param0, x) -//│ > $matchResult is $runtime.MatchResult.class then true +//│ > let $patternArgument0$ = (member:Char.)Letter‹member:Letter› +//│ > let $unapplyResult = (member:Nullable.)unapply($patternArgument0$, x) +//│ > $unapplyResult is $runtime.MatchResult.class(output, bindings) then true //│ > else false foo of null diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls index 0f0c1be3a3..d03b57a146 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls @@ -17,50 +17,50 @@ null is @compile Nullable //│ = false :e -null is @compile Nullable(pattern A, pattern A) +null is @compile Nullable(A, A) //│ ╔══[ERROR] Pattern `Nullable` has one pattern parameter. //│ ║ l.5: pattern Nullable(pattern T) = null | T //│ ║ ^ //│ ╟── But two pattern arguments were provided. -//│ ║ l.20: null is @compile Nullable(pattern A, pattern A) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.20: null is @compile Nullable(A, A) +//│ ╙── ^^^^^^^^^^^^^ //│ = false pattern Nullable(pattern T) = null | T -null is @compile Nullable(pattern Int) +null is @compile Nullable(Int) //│ = true -A is @compile Nullable(pattern A) +A is @compile Nullable(A) //│ = true -2 is @compile Nullable(pattern 1 | 2) +2 is @compile Nullable(1 | 2) //│ = true data class Pair[A, B](val first: A, val second: B) -pattern Stack(pattern T) = null | Pair(T, Stack(pattern T)) +pattern Stack(pattern T) = null | Pair(T, Stack(T)) -null is @compile Stack(pattern A) +null is @compile Stack(A) //│ = true fun (#:) pair(a, b) = Pair(a, b) -A #: null is @compile Stack(pattern A) +A #: null is @compile Stack(A) //│ = true -A #: A #: null is @compile Stack(pattern A) +A #: A #: null is @compile Stack(A) //│ = true -A #: A #: A #: null is @compile Stack(pattern A) +A #: A #: A #: null is @compile Stack(A) //│ = true object B -A #: B #: A #: null is @compile Stack(pattern A) +A #: B #: A #: null is @compile Stack(A) //│ = false -A #: B #: A #: null is @compile Stack(pattern B) +A #: B #: A #: null is @compile Stack(B) //│ = false let zeroOne = 0 #: 1 #: null @@ -71,32 +71,32 @@ let oneZero = 1 #: 0 #: null :global :expect true -A #: B #: A #: null is @compile Stack(pattern A | B) +A #: B #: A #: null is @compile Stack(A | B) //│ = true -A #: B #: A #: null is @compile Stack(pattern B | A) +A #: B #: A #: null is @compile Stack(B | A) //│ = true -zeroOne is @compile Stack(pattern 0 | 1) +zeroOne is @compile Stack(0 | 1) //│ = true -oneZero is @compile Stack(pattern 0 | 1) +oneZero is @compile Stack(0 | 1) //│ = true -null #: null is @compile Stack(pattern Stack(pattern 0 | 1)) +null #: null is @compile Stack(Stack(0 | 1)) //│ = true -zeroOne #: null is @compile Stack(pattern Stack(pattern 0 | 1)) +zeroOne #: null is @compile Stack(Stack(0 | 1)) //│ = true -null #: (1 #: null) #: null is @compile Stack(pattern Stack(pattern 0 | 1)) +null #: (1 #: null) #: null is @compile Stack(Stack(0 | 1)) //│ = true -zeroOne #: oneZero #: null is @compile Stack(pattern Stack(pattern 0 | 1)) +zeroOne #: oneZero #: null is @compile Stack(Stack(0 | 1)) //│ = true -zeroOne #: null #: oneZero #: null is @compile Stack(pattern Stack(pattern 0 | 1)) +zeroOne #: null #: oneZero #: null is @compile Stack(Stack(0 | 1)) //│ = true -null #: zeroOne #: zeroOne #: null is @compile Stack(pattern Stack(pattern 0 | 1)) +null #: zeroOne #: zeroOne #: null is @compile Stack(Stack(0 | 1)) //│ = true diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/ListLike.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/ListLike.mls index c4b2cf2380..dfcf9b16f1 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/ListLike.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/ListLike.mls @@ -6,9 +6,9 @@ import "../../../mlscript-compile/Stack.mls" open Stack -pattern ListLike(pattern T) = Nil | (T :: ListLike(pattern T)) +pattern ListLike(pattern T) = Nil | (T :: ListLike(T)) -fun isOddIntList(xs) = xs is ListLike(pattern ((Int as x) => x % 2 === 1) as true) +fun isOddIntList(xs) = xs is ListLike(((Int as x) => x % 2 === 1) as true) isOddIntList of Nil //│ = true @@ -27,4 +27,4 @@ isOddIntList of "hello" :: "world" :: Nil pattern Nullable(pattern T) = null | T -fun isNullableIntList(xs) = xs is @compile ListLike(pattern Nullable(pattern Int)) +fun isNullableIntList(xs) = xs is @compile ListLike(Nullable(Int)) diff --git a/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls index 316ed5e38a..0b96d1f0e6 100644 --- a/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls +++ b/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls @@ -11,7 +11,7 @@ pattern Nullable(pattern T) = null | T // This creates an inline object with `unapply` method and pass it to // `Nullable.unapply`. -fun isPositiveOrNull(x) = x is Nullable(pattern ((Int as x) => x > 0) as true) +fun isPositiveOrNull(x) = x is Nullable(((Int as x) => x > 0) as true) isPositiveOrNull of 0 //│ = false @@ -28,7 +28,7 @@ isPositiveOrNull of -2 // No transformation, so the output is the same as the input. fun mightBeInt(x) = - if x is (@compile Nullable(pattern Int)) as output then output + if x is (@compile Nullable(Int)) as output then output mightBeInt of 42 //│ = 42 @@ -46,13 +46,13 @@ open Option { Some, None } pattern Optional(pattern T) = (null => None) | ((T as x) => Some(x)) -null is Optional(pattern Int, None) +null is Optional(Int) as None //│ = true -42 is Optional(pattern Int, Some(42)) +42 is Optional(Int) as Some(42) //│ = true -null is Optional(pattern Int, Some(42)) +null is Optional(Int) as Some(42) //│ = false :e @@ -65,7 +65,7 @@ fun toIntOption(x) = if x is (@compile Optional) as o then o //│ ║ l.60: fun toIntOption(x) = if x is (@compile Optional) as o then o //│ ╙── ^^^^^^^^ -fun toIntOption(x) = if x is (@compile Optional(pattern Int)) as o then o +fun toIntOption(x) = if x is (@compile Optional(Int)) as o then o toIntOption of 42 //│ = Some(42) diff --git a/hkmc2/shared/src/test/mlscript/ups/regex/EmailAddress.mls b/hkmc2/shared/src/test/mlscript/ups/regex/EmailAddress.mls index 4de7f9a3ac..f25fac5c87 100644 --- a/hkmc2/shared/src/test/mlscript/ups/regex/EmailAddress.mls +++ b/hkmc2/shared/src/test/mlscript/ups/regex/EmailAddress.mls @@ -4,14 +4,16 @@ import "../../../mlscript-compile/Char.mls" import "../../../mlscript-compile/Iter.mls" import "../../../mlscript-compile/Stack.mls" +open Iter { joined } + // To match simple email addresses matched by the following regular expressions: // ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$ pattern UserNameLetter = Char.Letter | Char.Digit | "." | "_" | "%" | "+" | "-" -pattern Rep1(pattern S) = S ~ (Rep1(pattern S) | "") +pattern Rep1(pattern S) = S ~ (Rep1(S) | "") -pattern UserName = Rep1(pattern UserNameLetter) +pattern UserName = Rep1(UserNameLetter) :expect true "john.doe" is UserName @@ -33,11 +35,10 @@ pattern UserName = Rep1(pattern UserNameLetter) "" is UserName //│ = false -:todo // Nested pattern arguments seem problematic. -pattern DomainSuffix = Rep1(pattern ("." ~ Char.Letter ~ Rep1(pattern Char.Letter))) +pattern DomainSuffix = Rep1("." ~ Char.Letter ~ Rep1(Char.Letter)) -pattern DomainSuffixFragment = "." ~ Char.Letter ~ Rep1(pattern Char.Letter) +pattern DomainSuffixFragment = "." ~ Char.Letter ~ Rep1(Char.Letter) [".com", ".hk", ".fr", ".cn"] Iter.mapping of _ is DomainSuffixFragment @@ -49,11 +50,11 @@ pattern DomainSuffixFragment = "." ~ Char.Letter ~ Rep1(pattern Char.Letter) Iter.folded of false, _ || _ //│ = false -pattern DomainSuffix = Rep1(pattern DomainSuffixFragment) +pattern DomainSuffix = Rep1(DomainSuffixFragment) pattern DomainNameLetter = Char.Letter | Char.Digit | "-" -pattern DomainName = Rep1(pattern DomainNameLetter) +pattern DomainName = Rep1(DomainNameLetter) "google" is DomainName //│ = true @@ -84,12 +85,20 @@ emails open Stack pattern CommaSep(pattern S) = - ("" => Nil) | - ((S as head) ~ "," ~ (CommaSep(pattern S) as tail)) => head :: tail + (((S as head) ~ "," ~ (CommaSep(S) as tail)) => head :: tail) | + (S as head => head :: Nil) | + ("" => Nil) fun parseEmails(input) = - if input is CommaSep(pattern Email) as emails then emails else Nil - -:todo -parseEmails of emails.join(",") -//│ = Nil + if input is CommaSep(Email) as emails then emails else Nil + +parseEmails(emails.join(",")) Iter.fromStack() Iter.toArray() +//│ = [ +//│ "example@example.com", +//│ "john.doe@guardian.co.uk", +//│ "alice_bob123@sub-domain.example.org", +//│ "user+mailbox@my-domain.net", +//│ "test.user%filter@service-provider.co.in", +//│ "foo-bar@company-name.io", +//│ "simple123@abc.xyz" +//│ ] diff --git a/hkmc2/shared/src/test/mlscript/ups/regex/Separation.mls b/hkmc2/shared/src/test/mlscript/ups/regex/Separation.mls index 2f38c80f84..b7de505dd3 100644 --- a/hkmc2/shared/src/test/mlscript/ups/regex/Separation.mls +++ b/hkmc2/shared/src/test/mlscript/ups/regex/Separation.mls @@ -1,15 +1,52 @@ :js +import "../../../mlscript-compile/Iter.mls" pattern TailLines(pattern L) = - ("" => []) | - ("\n" ~ (Lines(pattern L) as t)) => t + (("\n" ~ (Lines(L) as t)) => t) | + ("" => []) pattern Lines(pattern L) = - ("" => []) | - (((L as h) ~ (TailLines(pattern L) as t)) => [h, ...t]) + ((((L as h) ~ (TailLines(L) as t)) => [h, ...t])) | + ("" => []) -fun foo(input) = input is Lines("hello") +:expect true +"hello" is Lines("hello") +//│ = true -:fixme -foo of "hello" -//│ ═══[RUNTIME ERROR] Error: Function 'unapply' expected 2 arguments but got 1 +:expect true +"hello\nworld" is Lines("hello" | "world") +//│ = true + +:expect false +"hello\nworld" is Lines("hello") +//│ = false + +pattern Digit = "0" ..= "9" + +pattern Integer = Digit ~ (Integer | "") + +"12345" is Integer +//│ = true + +Integer.unapplyStringPrefix("123") +//│ = MatchResult(["123", ""], {}) + +// Beautiful! +"123\n456\n789" is Lines of Integer +//│ = true + +fun parseIntegers(input) = + if input is (Lines of (Integer as n) => parseInt(n, 10)) as n then n + +parseIntegers of "123\n456\n789" +//│ = [123, 456, 789] + +:e +:re +// We may need a shorthand syntax to obtain the result of pattern matching. +// `t through P` means match `t` against `P` and returns the result. +"123\n456\n789" through Lines(Integer) // ==> [123, 456, 789] +//│ ╔══[ERROR] Illegal juxtaposition right-hand side (identifier). +//│ ║ l.48: "123\n456\n789" through Lines(Integer) // ==> [123, 456, 789] +//│ ╙── ^^^^^^^ +//│ ═══[RUNTIME ERROR] TypeError: Lines1 is not a function diff --git a/hkmc2/shared/src/test/mlscript/ups/regex/TailRepetition.mls b/hkmc2/shared/src/test/mlscript/ups/regex/TailRepetition.mls index 5cb2e71cc7..66ba609aed 100644 --- a/hkmc2/shared/src/test/mlscript/ups/regex/TailRepetition.mls +++ b/hkmc2/shared/src/test/mlscript/ups/regex/TailRepetition.mls @@ -42,7 +42,7 @@ pattern ManyZeros = "0" ~ (ManyZeros | "") "1000" is ManyZeros //│ = false -pattern Rep(pattern S) = S ~ (Rep(pattern S) | "") +pattern Rep(pattern S) = S ~ (Rep(S) | "") -"00000" is Rep(pattern "0") +"00000" is Rep("0") //│ = true diff --git a/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleList.mls b/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleList.mls index 43c53075d4..9d11ffec40 100644 --- a/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleList.mls +++ b/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleList.mls @@ -69,9 +69,7 @@ pattern D = Box(Box(Box) | Bowl) pattern D_0 = Box(Box(Box) | Box(Bowl)) -:todo pattern D_1 = Box(Box(Bowl) | Box) -//│ ═══[COMPILATION ERROR] No definition found in scope for member 'output' pattern D_2 = Box(Bowl | Bowl | Bowl) @@ -140,15 +138,13 @@ fun (::) cons(head, tail) = Pair(head, tail) 0 :: 1 :: 0 :: null is @compile BinSeq //│ = true -:todo -// Note the location of `null` is missing because of the parse rule doesn't -// attach location information to `UnitLit(true)`. +:e pattern List(a) = null | Pair(a, List) //│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. -//│ ║ l.146: pattern List(a) = null | Pair(a, List) +//│ ║ l.142: pattern List(a) = null | Pair(a, List) //│ ║ ^ //│ ╟── The variable is missing from this sub-pattern. -//│ ║ l.146: pattern List(a) = null | Pair(a, List) +//│ ║ l.142: pattern List(a) = null | Pair(a, List) //│ ╙── ^^^^ null is @compile List diff --git a/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleLiterals.mls b/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleLiterals.mls index 00d579bb8c..9bc936407e 100644 --- a/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleLiterals.mls +++ b/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleLiterals.mls @@ -6,24 +6,22 @@ pattern Zero = "0" pattern One = "1" pattern Two = "2" -:ucs normalized -:fixme fun checkZeroOneTwo(x) = if x is @compile Zero then 0 @compile One then 1 @compile Two then 2 else () -//│ ╔══[ERROR] Unrecognized pattern split (annotated). -//│ ║ l.12: @compile Zero then 0 -//│ ╙── ^^^^^^^^^^^^^^^^^^^ -//│ ╔══[ERROR] Unrecognized pattern split (annotated). -//│ ║ l.13: @compile One then 1 -//│ ╙── ^^^^^^^^^^^^^^^^^^ -//│ ╔══[ERROR] Unrecognized pattern split (annotated). -//│ ║ l.14: @compile Two then 2 -//│ ╙── ^^^^^^^^^^^^^^^^^^ -//│ Normalized: -//│ > if else + +checkZeroOneTwo of "0" +//│ = 0 + +checkZeroOneTwo of "1" +//│ = 1 + +checkZeroOneTwo of "2" +//│ = 2 + +checkZeroOneTwo of "hello" import "../../../mlscript-compile/Option.mls" open Option @@ -40,7 +38,7 @@ pattern ManyZero = ("0" ~ (ManyZero | "")) | "" :todo let res = "1" is @compile ManyZero //│ ╔══[ERROR] String concatenation is not supported in pattern compilation. -//│ ║ l.37: pattern ManyZero = ("0" ~ (ManyZero | "")) | "" +//│ ║ l.35: pattern ManyZero = ("0" ~ (ManyZero | "")) | "" //│ ╙── ^^^^^^^^^^^^^^^^^^^^ //│ res = false diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/CrossCompilation.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/CrossCompilation.mls new file mode 100644 index 0000000000..4561923e4c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/CrossCompilation.mls @@ -0,0 +1,43 @@ +:js + +open annotations + +// After sharing UCS desugaring with UPS, we can selectively efficiently compile +// sub-patterns in a naively compiled pattern. + +pattern ZeroOne = 0 | 1 + +pattern SevenEight = 7 | 8 + +:ucs instantiation +// Note that only `ZeroOne` is instantiated. +pattern Bar = SevenEight | @compile ZeroOne +//│ | Instantiating ZeroOne +//│ | Instantiated ZeroOne +//│ | > arity = 0 +//│ | > arguments = +//│ | > instantiated = (0 ∨ 1) + +:expect true +0 is Bar +//│ = true + +:expect true +1 is Bar +//│ = true + +:expect false +2 is Bar +//│ = false + +:expect true +0 is @compile Bar +//│ = true + +:expect true +1 is @compile Bar +//│ = true + +:expect false +2 is @compile Bar +//│ = false diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/InterestingPatterns.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/InterestingPatterns.mls index 7f627d6032..7db1b28eb4 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/InterestingPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/InterestingPatterns.mls @@ -10,7 +10,7 @@ pattern DropZ = ((:x, :y, :z)) => (x: x, y: y) :ucs ups pattern SumList = (Nil => 0) | ((Int as hd) :: (SumList as tl)) => hd + tl -//│ elaborated pattern body: member:Nil => 0 ∨ member:Cons(member:Int as hd, member:SumList as tl) => builtin:+(hd, tl) +//│ elaborated pattern body: Nil => 0 ∨ Cons(Int as hd, SumList as tl) => builtin:+(hd, tl) // Flatten the nested tuples. Note that it makes use of biased union. pattern Flatten = ([Flatten as hd, ...Flatten as tl] => Array.concat(hd, tl)) | _ @@ -33,7 +33,7 @@ pattern Transpose = (null => null) | (([[a, b], [c, d]]) => [[a, c], [b, d]]) // Return the middle element of a list. It won't match if the length is even. :ucs ups pattern Middle = ([x] => x) | ([x, ...(Middle as m), y] => m) -//│ elaborated pattern body: [x] => x ∨ [x, ...member:Middle as m, y] => m +//│ elaborated pattern body: [x] => x ∨ [x, ...Middle as m, y] => m // Return the leaves of a tree. pattern Leaves = (null => Nil) | ([x] => x :: Nil) | ([Leaves as left, Leaves as right] => left ::: right) diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls index e329715400..63c7ef4e9e 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls @@ -19,7 +19,7 @@ class Pair[A, B](val fst: A, val snd: B) :ucs ups pattern PlainList(pattern T) = (null => Nil) | (Pair(T as hd, PlainList(T) as tl) => hd :: tl) -//│ elaborated pattern body: null => (member:Stack.)Nil‹member:Nil› ∨ member:Pair(T as hd, member:PlainList(T) as tl) => (member:Stack.)Cons‹member:Cons›(hd, tl) +//│ elaborated pattern body: null => (member:Stack.)Nil‹member:Nil› ∨ Pair(T as hd, PlainList(T) as tl) => (member:Stack.)Cons‹member:Cons›(hd, tl) :todo :ucs ups @@ -44,7 +44,7 @@ pattern PairLike = [fst, snd] => Pair(fst, snd) :ucs ups pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] -//│ elaborated pattern body: member:Pair(fst, snd) ∨ [fst, snd] +//│ elaborated pattern body: Pair(fst, snd) ∨ [fst, snd] :w pattern UselessParameter = x @@ -54,49 +54,33 @@ pattern UselessParameter = x :ucs ups pattern PlainList(pattern T) = (null => Nil) | ([T as hd, PlainList(T) as tl] => hd :: tl) -//│ elaborated pattern body: null => (member:Stack.)Nil‹member:Nil› ∨ [T as hd, member:PlainList(T) as tl] => (member:Stack.)Cons‹member:Cons›(hd, tl) - -:fixme -[T as hd, PlainList(pattern T) as tl] -//│ ╔══[ERROR] Name not found: T -//│ ║ l.60: [T as hd, PlainList(pattern T) as tl] -//│ ╙── ^ -//│ ╔══[ERROR] Name not found: hd -//│ ║ l.60: [T as hd, PlainList(pattern T) as tl] -//│ ╙── ^^ -//│ ╔══[ERROR] Illegal type declaration in term position. -//│ ║ l.60: [T as hd, PlainList(pattern T) as tl] -//│ ╙── ^^^^^^^^^ -//│ ╔══[ERROR] Name not found: tl -//│ ║ l.60: [T as hd, PlainList(pattern T) as tl] -//│ ╙── ^^ -//│ = [] +//│ elaborated pattern body: null => (member:Stack.)Nil‹member:Nil› ∨ [T as hd, PlainList(T) as tl] => (member:Stack.)Cons‹member:Cons›(hd, tl) :ucs ups pattern Consistent = ((Int as x) | (Str as x)) => x.toString() -//│ elaborated pattern body: (member:Int as x ∨ member:Str as x) => x.toString() +//│ elaborated pattern body: (Int as x ∨ Str as x) => x.toString() :e pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.80: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.64: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.80: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.64: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.80: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.64: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╙── ^ :e pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() //│ ╔══[ERROR] Name not found: x -//│ ║ l.92: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() +//│ ║ l.76: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() //│ ╙── ^ :ucs ups pattern Negated = ~(Int | Str) -//│ elaborated pattern body: ¬(member:Int ∨ member:Str) +//│ elaborated pattern body: ¬(Int ∨ Str) :todo // The precedence of `=>` is higher than `~`? :pt @@ -112,42 +96,42 @@ pattern BadNegated = ~(Int as x) => x //│ lhs = Tup of Ls of //│ InfixApp: //│ lhs = Ident of "Int" -//│ kw = keyword 'as' +//│ kw = Keywrd of keyword 'as' //│ rhs = Ident of "x" -//│ kw = keyword '=>' +//│ kw = Keywrd of keyword '=>' //│ rhs = Ident of "x" :ucs ups :e pattern BadNegated = (~(Int as x)) => x //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.122: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.106: pattern BadNegated = (~(Int as x)) => x //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.122: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.106: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.122: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.106: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^ -//│ elaborated pattern body: ¬(member:Int as x) => +//│ elaborated pattern body: ¬(Int as x) => :ucs ups :e pattern BadNegated = (~Pair(Int, x)) => x //│ ╔══[ERROR] This variable cannot be accessed. -//│ ║ l.136: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.120: pattern BadNegated = (~Pair(Int, x)) => x //│ ║ ^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.136: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.120: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.136: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.120: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^ -//│ elaborated pattern body: ¬member:Pair(member:Int, x) => +//│ elaborated pattern body: ¬Pair(Int, x) => :ucs ups pattern DoubleNegated = ~(~Int) -//│ elaborated pattern body: ¬¬member:Int +//│ elaborated pattern body: ¬¬Int :ucs ups pattern Nothing = ~_ @@ -178,10 +162,22 @@ pattern Digit = "0" ..= "9" pattern Naughty = CanYouSeeMe(n) => n data class CanYouSeeMe(wow: Int) - +:e +// Even if a certain class is under the selection of multiple modules, we should +// still be able to resolve it correctly. fun y = HiddenCorner.m.m.k pattern Naughty = HiddenCorner.m.m.YouCantSeeMe(n) => n + HiddenCorner.m.m.k +pattern Obedient = HiddenCorner.m.m.CantYouSeeMe(n) => n + HiddenCorner.m.m.k module HiddenCorner with val m: module HiddenCorner = HiddenCorner class YouCantSeeMe(wow: Int) + class CantYouSeeMe(val wow: Int) val k = 0 +//│ ╔══[ERROR] This pattern cannot be matched +//│ ║ l.169: pattern Naughty = HiddenCorner.m.m.YouCantSeeMe(n) => n + HiddenCorner.m.m.k +//│ ║ ^ +//│ ╟── because the corresponding parameter `wow` is not publicly accessible +//│ ║ l.173: class YouCantSeeMe(wow: Int) +//│ ║ ^^^ +//│ ╟── Suggestion: use a wildcard pattern `_` in this position +//│ ╙── Suggestion: mark this parameter with `val` so it becomes accessible diff --git a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls index 98649d3c2e..e97a5bf186 100644 --- a/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls +++ b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls @@ -2,11 +2,30 @@ class Foo(val bar: Int) +pattern Bar = Foo(0) + +Foo(0) is Bar +//│ = true + +Foo(1) is Bar +//│ = false + +pattern Baz(pattern A) = A + +:e +pattern Baz(pattern A) = A() +//│ ╔══[ERROR] `A` is a pattern parameter. +//│ ║ l.16: pattern Baz(pattern A) = A() +//│ ║ ^ +//│ ╟── It cannot be applied. +//│ ║ l.16: pattern Baz(pattern A) = A() +//│ ╙── ^ + :e -pattern Bar = Foo(pattern 0) -//│ ╔══[ERROR] This literal pattern cannot be used as an argument here. -//│ ║ l.6: pattern Bar = Foo(pattern 0) -//│ ╙── ^ -//│ ╔══[ERROR] Expected one argument, but found only zero arguments. -//│ ║ l.6: pattern Bar = Foo(pattern 0) -//│ ╙── ^^^ +pattern Baz(pattern A) = A(1, 2, 3) +//│ ╔══[ERROR] `A` is a pattern parameter. +//│ ║ l.25: pattern Baz(pattern A) = A(1, 2, 3) +//│ ║ ^ +//│ ╟── It cannot be applied to any arguments. +//│ ║ l.25: pattern Baz(pattern A) = A(1, 2, 3) +//│ ╙── ^^^^^^^^^ diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/BbmlDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/BbmlDiffMaker.scala index 71b8248465..3ffa0837ab 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/BbmlDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/BbmlDiffMaker.scala @@ -37,6 +37,7 @@ abstract class BbmlDiffMaker extends JSBackendDiffMaker: if bbmlOpt.isSet then given Scope = Scope.empty if bbmlTyper.isEmpty then + given Elaborator.Ctx = curCtx bbmlTyper = S(BBTyper()) given hkmc2.bbml.BbCtx = bbCtx.copy(raise = summon) val typer = bbmlTyper.get