From 5a5d80611fa6322b1d276307a972c844b801ac36 Mon Sep 17 00:00:00 2001 From: Florent Ferrari Date: Tue, 10 Jun 2025 13:26:24 +0800 Subject: [PATCH 01/62] wip new Pattern enum --- .../scala/hkmc2/semantics/ups/Pattern.scala | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala new file mode 100644 index 0000000000..c01d570b3b --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala @@ -0,0 +1,237 @@ +package hkmc2 +package semantics +package ups + + +import mlscript.utils.AnyOps +import mlscript.utils.shorthands.* + +import syntax.Tree +import Tree.Ident +import Term.Lit + + +enum Pattern: + import Pattern.{Wildcard, Never} + + case Lit(lit: Term.Lit) + + /** Represents a constructor or a class, + * possibly with arguments. + * @param sym the class symbol corresponding to the class or the constructor + * @param arguments is None if no arguments were provided (as + * in ``Foo``, and is Some if there were arguments provided, even if + * it is an empty argument list, e.g. ``Bar()``) + * + * the list contains the patterns in the order with which they were given + * along with the corresponding identifier, even if it was not present before. + * e.g. if we have a class defintion ``class L = Nil | Cons(hd: Int, tl: L)`` + * then the pattern ``Cons(foo, bar)`` is expected to be + * ``ClassLike(sym=Cons, arguments=Som(List((hd, foo), tl, bar)))`` + */ + case ClassLike( + sym: ClassSymbol, + arguments: Opt[Ls[(Ident, Pattern)]] + ) + + case Record(entries: Map[Ident, Pattern]) + + /** @param entries is the ordered list of patterns, from left to right + * @param strict is true if we matchexactly these fields, and no more, + * it is false, if we are allowed to have more fields. + * + * Tuple([p0, ..., p(n-1)], true) <~> {0:p0, ..., n-1: p(n-1)} & Not({n:_}) + * Tuple([p0, ..., p(n-1)], false) <~> {0:p0, ..., n-1: p(n-1)} + */ + case Tuple(entries: List[Pattern], strict: Bool) + + case And(patterns: List[Pattern]) + + case Or(patterns: List[Pattern]) + + case Not(pattern: Pattern) + + case Rename(pattern: Pattern, name: VarSymbol) + + case Extract(pattern: Pattern, term: Term) + + /** Represents a pattern Synonym, + * possibly with arguments. + * @param sym the pattern symbol corresponding to the class or the constructor + * @param arguments is None if no arguments were provided (as + * in ``Foo``, and is Some if there were arguments provided, even if + * it is an empty argument list, e.g. ``Bar()``) + */ + case Synonym(sym: PatternSymbol, params: Opt[Ls[Pattern]]) + + /** A simplified fold for ``Pattern``. + * It is designed to be used when we want to + * compute a single value, starting from the leaves. + * It silently goes through ``Not``, ``Extract``, ``Rename`` and ``Synonym``. + * The function must provide all the other base cases, and how + * a list is merged (for ``And`` and ``Or`` nodes) + */ + def fold[A](f: (Lit | ClassLike | Record | Tuple | List[A]) => A): A = this match + case p: (Lit | ClassLike | Record | Tuple) => f(p) + case And(patterns) => f(patterns.map(_.fold(f))) + case Or(patterns) => f(patterns.map(_.fold(f))) + case Not(pattern) => pattern.fold(f) + case Rename(pattern, _) => pattern.fold(f) + case Extract(pattern, _) => pattern.fold(f) + case Synonym(_, params) => ??? // TODO call on the body + + def heads: Set[Term.Lit | ClassSymbol] = this.fold: + case Lit(lit) => Set(lit) + case ClassLike(sym, _) => Set(sym) + case _: (Record | Tuple) => Set() + case ls: List[Set[Term.Lit | ClassSymbol]] => ls.toSet.flatten + + + def fields: Set[Ident | Int] = this.fold: + case _: (Lit | ClassLike) => Set() + case Record(entries) => entries.keys.toSet[Ident | Int] + case Tuple(entries, strict) => + val n = entries.size + val subfields = Range(0, n).toSet[Ident | Int] + // if this is strict, then a condition is imposed on field n + if strict then subfields + n else subfields + case ls: List[Set[Ident | Int]] => ls.toSet.flatten + + def collectSubPatterns(id: Ident): Set[Pattern] = this.fold: + case Lit(_) => Set() + // TODO: raise a warning + case ClassLike(sym, arguments) => /* TODO:raise a warning */ arguments match + case None => Set() + case Some(arguments) => + arguments.find((id1, _) => id === id).map((_, p) => p) match + case None => Set() + case Some(value) => Set(value) + case Record(entries) => entries.get(id).toSet + case Tuple(entries, strict) => Set() + case ls: List[Set[Pattern]] => ls.toSet.flatten + + def collectSubPatterns(n: Int): Set[Pattern] = this.fold: + case _: (Lit | ClassLike) => /* TODO : riase a warning */ Set() + case Record(_) => Set() + case Tuple(entries, strict) => entries.lift(n).toSet + case ls: List[Set[Pattern]] => ls.toSet.flatten + + // old versions without fold + + // def heads: Set[Term.Lit | ClassSymbol] = this match + // case _: (Record | Tuple) => Set() + // case Lit(lit) => Set(lit) + // case ClassLike(sym, _) => Set(sym) + // case And(patterns) => patterns.toSet.flatMap(_.heads) + // case Or(patterns) => patterns.toSet.flatMap(_.heads) + // case Not(pattern) => pattern.heads + // case Rename(pattern, _) => pattern.heads + // case Extract(pattern, _) => pattern.heads + // case Synonym(sym, params) => ??? // TODO : raise a warning + + // def fields: Set[Ident | Int] = this match + // case _: (Lit | ClassLike) => Set() + // // TODO : raise a warning + // case Record(entries) => entries.keys.toSet[Ident | Int] + // case Tuple(entries, strict) => + // val n = entries.size + // val subfields = Range(0, n).toSet[Ident | Int] + // // if this is strict, then a condition is imposed on field n + // if strict then subfields + n else subfields + // case And(patterns) => patterns.toSet.flatMap(_.fields) + // case Or(patterns) => patterns.toSet.flatMap(_.fields) + // case Not(pattern) => pattern.fields + // case Rename(pattern, _) => pattern.fields + // case Extract(pattern, _) => pattern.fields + // case Synonym(sym, params) => ??? // TODO : raise a warning + + // def collectSubPatterns(id: Ident): Set[Pattern] = this match + // case Lit(_) => Set() + // // TODO: raise a warning + // case ClassLike(sym, arguments) => /* TODO:raise a warning */ arguments match + // case None => Set() + // case Some(arguments) => + // arguments.find((id1, _) => id === id).map((_, p) => p) match + // case None => Set() + // case Some(value) => Set(value) + // case Record(entries) => entries.get(id).toSet + // case Tuple(entries, strict) => Set() + // case And(patterns) => patterns.toSet.flatMap(_.collectSubPatterns(id)) + // case Or(patterns) => patterns.toSet.flatMap(_.collectSubPatterns(id)) + // case Not(pattern) => pattern.collectSubPatterns(id) + // case Rename(pattern, _) => pattern.collectSubPatterns(id) + // case Extract(pattern, _) => pattern.collectSubPatterns(id) + // case Synonym(sym, params) => ??? + + // def collectSubPatterns(n: Int): Set[Pattern] = this match + // case _: (Lit | ClassLike) => /* TODO : riase a warning */ Set() + // case Record(_) => Set() + // case Tuple(entries, strict) => entries.lift(n).toSet + // case And(patterns) => patterns.toSet.flatMap(_.collectSubPatterns(n)) + // case Or(patterns) => patterns.toSet.flatMap(_.collectSubPatterns(n)) + // case Not(pattern) => pattern.collectSubPatterns(n) + // case Rename(pattern, _) => pattern.collectSubPatterns(n) + // case Extract(pattern, _) => pattern.collectSubPatterns(n) + // case Synonym(sym, params) => ??? + + def simplify: Pattern = this match + case _: Lit => this + case ClassLike(sym, arguments) => + ClassLike(sym, arguments.map(_.map((id, p) => (id, p.simplify)))) + 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(entries, strict) => + val simplify = entries.map(_.simplify) + if simplify.contains(Never) then Never else Tuple(simplify, strict) + case And(patterns) => + val simplify = patterns.map(_.simplify) + // we cannot simplify wildcard, because we still return the scrutinee + if simplify.contains(Never) then Never else And(simplify) + case Or(patterns) => + def simplifyOr(patterns: List[Pattern]): List[Pattern] = patterns match + case Nil => Nil + case p :: tl => p.simplify match + case Never => simplifyOr(tl) + case Wildcard => Wildcard :: Nil + case pat => pat :: simplifyOr(tl) + Or(simplifyOr(patterns)) + case Not(pattern) => Not(pattern.simplify) + case Rename(pattern, name) => Rename(pattern.simplify, name) + case Extract(pattern, term) => Extract(pattern.simplify, term) + case Synonym(sym, params) => ??? + + def specialize(lit: Term.Lit): Pattern = this match + case Lit(lit1) => + if lit1 === lit then Wildcard else Never + case ClassLike(_, _) => Never + case Record(_) => Never + // TODO : are we sure that a literal can't have fields? + case Tuple(Nil, false) => ??? + case Tuple(_, _) => Never + case And(patterns) => And(patterns.map(_.specialize(lit))) + case Or(patterns) => Or(patterns.map(_.specialize(lit))) + case Not(pattern) => Not(pattern.specialize(lit)) + case Rename(pattern, name) => Rename(pattern.specialize(lit), name) + case Extract(pattern, term) => Extract(pattern.specialize(lit), term) + case Synonym(sym, params) => Synonym(sym, params.map(_.map(_.specialize(lit)))) + + def specialize(cons: ClassSymbol): Pattern = this match + case Lit(_) => Never + case ClassLike(sym, arguments) => + if sym === cons then Wildcard else Never + case Record(_) => this + case Tuple(Nil, false) => ??? + case Tuple(_, _) => Never + case And(patterns) => And(patterns.map(_.specialize(cons))) + case Or(patterns) => Or(patterns.map(_.specialize(cons))) + case Not(pattern) => Not(pattern.specialize(cons)) + case Rename(pattern, name) => Rename(pattern.specialize(cons), name) + case Extract(pattern, term) => Extract(pattern.specialize(cons), term) + case Synonym(sym, params) => Synonym(sym, params.map(_.map(_.specialize(cons)))) + +object Pattern: + + val Wildcard = Or(Nil) + + val Never = And(Nil) From d5c78ca08a57e1397019dc376eda013adf4b9640 Mon Sep 17 00:00:00 2001 From: Florent Ferrari Date: Tue, 10 Jun 2025 18:00:17 +0800 Subject: [PATCH 02/62] wip pattern compiler --- .../scala/hkmc2/semantics/ups/Compiler.scala | 61 +++++++ .../scala/hkmc2/semantics/ups/Pattern.scala | 157 ++++++++++++------ 2 files changed, 165 insertions(+), 53 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala new file mode 100644 index 0000000000..36de0ecb55 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -0,0 +1,61 @@ +package hkmc2 +package semantics +package ups + + +import mlscript.utils.shorthands.* + +import syntax.Tree, Tree.Ident +import codegen.{Block, Case, Match, End} +import Pattern.Head + +import collection.mutable.Map as MutMap +import collection.immutable.{Set, Map} +import hkmc2.codegen.Value.Rcd +import hkmc2.codegen.RcdArg + + +class Compiler(using Elaborator.State, Raise): + + private type Label = Int + + var labelMap: MutMap[Pattern, Label] = MutMap() + + var multiMatchers: MutMap[Set[Label], BlockLocalSymbol] = MutMap() + var implementations: MutMap[BlockLocalSymbol, Block] = MutMap() + + extension (pattern: Pattern) + + def label: Label = labelMap.getOrElseUpdate(pattern, labelMap.size) + + def buildMatchFunction(patterns: Set[Pattern]): BlockLocalSymbol = + val labels = patterns.map(_.label) + multiMatchers.get(labels) match + case Some(f) => f + case None => + val f = TempSymbol(N, s"multimatcher_${multiMatchers.size}") + multiMatchers += (labels -> f) + val expandedPatterns = patterns.map(p => (p.label, p.expand())) + val heads = expandedPatterns.flatMap((_, p) => p.heads).toList + val scrut = TempSymbol(N, s"scrut") + val branches = heads.map: head => + val specialized = expandedPatterns.map((l, p) => (l, p.specialize(Some(head)))) + val branch = multiMatcherBranch(specialized, scrut) + head match + case Term.Lit(lit) => (Case.Lit(lit), branch) + case sym: ClassSymbol => (Case.Cls(sym, ???), branch) + val els = + val specialized = expandedPatterns.map((l, p) => (l, p.specialize(None))) + multiMatcherBranch(specialized, scrut) + implementations += (f -> Match(/* scrut */???, branches, Some(els), ???)) + f + + def multiMatcherBranch(patterns: Set[(Label, Pattern)], scrut: BlockLocalSymbol): Block = + val labels = patterns.map((l, _) => l) + val fields = patterns.flatMap((_, p) => p.fields) + val subScrutineeVars = Map.from(fields.map(id => id -> TempSymbol(N, s"$scrut.$id"))) + val bindings = fields.map: field => + val subPatterns = patterns.flatMap((_, p) => p.collectSubPatterns(field)) + val f = buildMatchFunction(subPatterns) + ??? + ??? 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 c01d570b3b..f5d76d765f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala @@ -6,13 +6,12 @@ package ups import mlscript.utils.AnyOps import mlscript.utils.shorthands.* -import syntax.Tree -import Tree.Ident -import Term.Lit +import syntax.Tree, Tree.Ident +import Message.MessageContext enum Pattern: - import Pattern.{Wildcard, Never} + import Pattern.{Wildcard, Never, Head} case Lit(lit: Term.Lit) @@ -64,30 +63,29 @@ enum Pattern: */ case Synonym(sym: PatternSymbol, params: Opt[Ls[Pattern]]) - /** A simplified fold for ``Pattern``. + /** A simplified reduce for ``Pattern``. * It is designed to be used when we want to * compute a single value, starting from the leaves. * It silently goes through ``Not``, ``Extract``, ``Rename`` and ``Synonym``. * The function must provide all the other base cases, and how * a list is merged (for ``And`` and ``Or`` nodes) */ - def fold[A](f: (Lit | ClassLike | Record | Tuple | List[A]) => A): A = this match + def reduce[A](f: (Lit | ClassLike | Record | Tuple | List[A]) => A): A = this match case p: (Lit | ClassLike | Record | Tuple) => f(p) - case And(patterns) => f(patterns.map(_.fold(f))) - case Or(patterns) => f(patterns.map(_.fold(f))) - case Not(pattern) => pattern.fold(f) - case Rename(pattern, _) => pattern.fold(f) - case Extract(pattern, _) => pattern.fold(f) - case Synonym(_, params) => ??? // TODO call on the body - - def heads: Set[Term.Lit | ClassSymbol] = this.fold: + case And(patterns) => f(patterns.map(_.reduce(f))) + case Or(patterns) => f(patterns.map(_.reduce(f))) + case Not(pattern) => pattern.reduce(f) + case Rename(pattern, _) => pattern.reduce(f) + case Extract(pattern, _) => pattern.reduce(f) + case Synonym(_, params) => ??? // TODO call on the body + + def heads: Set[Head] = this.reduce: case Lit(lit) => Set(lit) case ClassLike(sym, _) => Set(sym) case _: (Record | Tuple) => Set() - case ls: List[Set[Term.Lit | ClassSymbol]] => ls.toSet.flatten - + case ls: List[Set[Head]] => ls.toSet.flatten - def fields: Set[Ident | Int] = this.fold: + def fields: Set[Ident | Int] = this.reduce: case _: (Lit | ClassLike) => Set() case Record(entries) => entries.keys.toSet[Ident | Int] case Tuple(entries, strict) => @@ -97,26 +95,24 @@ enum Pattern: if strict then subfields + n else subfields case ls: List[Set[Ident | Int]] => ls.toSet.flatten - def collectSubPatterns(id: Ident): Set[Pattern] = this.fold: - case Lit(_) => Set() - // TODO: raise a warning - case ClassLike(sym, arguments) => /* TODO:raise a warning */ arguments match - case None => Set() - case Some(arguments) => - arguments.find((id1, _) => id === id).map((_, p) => p) match - case None => Set() - case Some(value) => Set(value) - case Record(entries) => entries.get(id).toSet - case Tuple(entries, strict) => Set() - case ls: List[Set[Pattern]] => ls.toSet.flatten - - def collectSubPatterns(n: Int): Set[Pattern] = this.fold: - case _: (Lit | ClassLike) => /* TODO : riase a warning */ Set() - case Record(_) => Set() - case Tuple(entries, strict) => entries.lift(n).toSet - case ls: List[Set[Pattern]] => ls.toSet.flatten - - // old versions without fold + def collectSubPatterns(field: Ident | Int): Set[Pattern] = field match + case id: Ident => this.reduce: + case Lit(_) => Set() + // TODO: raise a warning + case ClassLike(sym, arguments) => /* TODO:raise a warning */ arguments match + case None => Set() + case Some(arguments) => + arguments.find((id1, _) => id === id).map((_, p) => p).toSet + case Record(entries) => entries.get(id).toSet + case Tuple(entries, strict) => Set() + case ls: List[Set[Pattern]] => ls.toSet.flatten + case n : Int => this.reduce: + case _: (Lit | ClassLike) => /* TODO : riase a warning */ Set() + case Record(_) => Set() + case Tuple(entries, strict) => entries.lift(n).toSet + case ls: List[Set[Pattern]] => ls.toSet.flatten + + // old versions without reduce // def heads: Set[Term.Lit | ClassSymbol] = this match // case _: (Record | Tuple) => Set() @@ -201,37 +197,92 @@ enum Pattern: case Extract(pattern, term) => Extract(pattern.simplify, term) case Synonym(sym, params) => ??? - def specialize(lit: Term.Lit): Pattern = this match + def map(f: (Lit | ClassLike | Record | Tuple | Synonym) => Pattern): Pattern = this match + case p :(Lit | ClassLike | Record | Tuple | Synonym) => f(p) + case And(patterns) => And(patterns.map(_.map(f))) + case Or(patterns) => Or(patterns.map(_.map(f))) + case Not(pattern) => Not(pattern.map(f)) + case Rename(pattern, name) => Rename(pattern.map(f), name) + case Extract(pattern, term) => Extract(pattern.map(f), term) + + def specialize(lit: Term.Lit): Pattern = this.map: case Lit(lit1) => - if lit1 === lit then Wildcard else Never + if lit1 === lit then Wildcard else Never case ClassLike(_, _) => Never case Record(_) => Never // TODO : are we sure that a literal can't have fields? case Tuple(Nil, false) => ??? case Tuple(_, _) => Never - case And(patterns) => And(patterns.map(_.specialize(lit))) - case Or(patterns) => Or(patterns.map(_.specialize(lit))) - case Not(pattern) => Not(pattern.specialize(lit)) - case Rename(pattern, name) => Rename(pattern.specialize(lit), name) - case Extract(pattern, term) => Extract(pattern.specialize(lit), term) - case Synonym(sym, params) => Synonym(sym, params.map(_.map(_.specialize(lit)))) - - def specialize(cons: ClassSymbol): Pattern = this match + case Synonym(sym, params) => ??? + + def specialize(cons: ClassSymbol): Pattern = this.map: case Lit(_) => Never case ClassLike(sym, arguments) => if sym === cons then Wildcard else Never case Record(_) => this case Tuple(Nil, false) => ??? case Tuple(_, _) => Never - case And(patterns) => And(patterns.map(_.specialize(cons))) - case Or(patterns) => Or(patterns.map(_.specialize(cons))) - case Not(pattern) => Not(pattern.specialize(cons)) - case Rename(pattern, name) => Rename(pattern.specialize(cons), name) - case Extract(pattern, term) => Extract(pattern.specialize(cons), term) - case Synonym(sym, params) => Synonym(sym, params.map(_.map(_.specialize(cons)))) + case Synonym(sym, params) => ??? + + def specialize(head: Option[Head]): Pattern = head match + case Some(h: Term.Lit) => this.specialize(h) + case Some(h: ClassSymbol) => this.specialize(h) + case None => this.map: + case _: (Lit | ClassLike) => Never + case _: (Record | Tuple) => this + case Synonym(sym, params) => ??? + + def expand(alreadyExpanded: Set[PatternSymbol] = Set())(using Raise): Pattern = this.map: + case _: (Lit | ClassLike | Record | Tuple) => this + case Synonym(sym, params) if sym in alreadyExpanded => + raise(ErrorReport(msg"expanding ${sym.nme} leads to an infinite loop." -> sym.toLoc :: Nil)) + this + case Synonym(sym, params) => params match + case None => sym.defn match + case None => + raise(ErrorReport(msg"No definition found for pattern synonym ${sym.nme}" -> sym.toLoc :: Nil)) + this + case Some(defn) => ??? + // TODO : transform the body into what we want + case Some(_) => + raise(ErrorReport(msg"Higher order patterns are not supported yet." -> sym.toLoc :: Nil)) + this + + // old version without map + + // def specialize(lit: Term.Lit): Pattern = this match + // case Lit(lit1) => + // if lit1 === lit then Wildcard else Never + // case ClassLike(_, _) => Never + // case Record(_) => Never + // // TODO : are we sure that a literal can't have fields? + // case Tuple(Nil, false) => ??? + // case Tuple(_, _) => Never + // case And(patterns) => And(patterns.map(_.specialize(lit))) + // case Or(patterns) => Or(patterns.map(_.specialize(lit))) + // case Not(pattern) => Not(pattern.specialize(lit)) + // case Rename(pattern, name) => Rename(pattern.specialize(lit), name) + // case Extract(pattern, term) => Extract(pattern.specialize(lit), term) + // case Synonym(sym, params) => Synonym(sym, params.map(_.map(_.specialize(lit)))) + + // def specialize(cons: ClassSymbol): Pattern = this match + // case Lit(_) => Never + // case ClassLike(sym, arguments) => + // if sym === cons then Wildcard else Never + // case Record(_) => this + // case Tuple(Nil, false) => ??? + // case Tuple(_, _) => Never + // case And(patterns) => And(patterns.map(_.specialize(cons))) + // case Or(patterns) => Or(patterns.map(_.specialize(cons))) + // case Not(pattern) => Not(pattern.specialize(cons)) + // case Rename(pattern, name) => Rename(pattern.specialize(cons), name) + // case Extract(pattern, term) => Extract(pattern.specialize(cons), term) + // case Synonym(sym, params) => Synonym(sym, params.map(_.map(_.specialize(cons)))) object Pattern: val Wildcard = Or(Nil) val Never = And(Nil) + + type Head = Term.Lit | ClassSymbol From 7dd1a6102fab616654b4c3915b808e3347b69c27 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Wed, 11 Jun 2025 21:47:10 +0800 Subject: [PATCH 03/62] Rename flat patterns --- .../src/main/scala/hkmc2/bbml/bbML.scala | 8 +-- .../main/scala/hkmc2/codegen/Lowering.scala | 14 ++-- .../scala/hkmc2/semantics/Elaborator.scala | 25 +++++-- .../main/scala/hkmc2/semantics/Split.scala | 6 +- .../src/main/scala/hkmc2/semantics/Term.scala | 10 ++- .../hkmc2/semantics/ucs/DeBrujinSplit.scala | 6 +- .../scala/hkmc2/semantics/ucs/Desugarer.scala | 20 +++--- .../hkmc2/semantics/ucs/DesugaringBase.scala | 8 +-- .../{Pattern.scala => ucs/FlatPattern.scala} | 17 ++--- .../hkmc2/semantics/ucs/Normalization.scala | 66 +++++++++---------- .../hkmc2/semantics/ucs/Translator.scala | 14 ++-- .../src/main/scala/hkmc2/syntax/Tree.scala | 1 + .../src/test/mlscript/rp/syntax/Arrow.mls | 36 ++++++++++ .../test/mlscript/rp/syntax/Declaration.mls | 29 ++++++++ 14 files changed, 173 insertions(+), 87 deletions(-) rename hkmc2/shared/src/main/scala/hkmc2/semantics/{Pattern.scala => ucs/FlatPattern.scala} (90%) create mode 100644 hkmc2/shared/src/test/mlscript/rp/syntax/Arrow.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala index d8b8956e05..9b15325d5f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala @@ -9,7 +9,7 @@ import mlscript.utils.*, shorthands.* import utils.* import Message.MessageContext -import semantics.*, semantics.Term.* +import semantics.*, Term.*, ucs.FlatPattern import Elaborator.Ctx import syntax.* import Tree.* @@ -258,7 +258,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(_, Pattern.Lit(BoolLit(true)), Split.Else(cons)), Split.Else(alts)))) => + case Term.IfLike(Keyword.`if`, Split.Let(_, cond, Split.Cons(Branch(_, FlatPattern.Lit(BoolLit(true)), Split.Else(cons)), Split.Else(alts)))) => val (condTy, condCtx, condEff) = typeCode(cond) val (consTy, consCtx, consEff) = typeCode(cons) val (altsTy, altsCtx, altsEff) = typeCode(alts) @@ -295,7 +295,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL): val nestCtx1 = ctx.nest val nestCtx2 = ctx.nest val patTy = pattern match - case pat: Pattern.ClassLike => + case pat: FlatPattern.ClassLike => pat.constructor.symbol.flatMap(_.asCls) match case S(sym) => val (clsTy, tv, emptyTy) = sym.defn.map(sym -> _) match @@ -313,7 +313,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL): case N => error(msg"Not a valid class: ${pat.constructor.describe}" -> pat.constructor.toLoc :: Nil) Bot - case Pattern.Lit(lit) => lit match + case FlatPattern.Lit(lit) => lit match case _: Tree.BoolLit => BbCtx.boolTy case _: Tree.IntLit => BbCtx.intTy case _: Tree.DecLit => BbCtx.numTy diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 8f335b6b99..e9ca0df0ce 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -10,7 +10,7 @@ import utils.* import hkmc2.Message.MessageContext -import semantics.* +import semantics.*, ucs.FlatPattern import hkmc2.{semantics => sem} import semantics.{Term => st} import semantics.Term.{Throw => _, *} @@ -465,8 +465,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx): End() ) pat match - case Pattern.Lit(lit) => mkMatch(Case.Lit(lit) -> go(tail, topLevel = false)) - case Pattern.ClassLike(ctor, argsOpt, _mode, _refined) => + 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) @@ -496,8 +496,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx): // 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 Pattern.Tuple(len, inf) => mkMatch(Case.Tup(len, inf) -> go(tail, topLevel = false)) - case Pattern.Record(entries) => + 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))), @@ -630,8 +630,8 @@ class Lowering()(using Config, TL, Raise, State, Ctx): def setupSymbol(symbol: Local)(k: Result => Block)(using Subst): Block = k(Instantiate(Value.Ref(State.termSymbol).selSN("Symbol"), Value.Lit(Tree.StrLit(symbol.nme)) :: Nil)) - def quotePattern(p: Pattern)(k: Result => Block)(using Subst): Block = p match - case Pattern.Lit(lit) => setupTerm("LitPattern", Value.Lit(lit) :: Nil)(k) + def quotePattern(p: FlatPattern)(k: Result => Block)(using Subst): Block = p match + case FlatPattern.Lit(lit) => setupTerm("LitPattern", Value.Lit(lit) :: Nil)(k) case _ => // TODO raise(ErrorReport( msg"Unsupported quasiquote pattern type ${p.showDbg}" -> diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index ad3f474b99..5814673ed0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1114,14 +1114,26 @@ extends Importer: val owner = ctx.outer.inner newCtx.nestInner(patSym).givenIn: assert(body.isEmpty) + // Filter out parameters marked as `pattern`. + val (patternParams, extractionParams) = ps match + case S(ParamList(_, params, _)) => params.partition: + case param @ Param(flags = FldFlags(false, false, false, true, false)) => true + case param @ Param(flags = FldFlags(false, false, false, false, false)) => false + case Param(flags, sym, _, _) => + raise(ErrorReport(msg"Unexpected pattern parameter ${sym.name} with flags ${flags.showDbg}" -> sym.toLoc :: Nil)) + false + case N => (Nil, Nil) + // Elaborate pattern RHS using term elaboration. + val rhsTree = td.rhs.getOrElse: + raise(ErrorReport(msg"Pattern definitions must have a body." -> td.toLoc :: Nil)) + Tree.Under() + val rhsTerm = term(rhsTree)(using ctx ++ patternParams.iterator.map(p => p.sym.name -> p.sym)) + scoped("ucs:ups"): + log(s"elaborated pattern body: ${rhsTerm.showDbg}") + // *** BEGIN OBSOLETE CODE *** td.rhs match case N => raise(ErrorReport(msg"Pattern definitions must have a body." -> td.toLoc :: Nil)) case S(tree) => - val (patternParams, extractionParams) = ps match // Filter out pattern parameters. - case S(ParamList(_, params, _)) => params.partition: - case param @ Param(flags = FldFlags(false, false, false, true, false)) => true - case param @ Param(flags = FldFlags(pat = false)) => false - case N => (Nil, Nil) // TODO: Implement extraction parameters. if extractionParams.nonEmpty then raise(ErrorReport(msg"Pattern extraction parameters are not yet supported." -> @@ -1138,7 +1150,8 @@ extends Importer: patSym.patternParams, Nil, // ps.map(_.params).getOrElse(Nil), // TODO[Luyu]: remove pattern parameters td.rhs.getOrElse(die)) - val pd = PatternDef(owner, patSym, sym, tps, ps, + // *** END OBSOLETE CODE *** + val pd = PatternDef(owner, patSym, sym, tps, ps, rhsTerm, ObjBody(Blk(bod, Term.Lit(UnitLit(false)))), annotations) patSym.defn = S(pd) pd diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala index bee92578d3..d42d90405a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala @@ -2,15 +2,15 @@ package hkmc2 package semantics import mlscript.utils.*, shorthands.* -import syntax.* +import syntax.*, ucs.FlatPattern -final case class Branch(scrutinee: Term.Ref, pattern: Pattern, continuation: Split) extends AutoLocated: +final case class Branch(scrutinee: Term.Ref, pattern: FlatPattern, continuation: Split) extends AutoLocated: 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, Pattern.Lit(Tree.BoolLit(true)), continuation) + Branch(scrutinee, FlatPattern.Lit(Tree.BoolLit(true)), continuation) enum Split extends AutoLocated with ProductWithTail: case Cons(head: Branch, tail: Split) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 9d07e6894c..9b4e428acd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -248,7 +248,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case td: TypeDef => td.rhs.toList ::: td.annotations.flatMap(_.subTerms) case pat: PatternDef => - pat.paramsOpt.toList.flatMap(_.subTerms) ::: pat.body.blk :: pat.annotations.flatMap(_.subTerms) + pat.paramsOpt.toList.flatMap(_.subTerms) ::: pat.patternTerm :: pat.body.blk :: pat.annotations.flatMap(_.subTerms) case Import(sym, pth) => Nil case Try(body, finallyDo) => body :: finallyDo :: Nil case Handle(lhs, rhs, args, derivedClsSym, defs, bod) => rhs :: args ::: defs.flatMap(_.td.subTerms) ::: bod :: Nil @@ -498,7 +498,13 @@ case class PatternDef( bsym: BlockMemberSymbol, tparams: Ls[TyParam], paramsOpt: Opt[ParamList], - body: ObjBody, + // We reuse `term` to represent `pattern`, so we can reuse the logic of + // `Elaborator` and `Resolver`. Before pattern compilation, they will be + // transformed into a separate `Pattern` class. + patternTerm: Term, + // Here, `ObjBody` contains methods `unapply` and `unapplyStringPrefix`, + // which are generated from the pattern definition. + body: ObjBody, annotations: Ls[Annot], ) extends ClassLikeDef: self => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala index fb2e280d7d..e9b2cd44df 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala @@ -352,7 +352,7 @@ extension (split: DeBrujinSplit) case (0, body) => go(body, ctx) pattern match case Literal(value) => - semantics.Branch(ctx(scrutinee - 1)(), Pattern.Lit(value), nullaryConsequent) ~: go(alternative, ctx) + semantics.Branch(ctx(scrutinee - 1)(), FlatPattern.Lit(value), nullaryConsequent) ~: go(alternative, ctx) case ClassLike(ConstructorLike.Symbol(symbol: ClassSymbol)) => log(s"make temporary symbols for $symbol") val subSymbols = (1 to symbol.arity).map(i => TempSymbol(N, s"arg_$i")).toList @@ -367,12 +367,12 @@ extension (split: DeBrujinSplit) // Here we add a speical case as a workaround: // If the class is virtual, then we don't make arguments empty. val arguments = if Elaborator.ctx.builtins.virtualClasses contains symbol then N else S(subSymbols) - val pattern = Pattern.ClassLike(select, arguments) + val pattern = FlatPattern.ClassLike(select, arguments) semantics.Branch(ctx(scrutinee - 1)(), pattern, consequent2) ~: go(alternative, ctx) case ClassLike(ConstructorLike.Symbol(symbol: ModuleSymbol)) => val select = scoped("ucs:sel"): reference(symbol).getOrElse(Term.Error) - val pattern = Pattern.ClassLike(select, N) + val pattern = FlatPattern.ClassLike(select, N) semantics.Branch(ctx(scrutinee - 1)(), pattern, nullaryConsequent) ~: go(alternative, ctx) case ClassLike(ConstructorLike.LocalPattern(id)) => log(s"apply scrutinee $scrutinee to local pattern $id") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala index caaa949933..fcf99a4db5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala @@ -10,7 +10,7 @@ import Keyword.{as, and, `do`, `else`, is, let, `then`, where} import collection.mutable.{Buffer, HashMap, SortedSet} import Elaborator.{Ctx, Ctxl, State, UnderCtx, ctx} import scala.annotation.targetName -import Pattern.MatchMode +import FlatPattern.MatchMode object Desugarer: extension (op: Keyword.Infix) @@ -445,7 +445,7 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten def expandMatch(scrutSymbol: BlockLocalSymbol, pattern: Tree, sequel: Sequel): Split => Sequel = def ref = scrutSymbol.ref(/* FIXME ident? */) def dealWithCtorCase(ctor: Ctor, mode: MatchMode)(fallback: Split): Sequel = ctx => - Branch(ref, Pattern.ClassLike(term(ctor), N, mode, false)(ctor), sequel(ctx)) ~: fallback + Branch(ref, FlatPattern.ClassLike(term(ctor), N, mode, false)(ctor), sequel(ctx)) ~: fallback def dealWithAppCtorCase( app: Tree, ctor: Ctor, args: Ls[Tree], mode: MatchMode )(fallback: Split): Sequel = ctx => @@ -461,7 +461,7 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten .toList Branch( ref, - Pattern.ClassLike(term(ctor), S(matches), mode, false)(app), // TODO: refined? + FlatPattern.ClassLike(term(ctor), S(matches), mode, false)(app), // TODO: refined? subMatches(matches, sequel)(Split.End)(ctx) ) ~: fallback pattern.deparenthesized.desugared match @@ -520,15 +520,15 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten (wrap, (sym, pat) :: matches) Branch( ref, - Pattern.Tuple(lead.length + rest.fold(0)(_._2.length), rest.isDefined), + FlatPattern.Tuple(lead.length + rest.fold(0)(_._2.length), rest.isDefined), // The outermost is a tuple, so pattern arguments are not possible. wrap(subMatches(matches.map { case (s, t) => (s, t, N) }, sequel)(Split.End)(ctx)) ) ~: fallback // Negative numeric literals case App(Ident("-"), Tup(IntLit(value) :: Nil)) => fallback => ctx => - Branch(ref, Pattern.Lit(IntLit(-value)), sequel(ctx)) ~: fallback + Branch(ref, FlatPattern.Lit(IntLit(-value)), sequel(ctx)) ~: fallback case App(Ident("-"), Tup(DecLit(value) :: Nil)) => fallback => ctx => - Branch(ref, Pattern.Lit(DecLit(-value)), sequel(ctx)) ~: fallback + Branch(ref, FlatPattern.Lit(DecLit(-value)), sequel(ctx)) ~: fallback case OpApp(lhs, Ident("&"), rhs :: Nil) => fallback => ctx => val newSequel = expandMatch(scrutSymbol, rhs, sequel)(fallback) expandMatch(scrutSymbol, lhs, newSequel)(fallback)(ctx) @@ -548,7 +548,7 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten pre = s"expandMatch: literal <<< $literal", post = (r: Split) => s"expandMatch: literal >>> ${r.showDbg}" ): - Branch(ref, Pattern.Lit(literal), sequel(ctx)) ~: fallback + Branch(ref, FlatPattern.Lit(literal), sequel(ctx)) ~: fallback // A single pattern in conjunction with more conditions case pattern and consequent => fallback => ctx => val innerSplit = termSplit(consequent, identity)(Split.End) @@ -565,14 +565,14 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten val symbol = scrutSymbol.getFieldScrutinee(fieldName) Branch( ref, - Pattern.Record((fieldName, symbol) :: Nil), + FlatPattern.Record((fieldName, symbol) :: Nil), subMatches((symbol, pat, N) :: Nil, sequel)(Split.End)(ctx) ) ~: fallback case Pun(false, fieldName) => fallback => ctx => val symbol = scrutSymbol.getFieldScrutinee(fieldName) Branch( ref, - Pattern.Record((fieldName, symbol) :: Nil), + FlatPattern.Record((fieldName, symbol) :: Nil), subMatches((symbol, fieldName, N) :: Nil, sequel)(Split.End)(ctx) ) ~: fallback case Block(st :: Nil) => fallback => ctx => @@ -593,7 +593,7 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten }.fold(fallback)(recordContent => Branch( ref, - Pattern.Record(recordContent.map((fieldName, symbol, _) => (fieldName, symbol))), + FlatPattern.Record(recordContent.map((fieldName, symbol, _) => (fieldName, symbol))), subMatches(recordContent.map((_, symbol, pat) => (symbol, pat, N)), sequel)(Split.End)(ctx) ) ~: fallback ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala index cfeb81c8a7..e7cb6ab091 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala @@ -28,16 +28,16 @@ trait DesugaringBase(using Ctx, State): sel(runtimeRef, "MatchResult", State.matchResultClsSymbol) /** Make a pattern that looks like `runtime.MatchResult.class`. */ - protected def matchResultPattern(parameters: Opt[Ls[BlockLocalSymbol]]): Pattern.ClassLike = - Pattern.ClassLike(sel(matchResultClass, "class", State.matchResultClsSymbol), parameters) + protected def matchResultPattern(parameters: Opt[Ls[BlockLocalSymbol]]): FlatPattern.ClassLike = + FlatPattern.ClassLike(sel(matchResultClass, "class", State.matchResultClsSymbol), parameters) /** 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]]): Pattern.ClassLike = - Pattern.ClassLike(sel(matchFailureClass, "class", State.matchFailureClsSymbol), parameters) + protected def matchFailurePattern(parameters: Opt[Ls[BlockLocalSymbol]]): FlatPattern.ClassLike = + FlatPattern.ClassLike(sel(matchFailureClass, "class", State.matchFailureClsSymbol), parameters) protected lazy val tupleSlice = sel(sel(runtimeRef, "Tuple"), "slice") protected lazy val tupleGet = sel(sel(runtimeRef, "Tuple"), "get") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala similarity index 90% rename from hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala rename to hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala index f63de8f857..8583b3d667 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -1,15 +1,16 @@ package hkmc2 package semantics +package ucs import mlscript.utils.*, shorthands.* import syntax.*, Tree.Ident import Elaborator.{Ctx, ctx} -import ucs.DeBrujinSplit +import DeBrujinSplit.* -import Pattern.* +import FlatPattern.* /** Flat patterns for pattern matching */ -enum Pattern extends AutoLocated: +enum FlatPattern extends AutoLocated: case Lit(literal: Literal) @@ -57,7 +58,7 @@ enum Pattern extends AutoLocated: case Record(entries) => entries.iterator.map(_.name + ": " + _).mkString("{ ", ", ", " }") -object Pattern: +object FlatPattern: /** Represent the type of arguments in `ClassLike` patterns. This type alias * is used to reduce repetition in the code. * @@ -70,14 +71,14 @@ object Pattern: /** A class-like pattern whose symbol is resolved to a class. */ object Class: - def unapply(p: Pattern): Opt[ClassSymbol] = p match - case p: Pattern.ClassLike => p.constructor.symbol.flatMap(_.asCls) + 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: Pattern): Opt[ModuleSymbol] = p match - case p: Pattern.ClassLike => p.constructor.symbol.flatMap(_.asModOrObj) + def unapply(p: FlatPattern): Opt[ModuleSymbol] = p match + case p: FlatPattern.ClassLike => p.constructor.symbol.flatMap(_.asModOrObj) case _ => N enum MatchMode: 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 676a4f9334..37c42ff4c4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -9,7 +9,7 @@ import Elaborator.{Ctx, State, ctx} import utils.* class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBase: - import Normalization.*, Mode.*, Pattern.MatchMode + import Normalization.*, Mode.*, FlatPattern.MatchMode import tl.* def reportUnreachableCase[T <: Located](unreachable: Located, subsumedBy: T, when: Bool = true): T = @@ -36,9 +36,9 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas case Split.Let(name, term, tail) => Split.Let(name, term, tail ++ those) case Split.Else(_) /* impossible */ | Split.End => those) - extension (lhs: Pattern.ClassLike) + extension (lhs: FlatPattern.ClassLike) /** Generate a term that really resolves to the class at runtime. */ - def selectClass: Pattern.ClassLike = + def selectClass: FlatPattern.ClassLike = val constructor = lhs.constructor.symbol match case S(cls: ClassSymbol) => lhs.constructor case S(mem: BlockMemberSymbol) => @@ -51,28 +51,28 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas case _ => lhs.constructor lhs.copy(constructor)(lhs.tree) - extension (lhs: Pattern) + extension (lhs: FlatPattern) /** Checks if two patterns are the same. */ - def =:=(rhs: Pattern): Bool = (lhs, rhs) match - case (lhs: Pattern.ClassLike, rhs: Pattern.ClassLike) => + def =:=(rhs: FlatPattern): Bool = (lhs, rhs) match + case (lhs: FlatPattern.ClassLike, rhs: FlatPattern.ClassLike) => lhs.constructor.symbol === rhs.constructor.symbol - case (Pattern.Lit(l1), Pattern.Lit(l2)) => l1 === l2 - case (Pattern.Tuple(n1, b1), Pattern.Tuple(n2, b2)) => n1 === n2 && b1 === b2 - case (Pattern.Record(ls1), Pattern.Record(ls2)) => + case (FlatPattern.Lit(l1), FlatPattern.Lit(l2)) => l1 === l2 + case (FlatPattern.Tuple(n1, b1), FlatPattern.Tuple(n2, b2)) => n1 === n2 && b1 === b2 + case (FlatPattern.Record(ls1), FlatPattern.Record(ls2)) => ls1.lazyZip(ls2).forall: case ((fieldName1, p1), (fieldName2, p2)) => fieldName1 === fieldName2 && p1 === p2 - case (_: Pattern.ClassLike, _) | (_: Pattern.Lit, _) | - (_: Pattern.Tuple, _) | (_: Pattern.Record, _) => false + case (_: FlatPattern.ClassLike, _) | (_: FlatPattern.Lit, _) | + (_: FlatPattern.Tuple, _) | (_: FlatPattern.Record, _) => false /** Checks if `lhs` can be subsumed under `rhs`. */ - def <:<(rhs: Pattern): Bool = compareCasePattern(lhs, rhs) + def <:<(rhs: FlatPattern): Bool = compareCasePattern(lhs, rhs) /** * If two class-like patterns has different `refined` flag. Report the * inconsistency as a warning. */ - infix def reportInconsistentRefinedWith(rhs: Pattern): Unit = (lhs, rhs) match + infix def reportInconsistentRefinedWith(rhs: FlatPattern): Unit = (lhs, rhs) match // case (Pattern.Class(n1, _, r1), Pattern.Class(n2, _, r2)) if r1 =/= r2 => - case (Pattern.ClassLike(c1, _, _, rfd1), Pattern.ClassLike(c2, _, _, rfd2)) if rfd1 =/= rfd2 => + case (FlatPattern.ClassLike(c1, _, _, rfd1), FlatPattern.ClassLike(c2, _, _, rfd2)) if rfd1 =/= rfd2 => def be(value: Bool): Str = if value then "is" else "is not" warn( msg"Found two inconsistently refined patterns:" -> rhs.toLoc, @@ -81,24 +81,24 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas case (_, _) => () /** If the pattern is a class-like pattern, override its `refined` flag. */ def markAsRefined: Unit = lhs match - case lhs: Pattern.ClassLike => lhs.refined = true + case lhs: FlatPattern.ClassLike => lhs.refined = true case _ => () - extension (lhs: Pattern.Record) + extension (lhs: FlatPattern.Record) /** reduces the record pattern `lhs` assuming we have matched `rhs`. * It removes field matches that may now be unnecessary */ - infix def assuming(rhs: Pattern): Pattern.Record = rhs match - case Pattern.Record(rhsEntries) => + infix def assuming(rhs: FlatPattern): FlatPattern.Record = rhs match + case FlatPattern.Record(rhsEntries) => val filteredEntries = lhs.entries.filter: (fieldName1, _) => rhsEntries.forall { (fieldName2, _) => !(fieldName1 === fieldName2)} - Pattern.Record(filteredEntries) - case rhs: Pattern.ClassLike => rhs.constructor.symbol.flatMap(_.asCls) match + 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)} - Pattern.Record(filteredEntries) + FlatPattern.Record(filteredEntries) case S(_) | N => lhs case S(_) | N => lhs case _ => lhs @@ -119,13 +119,13 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas def normalizeImpl(split: Split)(using vs: VarSet): Split = split match case Split.Cons(Branch(scrutinee, pattern, consequent), alternative) => pattern match - case pattern: (Pattern.Lit | Pattern.Tuple | Pattern.Record) => + case pattern: (FlatPattern.Lit | FlatPattern.Tuple | FlatPattern.Record) => log(s"MATCH: ${scrutinee.showDbg} is ${pattern.showDbg}") // TODO(ucs): deduplicate [1] val whenTrue = normalize(specialize(consequent ++ alternative, +, scrutinee, pattern)) val whenFalse = normalizeImpl(specialize(alternative, -, scrutinee, pattern).clearFallback) Branch(scrutinee, pattern, whenTrue) ~: whenFalse - case pattern @ Pattern.ClassLike(ctor, argsOpt, mode, _) => + 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 @@ -190,7 +190,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas private def validateClassPattern( ctorTerm: Term, ctorSymbol: ClassSymbol, - argsOpt: Opt[Ls[Pattern.Argument]] + argsOpt: Opt[Ls[FlatPattern.Argument]] ): Bool = // Obtain the `classHead` used for error reporting and the parameter list // from the class definitions. @@ -242,7 +242,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas case N => true /** Check whether the object pattern has an argument list. */ - private def validateObjectPattern(pattern: Pattern.ClassLike, mod: ModuleSymbol, argsOpt: Opt[Ls[Pattern.Argument]]): Bool = argsOpt match + private def validateObjectPattern(pattern: FlatPattern.ClassLike, mod: ModuleSymbol, 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, @@ -294,7 +294,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas scrutinee: Term.Ref, symbol: PatternSymbol, ctorTerm: Term, - argsOpt: Opt[Ls[Pattern.Argument]], + argsOpt: Opt[Ls[FlatPattern.Argument]], mode: MatchMode, consequent: Split, alternative: Split, @@ -396,7 +396,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas split: Split, mode: Mode, scrutinee: Term.Ref, - pattern: Pattern + pattern: FlatPattern )(using VarSet): Split = trace( pre = s"S$mode <<< ${scrutinee.showDbg} is ${pattern.showDbg} : ${Split.display(split)}", post = (r: Split) => s"S$mode >>> ${Split.display(r)}" @@ -423,7 +423,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas log(s"Case 1.1.3: $pattern is unrelated with $thatPattern") rec(tail) else thatPattern match - case thatPattern: Pattern.Record => + case thatPattern: FlatPattern.Record => log(s"Case 1.1.4: $thatPattern is a record") // we can use information if pattern is itself a record, or if it is a constructor with arguments val simplifiedRecord = thatPattern assuming pattern @@ -466,8 +466,8 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas end rec rec(split)(using mode, summon) - private def aliasBindings(p: Pattern, q: Pattern): Split => Split = (p, q) match - case (Pattern.ClassLike(_, S(ss1), _, _), Pattern.ClassLike(_, S(ss2), _, _)) => + private def aliasBindings(p: FlatPattern, q: FlatPattern): Split => Split = (p, q) match + 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.ref(), acc(innermost)) @@ -479,8 +479,8 @@ object Normalization: * Hard-coded subtyping relations used in normalization and coverage checking. * TODO use base classes and also handle modules */ - def compareCasePattern(lhs: Pattern, rhs: Pattern)(using ctx: Elaborator.Ctx): Bool = - import Pattern.*, ctx.builtins as blt + def compareCasePattern(lhs: FlatPattern, rhs: FlatPattern)(using ctx: Elaborator.Ctx): Bool = + 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`)) @@ -506,7 +506,7 @@ object Normalization: entries.forall { (fieldName, _) => clsParams.exists { case Param(flags = FldFlags(value = value), sym = sym) => value && fieldName === sym.id }} - case (_: Pattern, _: Pattern) => false + 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/Translator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala index 30e8f78d1c..efdb3db69c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala @@ -26,7 +26,7 @@ import Translator.* * perform pattern matching on terms described by the pattern. */ class Translator(val elaborator: Elaborator)(using State, Ctx) extends DesugaringBase: - import elaborator.term, elaborator.tl.*, HelperExtractors.*, Pattern.MatchMode + import elaborator.term, elaborator.tl.*, HelperExtractors.*, FlatPattern.MatchMode /** Each scrutinee is represented by a function that creates a reference to * the scrutinee symbol. It is sufficient for current implementation. @@ -64,22 +64,22 @@ class Translator(val elaborator: Elaborator)(using State, Ctx) extends Desugarin case (lo: Literal) to (_, hi: Literal) => error(msg"Incompatible range types: ${lo.describe} to ${hi.describe}" -> pat.toLoc) failure - case lit: Literal => Branch(scrut(), Pattern.Lit(lit), inner(Map.empty)) ~: Split.End + case lit: Literal => Branch(scrut(), FlatPattern.Lit(lit), inner(Map.empty)) ~: Split.End case App(Ident("-"), Tup(IntLit(value) :: Nil)) => - Branch(scrut(), Pattern.Lit(IntLit(-value)), inner(Map.empty)) ~: Split.End + Branch(scrut(), FlatPattern.Lit(IntLit(-value)), inner(Map.empty)) ~: Split.End case App(Ident("-"), Tup(DecLit(value) :: Nil)) => - Branch(scrut(), Pattern.Lit(DecLit(-value)), inner(Map.empty)) ~: Split.End + Branch(scrut(), FlatPattern.Lit(DecLit(-value)), inner(Map.empty)) ~: Split.End case prefix ~ postfix => stringPrefix(scrut, prefix, (captures1, postfixScrut) => full(postfixScrut, postfix, captures2 => inner(captures2 ++ captures1))) case Under() => inner(Map.empty) case ctor @ (_: Ident | _: Sel) => val ctorTrm = term(ctor) - val pattern = Pattern.ClassLike(ctorTrm, N, MatchMode.Default, false)(ctor) + val pattern = FlatPattern.ClassLike(ctorTrm, N, MatchMode.Default, false)(ctor) Branch(scrut(), pattern, inner(Map.empty)) ~: Split.End case App(ctor @ (_: Ident | _: Sel), Tup(params)) => // TODO(rp/str): handle input params val ctorTrm = term(ctor) - val pattern = Pattern.ClassLike(ctorTrm, N, MatchMode.Default, false)(ctor) + val pattern = FlatPattern.ClassLike(ctorTrm, N, MatchMode.Default, false)(ctor) Branch(scrut(), pattern, inner(Map.empty)) ~: Split.End case pat => error(msg"Unrecognized pattern (${pat.describe})" -> pat.toLoc) @@ -120,7 +120,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx) extends Desugarin val prefixSymbol = new TempSymbol(N, "prefix") val postfixSymbol = new TempSymbol(N, "postfix") val mode = MatchMode.StringPrefix(prefixSymbol, postfixSymbol) - val pattern = Pattern.ClassLike(ctorTrm, N, mode, false)(ctor) + val pattern = FlatPattern.ClassLike(ctorTrm, N, mode, false)(ctor) Branch(scrut(), pattern, inner(Map.empty, () => postfixSymbol.ref())) ~: Split.End case pat => error(msg"Unrecognized pattern (${pat.describe})" -> pat.toLoc) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index b501cb1d79..de2b1a370b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -250,6 +250,7 @@ enum Tree extends AutoLocated: case inner: InfixApp => inner.asParam(inUsing) // Param of form (using Type). Synthesize an identifier for it. case _ => S(N, Ident(""), S(inner)) + case _: Tree => N def isModuleModifier: Bool = this match case Tree.TypeDef(Mod, _, N, N) => true diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Arrow.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/Arrow.mls new file mode 100644 index 0000000000..c66c196453 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/Arrow.mls @@ -0,0 +1,36 @@ +:js + +:fixme +pattern BooleanLike = true | false | (0 => false) | (1 => true) +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing + +import "../../../mlscript-compile/Stack.mls" + +open Stack + +:fixme +pattern PlainList(pattern T) = (null as Nil) | ([T as hd, PlainList(pattern T) as tl] => hd :: tl) +//│ /!!!\ Uncaught error: scala.MatchError: TyTup(List(InfixApp(Ident(T),keyword 'as',Ident(hd)), InfixApp(App(Ident(PlainList),Tup(List(TypeDef(Pat,Ident(T),None,None)))),keyword 'as',Ident(tl)))) (of class hkmc2.syntax.Tree$TyTup) + +[T as hd, PlainList(pattern T) as tl] +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: subterm (Elaborator.scala:418) +//│ ╔══[ERROR] Name not found: hd +//│ ║ l.15: [T as hd, PlainList(pattern T) as tl] +//│ ╙── ^^ +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: subterm (Elaborator.scala:418) +//│ ╔══[ERROR] Name not found: PlainList +//│ ║ l.15: [T as hd, PlainList(pattern T) as tl] +//│ ╙── ^^^^^^^^^ +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: subterm (Elaborator.scala:671) +//│ ╔══[ERROR] Illegal type declaration in term position. +//│ ║ l.15: [T as hd, PlainList(pattern T) as tl] +//│ ╙── ^ +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: subterm (Elaborator.scala:418) +//│ ╔══[ERROR] Name not found: tl +//│ ║ l.15: [T as hd, PlainList(pattern T) as tl] +//│ ╙── ^^ +//│ = [undefined] diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls new file mode 100644 index 0000000000..a66adccd04 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls @@ -0,0 +1,29 @@ +:js + +:e +pattern Foo(val T) = null | T +//│ ╔══[ERROR] Unexpected pattern parameter T with flags val +//│ ║ l.4: pattern Foo(val T) = null | T +//│ ╙── ^ +//│ ╔══[ERROR] Pattern extraction parameters are not yet supported. +//│ ║ l.4: pattern Foo(val T) = null | T +//│ ╙── ^ +//│ ╔══[ERROR] Name not found: T +//│ ║ l.4: pattern Foo(val T) = null | T +//│ ╙── ^ +//│ ╔══[ERROR] Cannot use this reference as a pattern +//│ ║ l.4: pattern Foo(val T) = null | T +//│ ╙── ^ + +pattern Foo(pattern T) = null | T + +:fixme +pattern Foo(pattern T) +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. + +:fixme +pattern Foo +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. + +:todo +data pattern Foo = _ From a13f880ceb144a3f1e6773b11846eeeaa37aa7b8 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 16 Jun 2025 04:33:53 +0800 Subject: [PATCH 04/62] Reintroduce patterns --- .../scala/hkmc2/semantics/Elaborator.scala | 133 +++++++++++- .../main/scala/hkmc2/semantics/Pattern.scala | 205 ++++++++++++++++++ .../main/scala/hkmc2/semantics/Resolver.scala | 8 +- .../src/main/scala/hkmc2/semantics/Term.scala | 10 +- .../hkmc2/semantics/ucs/DeBrujinSplit.scala | 14 +- .../scala/hkmc2/semantics/ucs/Desugarer.scala | 3 +- .../hkmc2/semantics/ucs/Translator.scala | 16 +- .../src/main/scala/hkmc2/syntax/Tree.scala | 1 - hkmc2/shared/src/test/mlscript/rp/Future.mls | 63 +++--- .../src/test/mlscript/rp/RangePatterns.mls | 23 +- .../src/test/mlscript/rp/syntax/Arrow.mls | 36 --- .../rp/syntax/InterestingPatterns.mls | 138 ++++++++++++ .../test/mlscript/rp/syntax/PatternBody.mls | 156 +++++++++++++ 13 files changed, 705 insertions(+), 101 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala delete mode 100644 hkmc2/shared/src/test/mlscript/rp/syntax/Arrow.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 5814673ed0..f9991db018 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -408,15 +408,9 @@ extends Importer: case N => raise(ErrorReport(msg"Cannot use 'this' outside of an object scope." -> tree.toLoc :: Nil)) Term.Error - case id @ Ident(name) => - ctx.get(name) match - case S(elem) => elem.ref(id) - case N => - state.builtinOpsMap.get(name) match - case S(bi) => bi.ref(id) - case N => - raise(ErrorReport(msg"Name not found: $name" -> tree.toLoc :: Nil)) - Term.Error + case id @ Ident(name) => ident(id).getOrElse: + raise(ErrorReport(msg"Name not found: $name" -> id.toLoc :: Nil)) + Term.Error // A use[T] construct: analogous to Scala's summon[T]. case TyApp(Keywrd(Keyword.`use`), targs) => if targs.length != 1 then @@ -1127,9 +1121,9 @@ extends Importer: val rhsTree = td.rhs.getOrElse: raise(ErrorReport(msg"Pattern definitions must have a body." -> td.toLoc :: Nil)) Tree.Under() - val rhsTerm = term(rhsTree)(using ctx ++ patternParams.iterator.map(p => p.sym.name -> p.sym)) + val rhsPat = pattern(rhsTree)(using ctx ++ patternParams.iterator.map(p => p.sym.name -> p.sym)) scoped("ucs:ups"): - log(s"elaborated pattern body: ${rhsTerm.showDbg}") + log(s"elaborated pattern body: ${rhsPat.showDbg}") // *** BEGIN OBSOLETE CODE *** td.rhs match case N => raise(ErrorReport(msg"Pattern definitions must have a body." -> td.toLoc :: Nil)) @@ -1151,7 +1145,7 @@ extends Importer: Nil, // ps.map(_.params).getOrElse(Nil), // TODO[Luyu]: remove pattern parameters td.rhs.getOrElse(die)) // *** END OBSOLETE CODE *** - val pd = PatternDef(owner, patSym, sym, tps, ps, rhsTerm, + val pd = PatternDef(owner, patSym, sym, tps, ps, rhsPat, ObjBody(Blk(bod, Term.Lit(UnitLit(false)))), annotations) patSym.defn = S(pd) pd @@ -1276,6 +1270,121 @@ extends Importer: ??? go(ps, Nil, ctx, ParamListFlags.empty) + def ident(id: Ident)(using Ctx): Ctxl[Opt[Term]] = ctx.get(id.name) match + case S(elem) => S(elem.ref(id)) + case N => + state.builtinOpsMap.get(id.name) match + case S(bi) => S(bi.ref(id)) + case N => N + + def pattern(t: Tree): Ctxl[Pattern] = + import ucs.Desugarer.{Ctor, unapply}, Keyword.*, Pattern.*, InvalidReason.* + import ucs.Translator.isInvalidStringBounds, ucs.HelperExtractors.to + /** 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 ctx2 = ctx ++ pattern.variables.map: + case alias => alias.id.name -> alias.allocate + // Report all invalid variables we found in `pattern`. + pattern.variables.invalidVars.foreach: + case (Alias(_, id), Duplicated(previous)) => + raise(ErrorReport(msg"Duplicate pattern variable." -> id.toLoc :: + msg"The previous definition is here." -> previous.toLoc :: Nil)) + case (Alias(_, id), Inconsistent(disjunction, missingOnTheLeft)) => + raise(ErrorReport( + msg"Found an inconsistent variable in disjunction patterns." -> id.toLoc :: + msg"The variable is missing from this sub-pattern." -> ( + if missingOnTheLeft then disjunction.left else disjunction.right + ).toLoc :: Nil)) + case (Alias(pattern, id), Negated(negation)) => + raise(ErrorReport((pattern match + case Wildcard() => msg"This variable cannot be accessed." -> id.toLoc + case _: Pattern => msg"This pattern cannot be bound." -> pattern.toLoc + ) :: msg"Because the pattern it belongs to is negated." -> negation.toLoc :: Nil)) + Transform(pattern, term(rhs)(using ctx2)) + /** Elaborate tuple patterns like `[p1, p2, ...ps, pn]`. */ + def tuple(ts: Ls[Tree]): Ctxl[Pattern.Tuple] = + val z = (Ls[Pattern](), N: Opt[Pattern], Ls[Pattern]()) + val (leading, spread, trailing) = ts.foldLeft(z): + case (acc @ (_, S(_), _), Spread(`...`, _, _)) => + // Found two spreads 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(`...`, _, S(t))) => + // Elaborate the spread pattern and add it to spread element. + (leading, S(go(t)), trailing) + case ((leading, N, trailing), Spread(`...`, _, N)) => + // Empty spreads results in a wildcard pattern. + (leading, S(Wildcard()), trailing) + case ((leading, N, trailing), t) => + // The spread is not filled. Add new patterns to the leading. + (go(t) :: leading, N, trailing) + case ((leading, spread @ S(_), trailing), t) => + // The spread is filled. Add new patterns to the trailing. + (leading, spread, go(t) :: trailing) + Tuple(leading.reverse, spread, trailing.reverse) + /** 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, Pun(false, p)) => (p, Variable(p)) :: acc + case (acc, t) => + raise(ErrorReport(msg"Unexpected record property pattern." -> t.toLoc :: Nil)) + acc + Record(entries.reverse) + def go(t: Tree): Ctxl[Pattern] = t match + // Brackets. + case Bra(BracketKind.Round, t) => go(t) + // Tuple patterns like `[p1, p2, ...ps, pn]`. + case TyTup(ps) => tuple(ps) + case Tup(ps) => tuple(ps) + case t: syntax.Literal => Literal(t) + // Negation patterns: `~p` + case App(Ident("~"), Tup(p :: Nil)) => Negation(go(p)) + // Union and intersection patterns: `p | q` and `p & q` + case OpApp(lhs, Ident(op @ ("|" | "&")), rhs :: Nil) => + Composition(op === "|", go(lhs), go(rhs)) + // Constructor patterns with arguments. + case App(ctor: Ctor, Tup(args)) => + Constructor(term(ctor), args.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 lhs :: Nil => arrow(lhs, rhs) + case _ :: _ | Nil => ??? // TODO: When is this case reached? + case p as (id: Ident) => go(p) bind id + case Under() => Pattern.Wildcard() + // 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) + // Range patterns. We can also desugar them into disjunctions of all the + // literals in the range. + case (lower: StrLit) to (incl, upper: StrLit) => + if isInvalidStringBounds(lower, upper) then Pattern.Wildcard() + else Pattern.Range(lower, upper, incl) + case (lower: IntLit) to (incl, upper: IntLit) => Pattern.Range(lower, upper, incl) + case (lower: DecLit) to (incl, upper: DecLit) => Pattern.Range(lower, upper, incl) + case (lower: syntax.Literal) to (_, upper: syntax.Literal) => + raise(ErrorReport(msg"The upper and lower bounds of range patterns should be literals of the same type." -> t.toLoc :: Nil)) + Pattern.Wildcard() + // String concatenation patterns: `p ~ q`. Currently, not supported by the + // pattern compilation. We elaborate them to keep the consistency with the + // pattern translation. + case OpApp(lhs, Ident("~"), rhs :: Nil) => Pattern.Concatenation(go(lhs), go(rhs)) + // Constructor patterns can be written in the infix form. + case OpApp(lhs, op, rhs :: Nil) => Pattern.Constructor(term(op), Ls(go(lhs), go(rhs))) + // Constructor patterns without arguments + case id @ Ident(name) => ident(id) match + case S(target) => Constructor(target, Nil) + case N => Variable(id) // Fallback to variable pattern. + case sel: (SynthSel | Sel) => Constructor(term(sel), Nil) + go(t) + def typeParams(t: Tree): Ctxl[(Ls[Param], Ctx)] = t match case TyTup(ps) => val vs = ps.map: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala new file mode 100644 index 0000000000..f1c43461ad --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -0,0 +1,205 @@ +package hkmc2 +package semantics + +import mlscript.utils.*, shorthands.*, collection.immutable.HashMap +import syntax.Tree, Elaborator.State +import scala.annotation.tailrec, util.chaining.* + +object Pattern: + /** The reason why a variable obtained from `Pattern.variables` is invalid. */ + enum InvalidReason: + /** The variable shadowed another variable. */ + case Duplicated(previous: Tree.Ident) + /** The variable presents in one side of disjunction, but not the other. */ + case Inconsistent(disjunction: Pattern.Composition, missingOnTheLeft: Bool) + /** The variable is bound in a `Negation` pattern. */ + case Negated(negation: Pattern.Negation) + + + import InvalidReason.* + + final case class Variables( + varMap: HashMap[Str, Pattern.Alias], + invalidVars: Ls[(Pattern.Alias, InvalidReason)] + ): + /** Apply a function to all variables. */ + def map[A](f: Pattern.Alias => A): Iterator[A] = varMap.iterator.map(_._2).map(f) + + /** Add a single variable to the variable set. */ + def +(alias: Pattern.Alias): Variables = + varMap.get(alias.id.name) match + case N => Variables(varMap + (alias.id.name -> alias), invalidVars) + case S(oldVar) => Variables( + varMap.updated(alias.id.name, alias), + invalidVars :+ (oldVar, Duplicated(alias.id))) + + /** Union two variable sets. If the latter contains a variable that is + * already present in the former, the variable is considered duplicated. */ + def ++(that: Variables): Variables = Variables( + varMap.merged(that.varMap): + case ((name, _), (_, id)) => (name, id), + invalidVars ::: that.invalidVars ::: that.varMap.iterator.collect: + case (name, id) if varMap.contains(name) => + (id, Duplicated(varMap(name).id)) + .toList) + + /** 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. */ + def intersect(that: Variables, pattern: Pattern.Composition): Variables = + // Check if two variable sets are the same. + val notInThat = varMap.removedAll(that.varMap.keys) + val notInThis = that.varMap.removedAll(varMap.keys) + Variables( + // Remove variables that only present in one side. + varMap.removedAll(Iterable.concat(notInThat.keys, notInThis.keys)), + // Add variables that only present in one side to the invalid variables. + invalidVars ::: + notInThis.iterator.map(_._2 -> Inconsistent(pattern, true)).toList ::: + notInThat.iterator.map(_._2 -> Inconsistent(pattern, false)).toList) + + def invalidated(reason: InvalidReason): Variables = + Variables(HashMap.empty, invalidVars appendedAll varMap.iterator.map(_._2 -> reason)) + + object Variables: + lazy val empty: Variables = Variables(HashMap.empty, Nil) + + extension (patterns: IterableOnce[Pattern]) + def variables: Variables = patterns.iterator.foldLeft(Variables.empty): + case (vars, pattern) => vars ++ pattern.variables + + /** A shorthand for creating a variable pattern. */ + def Variable(id: Tree.Ident): Pattern.Alias = Pattern.Wildcard().bind(id) + + trait AliasImpl: + self: Pattern.Alias => + private var _symbol: Opt[VarSymbol] = N + def symbol_=(symbol: VarSymbol): Unit = _symbol = S(symbol) + def symbol: VarSymbol = _symbol.getOrElse(lastWords("symbol is not set")) + /** Allocate the symbol for the variable. This should be called in the + * elaborator and before elaborating the term from `Transform`. */ + def allocate(using State): VarSymbol = VarSymbol(self.id).tap(symbol_=) + +import Pattern.*, InvalidReason.* + +/** An inductive data type for patterns. */ +enum Pattern extends AutoLocated: + /** A pattern that matches a constructor and its arguments. */ + case Constructor(target: Term, arguments: Ls[Pattern]) + + /** A pattern that is the composition of two patterns. + * @param polarity `true` if the pattern is a disjunction, `false` if it is + * a conjunction. + */ + case Composition(polarity: Bool, left: Pattern, right: Pattern) + + /** A pattern that is the negation of another pattern. We don't allow negation + * patterns to be nested. They can only be used as the top-level pattern. + * Also, what's the point of binding the inner pattern? + */ + case Negation(pattern: Pattern) + + /** A pattern that matches any value. It is syntically denoted by `_`. */ + case Wildcard() + + /** A pattern that matches the given literal. */ + case Literal(literal: syntax.Literal) + + /** A pattern that matches a range of values. */ + case Range(lower: syntax.Literal, upper: syntax.Literal, rightInclusive: Bool) + + /** A pattern that matches the concatenation of two string patterns. */ + case Concatenation(left: Pattern, right: Pattern) + + /** A pattern that matches a tuple. */ + case Tuple(leading: Ls[Pattern], spread: Opt[Pattern], trailing: Ls[Pattern]) + + /** A pattern that matches a record. */ + case Record(fields: Ls[(Tree.Ident, Pattern)]) + + /** A pattern that matches the same value as another pattern, but bind the + * matched value to a new variable. This is the only class that holds the + * symbol for the variable. */ + case Alias(pattern: Pattern, id: Tree.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. + */ + case Transform(pattern: Pattern, transform: Term) + + infix def bind(id: Tree.Ident): Pattern.Alias = Pattern.Alias(this, id) + + /** Collect all variables in the pattern. Meanwhile, list invalid variables, + * which will be reported when constructing symbols for variables. We use a + * map becuase we want to replace variables. */ + lazy val variables: Variables = this match + case Constructor(_, arguments) => arguments.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 + // and the arguments represent the diagnostic information of match failure, + // then we can bind the variables in the pattern to the arguments. + // case Negation(Constructor(_, arguments)) => arguments.variables + // Otherwise, negation patterns should not bind any variables. + 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 Record(fields) => fields.iterator.map(_._2).variables + case alias @ Alias(pattern, _) => pattern.variables + alias + + def children: Ls[Located] = this match + case Constructor(target, arguments) => target :: arguments + case Composition(polarity, left, right) => left :: right :: Nil + case Negation(pattern) => pattern :: Nil + case Wildcard() => Nil + case 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 Record(fields) => fields.flatMap: + case (name, pattern) => name :: pattern.children + case Alias(pattern, alias) => pattern :: alias :: Nil + case Transform(pattern, transform) => pattern :: transform :: Nil + + def subTerms: Ls[Term] = this match + case Constructor(target, arguments) => target :: arguments.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 Record(fields) => fields.flatMap(_._2.subTerms) + case Alias(pattern, _) => pattern.subTerms + case Transform(pattern, transform) => pattern.subTerms :+ transform + + private def showDbgWithPar = + val addPar = this match + case _: (Constructor | Wildcard | Literal | Tuple | Record | Negation) => false + case Alias(Wildcard(), _) => false + case _: (Alias | Composition | Transform | Range | Concatenation) => true + if addPar then s"(${showDbg})" else showDbg + + def showDbg: Str = this match + case Constructor(target, arguments) => + val targetText = target.symbol.fold(target.showDbg)(_.nme.prepended('#')) + s"$targetText(${arguments.map(_.showDbg).mkString(", ")})" + case Composition(true, left, right) => s"${left.showDbg} \u2228 ${right.showDbg}" + case Composition(false, left, right) => s"${left.showDbg} \u2227 ${right.showDbg}" + case Negation(pattern) => s"\u00ac${pattern.showDbgWithPar}" + case Wildcard() => "_" + case Literal(literal) => literal.idStr + 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) => + val leadingStr = leading.map(_.showDbg).mkString(", ") + val spreadStr = spread.map(s => s", ...${s.showDbg}").mkStringOr("", ", ", "") + val trailingStr = trailing.map(_.showDbg).mkStringOr("", ", ", "") + List(leadingStr, spreadStr, trailingStr).filter(_.nonEmpty).mkString("[", ", ", "]") + case Record(fields) => s"{${fields.map((k, v) => s"${k.name}: ${v.showDbg}").mkString(", ")}}" + 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}" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index 3a21ab3d2d..79fd48d948 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -382,7 +382,13 @@ class Resolver(tl: TraceLogger) defn.paramsOpt.foreach(_.allParams.foreach(resolveParam(_))) defn.annotations.flatMap(_.subTerms).foreach(traverse(_, expect = NonModule(N))) defn.ext.foreach(traverse(_, expect = NonModule(N))) - + + // For pattern definitions, we need to traverse through the pattern body. + defn match + case defn: PatternDef => + defn.pattern.subTerms.foreach(traverse(_, expect = NonModule(N))) + case _: ClassLikeDef => () + traverseBlock(defn.body.blk)(using resolveCtxParams(defn.paramsOpt.toList)) // Case: other definition forms. Just traverse through the sub-terms. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 9b4e428acd..3f9601dbdb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -248,7 +248,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case td: TypeDef => td.rhs.toList ::: td.annotations.flatMap(_.subTerms) case pat: PatternDef => - pat.paramsOpt.toList.flatMap(_.subTerms) ::: pat.patternTerm :: pat.body.blk :: pat.annotations.flatMap(_.subTerms) + pat.paramsOpt.toList.flatMap(_.subTerms) ::: pat.body.blk :: pat.annotations.flatMap(_.subTerms) case Import(sym, pth) => Nil case Try(body, finallyDo) => body :: finallyDo :: Nil case Handle(lhs, rhs, args, derivedClsSym, defs, bod) => rhs :: args ::: defs.flatMap(_.td.subTerms) ::: bod :: Nil @@ -304,7 +304,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: 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 Lam(params, body) => s"λ${params.showDbg}. ${body.showDbg}" + case Lam(params, body) => s"λ${params.paramSyms.map(_.id).mkString(", ")}. ${body.showDbg}" case Blk(stats, res) => (stats.map(_.showDbg + "; ") :+ (res match { case Lit(Tree.UnitLit(false)) => "" case x => x.showDbg + " " })) .mkString("( ", "", ")") @@ -498,10 +498,8 @@ case class PatternDef( bsym: BlockMemberSymbol, tparams: Ls[TyParam], paramsOpt: Opt[ParamList], - // We reuse `term` to represent `pattern`, so we can reuse the logic of - // `Elaborator` and `Resolver`. Before pattern compilation, they will be - // transformed into a separate `Pattern` class. - patternTerm: Term, + /** The elaborated pattern right-hand side. */ + pattern: Pattern, // Here, `ObjBody` contains methods `unapply` and `unapplyStringPrefix`, // which are generated from the pattern definition. body: ObjBody, diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala index e9b2cd44df..1935640a8b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala @@ -17,7 +17,7 @@ object DeBrujinSplit: (using Elaborator.Ctx, Elaborator.State, Raise): DeBrujinSplit = - import elaborator.tl.*, syntax.Tree, Tree.*, PatternStub.*, HelperExtractors.* + import elaborator.tl.*, syntax.Tree, Tree.*, PatternStub.*, HelperExtractors.*, Desugarer.unapply, syntax.Keyword.{as, `=>`} type F = (Int, => DeBrujinSplit, => DeBrujinSplit) => DeBrujinSplit /** Resolve the constructor in the elaborator context. */ def resolve(ctor: Ident | Sel, params: Ls[Tree]): Opt[F] = @@ -83,6 +83,18 @@ object DeBrujinSplit: error(msg"Name not found: ${ctor.showDbg}" -> ctor.toLoc) alternative def go(tree: Tree): F = tree.deparenthesized match + // BEGIN OF TEMPORARY ALLOWANCE + // This allows the pattern `p => t`. + case _ `=>` _ => (_, _, alternative) => alternative + // This allows the pattern `p as id`. + case _ as _ => (_, _, alternative) => alternative + // This allows the pattern `~p`. + case App(Ident("~"), Tup(p :: Nil)) => (_, _, alternative) => alternative + // This allows the pattern `(a: p1, b: p2, ...pn)`. + case Block(_) => (_, _, alternative) => alternative + // This allows the pattern `[p1, p2, ...pn]`. + case Tup(_) => (_, _, alternative) => alternative + // END OF TEMPORARY ALLOWANCE case lhs or rhs => (scrutinee, consequence, alternative) => trace( pre = s"or <<<", post = (_: DeBrujinSplit) => s"or >>>" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala index fcf99a4db5..1828e5cf1c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala @@ -18,6 +18,8 @@ object Desugarer: 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 @@ -66,7 +68,6 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten type Sequel = Ctx => Split - type Ctor = SynthSel | Sel | Ident extension (sequel: Sequel) @targetName("traceSequel") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala index efdb3db69c..8961bacd7d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala @@ -5,9 +5,10 @@ package ucs import mlscript.utils.*, shorthands.* import Message.MessageContext import Split.display, ucs.Normalization -import syntax.{Fun, Keyword, Literal, ParamBind, Tree}, Tree.*, Keyword.`as` +import syntax.{Fun, Keyword, Literal, ParamBind, Tree}, Tree.*, Keyword.{`as`, `=>`} import scala.collection.mutable.{Buffer, Set as MutSet} import Elaborator.{Ctx, State} +import Desugarer.unapply object Translator: /** String range bounds must be single characters. */ @@ -56,6 +57,19 @@ class Translator(val elaborator: Elaborator)(using State, Ctx) extends Desugarin post = (split: Split) => s"full >>> $split" ): pat.deparenthesized match + // TODO: Implement this after we're about to finish the pattern compilation. + // BEGIN OF TEMPORARY ALLOWANCE + // This temporarily allows the pattern `p => t`. + case _ `=>` _ => errorSplit + // This temporarily allows the pattern `~p`. + case App(Ident("~"), Tup(p :: Nil)) => errorSplit + // This temporarily allows the pattern `(a: p1, b: p2, ...pn)`. + case Block(_) => errorSplit + // This temporarily allows the pattern `[p1, p2, ...pn]`. + case Tup(_) => errorSplit + // This temporarily allows the pattern `p as id`. + case _ as _ => errorSplit + // END OF TEMPORARY ALLOWANCE case lhs or rhs => full(scrut, lhs, inner) ~~: full(scrut, rhs, inner) case (lo: StrLit) to (incl, hi: StrLit) => if isInvalidStringBounds(lo, hi) then failure else makeRange(scrut, lo, hi, incl, inner) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index de2b1a370b..b501cb1d79 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -250,7 +250,6 @@ enum Tree extends AutoLocated: case inner: InfixApp => inner.asParam(inUsing) // Param of form (using Type). Synthesize an identifier for it. case _ => S(N, Ident(""), S(inner)) - case _: Tree => N def isModuleModifier: Bool = this match case Tree.TypeDef(Mod, _, N, N) => true diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/rp/Future.mls index 456aeef620..e65da33b34 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Future.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Future.mls @@ -2,44 +2,31 @@ :todo // Parameterized patterns. pattern Rep0[A] = "" | A ~ Rep0[A] -//│ ╔══[ERROR] Unrecognized pattern (application) -//│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] -//│ ╙── ^^^^^^^ -//│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] -//│ ╙── ^ -//│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] -//│ ╙── ^ +//│ /!!!\ Uncaught error: scala.MatchError: App(Ident(Rep0),TyTup(List(Ident(A)))) (of class hkmc2.syntax.Tree$App) :todo pattern Rep0(pattern A, B, C)(head) = "" | (A as head) ~ Rep0[A] //│ ╔══[ERROR] Multiple parameter lists are not supported for this definition. -//│ ║ l.16: pattern Rep0(pattern A, B, C)(head) = -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.17: "" | (A as head) ~ Rep0[A] -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╔══[ERROR] Pattern extraction parameters are not yet supported. -//│ ║ l.16: pattern Rep0(pattern A, B, C)(head) = -//│ ╙── ^^^^ +//│ ║ l.8: pattern Rep0(pattern A, B, C)(head) = +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.9: "" | (A as head) ~ Rep0[A] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ /!!!\ Uncaught error: scala.MatchError: App(Ident(Rep0),TyTup(List(Ident(A)))) (of class hkmc2.syntax.Tree$App) :todo // Pattern extractions via aliases. pattern Email(name, domain) = (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╔══[ERROR] Pattern extraction parameters are not yet supported. -//│ ║ l.28: pattern Email(name, domain) = +//│ ║ l.18: pattern Email(name, domain) = //│ ╙── ^^^^^^^^^^^^ //│ ╔══[ERROR] Unrecognized pattern (infix operator) -//│ ║ l.29: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^^^^^^^^^ :todo // View patterns pattern GreaterThan(value) = case n and n > value then n -//│ ╔══[ERROR] Pattern extraction parameters are not yet supported. -//│ ║ l.38: pattern GreaterThan(value) = case -//│ ╙── ^^^^^ //│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(InfixApp(Ident(n),keyword 'and',OpApp(Ident(n),Ident(>),List(Ident(value)))),keyword 'then',Ident(n))))) (of class hkmc2.syntax.Tree$Case) :todo @@ -52,11 +39,11 @@ fun foo(x) = if x is Unit then .... Arrow(...) then .... //│ ╔══[ERROR] Unrecognized pattern split (infix operator). -//│ ║ l.51: view as +//│ ║ l.38: view as //│ ║ ^^^^^^^ -//│ ║ l.52: Unit then .... +//│ ║ l.39: Unit then .... //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ║ l.53: Arrow(...) then .... +//│ ║ l.40: Arrow(...) then .... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -74,17 +61,17 @@ 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.71: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ║ l.58: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^ //│ /!!!\ Uncaught error: scala.MatchError: Jux(StrLit(a),Ident(to)) (of class hkmc2.syntax.Tree$Jux) :todo pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ╔══[ERROR] Pattern extraction parameters are not yet supported. -//│ ║ l.82: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) +//│ ║ l.69: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Unrecognized pattern (infix operator) -//│ ║ l.82: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) +//│ ║ l.69: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ╙── ^^^^^^^^^^^^ :todo @@ -93,7 +80,7 @@ pattern LineSep(pattern P) = case L(...nd) ~ "" then nd :: Nil "\n" ~ LineSep(P, t) then nd :: t -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Ident(Nil)), OpApp(App(Ident(L),Tup(List(Spread(keyword '...',Some(Loc(52,55,Future.mls:+91)),Some(Ident(nd)))))),Ident(~),List(Block(List(InfixApp(StrLit(),keyword 'then',OpApp(Ident(nd),Ident(::),List(Ident(Nil)))), InfixApp(OpApp(StrLit( +//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Ident(Nil)), OpApp(App(Ident(L),Tup(List(Spread(keyword '...',Some(Loc(52,55,Future.mls:+78)),Some(Ident(nd)))))),Ident(~),List(Block(List(InfixApp(StrLit(),keyword 'then',OpApp(Ident(nd),Ident(::),List(Ident(Nil)))), InfixApp(OpApp(StrLit( //│ ),Ident(~),List(App(Ident(LineSep),Tup(List(Ident(P), Ident(t)))))),keyword 'then',OpApp(Ident(nd),Ident(::),List(Ident(t))))))))))) (of class hkmc2.syntax.Tree$Case) :todo @@ -107,10 +94,10 @@ pattern Lines(pattern L) = case :todo if input is Lines of Email then //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.108: if input is Lines of Email then -//│ ╙── ^ +//│ ║ l.95: if input is Lines of Email then +//│ ╙── ^ //│ /!!!\ Uncaught error: scala.MatchError: InfixApp(Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Tup(List())), TypeDef(Pat,Ident(Tail),Some(Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Tup(List())), InfixApp(InfixApp(OpApp(StrLit( -//│ ),Ident(~),List(Bra(Round,App(Ident(Lines),Tup(List(Ident(L))))))),keyword 'as',Ident(t)),keyword 'then',Ident(t)))))),None), InfixApp(InfixApp(InfixApp(Ident(L),keyword 'as',OpApp(Ident(h),Ident(~),List(Ident(Tail)))),keyword 'as',Ident(t)),keyword 'then',Tup(List(Ident(h), Spread(keyword '...',Some(Loc(148,151,Future.mls:+100)),Some(Ident(t))))))))),keyword ':',Ident(todo)) (of class hkmc2.syntax.Tree$InfixApp) +//│ ),Ident(~),List(Bra(Round,App(Ident(Lines),Tup(List(Ident(L))))))),keyword 'as',Ident(t)),keyword 'then',Ident(t)))))),None), InfixApp(InfixApp(InfixApp(Ident(L),keyword 'as',OpApp(Ident(h),Ident(~),List(Ident(Tail)))),keyword 'as',Ident(t)),keyword 'then',Tup(List(Ident(h), Spread(keyword '...',Some(Loc(148,151,Future.mls:+87)),Some(Ident(t))))))))),keyword ':',Ident(todo)) (of class hkmc2.syntax.Tree$InfixApp) :todo pattern Opt(pattern P) = case @@ -124,33 +111,33 @@ pattern Email(name, domain) = ... :todo if input is Opt(Email, Some((n, d))) then ... //│ ╔══[ERROR] Name not found: input -//│ ║ l.125: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.112: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^^ //│ ╔══[ERROR] Name not found: Opt -//│ ║ l.125: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.112: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^ //│ ╔══[ERROR] Name not found: Some -//│ ║ l.125: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.112: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^ //│ ╔══[ERROR] invalid record field pattern -//│ ║ l.125: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.112: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^ :todo pattern Opt(pattern P) = case P(...values) then (Some(values)) "" then (None) -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+140)),Some(Ident(values)))))),keyword 'then',Bra(Round,App(Ident(Some),Tup(List(Ident(values)))))), InfixApp(StrLit(),keyword 'then',Bra(Round,Ident(None)))))) (of class hkmc2.syntax.Tree$Case) +//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+127)),Some(Ident(values)))))),keyword 'then',Bra(Round,App(Ident(Some),Tup(List(Ident(values)))))), InfixApp(StrLit(),keyword 'then',Bra(Round,Ident(None)))))) (of class hkmc2.syntax.Tree$Case) :todo pattern Opt(pattern P) = case P(...values) then Some(values) "" then None -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+146)),Some(Ident(values)))))),keyword 'then',App(Ident(Some),Tup(List(Ident(values))))), InfixApp(StrLit(),keyword 'then',Ident(None))))) (of class hkmc2.syntax.Tree$Case) +//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+133)),Some(Ident(values)))))),keyword 'then',App(Ident(Some),Tup(List(Ident(values))))), InfixApp(StrLit(),keyword 'then',Ident(None))))) (of class hkmc2.syntax.Tree$Case) :todo pattern Digits = "0" to "9" ~ (Digits | "") //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.152: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.139: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^ //│ /!!!\ Uncaught error: scala.MatchError: Jux(StrLit(0),Ident(to)) (of class hkmc2.syntax.Tree$Jux) diff --git a/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls b/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls index 6185d2d79d..48795087b9 100644 --- a/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls @@ -58,14 +58,20 @@ pattern UnsignedByte = 0..< 256 //│ ╔══[ERROR] Name not found: .< //│ ║ l.51: pattern UnsignedByte = 0..< 256 //│ ╙── ^^ +//│ ╔══[ERROR] Name not found: .< +//│ ║ l.51: pattern UnsignedByte = 0..< 256 +//│ ╙── ^^ //│ ╔══[ERROR] Unrecognized pattern (operator application) //│ ║ l.51: pattern UnsignedByte = 0..< 256 //│ ╙── ^^^^^^^^ :e pattern BadRange = "s"..=0 +//│ ╔══[ERROR] The upper and lower bounds of range patterns should be literals of the same type. +//│ ║ l.69: pattern BadRange = "s"..=0 +//│ ╙── ^^^^^^^ //│ ╔══[ERROR] Incompatible range types: string literal to integer literal -//│ ║ l.66: pattern BadRange = "s"..=0 +//│ ║ l.69: pattern BadRange = "s"..=0 //│ ╙── ^^^^^^^ // It becomes an absurd pattern. @@ -74,15 +80,24 @@ 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.82: pattern BadRange = 0 ..= "s" +//│ ╙── ^^^^^^^^^ //│ ╔══[ERROR] Incompatible range types: integer literal to string literal -//│ ║ l.76: pattern BadRange = 0 ..= "s" +//│ ║ l.82: pattern BadRange = 0 ..= "s" //│ ╙── ^^^^^^^^^ :e pattern BadRange = "yolo" ..= "swag" //│ ╔══[ERROR] String range bounds must have only one character. -//│ ║ l.82: pattern BadRange = "yolo" ..= "swag" +//│ ║ l.91: pattern BadRange = "yolo" ..= "swag" +//│ ║ ^^^^^^ +//│ ╟── String range bounds must have only one character. +//│ ║ l.91: pattern BadRange = "yolo" ..= "swag" +//│ ╙── ^^^^^^ +//│ ╔══[ERROR] String range bounds must have only one character. +//│ ║ l.91: pattern BadRange = "yolo" ..= "swag" //│ ║ ^^^^^^ //│ ╟── String range bounds must have only one character. -//│ ║ l.82: pattern BadRange = "yolo" ..= "swag" +//│ ║ l.91: pattern BadRange = "yolo" ..= "swag" //│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Arrow.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/Arrow.mls deleted file mode 100644 index c66c196453..0000000000 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/Arrow.mls +++ /dev/null @@ -1,36 +0,0 @@ -:js - -:fixme -pattern BooleanLike = true | false | (0 => false) | (1 => true) -//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing - -import "../../../mlscript-compile/Stack.mls" - -open Stack - -:fixme -pattern PlainList(pattern T) = (null as Nil) | ([T as hd, PlainList(pattern T) as tl] => hd :: tl) -//│ /!!!\ Uncaught error: scala.MatchError: TyTup(List(InfixApp(Ident(T),keyword 'as',Ident(hd)), InfixApp(App(Ident(PlainList),Tup(List(TypeDef(Pat,Ident(T),None,None)))),keyword 'as',Ident(tl)))) (of class hkmc2.syntax.Tree$TyTup) - -[T as hd, PlainList(pattern T) as tl] -//│ FAILURE: Unexpected type error -//│ FAILURE LOCATION: subterm (Elaborator.scala:418) -//│ ╔══[ERROR] Name not found: hd -//│ ║ l.15: [T as hd, PlainList(pattern T) as tl] -//│ ╙── ^^ -//│ FAILURE: Unexpected type error -//│ FAILURE LOCATION: subterm (Elaborator.scala:418) -//│ ╔══[ERROR] Name not found: PlainList -//│ ║ l.15: [T as hd, PlainList(pattern T) as tl] -//│ ╙── ^^^^^^^^^ -//│ FAILURE: Unexpected type error -//│ FAILURE LOCATION: subterm (Elaborator.scala:671) -//│ ╔══[ERROR] Illegal type declaration in term position. -//│ ║ l.15: [T as hd, PlainList(pattern T) as tl] -//│ ╙── ^ -//│ FAILURE: Unexpected type error -//│ FAILURE LOCATION: subterm (Elaborator.scala:418) -//│ ╔══[ERROR] Name not found: tl -//│ ║ l.15: [T as hd, PlainList(pattern T) as tl] -//│ ╙── ^^ -//│ = [undefined] diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls new file mode 100644 index 0000000000..5f7bfab4e2 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls @@ -0,0 +1,138 @@ +:js + +import "../../../mlscript-compile/Stack.mls" +import "../../../mlscript-compile/Option.mls" + +open Stack +open Option { Some, None } + +pattern DropZ = ((:x, :y, :z)) => (x: x, y: y) + +:ucs ups +pattern HomogeneousCoordinate = ((:x, :y, w: ~0 as w)) => (x: x / w, y: y / w) +//│ elaborated pattern body: {x: x, y: y, w: ¬0 as w} => { let x; x = builtin:/#0(x#666, w#666); "x": x#0; let y; y = builtin:/#1(y#666, w#666); "y": y#0; } + +:ucs ups +pattern SumList = (Nil => 0) | ((Int as hd) :: (SumList as tl)) => hd + tl +//│ elaborated pattern body: #Nil() => 0 ∨ #Cons(#Int() as hd, #SumList() as tl) => builtin:+#0(hd#666, tl#666) + +// Flatten the nested tuples. Note that it makes use of biased union. +pattern Flatten = ([Flatten as hd, ...Flatten as tl] => Array.concat(hd, tl)) | _ + +object Leaf +data class Node[A](value: A, left: Node[A], right: Node[A]) + +pattern TreeDepth = (Leaf => 0) | (Node(_, TreeDepth as left, TreeDepth as right) => 1 + Math.max(left, right)) + +pattern CountTree(pattern P) = + (Node(P as x, CountTree(P) as left, CountTree(P) as right) => 1 + left + right) | + ((Node(_, _, _) | Leaf) => 0) + +// Match a 2x2 matrix and return its transpose. +:ucs ups +pattern Transpose = (null => null) | (([[a, b], [c, d]]) => [[a, c], [b, d]]) +//│ elaborated pattern body: null => null ∨ [[a, b], [c, d]] => [[a#666, c#666], [b#666, d#666]] + +// Convert a color in RGB format to grayscale +pattern Grayscale = ((:r, :g, :b)) => r * 0.299 + g * 0.587 + b * 0.114 + +// Return the middle element of a list. It won't match if the length is even. +pattern Middle = ([x] => x) | ([x, ...Middle, y] => Middle) + +// Return the leaves of a tree. +pattern Leaves = (null => Nil) | ([x] => x :: Nil) | ([Leaves as left, Leaves as right] => left ::: right) + +// Extract the first and last elements of a list +pattern FirstLast = ([x, ..._, y] => (x, y)) + +// Check if a list is a palindrome +pattern IsPalindrome = ([x, ...IsPalindrome as m, y] => m && x == y) | (([_] | []) => true) | (_ => false) + +// Extract the diagonal elements of a 3x3 matrix +pattern MatrixDiagonal = ([[a, _, _], [_, b, _], [_, _, c]] => [a, b, c]) + +// Flatten a binary tree to a list using in-order traversal +pattern InOrderTraversal = + (Leaf => []) | + (Node(x, InOrderTraversal as left, InOrderTraversal as right) => left ::: [x] ::: right) + +fun max(ow, v) = if ow is + Some(w) and w > v then w + else v + +fun min(ow, v) = if ow is + Some(w) and w < v then w + else v + +// Extract the maximum value from a binary tree +pattern TreeMax = + (Leaf => None) | + (Node(x, TreeMax as left, TreeMax as right) => max(max(left, x), right)) +pattern TreeMin = + (Leaf => None) | + (Node(x, TreeMin as left, TreeMin as right) => min(min(left, x), right)) + +// Check if a binary tree is balanced +pattern IsBalanced = (Leaf => true) | (Node(_, IsBalanced as left, IsBalanced as right) => Math.abs(TreeDepth(left) - TreeDepth(right)) <= 1) + +// Extract the path from root to a target value in a binary tree +pattern PathToValue(pattern target) = + (Leaf => []) | + (Node(PathToValue(target) as left, target, _) => [0] ::: left) | + (Node(_, target, PathToValue(target) as right) => [1] ::: right) | + (Node(PathToValue(target) as left, _, _) => [0] ::: left) | + (Node(_, _, PathToValue(target) as right) => [1] ::: right) + +:e +// Convert a list of pairs to a map-like object +pattern ListToMap = (Nil => ()) | ([[Str as k, v], ...ListToMap as rest] => (...rest, [k]: v)) +//│ ╔══[ERROR] Unexpected record key shape. +//│ ║ l.88: pattern ListToMap = (Nil => ()) | ([[Str as k, v], ...ListToMap as rest] => (...rest, [k]: v)) +//│ ╙── ^^^ + +fun contains(xs, x) = if xs is + hd :: tl and + hd == x then true + else tl contains(x) + Nil then false + +// Apply a pattern to a list and return a list of unique elements. +pattern Unique(pattern P) = + (Nil => Nil) | + (((P as hd) :: (Unique(P) as tl)) => if tl contains(hd) then tl else hd :: tl) + +// Extract elements at even indices in a tuple. +pattern EvenIndicesTuple = + ([] => []) | + ([x] => [x]) | + ([x, _, ...EvenIndices as rest] => [x, ...rest]) + +// Extract elements at even indices in a list. +pattern EvenIndicesList = + (Nil => Nil) | + ((x :: Nil) => x) | + ([x, _, ...EvenIndices as xs] => x :: xs) + + +pattern CelsiusToFahrenheit = (Num as c) => c * 9 / 5 + 32 +pattern FahrenheitToCelsius = (Num as f) => (f - 32) * 5/9 + +// Compute Body Mass Index (BMI) from weight (kg) and height (m) +pattern CalculateBMI = ((:weight, :height)) => weight / (height * height) + +// Convert 24-hour time format to 12-hour format with AM/PM +pattern Time12Hour = + ((hour: hour & ((((0 ..= 12) => "AM") | ((13 ..= 23) => "PM")) as flag), :minute)) => + hour + ":" + minute + " " + flag + +// Calculate the area of different shapes +pattern ShapeArea = + (((:radius)) => Math.PI * radius * radius) | // Circle + (((:width, :height)) => width * height) | // Rectangle + (((:base, :height)) => base * height / 2) // Triangle + +// Convert between different number systems +pattern NumberSystem = + (((:value, radix: "binary")) => value.toString(2)) | + (((:value, radix: "hex")) => value.toString(16)) | + (((:value, radix: "octal")) => value.toString(8)) diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls new file mode 100644 index 0000000000..cd47f8c9a3 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls @@ -0,0 +1,156 @@ +:js + +:todo // Patterns at parameter position are not supported yet. +0 => false +//│ /!!!\ Uncaught error: scala.MatchError: IntLit(0) (of class hkmc2.syntax.Tree$IntLit) + +:ucs ups +pattern BooleanLike = true | false | (0 => false) | (1 => true) +//│ elaborated pattern body: true ∨ false ∨ 0 => false ∨ 1 => true + +import "../../../mlscript-compile/Stack.mls" + +open Stack + +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#666.)Nil‹member:Nil› ∨ #Pair(#T() as hd, #PlainList(#T()) as tl) => (member:Stack#666.)Cons‹member:Cons›(hd#666, tl#666) + +:fixme // Decide the operator precedence between `as` and `=>`. +pattern Identity(pattern T) = T as x => x +//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(Ident(T),keyword 'as',InfixApp(Tup(List(Ident(x))),keyword '=>',Ident(x))) (of class hkmc2.syntax.Tree$InfixApp) + +:ucs ups +pattern Identity(pattern T) = (T as x) => x +//│ elaborated pattern body: (#T() as x) => x#666 + +pattern Identity = x => x + +:ucs ups +pattern Sick = (0 as a as b as c as d as e as f) => a + b + c + d + e + f +//│ elaborated pattern body: ((((((0 as a) as b) as c) as d) as e) as f) => builtin:+#4(builtin:+#3(builtin:+#2(builtin:+#1(builtin:+#0(a#666, b#666), c#666), d#666), e#666), f#666) + +:ucs ups +pattern PairLike = [fst, snd] => Pair(fst, snd) +//│ elaborated pattern body: [fst, snd] => member:Pair#666(fst#666, snd#666) + +:ucs ups +pattern PlainList(pattern T) = (null => Nil) | ([T as hd, PlainList(T) as tl] => hd :: tl) +//│ elaborated pattern body: null => (member:Stack#666.)Nil‹member:Nil› ∨ [#T() as hd, #PlainList(#T()) as tl] => (member:Stack#666.)Cons‹member:Cons›(hd#666, tl#666) + +:fixme +[T as hd, PlainList(pattern T) as tl] +//│ ╔══[ERROR] Name not found: hd +//│ ║ l.44: [T as hd, PlainList(pattern T) as tl] +//│ ╙── ^^ +//│ ╔══[ERROR] Illegal type declaration in term position. +//│ ║ l.44: [T as hd, PlainList(pattern T) as tl] +//│ ╙── ^ +//│ ╔══[ERROR] Name not found: tl +//│ ║ l.44: [T as hd, PlainList(pattern T) as tl] +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] TypeError: PlainList3 is not a function + +:ucs ups +pattern Consistent = ((Int as x) | (Str as x)) => x.toString() +//│ elaborated pattern body: (#Int() as x ∨ #Str() as x) => x#666.toString() + +:e +pattern Inconsistent = ((Int as x) | Str) => x.toString() +//│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. +//│ ║ l.61: pattern Inconsistent = ((Int as x) | Str) => x.toString() +//│ ║ ^ +//│ ╟── The variable is missing from this sub-pattern. +//│ ║ l.61: pattern Inconsistent = ((Int as x) | Str) => x.toString() +//│ ╙── ^^^ +//│ ╔══[ERROR] Name not found: x +//│ ║ l.61: pattern Inconsistent = ((Int as x) | Str) => x.toString() +//│ ╙── ^ + +:ucs ups +pattern Negated = ~(Int | Str) +//│ elaborated pattern body: ¬(#Int() ∨ #Str()) + +:todo // The precedence of `=>` is higher than `~`? +:pt +pattern BadNegated = ~(Int as x) => x +//│ Parsed tree: +//│ TypeDef: +//│ k = Pat +//│ head = Ident of "BadNegated" +//│ rhs = S of App: +//│ lhs = Ident of "~" +//│ rhs = Tup of Ls of +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ InfixApp: +//│ lhs = Ident of "Int" +//│ kw = keyword 'as' +//│ rhs = Ident of "x" +//│ kw = keyword '=>' +//│ rhs = Ident of "x" +//│ body = N + +:ucs ups +:e +pattern BadNegated = (~(Int as x)) => x +//│ ╔══[ERROR] This pattern cannot be bound. +//│ ║ l.98: pattern BadNegated = (~(Int as x)) => x +//│ ║ ^^^ +//│ ╟── Because the pattern it belongs to is negated. +//│ ║ l.98: pattern BadNegated = (~(Int as x)) => x +//│ ╙── ^^^^^^^^ +//│ ╔══[ERROR] Name not found: x +//│ ║ l.98: pattern BadNegated = (~(Int as x)) => x +//│ ╙── ^ +//│ elaborated pattern body: ¬(#Int() as x) => + +:ucs ups +:e +pattern BadNegated = (~Pair(Int, x)) => x +//│ ╔══[ERROR] This variable cannot be accessed. +//│ ║ l.112: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ ^ +//│ ╟── Because the pattern it belongs to is negated. +//│ ║ l.112: pattern BadNegated = (~Pair(Int, x)) => x +//│ ╙── ^^^^^^^^^^^ +//│ ╔══[ERROR] Name not found: x +//│ ║ l.112: pattern BadNegated = (~Pair(Int, x)) => x +//│ ╙── ^ +//│ elaborated pattern body: ¬#Pair(#Int(), x) => + +:ucs ups +pattern Origin = (x: 0, y: 0) +//│ elaborated pattern body: {x: 0, y: 0} + +:ucs ups +pattern Origin = (x: 0, y: 0, z: 0) +//│ elaborated pattern body: {x: 0, y: 0, z: 0} + +:ucs ups +pattern PointLike = ((x: x, y: y)) => x + y +//│ elaborated pattern body: {x: x, y: y} => builtin:+#5(x#666, y#666) + +:ucs ups +pattern PointLike = ((:x, :y, :z)) => x + y +//│ elaborated pattern body: {x: x, y: y, z: z} => builtin:+#6(x#666, y#666) + +:ucs ups +pattern NestedTuple = [[[]]] +//│ elaborated pattern body: [[[]]] + +pattern Digit = "0" ..= "9" + +pattern Naughty = CanYouSeeMe(n) => n +class CanYouSeeMe(wow: Int) + +:r +fun y = HiddenCorner.m.m.k +pattern Naughty = HiddenCorner.m.m.YouCantSeeMe(n) => n + HiddenCorner.m.m.k +module HiddenCorner with + val m: module HiddenCorner = HiddenCorner + class YouCantSeeMe(wow: Int) + val k = 0 +//│ Resolved: ( ‹› fun member:y = member:HiddenCorner#666.m‹member:m›.m.k; Pat Naughty ( ‹› fun member:unapply(Param(‹›,scrut,None,Modulefulness(None)), ) = if { else }; ‹› fun member:unapplyStringPrefix(Param(‹›,topic,None,Modulefulness(None)), ) = if { else ($runtime#18.)MatchFailure‹class:MatchFailure›() }; ); Mod HiddenCorner ( ‹› val member:m: member:HiddenCorner#666 = member:HiddenCorner#666; Cls YouCantSeeMeParamList(‹›,List(Param(‹›,wow,Some(Ref(member:Int)),Modulefulness(None))),None) ( let class:YouCantSeeMe.wow; class:YouCantSeeMe.wow = wow#0; ); ‹› val member:k = 0; ); ) From 7ac28c7489b8afbdfe0030c07cbed0c79ca831f4 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 17 Jun 2025 20:05:08 +0800 Subject: [PATCH 05/62] Fix pattern examples --- .../main/scala/hkmc2/semantics/Pattern.scala | 7 ++- .../rp/syntax/InterestingPatterns.mls | 36 +++++++------ .../test/mlscript/rp/syntax/PatternBody.mls | 52 ++++++++++++------- 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index f1c43461ad..260fb6b77d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -195,10 +195,9 @@ enum Pattern extends AutoLocated: 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) => - val leadingStr = leading.map(_.showDbg).mkString(", ") - val spreadStr = spread.map(s => s", ...${s.showDbg}").mkStringOr("", ", ", "") - val trailingStr = trailing.map(_.showDbg).mkStringOr("", ", ", "") - List(leadingStr, spreadStr, trailingStr).filter(_.nonEmpty).mkString("[", ", ", "]") + (leading.iterator.map(_.showDbg) ++ + spread.iterator.map(s => "..." + s.showDbg) ++ + trailing.iterator.map(_.showDbg)).mkString("[", ", ", "]") case Record(fields) => s"{${fields.map((k, v) => s"${k.name}: ${v.showDbg}").mkString(", ")}}" case Alias(Wildcard(), alias) => alias.name case Alias(pattern, alias) => s"${pattern.showDbgWithPar} as ${alias.name}" diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls index 5f7bfab4e2..27eb3ea8d4 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls @@ -26,7 +26,8 @@ pattern TreeDepth = (Leaf => 0) | (Node(_, TreeDepth as left, TreeDepth as right pattern CountTree(pattern P) = (Node(P as x, CountTree(P) as left, CountTree(P) as right) => 1 + left + right) | - ((Node(_, _, _) | Leaf) => 0) + (Node(_, CountTree(P) as left, CountTree(P) as right) => left + right) | + (Leaf => 0) // Match a 2x2 matrix and return its transpose. :ucs ups @@ -37,16 +38,20 @@ pattern Transpose = (null => null) | (([[a, b], [c, d]]) => [[a, c], [b, d]]) pattern Grayscale = ((:r, :g, :b)) => r * 0.299 + g * 0.587 + b * 0.114 // Return the middle element of a list. It won't match if the length is even. -pattern Middle = ([x] => x) | ([x, ...Middle, y] => Middle) +:ucs ups +pattern Middle = ([x] => x) | ([x, ...(Middle as m), y] => m) +//│ elaborated pattern body: [x] => x#666 ∨ [x, ...#Middle() as m, y] => m#666 // Return the leaves of a tree. pattern Leaves = (null => Nil) | ([x] => x :: Nil) | ([Leaves as left, Leaves as right] => left ::: right) // Extract the first and last elements of a list -pattern FirstLast = ([x, ..._, y] => (x, y)) +pattern FirstLast = ([x, ..._, y] => [x, y]) // Check if a list is a palindrome +:ucs ups pattern IsPalindrome = ([x, ...IsPalindrome as m, y] => m && x == y) | (([_] | []) => true) | (_ => false) +//│ elaborated pattern body: [x, ...#IsPalindrome() as m, y] => builtin:&�(m#666, builtin:==#0(x#666, y#666)) ∨ ([_] ∨ []) => true ∨ _ => false // Extract the diagonal elements of a 3x3 matrix pattern MatrixDiagonal = ([[a, _, _], [_, b, _], [_, _, c]] => [a, b, c]) @@ -83,17 +88,11 @@ pattern PathToValue(pattern target) = (Node(PathToValue(target) as left, _, _) => [0] ::: left) | (Node(_, _, PathToValue(target) as right) => [1] ::: right) -:e // Convert a list of pairs to a map-like object -pattern ListToMap = (Nil => ()) | ([[Str as k, v], ...ListToMap as rest] => (...rest, [k]: v)) -//│ ╔══[ERROR] Unexpected record key shape. -//│ ║ l.88: pattern ListToMap = (Nil => ()) | ([[Str as k, v], ...ListToMap as rest] => (...rest, [k]: v)) -//│ ╙── ^^^ +pattern ListToMap = (Nil => ()) | (([Str as k, v] :: ListToMap as rest) => (...rest, (k): v)) fun contains(xs, x) = if xs is - hd :: tl and - hd == x then true - else tl contains(x) + hd :: tl and { hd === x then true, else tl contains(x) } Nil then false // Apply a pattern to a list and return a list of unique elements. @@ -110,9 +109,8 @@ pattern EvenIndicesTuple = // Extract elements at even indices in a list. pattern EvenIndicesList = (Nil => Nil) | - ((x :: Nil) => x) | - ([x, _, ...EvenIndices as xs] => x :: xs) - + ((x :: Nil) => x) | + ((x :: _ :: EvenIndices as xs) => x :: xs) pattern CelsiusToFahrenheit = (Num as c) => c * 9 / 5 + 32 pattern FahrenheitToCelsius = (Num as f) => (f - 32) * 5/9 @@ -120,10 +118,14 @@ pattern FahrenheitToCelsius = (Num as f) => (f - 32) * 5/9 // Compute Body Mass Index (BMI) from weight (kg) and height (m) pattern CalculateBMI = ((:weight, :height)) => weight / (height * height) +pattern HourAM = 0 ..= 12 +pattern HourPM = ((13 ..= 23) as hour) => hour - 12 + // Convert 24-hour time format to 12-hour format with AM/PM -pattern Time12Hour = - ((hour: hour & ((((0 ..= 12) => "AM") | ((13 ..= 23) => "PM")) as flag), :minute)) => - hour + ":" + minute + " " + flag +pattern Time12Hour = (( + hour: ((HourAM | HourPM) as hour) & ((HourAM => "AM" | HourPM => "PM") as flag), + :minute +)) => hour + ":" + minute + " " + flag // Calculate the area of different shapes pattern ShapeArea = diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls index cd47f8c9a3..08f630bac0 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls @@ -58,16 +58,22 @@ pattern Consistent = ((Int as x) | (Str as x)) => x.toString() //│ elaborated pattern body: (#Int() as x ∨ #Str() as x) => x#666.toString() :e -pattern Inconsistent = ((Int as x) | Str) => x.toString() -//│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. -//│ ║ l.61: pattern Inconsistent = ((Int as x) | Str) => x.toString() -//│ ║ ^ -//│ ╟── The variable is missing from this sub-pattern. -//│ ║ l.61: pattern Inconsistent = ((Int as x) | Str) => x.toString() -//│ ╙── ^^^ +pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ╔══[ERROR] This pattern cannot be bound. +//│ ║ l.61: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ ^^^ +//│ ╟── Because the pattern it belongs to is negated. +//│ ║ l.61: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ╙── ^^^^^^^^ +//│ ╔══[ERROR] Name not found: x +//│ ║ l.61: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ╙── ^ + +:e +pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() //│ ╔══[ERROR] Name not found: x -//│ ║ l.61: pattern Inconsistent = ((Int as x) | Str) => x.toString() -//│ ╙── ^ +//│ ║ l.73: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() +//│ ╙── ^ :ucs ups pattern Negated = ~(Int | Str) @@ -97,30 +103,38 @@ pattern BadNegated = ~(Int as x) => x :e pattern BadNegated = (~(Int as x)) => x //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.98: pattern BadNegated = (~(Int as x)) => x -//│ ║ ^^^ +//│ ║ l.104: pattern BadNegated = (~(Int as x)) => x +//│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.98: pattern BadNegated = (~(Int as x)) => x -//│ ╙── ^^^^^^^^ +//│ ║ l.104: pattern BadNegated = (~(Int as x)) => x +//│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.98: pattern BadNegated = (~(Int as x)) => x -//│ ╙── ^ +//│ ║ l.104: pattern BadNegated = (~(Int as x)) => x +//│ ╙── ^ //│ elaborated pattern body: ¬(#Int() as x) => :ucs ups :e pattern BadNegated = (~Pair(Int, x)) => x //│ ╔══[ERROR] This variable cannot be accessed. -//│ ║ l.112: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.118: pattern BadNegated = (~Pair(Int, x)) => x //│ ║ ^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.112: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.118: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.112: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.118: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^ //│ elaborated pattern body: ¬#Pair(#Int(), x) => +:ucs ups +pattern DoubleNegated = ~(~Int) +//│ elaborated pattern body: ¬¬#Int() + +:ucs ups +pattern Nothing = ~_ +//│ elaborated pattern body: ¬_ + :ucs ups pattern Origin = (x: 0, y: 0) //│ elaborated pattern body: {x: 0, y: 0} @@ -153,4 +167,4 @@ module HiddenCorner with val m: module HiddenCorner = HiddenCorner class YouCantSeeMe(wow: Int) val k = 0 -//│ Resolved: ( ‹› fun member:y = member:HiddenCorner#666.m‹member:m›.m.k; Pat Naughty ( ‹› fun member:unapply(Param(‹›,scrut,None,Modulefulness(None)), ) = if { else }; ‹› fun member:unapplyStringPrefix(Param(‹›,topic,None,Modulefulness(None)), ) = if { else ($runtime#18.)MatchFailure‹class:MatchFailure›() }; ); Mod HiddenCorner ( ‹› val member:m: member:HiddenCorner#666 = member:HiddenCorner#666; Cls YouCantSeeMeParamList(‹›,List(Param(‹›,wow,Some(Ref(member:Int)),Modulefulness(None))),None) ( let class:YouCantSeeMe.wow; class:YouCantSeeMe.wow = wow#0; ); ‹› val member:k = 0; ); ) +//│ Resolved: ( ‹› fun member:y = member:HiddenCorner#666.m‹member:m›.m.k; Pat Naughty ( ‹› fun member:unapply(Param(‹›,scrut,None,Modulefulness(None)), ) = if { else }; ‹› fun member:unapplyStringPrefix(Param(‹›,topic,None,Modulefulness(None)), ) = if { else ($runtime#21.)MatchFailure‹class:MatchFailure›() }; ); Mod HiddenCorner ( ‹› val member:m: member:HiddenCorner#666 = member:HiddenCorner#666; Cls YouCantSeeMeParamList(‹›,List(Param(‹›,wow,Some(Ref(member:Int)),Modulefulness(None))),None) ( let class:YouCantSeeMe.wow; class:YouCantSeeMe.wow = wow#0; ); ‹› val member:k = 0; ); ) From abc59262955fe7b3036262a9306d239e5d4ae546 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Wed, 18 Jun 2025 16:51:41 +0800 Subject: [PATCH 06/62] Support top-level pattern bindings --- .../scala/hkmc2/semantics/Elaborator.scala | 92 +++++++++++-------- .../main/scala/hkmc2/semantics/Pattern.scala | 63 +++++++++++-- .../hkmc2/semantics/ucs/Translator.scala | 14 +-- hkmc2/shared/src/test/mlscript/rp/Future.mls | 15 +-- .../mlscript/rp/specialization/SimpleList.mls | 8 +- .../test/mlscript/rp/syntax/Declaration.mls | 13 ++- .../rp/syntax/InterestingPatterns.mls | 6 +- .../test/mlscript/rp/syntax/PatternBody.mls | 82 ++++++++++++----- 8 files changed, 199 insertions(+), 94 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index f9991db018..99bab311a3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -464,7 +464,7 @@ extends Importer: Term.FunTy(subterm(lhs), subterm(rhs), N) case InfixApp(lhs, Keyword.`=>`, rhs) => ctx.nest(OuterCtx.LambdaOrHandlerBlock).givenIn: - val (syms, nestCtx) = params(lhs, false) + val (syms, nestCtx) = params(lhs, false, false) Term.Lam(syms, term(rhs)(using nestCtx)) case InfixApp(lhs, Keyword.`as`, rhs) => Term.Asc(subterm(lhs), subterm(rhs)) @@ -959,7 +959,7 @@ extends Importer: // * Add parameters to context var newCtx = newCtx1 val pss = td.paramLists.map: ps => - val (res, newCtx2) = params(ps, false)(using newCtx) + val (res, newCtx2) = params(ps, false, false)(using newCtx) newCtx = newCtx2 res // * Elaborate signature @@ -1045,7 +1045,7 @@ extends Importer: .map: ps => val (res, newCtx2) = given Ctx = newCtx - params(ps, isDataClass) + params(ps, isDataClass, k is Pat) newCtx = newCtx2 res def withFields(using Ctx)(fn: (Ctx) ?=> (Term.Blk, Ctx)): (Term.Blk, Ctx) = @@ -1106,32 +1106,53 @@ extends Importer: case Pat => val patSym = td.symbol.asInstanceOf[PatternSymbol] // TODO improve `asInstanceOf` val owner = ctx.outer.inner + // OK. It seems that the context after `nestInner` already have all parameters. newCtx.nestInner(patSym).givenIn: + // Pattern definition should not have a body like class definition. assert(body.isEmpty) - // Filter out parameters marked as `pattern`. - val (patternParams, extractionParams) = ps match - case S(ParamList(_, params, _)) => params.partition: - case param @ Param(flags = FldFlags(false, false, false, true, false)) => true - case param @ Param(flags = FldFlags(false, false, false, false, false)) => false + // 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)): + _.params.flatMap: + // Only `pat` flag is `true`. + case p @ Param(flags = FldFlags(false, false, false, true, false)) => S(p) + // All flags are `false`. + case p @ Param(flags = FldFlags(false, false, false, false, false)) => S(p) case Param(flags, sym, _, _) => raise(ErrorReport(msg"Unexpected pattern parameter ${sym.name} with flags ${flags.showDbg}" -> sym.toLoc :: Nil)) - false - case N => (Nil, Nil) - // Elaborate pattern RHS using term elaboration. - val rhsTree = td.rhs.getOrElse: + N + .partition(_.flags.pat) + log(s"pattern parameters: ${patternParams.mkString("[", ", ", "]")}") + log(s"extraction parameters: ${extractionParams.mkString("[", ", ", "]")}") + // Empty pattern body is considered as wildcard patterns. + val rhs = td.rhs.getOrElse: raise(ErrorReport(msg"Pattern definitions must have a body." -> td.toLoc :: Nil)) Tree.Under() - val rhsPat = pattern(rhsTree)(using ctx ++ patternParams.iterator.map(p => p.sym.name -> p.sym)) - scoped("ucs:ups"): - log(s"elaborated pattern body: ${rhsPat.showDbg}") + // Elaborate the pattern body with the pattern parameters. + val pat = pattern(rhs)(using ctx ++ patternParams.iterator.map(p => p.sym.name -> p.sym)) + // Report all invalid variables we found in the top-level pattern. + pat.variables.report + // Note that the remaining variables have not been bounded to any + // `VarSymbol` yet. Thus, we need to pair them with the extraction + // parameters. We only report warnings for unbounded variables + // because they are harmless. + pat.variables.varMap.foreach: (name, alias) => + extractionParams.find(_.sym.name == name) match + case S(param) => alias.symbol = param.sym + case N => raise(WarningReport(msg"Useless pattern binding: $name." -> alias.toLoc :: Nil)) + scoped("ucs:ups")(log(s"elaborated pattern body: ${pat.showDbg}")) + scoped("ucs:ups:tree")(log(s"elaborated pattern body: ${pat.showAsTree}")) + // *** BEGIN OBSOLETE CODE *** td.rhs match case N => raise(ErrorReport(msg"Pattern definitions must have a body." -> td.toLoc :: Nil)) case S(tree) => - // TODO: Implement extraction parameters. - if extractionParams.nonEmpty then - raise(ErrorReport(msg"Pattern extraction parameters are not yet supported." -> - Loc(extractionParams.iterator.map(_.sym)) :: Nil)) + // // TODO: Implement extraction parameters. + // if extractionParams.nonEmpty then + // raise(ErrorReport(msg"Pattern extraction parameters are not yet supported." -> + // Loc(extractionParams.iterator.map(_.sym)) :: Nil)) log(s"pattern parameters: ${patternParams.mkString("{ ", ", ", " }")}") patSym.patternParams = patternParams val split = ucs.DeBrujinSplit.elaborate(patternParams, tree, this) @@ -1145,7 +1166,7 @@ extends Importer: Nil, // ps.map(_.params).getOrElse(Nil), // TODO[Luyu]: remove pattern parameters td.rhs.getOrElse(die)) // *** END OBSOLETE CODE *** - val pd = PatternDef(owner, patSym, sym, tps, ps, rhsPat, + val pd = PatternDef(owner, patSym, sym, tps, ps, pat, ObjBody(Blk(bod, Term.Lit(UnitLit(false)))), annotations) patSym.defn = S(pd) pd @@ -1245,7 +1266,13 @@ extends Importer: go(t, inUsing, if inDataClass then FldFlags.empty.copy(value = true) else FldFlags.empty, false) - def params(t: Tree, inDataClass: Bool): Ctxl[(ParamList, Ctx)] = t match + /** Elaborate a parameter list of a term or a definition. + * @param inDataClass Whether the parameter list belongs to a data class. + * @param inPattern Whether the parameter list belongs to a pattern definition. + * If `inPattern` is `true`, only parameters with `pat` flag + * will be added to the context. + */ + def params(t: Tree, inDataClass: Bool, inPattern: Bool): Ctxl[(ParamList, Ctx)] = t match case Tup(ps) => def go(ps: Ls[Tree], acc: Ls[Param], ctx: Ctx, flags: ParamListFlags): (ParamList, Ctx) = ps match @@ -1256,7 +1283,7 @@ extends Importer: val isCtx = hd match case TermDef(k = Ins, rhs = N) => true case _ => false - val newCtx = ctx + (p.sym.name -> p.sym) + val newCtx = if !inPattern || p.flags.pat then ctx + (p.sym.name -> p.sym) else ctx val newFlags = if isCtx then flags.copy(ctx = true) else flags if isCtx && acc.nonEmpty then raise(ErrorReport(msg"Keyword `using` must occur before all parameters." -> hd.toLoc :: Nil)) @@ -1284,25 +1311,10 @@ extends Importer: * variables we found in `p`. */ def arrow(lhs: Tree, rhs: Tree): Ctxl[Pattern] = val pattern = go(lhs) - val ctx2 = ctx ++ pattern.variables.map: + val termCtx = ctx ++ pattern.variables.map: case alias => alias.id.name -> alias.allocate - // Report all invalid variables we found in `pattern`. - pattern.variables.invalidVars.foreach: - case (Alias(_, id), Duplicated(previous)) => - raise(ErrorReport(msg"Duplicate pattern variable." -> id.toLoc :: - msg"The previous definition is here." -> previous.toLoc :: Nil)) - case (Alias(_, id), Inconsistent(disjunction, missingOnTheLeft)) => - raise(ErrorReport( - msg"Found an inconsistent variable in disjunction patterns." -> id.toLoc :: - msg"The variable is missing from this sub-pattern." -> ( - if missingOnTheLeft then disjunction.left else disjunction.right - ).toLoc :: Nil)) - case (Alias(pattern, id), Negated(negation)) => - raise(ErrorReport((pattern match - case Wildcard() => msg"This variable cannot be accessed." -> id.toLoc - case _: Pattern => msg"This pattern cannot be bound." -> pattern.toLoc - ) :: msg"Because the pattern it belongs to is negated." -> negation.toLoc :: Nil)) - Transform(pattern, term(rhs)(using ctx2)) + pattern.variables.report // Report all invalid variables we found in `pattern`. + Transform(pattern, term(rhs)(using termCtx)) /** Elaborate tuple patterns like `[p1, p2, ...ps, pn]`. */ def tuple(ts: Ls[Tree]): Ctxl[Pattern.Tuple] = val z = (Ls[Pattern](), N: Opt[Pattern], Ls[Pattern]()) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index 260fb6b77d..897a06c0c9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -2,14 +2,14 @@ package hkmc2 package semantics import mlscript.utils.*, shorthands.*, collection.immutable.HashMap -import syntax.Tree, Elaborator.State +import syntax.Tree, Tree.Ident, Elaborator.State, Message.MessageContext, ucs.error import scala.annotation.tailrec, util.chaining.* object Pattern: /** The reason why a variable obtained from `Pattern.variables` is invalid. */ enum InvalidReason: /** The variable shadowed another variable. */ - case Duplicated(previous: Tree.Ident) + case Duplicated(previous: Ident) /** The variable presents in one side of disjunction, but not the other. */ case Inconsistent(disjunction: Pattern.Composition, missingOnTheLeft: Bool) /** The variable is bound in a `Negation` pattern. */ @@ -18,6 +18,16 @@ object Pattern: import InvalidReason.* + /** A set of variables that present in a pattern. + * + * These variables are represented by `Tree.Ident` because not every identifier + * can represent a meaningful binding. This set is used in the computed property + * on the `Pattern` tree. We only create `VarSymbol` for these variables at the + * root node or when elaborating the term of a `Transform` pattern. + * + * @param varMap A map from variable names to their latest aliases. + * @param invalidVars A list of variables and the reason why they are invalid. + */ final case class Variables( varMap: HashMap[Str, Pattern.Alias], invalidVars: Ls[(Pattern.Alias, InvalidReason)] @@ -58,9 +68,25 @@ object Pattern: notInThis.iterator.map(_._2 -> Inconsistent(pattern, true)).toList ::: notInThat.iterator.map(_._2 -> Inconsistent(pattern, false)).toList) + /** Mark all variables in this set as invalid. */ def invalidated(reason: InvalidReason): Variables = Variables(HashMap.empty, invalidVars appendedAll varMap.iterator.map(_._2 -> reason)) + /** Report all invalid variables. */ + def report(using Raise): Unit = invalidVars.foreach: + case (Alias(_, id), Duplicated(previous)) => error( + msg"Duplicate pattern variable." -> id.toLoc, + msg"The previous definition is here." -> previous.toLoc) + case (Alias(_, id), Inconsistent(disjunction, missingOnTheLeft)) => error( + msg"Found an inconsistent variable in disjunction patterns." -> id.toLoc, + msg"The variable is missing from this sub-pattern." -> ( + if missingOnTheLeft then disjunction.left else disjunction.right + ).toLoc) + case (Alias(pattern, id), Negated(negation)) => error(pattern match + case Wildcard() => msg"This variable cannot be accessed." -> id.toLoc + case _: Pattern => msg"This pattern cannot be bound." -> pattern.toLoc, + msg"Because the pattern it belongs to is negated." -> negation.toLoc) + object Variables: lazy val empty: Variables = Variables(HashMap.empty, Nil) @@ -69,11 +95,24 @@ object Pattern: case (vars, pattern) => vars ++ pattern.variables /** A shorthand for creating a variable pattern. */ - def Variable(id: Tree.Ident): Pattern.Alias = Pattern.Wildcard().bind(id) + def Variable(id: Ident): Pattern.Alias = Pattern.Wildcard().bind(id) + + trait ConstructorImpl: + self: Pattern.Constructor => + + /** Get the resolved symbol of the target term. */ + def symbol: Opt[Symbol] = self.target.resolvedSymbol + + /** Expect the `symbol` to be set. */ + def symbol_! : Symbol = symbol.getOrElse(lastWords("symbol is not set")) + /** Add a mutable field to the `Alias` pattern to store the symbol for the + * variable. Note that NOT every `Alias` pattern has a symbol. */ trait AliasImpl: self: Pattern.Alias => private var _symbol: Opt[VarSymbol] = N + /** Directly set the symbol for the variable. This should be called in the + * elaborator when elaborating the non-`Transform` top-level pattern. */ def symbol_=(symbol: VarSymbol): Unit = _symbol = S(symbol) def symbol: VarSymbol = _symbol.getOrElse(lastWords("symbol is not set")) /** Allocate the symbol for the variable. This should be called in the @@ -108,26 +147,30 @@ enum Pattern extends AutoLocated: /** A pattern that matches a range of values. */ case Range(lower: syntax.Literal, upper: syntax.Literal, rightInclusive: Bool) - /** A pattern that matches the concatenation of two string patterns. */ + /** A pattern that matches the concatenation of two string patterns. The + * concatenation of non-string patterns results in a never-match pattern. */ case Concatenation(left: Pattern, right: Pattern) - /** A pattern that matches a tuple. */ + /** 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]) - /** A pattern that matches a record. */ - case Record(fields: Ls[(Tree.Ident, Pattern)]) + /** A pattern that matches a record consisting of a list of fields. Note that + * the fields are not ordered semantically. */ + case Record(fields: Ls[(Ident, Pattern)]) /** A pattern that matches the same value as another pattern, but bind the * matched value to a new variable. This is the only class that holds the * symbol for the variable. */ - case Alias(pattern: Pattern, id: Tree.Ident) extends Pattern with AliasImpl + 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. */ case Transform(pattern: Pattern, transform: Term) - infix def bind(id: Tree.Ident): Pattern.Alias = Pattern.Alias(this, id) + infix def bind(id: Ident): Pattern.Alias = Pattern.Alias(this, id) /** Collect all variables in the pattern. Meanwhile, list invalid variables, * which will be reported when constructing symbols for variables. We use a @@ -184,7 +227,7 @@ enum Pattern extends AutoLocated: def showDbg: Str = this match case Constructor(target, arguments) => - val targetText = target.symbol.fold(target.showDbg)(_.nme.prepended('#')) + val targetText = target.symbol.fold(target.showDbg)(_.toString()) s"$targetText(${arguments.map(_.showDbg).mkString(", ")})" case Composition(true, left, right) => s"${left.showDbg} \u2228 ${right.showDbg}" case Composition(false, left, right) => s"${left.showDbg} \u2227 ${right.showDbg}" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala index 8961bacd7d..a545b23a99 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala @@ -26,7 +26,7 @@ import Translator.* /** This class translates a tree describing a pattern into functions that can * perform pattern matching on terms described by the pattern. */ -class Translator(val elaborator: Elaborator)(using State, Ctx) extends DesugaringBase: +class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends DesugaringBase: import elaborator.term, elaborator.tl.*, HelperExtractors.*, FlatPattern.MatchMode /** Each scrutinee is represented by a function that creates a reference to @@ -52,7 +52,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx) extends Desugarin plainTest(test1, "gtLo")(plainTest(test2, "ltHi")(inner(Map.empty))) /** Generate a split that consumes the entire scrutinee. */ - private def full(scrut: Scrut, pat: Tree, inner: Inner)(using patternParams: Ls[Param], raise: Raise): Split = trace( + private def full(scrut: Scrut, pat: Tree, inner: Inner)(using patternParams: Ls[Param]): Split = trace( pre = s"full <<< $pat", post = (split: Split) => s"full >>> $split" ): @@ -143,7 +143,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx) extends Desugarin /** Create a function that compiles the resulting term of each case. It checks * the captured references and sort them in the order of parameters. */ - private def success(params: Ls[Param])(using Raise): Inner = + private def success(params: Ls[Param]): Inner = val paramIndexMap = params.zipWithIndex.toMap captures => trace( pre = s"success <<< ${params.iterator.map(_.sym).mkString(", ")}", @@ -160,7 +160,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx) extends Desugarin Split.Else(makeMatchResult(Term.Tup(fields)(Tup(Nil)))) /* The successful matching result used in prefix matching functions. */ - private def prefixSuccess(params: Ls[Param])(using Raise): PrefixInner = + private def prefixSuccess(params: Ls[Param]): PrefixInner = val paramIndexMap = params.zipWithIndex.toMap (captures, postfixScrut) => trace( pre = s"prefixSuccess <<< ${params.iterator.map(_.sym).mkString(", ")}", @@ -183,7 +183,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx) extends Desugarin private def errorSplit: Split = Split.Else(Term.Error) /** Create a function definition from the given UCS splits. */ - private def makeMatcher(name: Str, scrut: VarSymbol, topmost: Split)(using Raise): TermDefinition = + private def makeMatcher(name: Str, scrut: VarSymbol, topmost: Split): TermDefinition = val sym = BlockMemberSymbol(name, Nil) val ps = PlainParamList(Param(FldFlags.empty, scrut, N, Modulefulness.none) :: Nil) val body = Term.IfLike(Keyword.`if`, topmost) @@ -200,7 +200,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx) extends Desugarin * values. If the given tree does not represent a string pattern, this * function will not be generated. */ - def apply(patternParams: Ls[Param], params: Ls[Param], body: Tree)(using Raise): Ls[TermDefinition] = trace( + def apply(patternParams: Ls[Param], params: Ls[Param], body: Tree): Ls[TermDefinition] = trace( pre = s"Translator <<< ${params.mkString(", ")} $body", post = (blk: Ls[TermDefinition]) => s"Translator >>> $blk" ): @@ -211,7 +211,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx) extends Desugarin else val unapply = scoped("ucs:cp"): val scrutSym = VarSymbol(Ident("scrut")) - val topmost = full(() => scrutSym.ref(), body, success(params))(using patternParams, raise) ~~: failure + val topmost = full(() => scrutSym.ref(), body, success(params))(using patternParams) ~~: failure log(s"Translated `unapply`: ${display(topmost)}") makeMatcher("unapply", scrutSym, topmost) val unapplyStringPrefix = scoped("ucs:cp"): diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/rp/Future.mls index e65da33b34..92c86f6f44 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Future.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Future.mls @@ -17,9 +17,15 @@ pattern Rep0(pattern A, B, C)(head) = :todo // Pattern extractions via aliases. pattern Email(name, domain) = (Identifier as name) ~ "@" ~ (Identifier as domain) -//│ ╔══[ERROR] Pattern extraction parameters are not yet supported. -//│ ║ l.18: pattern Email(name, domain) = -//│ ╙── ^^^^^^^^^^^^ +//│ ╔══[ERROR] Duplicate pattern variable. +//│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ ^^^^^^^^^^ +//│ ╟── The previous definition is here. +//│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ╙── ^^^^^^^^^^ +//│ ╔══[WARNING] Useless pattern binding: Identifier. +//│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ╙── ^^^^^^^^^^ //│ ╔══[ERROR] Unrecognized pattern (infix operator) //│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^^^^^^^^^ @@ -67,9 +73,6 @@ pattern Email(name, domains) = :todo pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╔══[ERROR] Pattern extraction parameters are not yet supported. -//│ ║ l.69: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Unrecognized pattern (infix operator) //│ ║ l.69: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ╙── ^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls b/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls index 37a0bd257b..43460088d2 100644 --- a/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls +++ b/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls @@ -140,9 +140,13 @@ fun (::) cons(head, tail) = Pair(head, tail) :todo pattern List(a) = null | Pair(a, List) -//│ ╔══[ERROR] Pattern extraction parameters are not yet supported. +//│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. //│ ║ l.142: pattern List(a) = null | Pair(a, List) -//│ ╙── ^ +//│ ║ ^ +//│ ╙── The variable is missing from this sub-pattern. +//│ ╔══[ERROR] Name not found: a +//│ ║ l.142: pattern List(a) = null | Pair(a, List) +//│ ╙── ^ //│ ╔══[ERROR] Name not found: a //│ ║ l.142: pattern List(a) = null | Pair(a, List) //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls index a66adccd04..12eb0d3133 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls @@ -5,13 +5,20 @@ pattern Foo(val T) = null | T //│ ╔══[ERROR] Unexpected pattern parameter T with flags val //│ ║ l.4: pattern Foo(val T) = null | T //│ ╙── ^ -//│ ╔══[ERROR] Pattern extraction parameters are not yet supported. +//│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. //│ ║ l.4: pattern Foo(val T) = null | T -//│ ╙── ^ +//│ ║ ^ +//│ ╙── The variable is missing from this sub-pattern. +//│ ╔══[ERROR] Name not found: T +//│ ║ l.4: pattern Foo(val T) = null | T +//│ ╙── ^ //│ ╔══[ERROR] Name not found: T //│ ║ l.4: pattern Foo(val T) = null | T //│ ╙── ^ -//│ ╔══[ERROR] Cannot use this reference as a pattern +//│ ╔══[ERROR] Name not found: T +//│ ║ l.4: pattern Foo(val T) = null | T +//│ ╙── ^ +//│ ╔══[ERROR] Name not found: T //│ ║ l.4: pattern Foo(val T) = null | T //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls index 27eb3ea8d4..f9f332fa2f 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls @@ -14,7 +14,7 @@ pattern HomogeneousCoordinate = ((:x, :y, w: ~0 as w)) => (x: x / w, y: y / w) :ucs ups pattern SumList = (Nil => 0) | ((Int as hd) :: (SumList as tl)) => hd + tl -//│ elaborated pattern body: #Nil() => 0 ∨ #Cons(#Int() as hd, #SumList() as tl) => builtin:+#0(hd#666, tl#666) +//│ elaborated pattern body: member:Nil() => 0 ∨ member:Cons(member:Int() as hd, member:SumList() as tl) => builtin:+#0(hd#666, tl#666) // Flatten the nested tuples. Note that it makes use of biased union. pattern Flatten = ([Flatten as hd, ...Flatten as tl] => Array.concat(hd, tl)) | _ @@ -40,7 +40,7 @@ pattern Grayscale = ((:r, :g, :b)) => r * 0.299 + g * 0.587 + b * 0.114 // 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#666 ∨ [x, ...#Middle() as m, y] => m#666 +//│ elaborated pattern body: [x] => x#666 ∨ [x, ...member:Middle() as m, y] => m#666 // Return the leaves of a tree. pattern Leaves = (null => Nil) | ([x] => x :: Nil) | ([Leaves as left, Leaves as right] => left ::: right) @@ -51,7 +51,7 @@ pattern FirstLast = ([x, ..._, y] => [x, y]) // Check if a list is a palindrome :ucs ups pattern IsPalindrome = ([x, ...IsPalindrome as m, y] => m && x == y) | (([_] | []) => true) | (_ => false) -//│ elaborated pattern body: [x, ...#IsPalindrome() as m, y] => builtin:&�(m#666, builtin:==#0(x#666, y#666)) ∨ ([_] ∨ []) => true ∨ _ => false +//│ elaborated pattern body: [x, ...member:IsPalindrome() as m, y] => builtin:&�(m#666, builtin:==#0(x#666, y#666)) ∨ ([_] ∨ []) => true ∨ _ => false // Extract the diagonal elements of a 3x3 matrix pattern MatrixDiagonal = ([[a, _, _], [_, b, _], [_, _, c]] => [a, b, c]) diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls index 08f630bac0..03f44644c1 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls @@ -16,7 +16,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#666.)Nil‹member:Nil› ∨ #Pair(#T() as hd, #PlainList(#T()) as tl) => (member:Stack#666.)Cons‹member:Cons›(hd#666, tl#666) +//│ elaborated pattern body: null => (member:Stack#666.)Nil‹member:Nil› ∨ member:Pair(T() as hd, member:PlainList(T()) as tl) => (member:Stack#666.)Cons‹member:Cons›(hd#666, tl#666) :fixme // Decide the operator precedence between `as` and `=>`. pattern Identity(pattern T) = T as x => x @@ -24,7 +24,7 @@ pattern Identity(pattern T) = T as x => x :ucs ups pattern Identity(pattern T) = (T as x) => x -//│ elaborated pattern body: (#T() as x) => x#666 +//│ elaborated pattern body: (T() as x) => x#666 pattern Identity = x => x @@ -36,48 +36,84 @@ pattern Sick = (0 as a as b as c as d as e as f) => a + b + c + d + e + f pattern PairLike = [fst, snd] => Pair(fst, snd) //│ elaborated pattern body: [fst, snd] => member:Pair#666(fst#666, snd#666) +:ucs ups +:e // The errors are from the old pattern compilation. +pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] +//│ elaborated pattern body: member:Pair(fst, snd) ∨ [fst, snd] +//│ ╔══[ERROR] Name not found: snd +//│ ║ l.41: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] +//│ ╙── ^^^ +//│ ╔══[ERROR] Name not found: snd +//│ ║ l.41: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] +//│ ╙── ^^^ +//│ ╔══[ERROR] Name not found: fst +//│ ║ l.41: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] +//│ ╙── ^^^ +//│ ╔══[ERROR] Name not found: fst +//│ ║ l.41: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] +//│ ╙── ^^^ + +:w +:e // The errors are from the old pattern translation and compilation. +pattern UselessParameter = x +//│ ╔══[WARNING] Useless pattern binding: x. +//│ ║ l.58: pattern UselessParameter = x +//│ ╙── ^ +//│ ╔══[ERROR] Name not found: x +//│ ║ l.58: pattern UselessParameter = x +//│ ╙── ^ +//│ ╔══[ERROR] Name not found: x +//│ ║ l.58: pattern UselessParameter = x +//│ ╙── ^ +//│ ╔══[ERROR] Name not found: x +//│ ║ l.58: pattern UselessParameter = x +//│ ╙── ^ +//│ ╔══[ERROR] Name not found: x +//│ ║ l.58: 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#666.)Nil‹member:Nil› ∨ [#T() as hd, #PlainList(#T()) as tl] => (member:Stack#666.)Cons‹member:Cons›(hd#666, tl#666) +//│ elaborated pattern body: null => (member:Stack#666.)Nil‹member:Nil› ∨ [T() as hd, member:PlainList(T()) as tl] => (member:Stack#666.)Cons‹member:Cons›(hd#666, tl#666) :fixme [T as hd, PlainList(pattern T) as tl] //│ ╔══[ERROR] Name not found: hd -//│ ║ l.44: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^^ //│ ╔══[ERROR] Illegal type declaration in term position. -//│ ║ l.44: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^ //│ ╔══[ERROR] Name not found: tl -//│ ║ l.44: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^^ //│ ═══[RUNTIME ERROR] TypeError: PlainList3 is not a function :ucs ups pattern Consistent = ((Int as x) | (Str as x)) => x.toString() -//│ elaborated pattern body: (#Int() as x ∨ #Str() as x) => x#666.toString() +//│ elaborated pattern body: (member:Int() as x ∨ member:Str() as x) => x#666.toString() :e pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.61: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.61: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.61: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╙── ^ :e pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() //│ ╔══[ERROR] Name not found: x -//│ ║ l.73: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() -//│ ╙── ^ +//│ ║ l.109: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() +//│ ╙── ^ :ucs ups pattern Negated = ~(Int | Str) -//│ elaborated pattern body: ¬(#Int() ∨ #Str()) +//│ elaborated pattern body: ¬(member:Int() ∨ member:Str()) :todo // The precedence of `=>` is higher than `~`? :pt @@ -103,33 +139,33 @@ pattern BadNegated = ~(Int as x) => x :e pattern BadNegated = (~(Int as x)) => x //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.104: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.104: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.104: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^ -//│ elaborated pattern body: ¬(#Int() as x) => +//│ elaborated pattern body: ¬(member:Int() as x) => :ucs ups :e pattern BadNegated = (~Pair(Int, x)) => x //│ ╔══[ERROR] This variable cannot be accessed. -//│ ║ l.118: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x //│ ║ ^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.118: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.118: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^ -//│ elaborated pattern body: ¬#Pair(#Int(), x) => +//│ elaborated pattern body: ¬member:Pair(member:Int(), x) => :ucs ups pattern DoubleNegated = ~(~Int) -//│ elaborated pattern body: ¬¬#Int() +//│ elaborated pattern body: ¬¬member:Int() :ucs ups pattern Nothing = ~_ @@ -167,4 +203,4 @@ module HiddenCorner with val m: module HiddenCorner = HiddenCorner class YouCantSeeMe(wow: Int) val k = 0 -//│ Resolved: ( ‹› fun member:y = member:HiddenCorner#666.m‹member:m›.m.k; Pat Naughty ( ‹› fun member:unapply(Param(‹›,scrut,None,Modulefulness(None)), ) = if { else }; ‹› fun member:unapplyStringPrefix(Param(‹›,topic,None,Modulefulness(None)), ) = if { else ($runtime#21.)MatchFailure‹class:MatchFailure›() }; ); Mod HiddenCorner ( ‹› val member:m: member:HiddenCorner#666 = member:HiddenCorner#666; Cls YouCantSeeMeParamList(‹›,List(Param(‹›,wow,Some(Ref(member:Int)),Modulefulness(None))),None) ( let class:YouCantSeeMe.wow; class:YouCantSeeMe.wow = wow#0; ); ‹› val member:k = 0; ); ) +//│ Resolved: ( ‹› fun member:y = member:HiddenCorner#666.m‹member:m›.m.k; Pat Naughty ( ‹› fun member:unapply(Param(‹›,scrut,None,Modulefulness(None)), ) = if { else }; ‹› fun member:unapplyStringPrefix(Param(‹›,topic,None,Modulefulness(None)), ) = if { else ($runtime#23.)MatchFailure‹class:MatchFailure›() }; ); Mod HiddenCorner ( ‹› val member:m: member:HiddenCorner#666 = member:HiddenCorner#666; Cls YouCantSeeMeParamList(‹›,List(Param(‹›,wow,Some(Ref(member:Int)),Modulefulness(None))),None) ( let class:YouCantSeeMe.wow; class:YouCantSeeMe.wow = wow#0; ); ‹› val member:k = 0; ); ) From c9711ff6af80d9a341e2f6af96c7a26d4f00b5d0 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Wed, 18 Jun 2025 17:20:25 +0800 Subject: [PATCH 07/62] Elaborate chain patterns --- .../scala/hkmc2/semantics/Elaborator.scala | 6 +- .../main/scala/hkmc2/semantics/Pattern.scala | 16 +++++- hkmc2/shared/src/test/mlscript/rp/Future.mls | 30 +++++----- .../rp/syntax/InterestingPatterns.mls | 17 ++++++ .../test/mlscript/rp/syntax/PatternBody.mls | 57 ++++++++++--------- 5 files changed, 80 insertions(+), 46 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 99bab311a3..ae03969620 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1368,7 +1368,11 @@ extends Importer: case p @ InfixApp(_: Ident, Keyword.`:`, _) :: Nil => record(p) case lhs :: Nil => arrow(lhs, rhs) case _ :: _ | Nil => ??? // TODO: When is this case reached? - case p as (id: Ident) => go(p) bind id + case p as q => q match + // `p as id` is elaborated into alias patterns + case id: Ident => go(p) binds id + // `p as q` is elaborated into chain patterns + case _: Tree => Chain(go(p), go(q)) case Under() => Pattern.Wildcard() // Record patterns like `(a: p1, b: p2, ...pn)`. case Block(ps) => record(ps) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index 897a06c0c9..da8267702e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -95,7 +95,7 @@ object Pattern: case (vars, pattern) => vars ++ pattern.variables /** A shorthand for creating a variable pattern. */ - def Variable(id: Ident): Pattern.Alias = Pattern.Wildcard().bind(id) + def Variable(id: Ident): Pattern.Alias = Pattern.Wildcard().binds(id) trait ConstructorImpl: self: Pattern.Constructor => @@ -160,6 +160,12 @@ enum Pattern extends AutoLocated: * the fields are not ordered semantically. */ case Record(fields: Ls[(Ident, Pattern)]) + /** Chain one pattern to another. The pattern matches the input value against + * the `first` pattern, and then matches its output against the `second` + * pattern. It fails when either of the patterns fails. + */ + case Chain(first: Pattern, second: Pattern) + /** A pattern that matches the same value as another pattern, but bind the * matched value to a new variable. This is the only class that holds the * symbol for the variable. */ @@ -170,7 +176,7 @@ enum Pattern extends AutoLocated: */ case Transform(pattern: Pattern, transform: Term) - infix def bind(id: Ident): Pattern.Alias = Pattern.Alias(this, id) + infix def binds(id: Ident): Pattern.Alias = Pattern.Alias(this, id) /** Collect all variables in the pattern. Meanwhile, list invalid variables, * which will be reported when constructing symbols for variables. We use a @@ -191,6 +197,7 @@ enum Pattern extends AutoLocated: leading.variables ++ spread.map(_.variables).getOrElse(Variables.empty) ++ 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 def children: Ls[Located] = this match case Constructor(target, arguments) => target :: arguments @@ -203,6 +210,7 @@ enum Pattern extends AutoLocated: case Tuple(leading, spread, trailing) => leading ::: spread.toList ::: 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 @@ -215,6 +223,7 @@ enum Pattern extends AutoLocated: case Tuple(leading, spread, trailing) => leading.flatMap(_.subTerms) ::: spread.fold(Nil)(_.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 @@ -222,7 +231,7 @@ enum Pattern extends AutoLocated: val addPar = this match case _: (Constructor | Wildcard | Literal | Tuple | Record | Negation) => false case Alias(Wildcard(), _) => false - case _: (Alias | Composition | Transform | Range | Concatenation) => true + case _: (Alias | Composition | Transform | Range | Concatenation | Chain) => true if addPar then s"(${showDbg})" else showDbg def showDbg: Str = this match @@ -242,6 +251,7 @@ enum Pattern extends AutoLocated: spread.iterator.map(s => "..." + s.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}" diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/rp/Future.mls index 92c86f6f44..67d577eaef 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Future.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Future.mls @@ -45,11 +45,11 @@ fun foo(x) = if x is Unit then .... Arrow(...) then .... //│ ╔══[ERROR] Unrecognized pattern split (infix operator). -//│ ║ l.38: view as +//│ ║ l.44: view as //│ ║ ^^^^^^^ -//│ ║ l.39: Unit then .... +//│ ║ l.45: Unit then .... //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ║ l.40: Arrow(...) then .... +//│ ║ l.46: Arrow(...) then .... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -67,14 +67,14 @@ 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.58: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ║ l.64: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^ //│ /!!!\ Uncaught error: scala.MatchError: Jux(StrLit(a),Ident(to)) (of class hkmc2.syntax.Tree$Jux) :todo pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ╔══[ERROR] Unrecognized pattern (infix operator) -//│ ║ l.69: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) +//│ ║ l.75: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ╙── ^^^^^^^^^^^^ :todo @@ -83,7 +83,7 @@ pattern LineSep(pattern P) = case L(...nd) ~ "" then nd :: Nil "\n" ~ LineSep(P, t) then nd :: t -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Ident(Nil)), OpApp(App(Ident(L),Tup(List(Spread(keyword '...',Some(Loc(52,55,Future.mls:+78)),Some(Ident(nd)))))),Ident(~),List(Block(List(InfixApp(StrLit(),keyword 'then',OpApp(Ident(nd),Ident(::),List(Ident(Nil)))), InfixApp(OpApp(StrLit( +//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Ident(Nil)), OpApp(App(Ident(L),Tup(List(Spread(keyword '...',Some(Loc(52,55,Future.mls:+81)),Some(Ident(nd)))))),Ident(~),List(Block(List(InfixApp(StrLit(),keyword 'then',OpApp(Ident(nd),Ident(::),List(Ident(Nil)))), InfixApp(OpApp(StrLit( //│ ),Ident(~),List(App(Ident(LineSep),Tup(List(Ident(P), Ident(t)))))),keyword 'then',OpApp(Ident(nd),Ident(::),List(Ident(t))))))))))) (of class hkmc2.syntax.Tree$Case) :todo @@ -97,10 +97,10 @@ pattern Lines(pattern L) = case :todo if input is Lines of Email then //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.95: if input is Lines of Email then +//│ ║ l.98: if input is Lines of Email then //│ ╙── ^ //│ /!!!\ Uncaught error: scala.MatchError: InfixApp(Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Tup(List())), TypeDef(Pat,Ident(Tail),Some(Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Tup(List())), InfixApp(InfixApp(OpApp(StrLit( -//│ ),Ident(~),List(Bra(Round,App(Ident(Lines),Tup(List(Ident(L))))))),keyword 'as',Ident(t)),keyword 'then',Ident(t)))))),None), InfixApp(InfixApp(InfixApp(Ident(L),keyword 'as',OpApp(Ident(h),Ident(~),List(Ident(Tail)))),keyword 'as',Ident(t)),keyword 'then',Tup(List(Ident(h), Spread(keyword '...',Some(Loc(148,151,Future.mls:+87)),Some(Ident(t))))))))),keyword ':',Ident(todo)) (of class hkmc2.syntax.Tree$InfixApp) +//│ ),Ident(~),List(Bra(Round,App(Ident(Lines),Tup(List(Ident(L))))))),keyword 'as',Ident(t)),keyword 'then',Ident(t)))))),None), InfixApp(InfixApp(InfixApp(Ident(L),keyword 'as',OpApp(Ident(h),Ident(~),List(Ident(Tail)))),keyword 'as',Ident(t)),keyword 'then',Tup(List(Ident(h), Spread(keyword '...',Some(Loc(148,151,Future.mls:+90)),Some(Ident(t))))))))),keyword ':',Ident(todo)) (of class hkmc2.syntax.Tree$InfixApp) :todo pattern Opt(pattern P) = case @@ -114,33 +114,33 @@ pattern Email(name, domain) = ... :todo if input is Opt(Email, Some((n, d))) then ... //│ ╔══[ERROR] Name not found: input -//│ ║ l.112: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.115: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^^ //│ ╔══[ERROR] Name not found: Opt -//│ ║ l.112: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.115: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^ //│ ╔══[ERROR] Name not found: Some -//│ ║ l.112: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.115: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^ //│ ╔══[ERROR] invalid record field pattern -//│ ║ l.112: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.115: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^ :todo pattern Opt(pattern P) = case P(...values) then (Some(values)) "" then (None) -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+127)),Some(Ident(values)))))),keyword 'then',Bra(Round,App(Ident(Some),Tup(List(Ident(values)))))), InfixApp(StrLit(),keyword 'then',Bra(Round,Ident(None)))))) (of class hkmc2.syntax.Tree$Case) +//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+130)),Some(Ident(values)))))),keyword 'then',Bra(Round,App(Ident(Some),Tup(List(Ident(values)))))), InfixApp(StrLit(),keyword 'then',Bra(Round,Ident(None)))))) (of class hkmc2.syntax.Tree$Case) :todo pattern Opt(pattern P) = case P(...values) then Some(values) "" then None -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+133)),Some(Ident(values)))))),keyword 'then',App(Ident(Some),Tup(List(Ident(values))))), InfixApp(StrLit(),keyword 'then',Ident(None))))) (of class hkmc2.syntax.Tree$Case) +//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+136)),Some(Ident(values)))))),keyword 'then',App(Ident(Some),Tup(List(Ident(values))))), InfixApp(StrLit(),keyword 'then',Ident(None))))) (of class hkmc2.syntax.Tree$Case) :todo pattern Digits = "0" to "9" ~ (Digits | "") //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.139: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.142: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^ //│ /!!!\ Uncaught error: scala.MatchError: Jux(StrLit(0),Ident(to)) (of class hkmc2.syntax.Tree$Jux) diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls index f9f332fa2f..b7de4bd6fd 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls @@ -53,6 +53,12 @@ pattern FirstLast = ([x, ..._, y] => [x, y]) pattern IsPalindrome = ([x, ...IsPalindrome as m, y] => m && x == y) | (([_] | []) => true) | (_ => false) //│ elaborated pattern body: [x, ...member:IsPalindrome() as m, y] => builtin:&�(m#666, builtin:==#0(x#666, y#666)) ∨ ([_] ∨ []) => true ∨ _ => false +:ucs ups +// A better `IsPalindrome`, which matches palindrome sequences exactly. +pattern IsPalindrome = + (([first, ...IsPalindrome, last] => first === last) as true) | [_] | [] +//│ elaborated pattern body: ([first, ...member:IsPalindrome(), last] => builtin:===#0(first#666, last#666)) as true ∨ [_] ∨ [] + // Extract the diagonal elements of a 3x3 matrix pattern MatrixDiagonal = ([[a, _, _], [_, b, _], [_, _, c]] => [a, b, c]) @@ -127,6 +133,17 @@ pattern Time12Hour = (( :minute )) => hour + ":" + minute + " " + flag +:ucs ups +// This makes use of the chain pattern. +pattern Time12Hour = (( + hour: ( + ((hour as (0 ..= 12)) => ["AM", hour]) | + ((hour as (13 ..= 23)) => ["PM", hour - 12]) + ) as [hour, flag], + :minute +)) => hour + ":" + minute + " " + flag +//│ elaborated pattern body: {hour: ((hour as (0 to 12)) => ["AM", hour#666] ∨ (hour as (13 to 23)) => ["PM", builtin:-#4(hour#666, 12)]) as [hour, flag], minute: minute} => builtin:+#15(builtin:+#14(builtin:+#13(builtin:+#12(hour#666, ":"), minute#666), " "), flag#666) + // Calculate the area of different shapes pattern ShapeArea = (((:radius)) => Math.PI * radius * radius) | // Circle diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls index 03f44644c1..2051bcc636 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls @@ -18,9 +18,12 @@ class Pair[A, B](val fst: A, val snd: B) pattern PlainList(pattern T) = (null => Nil) | (Pair(T as hd, PlainList(T) as tl) => hd :: tl) //│ elaborated pattern body: null => (member:Stack#666.)Nil‹member:Nil› ∨ member:Pair(T() as hd, member:PlainList(T()) as tl) => (member:Stack#666.)Cons‹member:Cons›(hd#666, tl#666) -:fixme // Decide the operator precedence between `as` and `=>`. +:todo +:ucs ups +// Decide the operator precedence between `as` and `=>`. +// This is elaborated into `T as (x => x)`, which might not be what we want. pattern Identity(pattern T) = T as x => x -//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(Ident(T),keyword 'as',InfixApp(Tup(List(Ident(x))),keyword '=>',Ident(x))) (of class hkmc2.syntax.Tree$InfixApp) +//│ elaborated pattern body: T() as (x => x#666) :ucs ups pattern Identity(pattern T) = (T as x) => x @@ -41,35 +44,35 @@ pattern PairLike = [fst, snd] => Pair(fst, snd) pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] //│ elaborated pattern body: member:Pair(fst, snd) ∨ [fst, snd] //│ ╔══[ERROR] Name not found: snd -//│ ║ l.41: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] +//│ ║ l.44: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] //│ ╙── ^^^ //│ ╔══[ERROR] Name not found: snd -//│ ║ l.41: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] +//│ ║ l.44: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] //│ ╙── ^^^ //│ ╔══[ERROR] Name not found: fst -//│ ║ l.41: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] +//│ ║ l.44: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] //│ ╙── ^^^ //│ ╔══[ERROR] Name not found: fst -//│ ║ l.41: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] +//│ ║ l.44: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] //│ ╙── ^^^ :w :e // The errors are from the old pattern translation and compilation. pattern UselessParameter = x //│ ╔══[WARNING] Useless pattern binding: x. -//│ ║ l.58: pattern UselessParameter = x +//│ ║ l.61: pattern UselessParameter = x //│ ╙── ^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.58: pattern UselessParameter = x +//│ ║ l.61: pattern UselessParameter = x //│ ╙── ^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.58: pattern UselessParameter = x +//│ ║ l.61: pattern UselessParameter = x //│ ╙── ^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.58: pattern UselessParameter = x +//│ ║ l.61: pattern UselessParameter = x //│ ╙── ^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.58: pattern UselessParameter = x +//│ ║ l.61: pattern UselessParameter = x //│ ╙── ^ :ucs ups @@ -79,13 +82,13 @@ pattern PlainList(pattern T) = (null => Nil) | ([T as hd, PlainList(T) as tl] => :fixme [T as hd, PlainList(pattern T) as tl] //│ ╔══[ERROR] Name not found: hd -//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.83: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^^ //│ ╔══[ERROR] Illegal type declaration in term position. -//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.83: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^ //│ ╔══[ERROR] Name not found: tl -//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.83: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^^ //│ ═══[RUNTIME ERROR] TypeError: PlainList3 is not a function @@ -96,19 +99,19 @@ pattern Consistent = ((Int as x) | (Str as x)) => x.toString() :e pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() -//│ ║ ^^^ +//│ ║ l.100: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() -//│ ╙── ^^^^^^^^ +//│ ║ l.100: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() -//│ ╙── ^ +//│ ║ l.100: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ╙── ^ :e pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() //│ ╔══[ERROR] Name not found: x -//│ ║ l.109: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() +//│ ║ l.112: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() //│ ╙── ^ :ucs ups @@ -139,13 +142,13 @@ pattern BadNegated = ~(Int as x) => x :e pattern BadNegated = (~(Int as x)) => x //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.143: pattern BadNegated = (~(Int as x)) => x //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.143: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.143: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^ //│ elaborated pattern body: ¬(member:Int() as x) => @@ -153,13 +156,13 @@ pattern BadNegated = (~(Int as x)) => x :e pattern BadNegated = (~Pair(Int, x)) => x //│ ╔══[ERROR] This variable cannot be accessed. -//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.157: pattern BadNegated = (~Pair(Int, x)) => x //│ ║ ^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.157: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.157: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^ //│ elaborated pattern body: ¬member:Pair(member:Int(), x) => From d00e7c90d50068ee4f74fddef4ab61f9d9bd0299 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Thu, 19 Jun 2025 22:58:21 +0800 Subject: [PATCH 08/62] Renovate `Translator` --- .../scala/hkmc2/semantics/Elaborator.scala | 10 +- .../main/scala/hkmc2/semantics/Pattern.scala | 2 +- .../hkmc2/semantics/ucs/DesugaringBase.scala | 23 +- .../hkmc2/semantics/ucs/FlatPattern.scala | 2 + .../hkmc2/semantics/ucs/Normalization.scala | 11 +- .../hkmc2/semantics/ucs/Translator.scala | 268 +++++++++++++++++- .../src/test/mlscript/rp/SimpleTransform.mls | 148 ++++++++++ 7 files changed, 442 insertions(+), 22 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index ae03969620..876f4a8eaf 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1160,12 +1160,14 @@ extends Importer: log(s"elaborated ${patSym.nme}:\n${split.display}") patSym.split = split log(s"pattern body is ${td.rhs}") - val translate = new ucs.Translator(this) - val bod = translate( + // *** END OBSOLETE CODE *** + + // Translate the pattern directly into methods that perform matching + // using backtracking. + val bod = new ucs.Translator(this)( patSym.patternParams, Nil, // ps.map(_.params).getOrElse(Nil), // TODO[Luyu]: remove pattern parameters - td.rhs.getOrElse(die)) - // *** END OBSOLETE CODE *** + td.rhs.getOrElse(die), pat) val pd = PatternDef(owner, patSym, sym, tps, ps, pat, ObjBody(Blk(bod, Term.Lit(UnitLit(false)))), annotations) patSym.defn = S(pd) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index da8267702e..bc7763ea25 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -95,7 +95,7 @@ object Pattern: case (vars, pattern) => vars ++ pattern.variables /** A shorthand for creating a variable pattern. */ - def Variable(id: Ident): Pattern.Alias = Pattern.Wildcard().binds(id) + def Variable = Pattern.Wildcard() binds (_: Ident) trait ConstructorImpl: self: Pattern.Constructor => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala index e7cb6ab091..ce73e9d0e2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala @@ -93,12 +93,33 @@ trait DesugaringBase(using Ctx, State): protected final def makeUnapplyBranch( scrut: => Term.Ref, clsTerm: Term, + argsOpt: Opt[Ls[FlatPattern.Argument]], inner: => Split, method: Str = "unapply" )(fallback: Split): Split = val call = app(sel(clsTerm, method).withIArgs(Nil), tup(fld(scrut)), s"result of $method") tempLet("matchResult", call): resultSymbol => - Branch(resultSymbol.ref().withIArgs(Nil), matchResultPattern(N), inner) ~: fallback + argsOpt match + case N => Branch(resultSymbol.ref().withIArgs(Nil), matchResultPattern(N), inner) ~: fallback + case S(args) => + val tupleSymbol = TempSymbol(N, "tuple") + Branch(resultSymbol.ref().withIArgs(Nil), matchResultPattern(S(tupleSymbol :: Nil)), + makeTupleBranch(tupleSymbol.ref().withIArgs(Nil), args.map(_.scrutinee), inner, Split.End) + ) ~: fallback + + protected final def makeTupleBranch( + scrut: => Term.Ref, + subScrutinees: Ls[BlockLocalSymbol], + consequent: => Split, + alternative: Split + ): Split = + 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" + Split.Let(arg, callTupleGet(scrut, index, label), innerSplit) + ) ~: alternative + /** Make a `Branch` that calls `Pattern` symbols' `unapplyStringPrefix` functions. */ protected final def makeUnapplyStringPrefixBranch( 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 8583b3d667..d9c8ab8048 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -90,6 +90,8 @@ object FlatPattern: case Default /** Call `unapplyStringPrefix` instead of `unapply`. */ case StringPrefix(prefix: TempSymbol, postfix: TempSymbol) + /** Call `unapplyTuplePrefix` instead of `unapply`. */ + case TuplePrefix(prefix: TempSymbol, postfix: TempSymbol) /** The pattern is annotated. The normalization will intepret the pattern * matching behavior based on the resolved symbol */ 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 37c42ff4c4..30858c243d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -159,7 +159,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // the current implementation does not use it. The future version // should properly handle the pattern arguments. case MatchMode.Default => - normalizeExtractorPattern(scrutinee, pat, ctor, consequent, alternative) + normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, consequent, alternative) case MatchMode.StringPrefix(prefix, postfix) => normalizeStringPrefixPattern(scrutinee, pat, ctor, postfix, consequent, alternative) case MatchMode.Annotated(annotation) => annotation.symbol match @@ -168,11 +168,11 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas 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, consequent, alternative) + normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, consequent, alternative) case N => // Name resolution should have already reported an error. We // treat this as an extractor pattern. - normalizeExtractorPattern(scrutinee, pat, ctor, consequent, alternative) + normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, consequent, alternative) case Split.Let(v, _, tail) if vs has v => log(s"LET: SKIP already declared scrutinee $v") normalizeImpl(tail) @@ -274,10 +274,11 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas scrutinee: Term.Ref, patternSymbol: PatternSymbol, ctorTerm: Term, + argsOpt: Opt[Ls[FlatPattern.Argument]], consequent: Split, alternative: Split, )(using VarSet): Split = - normalize(makeUnapplyBranch(scrutinee, ctorTerm, consequent)(alternative)) + normalize(makeUnapplyBranch(scrutinee, ctorTerm, argsOpt, consequent)(alternative)) private def normalizeStringPrefixPattern( scrutinee: Term.Ref, @@ -470,7 +471,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas 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.ref(), acc(innermost)) + case (acc, (l, r)) => innermost => Split.Let(r.scrutinee, l.scrutinee.ref().withIArgs(Nil), acc(innermost)) case (_, _) => identity end Normalization diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala index a545b23a99..d3c701955f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala @@ -44,6 +44,14 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De private lazy val lt = State.builtinOpsMap("<") private lazy val eq = State.builtinOpsMap("==") + private def makeRangeTest(scrut: Scrut, lo: Literal, hi: Literal, rightInclusive: Bool, innerSplit: Split) = + def scrutFld = fld(scrut()) + val test1 = app(lteq.ref(), tup(fld(Term.Lit(lo)), scrutFld), "isGreaterThanLower") + val upperOp = if rightInclusive then lteq else lt + val test2 = app(upperOp.ref(), tup(scrutFld, fld(Term.Lit(hi))), "isLessThanUpper") + plainTest(test1, "isGreaterThanLower")(plainTest(test2, "isLessThanUpper")(innerSplit)) + + @deprecated("Remove after we finished the new pattern translation.") private def makeRange(scrut: Scrut, lo: Literal, hi: Literal, rightInclusive: Bool, inner: Inner) = def scrutFld = fld(scrut()) val test1 = app(lteq.ref(), tup(fld(Term.Lit(lo)), scrutFld), "gtLo") @@ -51,6 +59,222 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De val test2 = app(upperOp.ref(), tup(scrutFld, fld(Term.Lit(hi))), "ltHi") plainTest(test1, "gtLo")(plainTest(test2, "ltHi")(inner(Map.empty))) + 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) + + + /** The continuation function returned by `makeStringPrefixMatchSplit`. */ + type MakePrefixSplit = (makeConsequent: (prefixOutput: Scrut, postfixScrutinee: Scrut) => Split, alternative: Split) => Split + + /** 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. + * + * @param bindings Variables that can be bound in this pattern. Currently, + * when we encounter an `Alias` pattern, we check whether + * its symbol exists in this list before binding it. But I'm + * not sure whether this check is redundant. + */ + private def makeMatchSplit( + scrutinee: Scrut, + pattern: Pattern, + allowedBindings: Ls[VarSymbol] + ): MakeSplit = + import Pattern.* + pattern match + case Constructor(target, 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. + val z = (Nil: Ls[TempSymbol], makeConsequent) + val (subScrutinees, makeChainedConsequent) = arguments.iterator.zipWithIndex.foldRight(z): + case ((argument, index), (subScrutinees, makeInnerSplit)) => + val subScrutinee = TempSymbol(N, s"argument$index") + val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => + makeMatchSplit(() => subScrutinee.ref().withIArgs(Nil), argument, allowedBindings)( + (argumentOutput, argumentBindings) => makeInnerSplit( + argumentOutput, // TODO: Combine `outerOutput` and `argumentOutput` + outerBindings ++ argumentBindings + ), + Split.End + ) + (subScrutinee :: subScrutinees, makeThisSplit) + val consequent = makeChainedConsequent(scrutinee, Map.empty) + Branch(scrutinee(), FlatPattern.ClassLike(target, S(subScrutinees)), consequent) ~: alternative + case Composition(true, left, right) => + makeMatchSplit(scrutinee, left, allowedBindings) | makeMatchSplit(scrutinee, right, allowedBindings) + case Composition(false, left, right) => (makeConsequent, alternative) => + makeMatchSplit(scrutinee, left, allowedBindings)( + (leftOutput, leftBindings) => makeMatchSplit(scrutinee, right, allowedBindings)( + (rightOutput, rightBindings) => + val tupleIdent = Ident("tupledResults") + val tupleSymbol = TempSymbol(N, "tupledResults") + val tupleTerm = tup(leftOutput() |> fld, rightOutput() |> fld) + Split.Let(tupleSymbol, tupleTerm, makeConsequent(() => tupleSymbol.ref(), leftBindings ++ rightBindings) ~~: alternative), + alternative), + alternative) + case Negation(pattern) => (makeConsequent, alternative) => + // Currently, the negation pattern produces an unit. 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 outputIdent = Ident("negationOutput") + val outputSymbol = TempSymbol(N, "negationOutput") + val outputTerm = Term.Lit(UnitLit(true)) + makeMatchSplit(scrutinee, pattern, allowedBindings)( + (_output, _bindings) => alternative, // The output and bindings are discarded. + Split.Let(outputSymbol, outputTerm, makeConsequent(() => outputSymbol.ref(), 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), 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) => + makeStringPrefixMatchSplit(scrutinee, left, allowedBindings)( + (prefixOutput, postfixScrutinee) => + makeMatchSplit(postfixScrutinee, right, allowedBindings)( + // Here we discard the postfix output because I still haven't + // figured out the semantics of string concatenation. + (_postfixOutput, bindings) => makeConsequent(scrutinee, bindings) ~~: 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.ref().withIArgs(Nil), element, allowedBindings)( + (elementOutput, elementBindings) => makeInnerSplit( + elementOutput, // TODO: Combine `outerOutput` and `elementOutput` + outerBindings ++ elementBindings + ), + Split.End + ) + (subScrutinee :: subScrutinees, makeThisSplit) + // END TODO + makeTupleBranch(scrutinee().withIArgs(Nil), subScrutinees, makeChainedConsequent(scrutinee, Map.empty), alternative) + case Tuple(leading, spread, trailing) => ??? + 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.ref().withIArgs(Nil), pattern, allowedBindings)( + (fieldOutput, fieldBindings) => makeInnerSplit( + fieldOutput, // TODO: Combine `outerOutput` and `fieldOutput` + outerBindings ++ fieldBindings + ), + Split.End + ) + ((key, subScrutinee) :: fields, makeThisSplit) + // END TODO + Branch(scrutinee(), FlatPattern.Record(entries), makeChainedConsequent(scrutinee, Map.empty)) ~: alternative + case Chain(first, second) => (makeConsequent, alternative) => + makeMatchSplit(scrutinee, first, allowedBindings)( + (firstOutput, firstBindings) => makeMatchSplit(firstOutput, second, allowedBindings)( + (secondOutput, secondBindings) => makeConsequent(secondOutput, firstBindings ++ secondBindings), + alternative + ), + alternative + ) + case alias @ Alias(pattern, id) => (makeConsequent, alternative) => + // Not sure how to handle this case. Should we just check if `id` is in + // `bindings` and set the symbol to the corresponding parameter? + makeMatchSplit(scrutinee, pattern, allowedBindings)( + (output, bindings) => + log(s"bind pattern $pattern to ${output()}") + makeConsequent(output, bindings + (alias.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.varMap.values.map(_.symbol).toList + 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, symbols)( + // 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 + log(s"arguments: ${arguments.mkString(", ")}") + val resultTerm = app(lambdaSymbol.ref(), tup(arguments*), "the transform's result") + val resultSymbol = TempSymbol(N, "transformResult") + Split.Let(resultSymbol, resultTerm, makeConsequent(() => resultSymbol.ref().withIArgs(Nil), Map.empty) ~~: Split.End), + alternative + ) + ) + + private def makeStringPrefixMatchSplit( + scrutinee: Scrut, + pattern: Pattern, + bindings: Ls[VarSymbol] + ): MakePrefixSplit = ??? + + /** Generate an `unapply` method that matches the given pattern. */ + private def makeUnapplyMethod(scrutinee: Scrut, pattern: Pattern): TermDefinition = ??? + /** Generate a split that consumes the entire scrutinee. */ private def full(scrut: Scrut, pat: Tree, inner: Inner)(using patternParams: Ls[Param]): Split = trace( pre = s"full <<< $pat", @@ -182,12 +406,18 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De private def errorSplit: Split = Split.Else(Term.Error) - /** Create a function definition from the given UCS splits. */ - private def makeMatcher(name: Str, scrut: VarSymbol, topmost: Split): TermDefinition = + /** Create a function definition 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 makeMatcher(name: Str, patternParameters: List[Param], scrut: VarSymbol, topmost: Split): TermDefinition = val sym = BlockMemberSymbol(name, Nil) - val ps = PlainParamList(Param(FldFlags.empty, scrut, N, Modulefulness.none) :: Nil) + // Pattern parameters are passed as objects. + val patternInputs = patternParameters.map(_.copy(flags = FldFlags.empty)) + val scrutParam = Param(FldFlags.empty, scrut, N, Modulefulness.none) + val ps = PlainParamList(patternInputs :+ scrutParam) val body = Term.IfLike(Keyword.`if`, topmost) - val res = FlowSymbol(s"result of $name") + val res = FlowSymbol(s"the return value of $name") TermDefinition(N, Fun, sym, ps :: Nil, N, N, S(body), res, TermDefFlags.empty, Modulefulness.none, Nil) /** Translate a list of extractor/matching functions for the given pattern. @@ -199,8 +429,12 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De * 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 apply(patternParams: Ls[Param], params: Ls[Param], body: Tree): Ls[TermDefinition] = trace( + def apply(patternParams: Ls[Param], params: Ls[Param], body: Tree, pattern: Pattern): Ls[TermDefinition] = trace( pre = s"Translator <<< ${params.mkString(", ")} $body", post = (blk: Ls[TermDefinition]) => s"Translator >>> $blk" ): @@ -209,11 +443,23 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De // TODO(rp): pass pattern parameters as objects to the `unapply` function Nil else - val unapply = scoped("ucs:cp"): - val scrutSym = VarSymbol(Ident("scrut")) - val topmost = full(() => scrutSym.ref(), body, success(params))(using patternParams) ~~: failure + // val unapply = scoped("ucs:cp"): + // val scrutSym = VarSymbol(Ident("scrut")) + // val topmost = full(() => scrutSym.ref(), body, success(params))(using patternParams) ~~: failure + // log(s"Translated `unapply`: ${display(topmost)}") + // makeMatcher("unapply", scrutSym, topmost) + val unapply = scoped("ucs:translation"): + val inputSymbol = VarSymbol(Ident("input")) + val topmost = makeMatchSplit( + scrutinee = () => inputSymbol.ref(), + pattern = pattern, + allowedBindings = Nil // TODO: pass proper bindings + )( + (output, bindings) => Split.Else(makeMatchResult(tup(output() |> fld))), + failure + ) log(s"Translated `unapply`: ${display(topmost)}") - makeMatcher("unapply", scrutSym, topmost) + makeMatcher("unapply", patternParams, inputSymbol, topmost) val unapplyStringPrefix = scoped("ucs:cp"): // We don't report errors here because they are already reported in the // translation of `unapply` function. @@ -221,9 +467,9 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De val scrutSym = VarSymbol(Ident("topic")) stringPrefix(() => scrutSym.ref(), body, prefixSuccess(params)) match case Split.Else(Term.Error) => - makeMatcher("unapplyStringPrefix", scrutSym, failure) + makeMatcher("unapplyStringPrefix", patternParams, scrutSym, failure) case split => val topmost = split ~~: failure log(s"Translated `unapplyStringPrefix`: ${display(topmost)}") - makeMatcher("unapplyStringPrefix", scrutSym, topmost) + makeMatcher("unapplyStringPrefix", patternParams, scrutSym, topmost) unapply :: unapplyStringPrefix :: Nil diff --git a/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls b/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls new file mode 100644 index 0000000000..0a4a70c635 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls @@ -0,0 +1,148 @@ +:js + +data class Pair[A, B](first: A, second: B) + +pattern SumPair = Pair(a, b) => a + b + +fun foo(value) = if value is + SumPair(sum) then print("sum: " + sum) + else print("not a pair") + +foo of Pair(1, 2) +foo of Pair(10, 25) +foo of () +foo of 10 +foo of "hello" +//│ > sum: 3 +//│ > sum: 35 +//│ > not a pair +//│ > not a pair +//│ > not a pair + +:fixme +pattern BoolLike = true | false | ((Int as x) => x !== 0) +//│ ╔══[ERROR] Class Int does not have a parameter list +//│ ║ l.23: pattern BoolLike = true | false | ((Int as x) => x !== 0) +//│ ╙── ^^^ + +fun foo(value) = if value is + BoolLike(b) then print("" + value + " is equivalent to " + b) + else print("the input " + value + " is not a boolean") + +foo of true +foo of false +foo of 0 +foo of 1 +foo of 2 +//│ > true is equivalent to true +//│ > false is equivalent to false +//│ > 0 is equivalent to false +//│ > 1 is equivalent to true +//│ > 2 is equivalent to true + +:todo // Similar problem as `EvaluatedTerm`. +pattern TrueLike = BoolLike as true + +:expect true +:fixme +true is TrueLike +//│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' +//│ = false + +42 is BoolLike(true) +//│ = true + +data class Term with + constructor + Literal(value: Int) + Infix(op: Str, left: Term, right: Term) + Prefix(op: Str, right: Term) + +// The following pattern generates a lot of code. +// pattern EvaluatedTerm = +// (Literal(value) => value) | +// (Infix("+", EvaluatedTerm as left, EvaluatedTerm as right) => left + right) | +// (Infix("-", EvaluatedTerm as left, EvaluatedTerm as right) => left - right) | +// (Infix("*", EvaluatedTerm as left, EvaluatedTerm as right) => left * right) | +// (Infix("/", EvaluatedTerm as left, EvaluatedTerm as right) => left / right) | +// (Prefix("-", EvaluatedTerm as operand) => -operand) | +// (Prefix("+", EvaluatedTerm as operand) => +operand) + +pattern EvaluatedTerm = + (Literal(value) => value) | + (Infix("+", EvaluatedTerm as left, EvaluatedTerm as right) => left + right) | + (Infix("-", EvaluatedTerm as left, EvaluatedTerm as right) => left - right) + +fun eval(term) = if term is + EvaluatedTerm(value) then print("value: " + value) + else print("invalid term") + +:todo +// This doesn't work because the semantics of `as` is different in `Desugarer` +// and `Translator`. Operator `as` was to bind the LHS pattern to the RHS +// variable, but now it is to match the LHS pattern against the RHS pattern. +eval of Literal(1) +eval of Infix("+", Literal(1), Literal(2)) +eval of Infix("-", Literal(1), Literal(2)) +eval of Infix("*", Literal(1), Literal(2)) +eval of Infix("/", Literal(1), Literal(2)) +eval of Prefix("-", Literal(1)) +//│ > value: 1 +//│ > invalid term +//│ > invalid term +//│ > invalid term +//│ > invalid term +//│ > invalid term + +pattern DropZ = ((:x, :y, :z)) => (x: x, y: y) + +(x: 1, y: 2, z: 3) is DropZ((x: 1, y: 2)) +//│ = true + +(x: 1, y: 2, z: 3) is DropZ((x: 1, y: 3)) +//│ = false + +pattern ShapeArea = + (((:radius)) => Math.PI * radius * radius) | // Circle + (((:width, :height)) => width * height) | // Rectangle + (((:base, :height)) => base * height / 2) // Triangle + +fun area(shape) = if shape is + ShapeArea(area) then print("area: " + area) + else print("invalid shape") + +area of radius: 10 +area of width: 10, height: 20 +area of base: 10, height: 20 +//│ > area: 314.1592653589793 +//│ > area: 200 +//│ > area: 100 + +pattern PairLike = [_, _] | Pair(_, _) + +[1, 2] is PairLike +//│ = true + +[] is PairLike +//│ = false + +[1] is PairLike +//│ = false + +Pair(1, 2) is PairLike +//│ = true + +:todo +pattern TransformTwice = (((Int as x) => x + 4) as y) => y * 2 +//│ ╔══[ERROR] Class Int does not have a parameter list +//│ ║ l.136: pattern TransformTwice = (((Int as x) => x + 4) as y) => y * 2 +//│ ╙── ^^^ + +2 is TransformTwice(12) +//│ = true + +100 is TransformTwice(0) +//│ = false + +17 is TransformTwice(42) +//│ = true From 11a21957dc9e06db44250c998101d2272a98cff3 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 24 Jun 2025 19:45:50 +0800 Subject: [PATCH 09/62] Major update on translation and add many tests --- .../shared/src/main/scala/hkmc2/Message.scala | 1 + .../main/scala/hkmc2/codegen/Lowering.scala | 4 +- .../scala/hkmc2/semantics/Elaborator.scala | 58 +- .../main/scala/hkmc2/semantics/Pattern.scala | 135 +++-- .../main/scala/hkmc2/semantics/Split.scala | 2 +- .../main/scala/hkmc2/semantics/Symbol.scala | 2 +- .../src/main/scala/hkmc2/semantics/Term.scala | 6 + .../hkmc2/semantics/ucs/DeBrujinSplit.scala | 10 +- .../scala/hkmc2/semantics/ucs/Desugarer.scala | 106 ++-- .../hkmc2/semantics/ucs/DesugaringBase.scala | 55 +- .../hkmc2/semantics/ucs/FlatPattern.scala | 54 +- .../hkmc2/semantics/ucs/Normalization.scala | 167 +++++- .../hkmc2/semantics/ucs/Translator.scala | 549 ++++++++++++------ .../scala/hkmc2/semantics/ucs/package.scala | 7 + .../src/test/mlscript-compile/Runtime.mjs | 7 +- .../src/test/mlscript-compile/Runtime.mls | 2 + .../test/mlscript/apps/parsing/LexerTest.mls | 3 - .../test/mlscript/codegen/FieldSymbols.mls | 9 +- hkmc2/shared/src/test/mlscript/rp/Future.mls | 120 +++- .../src/test/mlscript/rp/JoinPatterns.mls | 8 + .../src/test/mlscript/rp/LocalPatterns.mls | 8 +- .../src/test/mlscript/rp/MatchResult.mls | 2 +- .../src/test/mlscript/rp/RangePatterns.mls | 25 +- hkmc2/shared/src/test/mlscript/rp/Simple.mls | 27 + .../src/test/mlscript/rp/SimpleTransform.mls | 125 ++-- .../test/mlscript/rp/examples/Computation.mls | 88 +++ .../mlscript/rp/examples/ListPredicates.mls | 58 ++ .../test/mlscript/rp/examples/Negation.mls | 28 + .../src/test/mlscript/rp/examples/Record.mls | 86 +++ .../test/mlscript/rp/examples/TupleSpread.mls | 95 +++ .../rp/nondeterminism/BitArithmetic.mls | 36 ++ .../rp/nondeterminism/EvenOddTree.mls | 38 ++ .../test/mlscript/rp/parametric/ListLike.mls | 24 + .../test/mlscript/rp/parametric/Nullable.mls | 24 + .../mlscript/rp/specialization/SimpleList.mls | 10 +- .../test/mlscript/rp/syntax/Declaration.mls | 3 - .../rp/syntax/InterestingPatterns.mls | 81 +-- .../test/mlscript/rp/syntax/PatternBody.mls | 58 +- .../test/mlscript/rp/syntax/Precedence.mls | 53 ++ .../mlscript/ucs/patterns/AliasPattern.mls | 4 +- 40 files changed, 1594 insertions(+), 584 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/rp/examples/Computation.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/examples/ListPredicates.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/examples/Negation.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/examples/Record.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/examples/TupleSpread.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/parametric/ListLike.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/parametric/Nullable.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/syntax/Precedence.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/Message.scala b/hkmc2/shared/src/main/scala/hkmc2/Message.scala index 104a1c5c9b..e9053088b8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Message.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Message.scala @@ -31,6 +31,7 @@ object Message: final case class Code(ty: TypeLike) extends Bit implicit def fromType(ty: TypeLike): Message = Message(Code(ty)::Nil) implicit def fromStr(str: Str): Message = Message(Text(str)::Nil) + implicit def fromInt(int: Int): Message = Message(Text(int.toString)::Nil) implicit class MessageContext(private val ctx: StringContext): def msg(inserted: Message*): Message = diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index e9ca0df0ce..8ca7fbce8f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -311,7 +311,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx): case _: sem.BuiltinSymbol => true case sym: sem.BlockMemberSymbol => sym.trmImplTree.fold(sym.clsTree.isDefined)(_.k is syntax.Fun) - case _ => false + // Do not perform safe check on `MatchResult` and `MatchFailure`. + case sym => (sym is State.matchResultClsSymbol) || + (sym is State.matchFailureClsSymbol) def conclude(fr: Path) = arg match case Tup(fs) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 876f4a8eaf..a907d8d335 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1124,8 +1124,8 @@ extends Importer: raise(ErrorReport(msg"Unexpected pattern parameter ${sym.name} with flags ${flags.showDbg}" -> sym.toLoc :: Nil)) N .partition(_.flags.pat) - log(s"pattern parameters: ${patternParams.mkString("[", ", ", "]")}") - log(s"extraction parameters: ${extractionParams.mkString("[", ", ", "]")}") + 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. val rhs = td.rhs.getOrElse: raise(ErrorReport(msg"Pattern definitions must have a body." -> td.toLoc :: Nil)) @@ -1138,10 +1138,10 @@ extends Importer: // `VarSymbol` yet. Thus, we need to pair them with the extraction // parameters. We only report warnings for unbounded variables // because they are harmless. - pat.variables.varMap.foreach: (name, alias) => + pat.variables.varMap.foreach: (name, aliases) => extractionParams.find(_.sym.name == name) match - case S(param) => alias.symbol = param.sym - case N => raise(WarningReport(msg"Useless pattern binding: $name." -> alias.toLoc :: Nil)) + case S(param) => aliases.foreach(_.symbol = param.sym) + case N => raise(WarningReport(msg"Useless pattern binding: $name." -> aliases.head.toLoc :: Nil)) scoped("ucs:ups")(log(s"elaborated pattern body: ${pat.showDbg}")) scoped("ucs:ups:tree")(log(s"elaborated pattern body: ${pat.showAsTree}")) @@ -1168,7 +1168,10 @@ extends Importer: patSym.patternParams, Nil, // ps.map(_.params).getOrElse(Nil), // TODO[Luyu]: remove pattern parameters td.rhs.getOrElse(die), pat) - val pd = PatternDef(owner, patSym, sym, tps, ps, pat, + // `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, N, + patternParams, extractionParams, pat, ObjBody(Blk(bod, Term.Lit(UnitLit(false)))), annotations) patSym.defn = S(pd) pd @@ -1309,12 +1312,12 @@ extends Importer: def pattern(t: Tree): Ctxl[Pattern] = import ucs.Desugarer.{Ctor, unapply}, Keyword.*, Pattern.*, InvalidReason.* import ucs.Translator.isInvalidStringBounds, ucs.HelperExtractors.to + given TraceLogger = tl /** 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.map: - case alias => alias.id.name -> alias.allocate + val termCtx = ctx ++ pattern.variables.allocate pattern.variables.report // Report all invalid variables we found in `pattern`. Transform(pattern, term(rhs)(using termCtx)) /** Elaborate tuple patterns like `[p1, p2, ...ps, pn]`. */ @@ -1347,6 +1350,10 @@ extends Importer: raise(ErrorReport(msg"Unexpected record property pattern." -> t.toLoc :: Nil)) acc Record(entries.reverse) + /** Elaborate a pattern argument. */ + def arg(t: Tree): Ctxl[Pattern \/ Pattern] = t match + case TypeDef(syntax.Pat, body, N, N) => L(go(body)) + case _ => R(go(t)) def go(t: Tree): Ctxl[Pattern] = t match // Brackets. case Bra(BracketKind.Round, t) => go(t) @@ -1356,12 +1363,18 @@ extends Importer: case t: syntax.Literal => Literal(t) // Negation patterns: `~p` case App(Ident("~"), Tup(p :: Nil)) => Negation(go(p)) + // Negative integer and decimal literals. + case app @ App(Ident("-"), Tup(IntLit(n) :: Nil)) => + Literal(IntLit(-n).withLocOf(app)) + case app @ App(Ident("-"), Tup(DecLit(n) :: Nil)) => + Literal(DecLit(-n).withLocOf(app)) // Union and intersection patterns: `p | q` and `p & q` case OpApp(lhs, Ident(op @ ("|" | "&")), rhs :: Nil) => Composition(op === "|", go(lhs), go(rhs)) - // Constructor patterns with arguments. - case App(ctor: Ctor, Tup(args)) => - Constructor(term(ctor), args.map(go(_))) + // 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)) // `[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`. @@ -1371,15 +1384,23 @@ extends Importer: case lhs :: Nil => arrow(lhs, rhs) case _ :: _ | Nil => ??? // TODO: When is this case reached? case p as q => q match - // `p as id` is elaborated into alias patterns - case id: Ident => go(p) binds id - // `p as q` is elaborated into chain patterns + // `p as id` is elaborated into alias if `id` is not a constructor. + case id: Ident => ident(id) match + case S(target) if target.symbol.exists(_.isInstanceOf[VarSymbol]) => + // If the target is a variable, we should shadow it. This check is + // probably insufficient as there are more cases. + go(p) binds id + case S(target) => Chain(go(p), Constructor(target, N)) + 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() // 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) // Range patterns. We can also desugar them into disjunctions of all the // literals in the range. case (lower: StrLit) to (incl, upper: StrLit) => @@ -1395,12 +1416,15 @@ extends Importer: // pattern translation. case OpApp(lhs, Ident("~"), rhs :: Nil) => Pattern.Concatenation(go(lhs), go(rhs)) // Constructor patterns can be written in the infix form. - case OpApp(lhs, op, rhs :: Nil) => Pattern.Constructor(term(op), Ls(go(lhs), go(rhs))) + case OpApp(lhs, op, rhs :: Nil) => Pattern.Constructor(term(op), S(Ls(go(lhs), go(rhs)))) // Constructor patterns without arguments case id @ Ident(name) => ident(id) match - case S(target) => Constructor(target, Nil) + case S(target) => Constructor(target, N) case N => Variable(id) // Fallback to variable pattern. - case sel: (SynthSel | Sel) => Constructor(term(sel), Nil) + case sel: (SynthSel | Sel) => Constructor(term(sel), N) + case other: Tree => + raise(ErrorReport(msg"Found an unrecognizable pattern." -> t.toLoc :: Nil)) + Pattern.Wildcard() go(t) def typeParams(t: Tree): Ctxl[(Ls[Param], Ctx)] = t match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index bc7763ea25..8f03e1ed30 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -1,23 +1,37 @@ package hkmc2 package semantics -import mlscript.utils.*, shorthands.*, collection.immutable.HashMap +import mlscript.utils.*, shorthands.* +import collection.immutable.HashMap, collection.mutable.Buffer import syntax.Tree, Tree.Ident, Elaborator.State, Message.MessageContext, ucs.error import scala.annotation.tailrec, util.chaining.* +import utils.{TraceLogger, tl} object Pattern: /** The reason why a variable obtained from `Pattern.variables` is invalid. */ enum InvalidReason: - /** The variable shadowed another variable. */ - case Duplicated(previous: Ident) + /** The variable shadowed another variable. + * @param previous The identifiers shadowed by the current aliases. */ + case Duplicated(previous: Ls[Ident]) /** The variable presents in one side of disjunction, but not the other. */ case Inconsistent(disjunction: Pattern.Composition, missingOnTheLeft: Bool) /** The variable is bound in a `Negation` pattern. */ case Negated(negation: Pattern.Negation) - + /** Higher-order pattern arguments contain free variables. For example, `x` + * in `pattern MaybeInt = Nullable(pattern Int as x)` is invalid. Because + * `MaybeInt` does not guarantee that `Int as x` is used in `Nullable`. */ + case Escaped(pattern: 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 @@ -25,33 +39,50 @@ object Pattern: * on the `Pattern` tree. We only create `VarSymbol` for these variables at the * root node or when elaborating the term of a `Transform` pattern. * - * @param varMap A map from variable names to their latest aliases. + * @param varMap A map from variable names to their valid aliases. Multiple + * aliases are allowed when they are in different disjunctions. * @param invalidVars A list of variables and the reason why they are invalid. */ final case class Variables( - varMap: HashMap[Str, Pattern.Alias], + varMap: HashMap[Str, Ls[Pattern.Alias]], invalidVars: Ls[(Pattern.Alias, InvalidReason)] ): - /** Apply a function to all variables. */ - def map[A](f: Pattern.Alias => A): Iterator[A] = varMap.iterator.map(_._2).map(f) + /** 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. + aliases.foreach: alias => + tl.scoped("ucs:translation"): + tl.log(s"allocated symbol for alias: ${alias.showDbg}") + alias.symbol = symbol + (name, symbol) + .toSeq + + /** Get all symbols for the variables. */ + def symbols: Ls[VarSymbol] = varMap.iterator.map(_._2.head.symbol).toList /** Add a single variable to the variable set. */ def +(alias: Pattern.Alias): Variables = varMap.get(alias.id.name) match - case N => Variables(varMap + (alias.id.name -> alias), invalidVars) - case S(oldVar) => Variables( - varMap.updated(alias.id.name, alias), - invalidVars :+ (oldVar, Duplicated(alias.id))) + // Add the alias to the variable set if the name has not been used. + case N => Variables(varMap + (alias.id.name -> Ls(alias)), invalidVars) + case S(oldAliases) => Variables( + varMap.updated(alias.id.name, Ls(alias)), + // Append the invalid reason: `alias` shadows `oldAliases`. + invalidVars :+ (alias -> Duplicated(oldAliases.map(_.id)))) /** Union two variable sets. If the latter contains a variable that is * already present in the former, the variable is considered duplicated. */ - def ++(that: Variables): Variables = Variables( - varMap.merged(that.varMap): - case ((name, _), (_, id)) => (name, id), - invalidVars ::: that.invalidVars ::: that.varMap.iterator.collect: - case (name, id) if varMap.contains(name) => - (id, Duplicated(varMap(name).id)) - .toList) + def ++(that: Variables): Variables = + val duplicated: Buffer[(Alias, InvalidReason)] = Buffer.empty + Variables( + varMap.merged(that.varMap): + case ((name, previous), (_, aliases)) => + duplicated ++= aliases.map(_ -> Duplicated(previous.map(_.id))) + (name, aliases), + invalidVars ::: that.invalidVars ::: duplicated.toList) /** Intersect two variable sets and move variables that are only present in * one side to the invalid variables. This method considers `this` as the @@ -61,22 +92,25 @@ object Pattern: val notInThat = varMap.removedAll(that.varMap.keys) val notInThis = that.varMap.removedAll(varMap.keys) Variables( - // Remove variables that only present in one side. - varMap.removedAll(Iterable.concat(notInThat.keys, notInThis.keys)), + // Merge two sets and remove variables that only present in one side. + varMap.merged(that.varMap): + case ((name, left), (_, right)) => (name, left ::: right) + .removedAll(Iterable.concat(notInThat.keys, notInThis.keys)), // Add variables that only present in one side to the invalid variables. invalidVars ::: - notInThis.iterator.map(_._2 -> Inconsistent(pattern, true)).toList ::: - notInThat.iterator.map(_._2 -> Inconsistent(pattern, false)).toList) + notInThis.iterator.flatMap(_._2.map(_ -> Inconsistent(pattern, true))).toList ::: + notInThat.iterator.flatMap(_._2.map(_ -> Inconsistent(pattern, false))).toList) /** Mark all variables in this set as invalid. */ def invalidated(reason: InvalidReason): Variables = - Variables(HashMap.empty, invalidVars appendedAll varMap.iterator.map(_._2 -> reason)) + Variables(HashMap.empty, invalidVars appendedAll varMap.iterator.flatMap(_._2.map(_ -> reason))) /** Report all invalid variables. */ def report(using Raise): Unit = invalidVars.foreach: - case (Alias(_, id), Duplicated(previous)) => error( - msg"Duplicate pattern variable." -> id.toLoc, - msg"The previous definition is here." -> previous.toLoc) + case (Alias(_, id), Duplicated(previous)) => raise(ErrorReport( + msg"Duplicate pattern variable." -> id.toLoc :: + msg"The previous definition ${if previous.size === 1 then "is" else "are"} as follows." -> previous.head.toLoc :: + previous.tail.map(msg"" -> _.toLoc))) case (Alias(_, id), Inconsistent(disjunction, missingOnTheLeft)) => error( msg"Found an inconsistent variable in disjunction patterns." -> id.toLoc, msg"The variable is missing from this sub-pattern." -> ( @@ -97,6 +131,10 @@ 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 => @@ -104,7 +142,8 @@ object Pattern: def symbol: Opt[Symbol] = self.target.resolvedSymbol /** Expect the `symbol` to be set. */ - def symbol_! : Symbol = symbol.getOrElse(lastWords("symbol is not set")) + def symbol_! : Symbol = symbol.getOrElse: + lastWords(s"target term `${self.target.showDbg}` does not resolve to a symbol") /** Add a mutable field to the `Alias` pattern to store the symbol for the * variable. Note that NOT every `Alias` pattern has a symbol. */ @@ -114,17 +153,27 @@ object Pattern: /** Directly set the symbol for the variable. This should be called in the * elaborator when elaborating the non-`Transform` top-level pattern. */ def symbol_=(symbol: VarSymbol): Unit = _symbol = S(symbol) - def symbol: VarSymbol = _symbol.getOrElse(lastWords("symbol is not set")) - /** Allocate the symbol for the variable. This should be called in the - * elaborator and before elaborating the term from `Transform`. */ - def allocate(using State): VarSymbol = VarSymbol(self.id).tap(symbol_=) + def symbolOption: Opt[VarSymbol] = _symbol + def symbol: VarSymbol = _symbol.getOrElse: + lastWords(s"no symbol was assigned to variable `${id.name}` at ${id.toLoc}") import Pattern.*, InvalidReason.* /** An inductive data type for patterns. */ enum Pattern extends AutoLocated: - /** A pattern that matches a constructor and its arguments. */ - case Constructor(target: Term, arguments: Ls[Pattern]) + /** 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 /** A pattern that is the composition of two patterns. * @param polarity `true` if the pattern is a disjunction, `false` if it is @@ -182,7 +231,7 @@ enum Pattern extends AutoLocated: * which will be reported when constructing symbols for variables. We use a * map becuase we want to replace variables. */ lazy val variables: Variables = this match - case Constructor(_, arguments) => arguments.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 @@ -200,11 +249,12 @@ enum Pattern extends AutoLocated: case Chain(first, second) => first.variables ++ second.variables def children: Ls[Located] = this match - case Constructor(target, arguments) => target :: arguments + case Constructor(target, patternArguments, arguments) => + target :: patternArguments ::: arguments.getOrElse(Nil) case Composition(polarity, left, right) => left :: right :: Nil case Negation(pattern) => pattern :: Nil case Wildcard() => Nil - case Literal(literal) => 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 @@ -215,7 +265,8 @@ enum Pattern extends AutoLocated: case Transform(pattern, transform) => pattern :: transform :: Nil def subTerms: Ls[Term] = this match - case Constructor(target, arguments) => target :: arguments.flatMap(_.subTerms) + case Constructor(target, patternArguments, arguments) => + target :: patternArguments.flatMap(_.subTerms) ::: arguments.fold(Nil)(_.flatMap(_.subTerms)) case Composition(_, left, right) => left.subTerms ::: right.subTerms case Negation(pattern) => pattern.subTerms case _: (Wildcard | Literal | Range) => Nil @@ -235,9 +286,13 @@ enum Pattern extends AutoLocated: if addPar then s"(${showDbg})" else showDbg def showDbg: Str = this match - case Constructor(target, arguments) => + case Constructor(target, patternArguments, arguments) => val targetText = target.symbol.fold(target.showDbg)(_.toString()) - s"$targetText(${arguments.map(_.showDbg).mkString(", ")})" + val patternArgumentsText = patternArguments.iterator.map(_.showDbg) + .map("pattern " + _).mkStringOr("(", ", ", ")", "") + val argumentsText = arguments.fold(""): args => + s"(${args.map(_.showDbg).mkString(", ")})" + s"$targetText$patternArgumentsText$argumentsText" case Composition(true, left, right) => s"${left.showDbg} \u2228 ${right.showDbg}" case Composition(false, left, right) => s"${left.showDbg} \u2227 ${right.showDbg}" case Negation(pattern) => s"\u00ac${pattern.showDbgWithPar}" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala index d42d90405a..dd1dc86387 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala @@ -10,7 +10,7 @@ final case class Branch(scrutinee: Term.Ref, pattern: FlatPattern, continuation: object Branch: def apply(scrutinee: Term.Ref, continuation: Split): Branch = - Branch(scrutinee, FlatPattern.Lit(Tree.BoolLit(true)), continuation) + Branch(scrutinee, FlatPattern.Lit(Tree.BoolLit(true))(Nil), continuation) enum Split extends AutoLocated with ProductWithTail: case Cons(head: Branch, tail: Split) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 654ebdb78a..55ae1945f5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -281,7 +281,7 @@ class PatternSymbol(val id: Tree.Ident, val params: Opt[Tree.Tup], val body: Tre * `T` in `pattern Nullable(pattern T) = null | T`. */ var patternParams: Ls[Param] = Nil - + var extractionParams: Ls[Param] = Nil override def subst(using sub: SymbolSubst): PatternSymbol = sub.mapPatSym(this) class TopLevelSymbol(blockNme: Str)(using State) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 3f9601dbdb..30d6773331 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -498,6 +498,12 @@ case class PatternDef( bsym: BlockMemberSymbol, tparams: Ls[TyParam], paramsOpt: Opt[ParamList], + /** The pattern parameters, for example, `T` in + * `pattern Nullable(pattern T) = null | T`. */ + patternParams: Ls[Param], + /** The extraction parameters, for example, `x` in + * `pattern PairLike(x, y) = [x, y] | Pair(x, y)`. */ + extractionParams: Ls[Param], /** The elaborated pattern right-hand side. */ pattern: Pattern, // Here, `ObjBody` contains methods `unapply` and `unapplyStringPrefix`, diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala index 1935640a8b..c2a84a2fcc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala @@ -17,7 +17,7 @@ object DeBrujinSplit: (using Elaborator.Ctx, Elaborator.State, Raise): DeBrujinSplit = - import elaborator.tl.*, syntax.Tree, Tree.*, PatternStub.*, HelperExtractors.*, Desugarer.unapply, syntax.Keyword.{as, `=>`} + import elaborator.tl.*, syntax.Tree, Tree.*, PatternStub.*, HelperExtractors.*, Desugarer.unapply, syntax.Keyword.{as, `=>`, `:`} type F = (Int, => DeBrujinSplit, => DeBrujinSplit) => DeBrujinSplit /** Resolve the constructor in the elaborator context. */ def resolve(ctor: Ident | Sel, params: Ls[Tree]): Opt[F] = @@ -88,6 +88,8 @@ object DeBrujinSplit: case _ `=>` _ => (_, _, alternative) => alternative // This allows the pattern `p as id`. case _ as _ => (_, _, alternative) => alternative + // This allows the pattern `a: p1`. + case _ `:` _ => (_, _, alternative) => alternative // This allows the pattern `~p`. case App(Ident("~"), Tup(p :: Nil)) => (_, _, alternative) => alternative // This allows the pattern `(a: p1, b: p2, ...pn)`. @@ -364,7 +366,7 @@ extension (split: DeBrujinSplit) case (0, body) => go(body, ctx) pattern match case Literal(value) => - semantics.Branch(ctx(scrutinee - 1)(), FlatPattern.Lit(value), nullaryConsequent) ~: go(alternative, ctx) + semantics.Branch(ctx(scrutinee - 1)(), FlatPattern.Lit(value)(Nil), nullaryConsequent) ~: go(alternative, ctx) case ClassLike(ConstructorLike.Symbol(symbol: ClassSymbol)) => log(s"make temporary symbols for $symbol") val subSymbols = (1 to symbol.arity).map(i => TempSymbol(N, s"arg_$i")).toList @@ -379,12 +381,12 @@ extension (split: DeBrujinSplit) // Here we add a speical case as a workaround: // If the class is virtual, then we don't make arguments empty. val arguments = if Elaborator.ctx.builtins.virtualClasses contains symbol then N else S(subSymbols) - val pattern = FlatPattern.ClassLike(select, arguments) + val pattern = FlatPattern.ClassLike(select, arguments)(Nil) semantics.Branch(ctx(scrutinee - 1)(), pattern, consequent2) ~: go(alternative, ctx) case ClassLike(ConstructorLike.Symbol(symbol: ModuleSymbol)) => val select = scoped("ucs:sel"): reference(symbol).getOrElse(Term.Error) - val pattern = FlatPattern.ClassLike(select, N) + val pattern = FlatPattern.ClassLike(select, N)(Nil) semantics.Branch(ctx(scrutinee - 1)(), pattern, nullaryConsequent) ~: go(alternative, ctx) case ClassLike(ConstructorLike.LocalPattern(id)) => log(s"apply scrutinee $scrutinee to local pattern $id") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala index 1828e5cf1c..bc0b5b81fb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala @@ -10,7 +10,7 @@ import Keyword.{as, and, `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.MatchMode +import FlatPattern.{Argument,MatchMode} object Desugarer: extension (op: Keyword.Infix) @@ -146,7 +146,7 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten post = (res: Split) => s"conjunct matches >>> $res" ): nominate(ctx, term(coda)(using ctx)): - expandMatch(_, pat, sequel)(fallback) + 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 @@ -156,7 +156,7 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten post = (res: Split) => s"shorthands >>> $res" ): nominate(ctx, finish(term(headCoda)(using ctx))): - expandMatch(_, headPattern, tailSplit)(fallback) + expandMatch(_, headPattern, tailSplit, Nil)(fallback) private def patternSplitShorthands(tree: Tree, scrutSymbol: BlockLocalSymbol): Split => Sequel = tree match case blk: Block => blk.desugStmts match @@ -178,8 +178,8 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten tail.foldRight(innermostSplit): case ((coda, pat), sequel) => ctx => nominate(ctx, term(coda)(using ctx)): - expandMatch(_, pat, sequel)(fallback) - expandMatch(scrutSymbol, headPattern, tailSplit)(fallback) + 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) => @@ -241,7 +241,7 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten post = (res: Split) => s"conjunct matches >>> $res" ): nominate(ctx, term(coda)(using ctx)): - expandMatch(_, pat, sequel)(Split.End) + 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 @@ -251,7 +251,7 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten post = (res: Split) => s"termBranch >>> $res" ): nominate(ctx, finish(term(headCoda)(using ctx))): - expandMatch(_, headPattern, tailSplit)(fallback) + 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", @@ -425,11 +425,11 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten tail.foldRight(innermostSplit): case ((coda, pat), sequel) => ctx => nominate(ctx, term(coda)(using ctx)): - expandMatch(_, pat, sequel)(Split.End) + expandMatch(_, pat, sequel, Nil)(Split.End) .traced( pre = s"conjunct matches <<< $tail", post = (res: Split) => s"conjunct matches >>> $res") - expandMatch(scrutSymbol, realPattern, tailSplit)(fallback).traced( + expandMatch(scrutSymbol, realPattern, tailSplit, Nil)(fallback).traced( pre = s"patternBranch <<< $patternAndMatches -> ${consequent.fold(_.showDbg, _.showDbg)}", post = (res: Split) => s"patternBranch >>> ${res.showDbg}") case _ => @@ -443,42 +443,58 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten * @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): Split => Sequel = + 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), sequel(ctx)) ~: fallback + 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) => + // BEGIN OBSOLETE CODE val argument = tree match case TypeDef(syntax.Pat, body, N, N) => S(DeBrujinSplit.elaborate(Nil, body, elaborator)) case td @ TypeDef(k = syntax.Pat) => error(msg"Ill-formed pattern argument" -> td.toLoc); N case _ => N - (symbol, tree, argument) + // END OBSOLETE CODE + // 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. + val pattern = tree match + case TypeDef(syntax.Pat, body, N, N) => + val pattern = elaborator.pattern(body) + S(new Translator(elaborator).translateAnonymousPattern(Nil, Nil, pattern)) + case td @ TypeDef(k = syntax.Pat) => + error(msg"Ill-formed pattern argument" -> td.toLoc); N + case _ => N + Argument(symbol, tree, argument, pattern) .toList Branch( ref, - FlatPattern.ClassLike(term(ctor), S(matches), mode, false)(app), // TODO: refined? + 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 (alias @ Ident(_)) => fallback => - val aliasSymbol = VarSymbol(alias) + case pat as (id: Ident) => fallback => + val aliasSymbol = VarSymbol(id) val inner = (ctx: Ctx) => - val ctxWithAlias = ctx + (alias.name -> aliasSymbol) - Split.Let(aliasSymbol, ref, sequel(ctxWithAlias)) - expandMatch(scrutSymbol, pat, inner)(fallback) + 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) - Split.Let(aliasSymbol, ref, sequel(ctxWithAlias) ++ fallback) + // 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))) @@ -521,21 +537,21 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten (wrap, (sym, pat) :: matches) Branch( ref, - FlatPattern.Tuple(lead.length + rest.fold(0)(_._2.length), rest.isDefined), + FlatPattern.Tuple(lead.length + rest.fold(0)(_._2.length), rest.isDefined)(output), // The outermost is a tuple, so pattern arguments are not possible. - wrap(subMatches(matches.map { case (s, t) => (s, t, N) }, sequel)(Split.End)(ctx)) + wrap(subMatches(matches.map { case (s, t) => Argument(s, t, N, N) }, sequel)(Split.End)(ctx)) ) ~: fallback // Negative numeric literals case App(Ident("-"), Tup(IntLit(value) :: Nil)) => fallback => ctx => - Branch(ref, FlatPattern.Lit(IntLit(-value)), sequel(ctx)) ~: fallback + 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)), sequel(ctx)) ~: fallback + Branch(ref, FlatPattern.Lit(DecLit(-value))(output), sequel(ctx)) ~: fallback case OpApp(lhs, Ident("&"), rhs :: Nil) => fallback => ctx => - val newSequel = expandMatch(scrutSymbol, rhs, sequel)(fallback) - expandMatch(scrutSymbol, lhs, newSequel)(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)(fallback)(ctx) - expandMatch(scrutSymbol, lhs, sequel)(newFallback)(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))) @@ -549,16 +565,16 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten pre = s"expandMatch: literal <<< $literal", post = (r: Split) => s"expandMatch: literal >>> ${r.showDbg}" ): - Branch(ref, FlatPattern.Lit(literal), sequel(ctx)) ~: fallback + 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)(fallback)(ctx) + 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)(fallback) + 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)(fallback)(ctx) + expandMatch(scrutSymbol, pattern, newNewSequel, output)(fallback)(ctx) case Jux(Ident(".."), Ident(_)) => fallback => _ => raise(ErrorReport(msg"Illegal rest pattern." -> pattern.toLoc :: Nil)) fallback @@ -566,18 +582,18 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten val symbol = scrutSymbol.getFieldScrutinee(fieldName) Branch( ref, - FlatPattern.Record((fieldName, symbol) :: Nil), - subMatches((symbol, pat, N) :: Nil, sequel)(Split.End)(ctx) + 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), - subMatches((symbol, fieldName, N) :: Nil, sequel)(Split.End)(ctx) + 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)(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 @@ -594,12 +610,12 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten }.fold(fallback)(recordContent => Branch( ref, - FlatPattern.Record(recordContent.map((fieldName, symbol, _) => (fieldName, symbol))), - subMatches(recordContent.map((_, symbol, pat) => (symbol, pat, N)), sequel)(Split.End)(ctx) + 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)(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)) @@ -612,21 +628,21 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten * @param matches a list of pairs consisting of a scrutinee and a pattern * @param sequel the innermost split */ - def subMatches(matches: Ls[(scrutinee : BlockLocalSymbol, pattern : Tree, split : Opt[DeBrujinSplit])], + 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 (_, Under(), _) :: rest => subMatches(rest, sequel) // Skip wildcards - case (_, _, S(_)) :: rest => subMatches(rest, sequel) // Skip pattern arguments - case (scrutinee, pattern, _) :: rest => fallback => trace( - pre = s"subMatches (nested) <<< $scrutinee is $pattern", + case Argument(_, Under(), _, _) :: rest => subMatches(rest, sequel) // Skip wildcards + case Argument(_, _, S(_), _) :: rest => subMatches(rest, sequel) // Skip pattern arguments + case Argument(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, pattern, innermostSplit)(fallback) + expandMatch(scrutinee, tree, innermostSplit, Nil)(fallback) /** Desugar `case` expressions. */ def apply(tree: Case, scrut: VarSymbol)(using Ctx): Split = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala index ce73e9d0e2..09f275bb71 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala @@ -29,7 +29,7 @@ trait DesugaringBase(using Ctx, State): /** 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) + FlatPattern.ClassLike(sel(matchResultClass, "class", State.matchResultClsSymbol), parameters)(Nil) /** Make a term that looks like `runtime.MatchFailure` with its symbol. */ protected lazy val matchFailureClass = @@ -37,12 +37,13 @@ trait DesugaringBase(using Ctx, State): /** 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) + FlatPattern.ClassLike(sel(matchFailureClass, "class", State.matchFailureClsSymbol), parameters)(Nil) protected lazy val tupleSlice = sel(sel(runtimeRef, "Tuple"), "slice") protected lazy val tupleGet = sel(sel(runtimeRef, "Tuple"), "get") protected lazy val stringStartsWith = sel(sel(runtimeRef, "Str"), "startsWith") protected lazy val stringGet = sel(sel(runtimeRef, "Str"), "get") + protected lazy val stringTake = sel(sel(runtimeRef, "Str"), "take") protected lazy val stringDrop = sel(sel(runtimeRef, "Str"), "drop") /** Make a term that looks like `runtime.Tuple.get(t, i)`. */ @@ -52,6 +53,10 @@ trait DesugaringBase(using Ctx, State): /** Make a term that looks like `runtime.Tuple.slice(t, i)`. */ protected final def callTupleGet(t: Term, i: Int, s: FlowSymbol): Term = app(tupleGet, tup(fld(t), fld(int(i))), s) + + /** Make a term that looks like `runtime.Tuple.slice(t, i, j)`. */ + protected final def callTupleSlice(t: Term, i: Int, j: Int, label: Str): Term = + app(tupleSlice, tup(fld(t), fld(int(i)), fld(int(j))), label) /** Make a term that looks like `runtime.Str.startsWith(t, p)`. */ protected final def callStringStartsWith(t: Term.Ref, p: Term, label: Str) = @@ -61,6 +66,10 @@ trait DesugaringBase(using Ctx, State): protected final def callStringGet(t: Term.Ref, i: Int, label: Str) = app(stringGet, tup(fld(t), fld(int(i))), label) + /** Make a term that looks like `runtime.Str.drop(t, n)`. */ + protected final def callStringTake(t: Term.Ref, n: Int, label: Str) = + app(stringTake, tup(fld(t), fld(int(n))), label) + /** Make a term that looks like `runtime.Str.drop(t, n)`. */ protected final def callStringDrop(t: Term.Ref, n: Int, label: Str) = app(stringDrop, tup(fld(t), fld(int(n))), label) @@ -88,24 +97,6 @@ trait DesugaringBase(using Ctx, State): val call = app(localPatternSymbol.ref().withIArgs(Nil), tup(fld(scrut)), s"result of ${localPatternSymbol.nme}") tempLet("matchResult", call): resultSymbol => Branch(resultSymbol.ref().withIArgs(Nil), matchResultPattern(N), inner) ~: fallback - - /** Make a `Branch` that calls `Pattern` symbols' `unapply` functions. */ - protected final def makeUnapplyBranch( - scrut: => Term.Ref, - clsTerm: Term, - argsOpt: Opt[Ls[FlatPattern.Argument]], - inner: => Split, - method: Str = "unapply" - )(fallback: Split): Split = - val call = app(sel(clsTerm, method).withIArgs(Nil), tup(fld(scrut)), s"result of $method") - tempLet("matchResult", call): resultSymbol => - argsOpt match - case N => Branch(resultSymbol.ref().withIArgs(Nil), matchResultPattern(N), inner) ~: fallback - case S(args) => - val tupleSymbol = TempSymbol(N, "tuple") - Branch(resultSymbol.ref().withIArgs(Nil), matchResultPattern(S(tupleSymbol :: Nil)), - makeTupleBranch(tupleSymbol.ref().withIArgs(Nil), args.map(_.scrutinee), inner, Split.End) - ) ~: fallback protected final def makeTupleBranch( scrut: => Term.Ref, @@ -113,13 +104,33 @@ trait DesugaringBase(using Ctx, State): consequent: => Split, alternative: Split ): Split = - Branch(scrut, FlatPattern.Tuple(subScrutinees.size, false), + Branch(scrut, FlatPattern.Tuple(subScrutinees.size, false)(Nil), subScrutinees.iterator.zipWithIndex.foldRight(consequent): case ((arg, index), innerSplit) => val label = s"the $index-th element of the match result" Split.Let(arg, callTupleGet(scrut, index, label), innerSplit) ) ~: alternative - + + protected final def makeTupleBranch( + scrut: => Term.Ref, + leading: Ls[BlockLocalSymbol], + spread: BlockLocalSymbol, + trailing: Ls[BlockLocalSymbol], + consequent: => Split, + alternative: Split + ): Split = + val split0 = trailing.iterator.zipWithIndex.foldRight(consequent): + case ((arg, index), innerSplit) => + val reverseIndex = trailing.size - index + val label = s"the last $reverseIndex-th element of the tuple" + Split.Let(arg, callTupleGet(scrut, -reverseIndex, label), innerSplit) + val split1 = Split.Let(spread, callTupleSlice( + scrut, leading.size, trailing.size, "the middle part of the tuple"), split0) + val split2 = leading.iterator.zipWithIndex.foldRight(split1): + 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 /** Make a `Branch` that calls `Pattern` symbols' `unapplyStringPrefix` functions. */ protected final def makeUnapplyStringPrefixBranch( 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 d9c8ab8048..045ed9e696 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -4,15 +4,18 @@ package ucs import mlscript.utils.*, shorthands.* import syntax.*, Tree.Ident -import Elaborator.{Ctx, ctx} +import Elaborator.{Ctx, ctx, State} import DeBrujinSplit.* +import collection.mutable.Buffer import FlatPattern.* /** Flat patterns for pattern matching */ enum FlatPattern extends AutoLocated: + /** The symbol that binds the output of this pattern. */ + val output: Ls[BlockLocalSymbol] - case Lit(literal: Literal) + case Lit(literal: Literal)(val output: Ls[BlockLocalSymbol]) /** 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 ...`. */ @@ -21,18 +24,19 @@ enum FlatPattern extends AutoLocated: val arguments: Opt[Ls[Argument]], val mode: MatchMode, var refined: Bool - )(val tree: Tree) + )(val tree: Tree, 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 subTerms: Ls[Term] = this match case p: ClassLike => p.constructor :: (p.mode match - case MatchMode.Default | _: MatchMode.StringPrefix => Nil + case MatchMode.Default => p.arguments.fold(Nil): + _.iterator.flatMap(_.pattern).toList + case _: MatchMode.StringPrefix => Nil case MatchMode.Annotated(annotation) => annotation :: Nil) - case _: (Lit | Tuple | Record) => Nil + case _: (Lit | Tuple | Record) => Nil def children: Ls[Located] = this match case Lit(literal) => literal :: Nil @@ -40,7 +44,8 @@ enum FlatPattern extends AutoLocated: case Tuple(fields, _) => Nil case Record(entries) => entries.flatMap { case (nme, als) => nme :: als :: Nil } - def showDbg: Str = this match + def showDbg: Str = + (this match case Lit(literal) => literal.idStr case ClassLike(ctor, args, _, rfd) => def showCtor(ctor: Term): Str = ctor match @@ -56,18 +61,33 @@ enum FlatPattern extends AutoLocated: case Tuple(size, inf) => "[]" + (if inf then ">=" else "=") + size case Record(Nil) => "{}" case Record(entries) => - entries.iterator.map(_.name + ": " + _).mkString("{ ", ", ", " }") + entries.iterator.map(_.name + ": " + _).mkString("{ ", ", ", " }")) + + output.iterator.map(s => s.nme).mkStringOr("as ", " as ", "", "") object FlatPattern: /** Represent the type of arguments in `ClassLike` patterns. This type alias * is used to reduce repetition in the code. * - * - Field `pattern` is for error messages. - * - Field `split` is for pattern compilation. - * **TODO(ucs/rp)**: Replace with suitable representation when implement - * the new pattern compilation. + * @param scrutinee the symbol representing the scrutinee + * @param tree the original `Tree` for making error messages + * @param split is for the old pattern compilation. **TODO(ucs/rp)**: Replace + * with suitable representation when implementing the new pattern + * compilation. + * @param pattern is for the new pattern compilation and translation. */ - type Argument = (scrutinee: BlockLocalSymbol, pattern: Tree, split: Opt[DeBrujinSplit]) + final case class Argument( + scrutinee: BlockLocalSymbol, + tree: Tree, + split: Opt[DeBrujinSplit], + pattern: Opt[Term.Rcd] + ) extends Located: + override def toLoc: Opt[Loc] = tree.toLoc + + object Argument: + def apply(scrutinee: BlockLocalSymbol, tree: Tree): Argument = + Argument(scrutinee, tree, N, N) + def apply(scrutinee: BlockLocalSymbol): Argument = + Argument(scrutinee, Tree.Dummy, N, N) /** A class-like pattern whose symbol is resolved to a class. */ object Class: @@ -98,5 +118,5 @@ object FlatPattern: case Annotated(annotation: Term) object ClassLike: - def apply(constructor: Term, arguments: Opt[Ls[BlockLocalSymbol]]): ClassLike = - ClassLike(constructor, arguments.map(_.map(s => (s, Tree.Dummy, N))), MatchMode.Default, false)(Tree.Dummy) + def apply(constructor: Term, arguments: Opt[Ls[BlockLocalSymbol]])(output: Ls[BlockLocalSymbol]): ClassLike = + ClassLike(constructor, arguments.map(_.map(Argument(_))), MatchMode.Default, false)(Tree.Dummy, output) 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 30858c243d..0744f780c7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -7,6 +7,7 @@ import syntax.{Literal, Tree}, utils.TraceLogger import Message.MessageContext import Elaborator.{Ctx, State, ctx} import utils.* +import FlatPattern.Argument class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBase: import Normalization.*, Mode.*, FlatPattern.MatchMode @@ -49,7 +50,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas else Term.SynthSel(lhs.constructor, Tree.Ident("class"))(mem.clsTree.orElse(mem.modOrObjTree).map(_.symbol)).withIArgs(Nil) case _ => lhs.constructor - lhs.copy(constructor)(lhs.tree) + lhs.copy(constructor)(lhs.tree, lhs.output) extension (lhs: FlatPattern) /** Checks if two patterns are the same. */ @@ -92,13 +93,13 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas case FlatPattern.Record(rhsEntries) => val filteredEntries = lhs.entries.filter: (fieldName1, _) => rhsEntries.forall { (fieldName2, _) => !(fieldName1 === fieldName2)} - FlatPattern.Record(filteredEntries) + FlatPattern.Record(filteredEntries)(lhs.output) 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) + FlatPattern.Record(filteredEntries)(lhs.output) case S(_) | N => lhs case S(_) | N => lhs case _ => lhs @@ -117,12 +118,19 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas ): 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 = normalize(specialize(consequent ++ alternative, +, scrutinee, pattern)) + 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, _) => @@ -132,16 +140,32 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas 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. Report the error and skip the branch. - error(msg"Cannot use this ${ctor.describe} as a pattern" -> ctor.toLoc) - normalizeImpl(alternative) + // 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))) => + // We're missing two checks here. The first one is to make + // sure that the pattern parameter is accessible from the + // context. The second one is to make sure that the current + // is in a pattern translation. + if argsOpt.fold(false)(_.nonEmpty) then + error(msg"Pattern parameters cannot be applied." -> ctor.toLoc) + normalizeExtractorPatternParameter(scrutinee, ctor, pattern.output, consequent, 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 | ModuleSymbol))) 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, argsOpt) then // TODO(ucs): deduplicate [1] - val whenTrue = normalize(specialize(consequent ++ alternative, +, scrutinee, pattern)) + 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. @@ -149,7 +173,8 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas case S(S(mod: ModuleSymbol)) => validateMatchMode(ctor, mod, mode) if validateObjectPattern(pattern, mod, argsOpt) then // TODO(ucs): deduplicate [1] - val whenTrue = normalize(specialize(consequent ++ alternative, +, scrutinee, pattern)) + 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. @@ -159,7 +184,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // the current implementation does not use it. The future version // should properly handle the pattern arguments. case MatchMode.Default => - normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, consequent, alternative) + normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, pattern.output, consequent, alternative) case MatchMode.StringPrefix(prefix, postfix) => normalizeStringPrefixPattern(scrutinee, pat, ctor, postfix, consequent, alternative) case MatchMode.Annotated(annotation) => annotation.symbol match @@ -168,11 +193,11 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas 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, consequent, alternative) + normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, pattern.output,consequent, alternative) case N => // Name resolution should have already reported an error. We // treat this as an extractor pattern. - normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, consequent, alternative) + normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, pattern.output, consequent, alternative) case Split.Let(v, _, tail) if vs has v => log(s"LET: SKIP already declared scrutinee $v") normalizeImpl(tail) @@ -204,7 +229,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas val n = args.size.toString val m = paramList.params.size.toString if n != m then - val argsLoc = Loc(args.iterator.map(_.pattern)) + val argsLoc = Loc(args) error: if paramList.params.isEmpty then msg"the constructor does not take any arguments but found $n" -> argsLoc @@ -212,9 +237,9 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas msg"mismatched arity: expect $m, found $n" -> argsLoc // Check the fields are accessible. paramList.params.iterator.zip(args).map: - case (_, (_, Tree.Under(), _)) => true + case (_, Argument(_, Tree.Under(), _, _)) => true case (Param(flags, sym, _, _), arg) if !flags.value => - error(msg"This pattern cannot be matched" -> arg.pattern.toLoc, // TODO: use correct location + 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) @@ -226,7 +251,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas 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".pluralize(args.size, true, false)}" -> Loc(args.iterator.map(_.pattern))) + msg"but the pattern has ${"sub-pattern".pluralize(args.size, true, false)}" -> Loc(args)) false case N => true // No parameters, no arguments. This is fine. case N => @@ -237,7 +262,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas true case S(args) => error(msg"Class ${ctorSymbol.name} does not have a parameter list" -> ctorTerm.toLoc, - msg"but the pattern has ${"sub-pattern".pluralize(args.size, true, false)}" -> Loc(args.iterator.map(_.pattern))) + msg"but the pattern has ${"sub-pattern".pluralize(args.size, true, false)}" -> Loc(args)) false case N => true @@ -270,15 +295,113 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas 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").withIArgs(Nil), 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 extractionSymbol = TempSymbol(N, "extraction") + Branch(resultSymbol.safeRef, matchResultPattern(S(extractionSymbol :: Nil)), + aliasOutputSymbols(extractionSymbol.safeRef, outputSymbols, consequent) + ) ~: alternative + normalize(split) + private def normalizeExtractorPattern( scrutinee: Term.Ref, patternSymbol: PatternSymbol, ctorTerm: Term, - argsOpt: Opt[Ls[FlatPattern.Argument]], + allArgsOpt: Opt[Ls[FlatPattern.Argument]], + outputSymbols: Ls[BlockLocalSymbol], consequent: Split, alternative: Split, )(using VarSet): Split = - normalize(makeUnapplyBranch(scrutinee, ctorTerm, argsOpt, consequent)(alternative)) + scoped("ucs:np"): + log(s"patternSymbol: ${patternSymbol.nme}") + log: + allArgsOpt.fold(Iterator.empty[Str]): + _.iterator.map: + case Argument(scrutinee, _, _, N) => s"extraction: ${scrutinee.nme}" + case Argument(scrutinee, _, _, S(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(scrutinee, _, _, N) => Left(scrutinee) + case Argument(scrutinee, _, _, S(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.ref().withIArgs(Nil) |> fld) :+ fld(scrutinee) + val unapplyCall = app(sel(ctorTerm, "unapply").withIArgs(Nil), tup(unapplyArgs*), s"result of unapply") + // Create a split that binds the pattern arguments. + def bindPatternArguments(split: Split): Split = + patternArguments.foldRight(split): + case ((sym, rcd), innerSplit) => Split.Let(sym, rcd, innerSplit) + val split = bindPatternArguments(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, "extraction") + Branch(resultSymbol.safeRef, matchResultPattern(S(extractionSymbol :: 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") + 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 :: 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 :: 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"mismatched arity: expect ${extractionParams.size}, found ${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, @@ -309,7 +432,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas val arguments = argsOpt match case S(args) => val patternArgs = args.collect: - case (_, pattern, S(split)) => (split, pattern) + case Argument(_, pattern, S(split), _) => (split, pattern) // if symbol.patternParams.size != patternArgs.size then error( // msg"Pattern `${symbol.nme}` expects ${"pattern argument".pluralize(symbol.patternParams.size, true)}" -> // Loc(symbol.patternParams.iterator.map(_.sym)), @@ -373,7 +496,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas val failure = Split.Else(makeMatchFailure) val bodySplit = scoped("ucs:rp:split"): val bodySplit = split.toSplit( - scrutinees = paramSymbols.map(symbol => () => symbol.ref().withIArgs(Nil)), + scrutinees = paramSymbols.map(symbol => () => symbol.safeRef), localPatterns = idSymbolMap, outcomes = Map(S(0) -> success, N -> failure) ) ++ Split.Else(makeMatchFailure) @@ -471,7 +594,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas 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.ref().withIArgs(Nil), acc(innermost)) + case (acc, (l, r)) => innermost => Split.Let(r.scrutinee, l.scrutinee.safeRef, acc(innermost)) case (_, _) => identity end Normalization diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala index d3c701955f..c2b786dae4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala @@ -5,9 +5,9 @@ package ucs import mlscript.utils.*, shorthands.* import Message.MessageContext import Split.display, ucs.Normalization -import syntax.{Fun, Keyword, Literal, ParamBind, Tree}, Tree.*, Keyword.{`as`, `=>`} +import syntax.{Fun, Keyword, ParamBind, Tree}, Tree.*, Keyword.{`as`, `=>`} import scala.collection.mutable.{Buffer, Set as MutSet} -import Elaborator.{Ctx, State} +import Elaborator.{Ctx, State, ctx} import Desugarer.unapply object Translator: @@ -20,44 +20,14 @@ object Translator: ds += msg"String range bounds must have only one character." -> hi.toLoc if ds.nonEmpty then error(ds.toSeq*) ds.nonEmpty - -import Translator.* - -/** This class translates a tree describing a pattern into functions that can - * perform pattern matching on terms described by the pattern. - */ -class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends DesugaringBase: - import elaborator.term, elaborator.tl.*, HelperExtractors.*, FlatPattern.MatchMode /** Each scrutinee is represented by a function that creates a reference to * the scrutinee symbol. It is sufficient for current implementation. */ - private type Scrut = () => Term.Ref - - private type CaptureMap = Map[Param, Term.Ref] - - private type Inner = CaptureMap => Split + type Scrut = () => Term.Ref - private type PrefixInner = (CaptureMap, Scrut) => Split - - private lazy val lteq = State.builtinOpsMap("<=") - private lazy val lt = State.builtinOpsMap("<") - private lazy val eq = State.builtinOpsMap("==") - - private def makeRangeTest(scrut: Scrut, lo: Literal, hi: Literal, rightInclusive: Bool, innerSplit: Split) = - def scrutFld = fld(scrut()) - val test1 = app(lteq.ref(), tup(fld(Term.Lit(lo)), scrutFld), "isGreaterThanLower") - val upperOp = if rightInclusive then lteq else lt - val test2 = app(upperOp.ref(), tup(scrutFld, fld(Term.Lit(hi))), "isLessThanUpper") - plainTest(test1, "isGreaterThanLower")(plainTest(test2, "isLessThanUpper")(innerSplit)) - - @deprecated("Remove after we finished the new pattern translation.") - private def makeRange(scrut: Scrut, lo: Literal, hi: Literal, rightInclusive: Bool, inner: Inner) = - def scrutFld = fld(scrut()) - val test1 = app(lteq.ref(), tup(fld(Term.Lit(lo)), scrutFld), "gtLo") - val upperOp = if rightInclusive then lteq else lt - val test2 = app(upperOp.ref(), tup(scrutFld, fld(Term.Lit(hi))), "ltHi") - plainTest(test1, "gtLo")(plainTest(test2, "ltHi")(inner(Map.empty))) + extension (symbol: BlockLocalSymbol) + def toScrut: Scrut = () => symbol.ref().withIArgs(Nil) type BindingMap = Map[VarSymbol, Scrut] @@ -95,10 +65,76 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De ), alternative), alternative) - + + type MakePrefixConsequent = (consumedOutput: Scrut, remainingOutput: Scrut, bindings: BindingMap) => Split /** The continuation function returned by `makeStringPrefixMatchSplit`. */ - type MakePrefixSplit = (makeConsequent: (prefixOutput: Scrut, postfixScrutinee: Scrut) => Split, alternative: Split) => Split + type MakePrefixSplit = (makeConsequent: MakePrefixConsequent, alternative: Split) => Split + + val rejectPrefixSplit: MakePrefixSplit = (_, alternative) => alternative + +import Translator.* + +/** This class translates a tree describing a pattern into functions that can + * perform pattern matching on terms described by the pattern. + */ +class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends DesugaringBase: + import elaborator.term, elaborator.tl.*, HelperExtractors.*, FlatPattern.MatchMode + import Pattern.* + + private type CaptureMap = Map[Param, Term.Ref] + + private type Inner = CaptureMap => Split + + private type PrefixInner = (CaptureMap, Scrut) => Split + + private lazy val lteq = State.builtinOpsMap("<=") + private lazy val lt = State.builtinOpsMap("<") + private lazy val eq = 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.ref(), tup(fld(Term.Lit(lo)), scrutFld), "isGreaterThanLower") + val upperOp = if rightInclusive then lteq else lt + val test2 = app(upperOp.ref(), tup(scrutFld, fld(Term.Lit(hi))), "isLessThanUpper") + plainTest(test1, "isGreaterThanLower")(plainTest(test2, "isLessThanUpper")(innerSplit)) + + @deprecated("Remove after we finished the new pattern translation.") + private def makeRange(scrut: Scrut, lo: syntax.Literal, hi: syntax.Literal, rightInclusive: Bool, inner: Inner) = + def scrutFld = fld(scrut()) + val test1 = app(lteq.ref(), tup(fld(Term.Lit(lo)), scrutFld), "gtLo") + val upperOp = if rightInclusive then lteq else lt + val test2 = app(upperOp.ref(), tup(scrutFld, fld(Term.Lit(hi))), "ltHi") + plainTest(test1, "gtLo")(plainTest(test2, "ltHi")(inner(Map.empty))) + + /** Create a pattern object that contains the given pattern. */ + def makeAnonymousPatternObject( + 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.ref()) + decl :: defineVar :: field :: Nil + + extension (patterns: Ls[Pattern]) + 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, Nil /* TODO */)( + (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 @@ -117,25 +153,45 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De ): MakeSplit = import Pattern.* pattern match - case Constructor(target, arguments) => (makeConsequent, alternative) => + 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. - val z = (Nil: Ls[TempSymbol], makeConsequent) - val (subScrutinees, makeChainedConsequent) = arguments.iterator.zipWithIndex.foldRight(z): - case ((argument, index), (subScrutinees, makeInnerSplit)) => - val subScrutinee = TempSymbol(N, s"argument$index") - val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(() => subScrutinee.ref().withIArgs(Nil), argument, allowedBindings)( - (argumentOutput, argumentBindings) => makeInnerSplit( - argumentOutput, // TODO: Combine `outerOutput` and `argumentOutput` - outerBindings ++ argumentBindings - ), - Split.End - ) - (subScrutinee :: subScrutinees, makeThisSplit) - val consequent = makeChainedConsequent(scrutinee, Map.empty) - Branch(scrutinee(), FlatPattern.ClassLike(target, S(subScrutinees)), consequent) ~: alternative + // 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, allowedBindings)( + (argumentOutput, argumentBindings) => makeInnerSplit( + argumentOutput, // TODO: Combine `outerOutput` and `argumentOutput` + outerBindings ++ argumentBindings), + Split.End) + val theArgument = FlatPattern.Argument(subScrutinee, Tree.Empty().withLocOf(argument), N, N) + (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) => + val patternSymbol = TempSymbol(N, s"patternArgument$index$$") + val patternObject = translateAnonymousPattern(Nil, Nil, pattern) + FlatPattern.Argument(patternSymbol, Tree.Empty().withLocOf(pattern), N, S(patternObject)) + .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, MatchMode.Default, false)(Tree.Dummy, outputSymbol :: Nil), consequent) ~: alternative case Composition(true, left, right) => makeMatchSplit(scrutinee, left, allowedBindings) | makeMatchSplit(scrutinee, right, allowedBindings) case Composition(false, left, right) => (makeConsequent, alternative) => @@ -149,13 +205,14 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De alternative), alternative) case Negation(pattern) => (makeConsequent, alternative) => - // Currently, the negation pattern produces an unit. 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 outputIdent = Ident("negationOutput") + // 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") - val outputTerm = Term.Lit(UnitLit(true)) + // The place where the diagnostic information should be stored. + val outputTerm = scrutinee() makeMatchSplit(scrutinee, pattern, allowedBindings)( (_output, _bindings) => alternative, // The output and bindings are discarded. Split.Let(outputSymbol, outputTerm, makeConsequent(() => outputSymbol.ref(), Map.empty) ~~: alternative) @@ -163,13 +220,13 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De // 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), makeConsequent(scrutinee, Map.empty)) ~: 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) => - makeStringPrefixMatchSplit(scrutinee, left, allowedBindings)( - (prefixOutput, postfixScrutinee) => - makeMatchSplit(postfixScrutinee, right, allowedBindings)( + makeStringPrefixMatchSplit(scrutinee, left)( + (consumedOutput, remainingOutput, bindings) => + makeMatchSplit(remainingOutput, right, allowedBindings)( // Here we discard the postfix output because I still haven't // figured out the semantics of string concatenation. (_postfixOutput, bindings) => makeConsequent(scrutinee, bindings) ~~: alternative, @@ -183,19 +240,29 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De // 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 subScrutinee = TempSymbol(N, s"element$index$$") val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(() => subScrutinee.ref().withIArgs(Nil), element, allowedBindings)( + makeMatchSplit(subScrutinee.toScrut, element, allowedBindings)( (elementOutput, elementBindings) => makeInnerSplit( elementOutput, // TODO: Combine `outerOutput` and `elementOutput` - outerBindings ++ elementBindings - ), - Split.End - ) + outerBindings ++ elementBindings), + Split.End) (subScrutinee :: subScrutinees, makeThisSplit) // END TODO - makeTupleBranch(scrutinee().withIArgs(Nil), subScrutinees, makeChainedConsequent(scrutinee, Map.empty), alternative) - case Tuple(leading, spread, trailing) => ??? + 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, allowedBindings)( + (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) @@ -204,33 +271,33 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De case (((key, pattern), index), (fields, makeInnerSplit)) => val subScrutinee = TempSymbol(N, s"field_${key.name}$$") val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(() => subScrutinee.ref().withIArgs(Nil), pattern, allowedBindings)( + makeMatchSplit(subScrutinee.toScrut, pattern, allowedBindings)( (fieldOutput, fieldBindings) => makeInnerSplit( fieldOutput, // TODO: Combine `outerOutput` and `fieldOutput` - outerBindings ++ fieldBindings - ), - Split.End - ) + outerBindings ++ fieldBindings), + alternative) // We fill in the alternative here. This is + // different from the `Constructor` case. ((key, subScrutinee) :: fields, makeThisSplit) // END TODO - Branch(scrutinee(), FlatPattern.Record(entries), makeChainedConsequent(scrutinee, Map.empty)) ~: alternative + val consequent = makeChainedConsequent(scrutinee, Map.empty) + Branch(scrutinee(), FlatPattern.Record(entries)(Nil), consequent) ~: alternative case Chain(first, second) => (makeConsequent, alternative) => makeMatchSplit(scrutinee, first, allowedBindings)( (firstOutput, firstBindings) => makeMatchSplit(firstOutput, second, allowedBindings)( (secondOutput, secondBindings) => makeConsequent(secondOutput, firstBindings ++ secondBindings), - alternative - ), - alternative - ) - case alias @ Alias(pattern, id) => (makeConsequent, alternative) => - // Not sure how to handle this case. Should we just check if `id` is in - // `bindings` and set the symbol to the corresponding parameter? - makeMatchSplit(scrutinee, pattern, allowedBindings)( - (output, bindings) => - log(s"bind pattern $pattern to ${output()}") - makeConsequent(output, bindings + (alias.symbol -> output)), - alternative - ) + 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, allowedBindings) + case S(symbol) => (makeConsequent, alternative) => + makeMatchSplit(scrutinee, pattern, allowedBindings)( + (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. @@ -238,7 +305,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De // 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.varMap.values.map(_.symbol).toList + val symbols = pattern.variables.symbols val params = symbols.map: Param(FldFlags.empty, _, N, Modulefulness.none) val lambdaSymbol = new TempSymbol(N, "transform") @@ -257,71 +324,156 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De // 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 - log(s"arguments: ${arguments.mkString(", ")}") val resultTerm = app(lambdaSymbol.ref(), tup(arguments*), "the transform's result") val resultSymbol = TempSymbol(N, "transformResult") - Split.Let(resultSymbol, resultTerm, makeConsequent(() => resultSymbol.ref().withIArgs(Nil), Map.empty) ~~: Split.End), - alternative - ) - ) + 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: Pattern, - bindings: Ls[VarSymbol] - ): MakePrefixSplit = ??? - - /** Generate an `unapply` method that matches the given pattern. */ - private def makeUnapplyMethod(scrutinee: Scrut, pattern: Pattern): TermDefinition = ??? - - /** Generate a split that consumes the entire scrutinee. */ - private def full(scrut: Scrut, pat: Tree, inner: Inner)(using patternParams: Ls[Param]): Split = trace( - pre = s"full <<< $pat", - post = (split: Split) => s"full >>> $split" - ): - pat.deparenthesized match - // TODO: Implement this after we're about to finish the pattern compilation. - // BEGIN OF TEMPORARY ALLOWANCE - // This temporarily allows the pattern `p => t`. - case _ `=>` _ => errorSplit - // This temporarily allows the pattern `~p`. - case App(Ident("~"), Tup(p :: Nil)) => errorSplit - // This temporarily allows the pattern `(a: p1, b: p2, ...pn)`. - case Block(_) => errorSplit - // This temporarily allows the pattern `[p1, p2, ...pn]`. - case Tup(_) => errorSplit - // This temporarily allows the pattern `p as id`. - case _ as _ => errorSplit - // END OF TEMPORARY ALLOWANCE - case lhs or rhs => full(scrut, lhs, inner) ~~: full(scrut, rhs, inner) - case (lo: StrLit) to (incl, hi: StrLit) => if isInvalidStringBounds(lo, hi) then failure else - makeRange(scrut, lo, hi, incl, inner) - case (lo: IntLit) to (incl, hi: IntLit) => makeRange(scrut, lo, hi, incl, inner) - case (lo: DecLit) to (incl, hi: DecLit) => makeRange(scrut, lo, hi, incl, inner) - case (lo: Literal) to (_, hi: Literal) => - error(msg"Incompatible range types: ${lo.describe} to ${hi.describe}" -> pat.toLoc) - failure - case lit: Literal => Branch(scrut(), FlatPattern.Lit(lit), inner(Map.empty)) ~: Split.End - case App(Ident("-"), Tup(IntLit(value) :: Nil)) => - Branch(scrut(), FlatPattern.Lit(IntLit(-value)), inner(Map.empty)) ~: Split.End - case App(Ident("-"), Tup(DecLit(value) :: Nil)) => - Branch(scrut(), FlatPattern.Lit(DecLit(-value)), inner(Map.empty)) ~: Split.End - case prefix ~ postfix => stringPrefix(scrut, prefix, (captures1, postfixScrut) => - full(postfixScrut, postfix, captures2 => inner(captures2 ++ captures1))) - case Under() => inner(Map.empty) - case ctor @ (_: Ident | _: Sel) => - val ctorTrm = term(ctor) - val pattern = FlatPattern.ClassLike(ctorTrm, N, MatchMode.Default, false)(ctor) - Branch(scrut(), pattern, inner(Map.empty)) ~: Split.End - case App(ctor @ (_: Ident | _: Sel), Tup(params)) => - // TODO(rp/str): handle input params - val ctorTrm = term(ctor) - val pattern = FlatPattern.ClassLike(ctorTrm, N, MatchMode.Default, false)(ctor) - Branch(scrut(), pattern, inner(Map.empty)) ~: Split.End - case pat => - error(msg"Unrecognized pattern (${pat.describe})" -> pat.toLoc) - errorSplit + ): MakePrefixSplit = pattern match + case Constructor(target, patternArguments, arguments) => (makeConsequent, alternative) => + // TODO: Handle `patternArguments` and `arguments` accordingly. + // This case is very different from the `Constructor` case in + // `makeMatchSplit` because `target` can only be a pattern. Currently, + // I have not figured out how to handle `arguments`. So, let me just + // ignore them for now. + val outputSymbol = TempSymbol(N, "output") // Denotes the pattern's output. + val remainingSymbol = TempSymbol(N, "remaining") // Denotes the remaining value. + val consequent = makeConsequent(outputSymbol.toScrut, remainingSymbol.toScrut, Map.empty) + val mode = MatchMode.StringPrefix(outputSymbol, remainingSymbol) + // `Normalization.normalizeStringPrefixPattern` is responsible for + // declaring the symbols we created here. + val pattern = FlatPattern.ClassLike(target, N, mode, false)(Tree.Dummy, outputSymbol :: Nil) + Branch(scrutinee(), pattern, consequent) ~: alternative + 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, Nil /* TODO */)( + (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) => (makeConsequent, alternative) => + // 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. + ??? + 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.ref(), 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.ref(), + 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.ref(), tup(fld(int(0)), fld(sel(scrutinee(), "length"))), "string is not empty") + Split.Let(nonEmptySymbol, nonEmptyTerm, // `0 < string.length` + Branch(nonEmptySymbol.ref(), + 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, Nil /* TODO */)( + (secondOutput, secondBindings) => + makeConsequent(secondOutput, firstRemains, firstBindings ++ secondBindings), + alternative), + alternative) + case alias @ Alias(pattern, id) => (makeConsequent, alternative) => + // TODO: Duplicate code with the `Alias` case in `makeMatchSplit`. + makeStringPrefixMatchSplit(scrutinee, pattern)( + (output, remains, bindings) => + makeConsequent(output, remains, bindings + (alias.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.ref(), tup(arguments*), "the transform's result") + val resultSymbol = TempSymbol(N, "transformResult") + Split.Let(resultSymbol, resultTerm, makeConsequent(resultSymbol.toScrut, remains, Map.empty)), + alternative)) /** Generate a split that consumes the prefix of the scrutinee. */ private def stringPrefix(scrut: Scrut, pat: Tree, inner: PrefixInner)(using Raise): Split = trace( @@ -341,7 +493,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De inner(Map.empty, () => tailSym.ref())) case (lo: IntLit) to (incl, hi: IntLit) => Split.End case (lo: DecLit) to (incl, hi: DecLit) => Split.End - case (lo: Literal) to (_, hi: Literal) => + case (lo: syntax.Literal) to (_, hi: syntax.Literal) => error(msg"Incompatible range types: ${lo.describe} to ${hi.describe}" -> pat.toLoc) errorSplit case lit @ StrLit(value) => @@ -358,7 +510,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De val prefixSymbol = new TempSymbol(N, "prefix") val postfixSymbol = new TempSymbol(N, "postfix") val mode = MatchMode.StringPrefix(prefixSymbol, postfixSymbol) - val pattern = FlatPattern.ClassLike(ctorTrm, N, mode, false)(ctor) + val pattern = FlatPattern.ClassLike(ctorTrm, N, mode, false)(ctor, Nil) Branch(scrut(), pattern, inner(Map.empty, () => postfixSymbol.ref())) ~: Split.End case pat => error(msg"Unrecognized pattern (${pat.describe})" -> pat.toLoc) @@ -438,38 +590,63 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De pre = s"Translator <<< ${params.mkString(", ")} $body", post = (blk: Ls[TermDefinition]) => s"Translator >>> $blk" ): - if patternParams.nonEmpty then - // Temporarily disable the translation of pattern with pattern parameters. - // TODO(rp): pass pattern parameters as objects to the `unapply` function - Nil - else - // val unapply = scoped("ucs:cp"): - // val scrutSym = VarSymbol(Ident("scrut")) - // val topmost = full(() => scrutSym.ref(), body, success(params))(using patternParams) ~~: failure - // log(s"Translated `unapply`: ${display(topmost)}") - // makeMatcher("unapply", scrutSym, topmost) - val unapply = scoped("ucs:translation"): - val inputSymbol = VarSymbol(Ident("input")) - val topmost = makeMatchSplit( - scrutinee = () => inputSymbol.ref(), - pattern = pattern, - allowedBindings = Nil // TODO: pass proper bindings - )( - (output, bindings) => Split.Else(makeMatchResult(tup(output() |> fld))), - failure - ) - log(s"Translated `unapply`: ${display(topmost)}") - makeMatcher("unapply", patternParams, inputSymbol, topmost) - val unapplyStringPrefix = scoped("ucs:cp"): - // We don't report errors here because they are already reported in the - // translation of `unapply` function. - given Raise = Function.const(()) - val scrutSym = VarSymbol(Ident("topic")) - stringPrefix(() => scrutSym.ref(), body, prefixSuccess(params)) match - case Split.Else(Term.Error) => - makeMatcher("unapplyStringPrefix", patternParams, scrutSym, failure) - case split => - val topmost = split ~~: failure - log(s"Translated `unapplyStringPrefix`: ${display(topmost)}") - makeMatcher("unapplyStringPrefix", patternParams, scrutSym, topmost) - unapply :: unapplyStringPrefix :: Nil + val unapply = scoped("ucs:translation"): + val inputSymbol = VarSymbol(Ident("input")) + val topmost = makeMatchSplit( + scrutinee = () => inputSymbol.ref().withIArgs(Nil), + pattern = pattern, + allowedBindings = Nil // TODO: pass proper bindings + )( + (output, bindings) => Split.Else(makeMatchResult(output())), + failure + ) + log(s"Translated `unapply`: ${display(topmost)}") + makeMatcher("unapply", patternParams, inputSymbol, topmost) + val unapplyStringPrefix = scoped("ucs:cp"): + // We don't report errors here because they are already reported in the + // translation of `unapply` function. + given Raise = Function.const(()) + val scrutSym = VarSymbol(Ident("topic")) + stringPrefix(() => scrutSym.ref(), body, prefixSuccess(params)) match + case Split.Else(Term.Error) => + makeMatcher("unapplyStringPrefix", patternParams, scrutSym, failure) + case split => + val topmost = split ~~: failure + log(s"Translated `unapplyStringPrefix`: ${display(topmost)}") + makeMatcher("unapplyStringPrefix", patternParams, scrutSym, topmost) + unapply :: unapplyStringPrefix :: Nil + + /** Translate an anonymous pattern. They are usually pattern arguments. */ + def translateAnonymousPattern(patternParams: Ls[Param], params: Ls[Param], pattern: Pattern): Term.Rcd = trace( + pre = s"translateAnonymousPattern <<< $pattern", + post = (blk: Term.Rcd) => s"translateAnonymousPattern >>> $blk" + ): + // We should apply an optimization to avoid generating unnecessary objects. + // If the pattern is a constructor pattern, we can just reference the + // `target` term. Currently, we don't do this because it is until resolution + // stage that we can know the `target` refers to a pattern or not. + val unapplyStmts = scoped("ucs:translation"): + val inputSymbol = VarSymbol(Ident("input")) + val topmost = makeMatchSplit( + scrutinee = () => inputSymbol.ref().withIArgs(Nil), + pattern = pattern, + allowedBindings = Nil // TODO: pass proper bindings + )( + (output, bindings) => Split.Else(makeMatchResult(output())), + failure + ) + log(s"Translated `unapply`: ${display(topmost)}") + makeAnonymousPatternObject("unapply", patternParams, inputSymbol, topmost) + // val stmts2 = scoped("ucs:cp"): + // // We don't report errors here because they are already reported in the + // // translation of `unapply` function. + // given Raise = Function.const(()) + // val scrutSym = VarSymbol(Ident("input")) + // stringPrefix(() => scrutSym.ref(), body, prefixSuccess(params)) match + // case Split.Else(Term.Error) => + // makeAnonymousPatternObject("unapplyStringPrefix", patternParams, scrutSym, failure) + // case split => + // val topmost = split ~~: failure + // log(s"Translated `unapplyStringPrefix`: ${display(topmost)}") + // makeAnonymousPatternObject("unapplyStringPrefix", patternParams, scrutSym, topmost) + Term.Rcd(unapplyStmts) 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 4138ad56ab..ced005efb1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/package.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/package.scala @@ -9,4 +9,11 @@ package object ucs: def warn(using Line, FileName, Name, Raise)(msgs: (Message, Option[Loc])*): Unit = raise(WarningReport(msgs.toList)) + + extension (symbol: BlockLocalSymbol) + /** Create a `Ref` that does not have any implicit arguments. We need this + * function because we generate a lot of `Ref`s after implicit resolution. + * Writing `.withIArgs(Nil)` is too verbose. + */ + def safeRef: Term.Ref = symbol.ref().withIArgs(Nil) end ucs diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index cee1c9ae88..b0f3c5b80e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -93,8 +93,11 @@ let Runtime1; return runtime.safeCall(string1.at(i)) } } - static drop(string2, n) { - return runtime.safeCall(string2.slice(n)) + static take(string2, n) { + return string2.slice(0, n) + } + static drop(string3, n1) { + return runtime.safeCall(string3.slice(n1)) } static toString() { return "Str"; } }); diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index 381e7ccfe0..1c5450f2ea 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -75,6 +75,8 @@ module Str with if i >= string.length then throw RangeError("Str.get: index out of bounds") else string.at(i) + + fun take(string, n) = string.slice(0, n) fun drop(string, n) = string.slice(n) diff --git a/hkmc2/shared/src/test/mlscript/apps/parsing/LexerTest.mls b/hkmc2/shared/src/test/mlscript/apps/parsing/LexerTest.mls index 84cd3d920b..f30b24ef9f 100644 --- a/hkmc2/shared/src/test/mlscript/apps/parsing/LexerTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/parsing/LexerTest.mls @@ -246,7 +246,4 @@ fun take(pattern P, idx: Str, acc: Str) = //│ ╔══[ERROR] Name not found: str //│ ║ l.238: while idx < str.length and str.charAt(idx) is ch and ch is P //│ ╙── ^^^ -//│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.238: while idx < str.length and str.charAt(idx) is ch and ch is P -//│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls index 9bb20bfd5f..d91243f11a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls @@ -90,10 +90,11 @@ case //│ sym = member:Foo //│ iargsLs = N //│ arguments = S of Ls of -//│ Tuple3: -//│ _1 = $param0 -//│ _2 = Ident of "a" -//│ _3 = N +//│ Argument: +//│ scrutinee = $param0 +//│ tree = Ident of "a" +//│ split = N +//│ pattern = N //│ mode = Default //│ refined = false //│ continuation = Let: diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/rp/Future.mls index 67d577eaef..f07bf94757 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Future.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Future.mls @@ -2,7 +2,15 @@ :todo // Parameterized patterns. pattern Rep0[A] = "" | A ~ Rep0[A] -//│ /!!!\ Uncaught error: scala.MatchError: App(Ident(Rep0),TyTup(List(Ident(A)))) (of class hkmc2.syntax.Tree$App) +//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] +//│ ╙── ^^^^^^^ +//│ ╔══[ERROR] Cannot use this reference as a pattern +//│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] +//│ ╙── ^ +//│ ╔══[ERROR] Cannot use this reference as a pattern +//│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] +//│ ╙── ^ :todo pattern Rep0(pattern A, B, C)(head) = @@ -12,7 +20,16 @@ pattern Rep0(pattern A, B, C)(head) = //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.9: "" | (A as head) ~ Rep0[A] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ /!!!\ Uncaught error: scala.MatchError: App(Ident(Rep0),TyTup(List(Ident(A)))) (of class hkmc2.syntax.Tree$App) +//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ║ l.9: "" | (A as head) ~ Rep0[A] +//│ ╙── ^^^^^^^ +//│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. +//│ ║ l.9: "" | (A as head) ~ Rep0[A] +//│ ║ ^^^^ +//│ ╟── The variable is missing from this sub-pattern. +//│ ║ l.9: "" | (A as head) ~ Rep0[A] +//│ ╙── ^^ +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: no symbol was assigned to variable `head` at Some(Loc(51,55,Future.mls:+8)) :todo // Pattern extractions via aliases. pattern Email(name, domain) = @@ -20,19 +37,20 @@ pattern Email(name, domain) = //│ ╔══[ERROR] Duplicate pattern variable. //│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ║ ^^^^^^^^^^ -//│ ╟── The previous definition is here. +//│ ╟── The previous definition is as follows. //│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^ //│ ╔══[WARNING] Useless pattern binding: Identifier. //│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^ -//│ ╔══[ERROR] Unrecognized pattern (infix operator) -//│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) -//│ ╙── ^^^^^^^^^^^^^^^^^^ +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: no symbol was assigned to variable `Identifier` at Some(Loc(33,43,Future.mls:+18)) :todo // View patterns pattern GreaterThan(value) = case n and n > value then n +//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ║ l.33: n and n > value then n +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ //│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(InfixApp(Ident(n),keyword 'and',OpApp(Ident(n),Ident(>),List(Ident(value)))),keyword 'then',Ident(n))))) (of class hkmc2.syntax.Tree$Case) :todo @@ -45,11 +63,11 @@ fun foo(x) = if x is Unit then .... Arrow(...) then .... //│ ╔══[ERROR] Unrecognized pattern split (infix operator). -//│ ║ l.44: view as +//│ ║ l.42: view as //│ ║ ^^^^^^^ -//│ ║ l.45: Unit then .... +//│ ║ l.43: Unit then .... //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ║ l.46: Arrow(...) then .... +//│ ║ l.44: Arrow(...) then .... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -67,15 +85,18 @@ 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.64: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ║ l.62: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^ +//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ║ l.62: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ╙── ^^^^^^ //│ /!!!\ Uncaught error: scala.MatchError: Jux(StrLit(a),Ident(to)) (of class hkmc2.syntax.Tree$Jux) :todo pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╔══[ERROR] Unrecognized pattern (infix operator) -//│ ║ l.75: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╙── ^^^^^^^^^^^^ +//│ ╔══[ERROR] Cannot use this reference as a pattern +//│ ║ l.73: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) +//│ ╙── ^^^ :todo pattern LineSep(pattern P) = case @@ -83,7 +104,14 @@ pattern LineSep(pattern P) = case L(...nd) ~ "" then nd :: Nil "\n" ~ LineSep(P, t) then nd :: t -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Ident(Nil)), OpApp(App(Ident(L),Tup(List(Spread(keyword '...',Some(Loc(52,55,Future.mls:+81)),Some(Ident(nd)))))),Ident(~),List(Block(List(InfixApp(StrLit(),keyword 'then',OpApp(Ident(nd),Ident(::),List(Ident(Nil)))), InfixApp(OpApp(StrLit( +//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ║ l.80: "" then Nil +//│ ║ ^^^^^^^^^^^ +//│ ║ l.81: L(...nd) ~ +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.82: "" then nd :: Nil +//│ ╙── ^^^^ +//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Ident(Nil)), OpApp(App(Ident(L),Tup(List(Spread(keyword '...',Some(Loc(52,55,Future.mls:+79)),Some(Ident(nd)))))),Ident(~),List(Block(List(InfixApp(StrLit(),keyword 'then',OpApp(Ident(nd),Ident(::),List(Ident(Nil)))), InfixApp(OpApp(StrLit( //│ ),Ident(~),List(App(Ident(LineSep),Tup(List(Ident(P), Ident(t)))))),keyword 'then',OpApp(Ident(nd),Ident(::),List(Ident(t))))))))))) (of class hkmc2.syntax.Tree$Case) :todo @@ -97,10 +125,29 @@ pattern Lines(pattern L) = case :todo if input is Lines of Email then //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.98: if input is Lines of Email then +//│ ║ l.96: if input is Lines of Email then //│ ╙── ^ -//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Tup(List())), TypeDef(Pat,Ident(Tail),Some(Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Tup(List())), InfixApp(InfixApp(OpApp(StrLit( -//│ ),Ident(~),List(Bra(Round,App(Ident(Lines),Tup(List(Ident(L))))))),keyword 'as',Ident(t)),keyword 'then',Ident(t)))))),None), InfixApp(InfixApp(InfixApp(Ident(L),keyword 'as',OpApp(Ident(h),Ident(~),List(Ident(Tail)))),keyword 'as',Ident(t)),keyword 'then',Tup(List(Ident(h), Spread(keyword '...',Some(Loc(148,151,Future.mls:+90)),Some(Ident(t))))))))),keyword ':',Ident(todo)) (of class hkmc2.syntax.Tree$InfixApp) +//│ ╔══[ERROR] Unexpected record property pattern. +//│ ║ l.89: "" then [] +//│ ║ ^^^^^^^^^^ +//│ ║ l.90: pattern Tail = case +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.91: "" then [] +//│ ║ ^^^^^^^^^^^^^^ +//│ ║ l.92: "\n" ~ (Lines of L) as t then t +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.93: L as h ~ Tail as t then [h, ...t] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.94: +//│ ║ ^^^ +//│ ║ l.95: :todo +//│ ╙── ^^^^^ +//│ ╔══[ERROR] Name not found: input +//│ ║ l.96: if input is Lines of Email then +//│ ╙── ^^^^^ +//│ ╔══[ERROR] Name not found: Email +//│ ║ l.96: if input is Lines of Email then +//│ ╙── ^^^^^ :todo pattern Opt(pattern P) = case @@ -109,38 +156,57 @@ pattern Opt(pattern P) = case :todo pattern Email(name, domain) = ... -//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Ident(value)))),keyword 'then',Bra(Round,App(Ident(Some),Tup(List(Ident(value)))))), InfixApp(StrLit(),keyword 'then',Bra(Round,Ident(None)))))),keyword ':',Ident(todo)) (of class hkmc2.syntax.Tree$InfixApp) +//│ ╔══[ERROR] Unexpected record property pattern. +//│ ║ l.124: P(value) then (Some(value)) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.125: "" then (None) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.126: +//│ ║ ^^^ +//│ ║ l.127: :todo +//│ ╙── ^^^^^ :todo if input is Opt(Email, Some((n, d))) then ... //│ ╔══[ERROR] Name not found: input -//│ ║ l.115: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.140: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^^ -//│ ╔══[ERROR] Name not found: Opt -//│ ║ l.115: if input is Opt(Email, Some((n, d))) then ... -//│ ╙── ^^^ //│ ╔══[ERROR] Name not found: Some -//│ ║ l.115: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.140: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^ //│ ╔══[ERROR] invalid record field pattern -//│ ║ l.115: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.140: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^ +//│ ═══[ERROR] mismatched arity: expect 0, found 2 :todo pattern Opt(pattern P) = case P(...values) then (Some(values)) "" then (None) -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+130)),Some(Ident(values)))))),keyword 'then',Bra(Round,App(Ident(Some),Tup(List(Ident(values)))))), InfixApp(StrLit(),keyword 'then',Bra(Round,Ident(None)))))) (of class hkmc2.syntax.Tree$Case) +//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ║ l.154: P(...values) then (Some(values)) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.155: "" then (None) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+153)),Some(Ident(values)))))),keyword 'then',Bra(Round,App(Ident(Some),Tup(List(Ident(values)))))), InfixApp(StrLit(),keyword 'then',Bra(Round,Ident(None)))))) (of class hkmc2.syntax.Tree$Case) :todo pattern Opt(pattern P) = case P(...values) then Some(values) "" then None -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+136)),Some(Ident(values)))))),keyword 'then',App(Ident(Some),Tup(List(Ident(values))))), InfixApp(StrLit(),keyword 'then',Ident(None))))) (of class hkmc2.syntax.Tree$Case) +//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ║ l.160: P(...values) then Some(values) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.161: "" then None +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ +//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+159)),Some(Ident(values)))))),keyword 'then',App(Ident(Some),Tup(List(Ident(values))))), InfixApp(StrLit(),keyword 'then',Ident(None))))) (of class hkmc2.syntax.Tree$Case) :todo pattern Digits = "0" to "9" ~ (Digits | "") //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.142: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.165: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^ +//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ║ l.165: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ╙── ^^^^^^ //│ /!!!\ Uncaught error: scala.MatchError: Jux(StrLit(0),Ident(to)) (of class hkmc2.syntax.Tree$Jux) diff --git a/hkmc2/shared/src/test/mlscript/rp/JoinPatterns.mls b/hkmc2/shared/src/test/mlscript/rp/JoinPatterns.mls index 2c3b45b8f6..d9843f044b 100644 --- a/hkmc2/shared/src/test/mlscript/rp/JoinPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/JoinPatterns.mls @@ -25,6 +25,14 @@ pattern SimpleJoin = ("abc" | "xyz") ~ ("def" | "") :todo pattern Exponential = ("a" | "b") ~ ("c" | "d") ~ ("e" | "f") // ~ ("g" | "h") ~ ("i" | "j") ~ ("k" | "l") ~ ("m" | "n") ~ ("o" | "p") ~ ("q" | "r") ~ ("s" | "t") ~ ("u" | "v") ~ ("w" | "x") ~ ("y" | "z") +:expect true +"ace" is Exponential +//│ = true + +:expect true +"bcf" is Exponential +//│ = true + :todo "abcdefghijklm" is Exponential //│ = false diff --git a/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls b/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls index bc07f7328f..a6504a31a0 100644 --- a/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls @@ -17,7 +17,7 @@ Playground.Zero //│ = Zero Playground.Zero.unapply("0") -//│ = MatchResult([]) +//│ = MatchResult("0") :expect true "0" is Playground.Zero @@ -30,7 +30,7 @@ Playground.DoubleZero //│ = DoubleZero Playground.DoubleZero.unapply("00") -//│ = MatchResult([]) +//│ = MatchResult("00") :expect true "00" is Playground.DoubleZero @@ -43,7 +43,7 @@ Playground.ZeroOne //│ = ZeroOne Playground.ZeroOne.unapply("01") -//│ = MatchResult([]) +//│ = MatchResult("01") :expect true "01" is Playground.ZeroOne @@ -58,7 +58,7 @@ TripleZero //│ = TripleZero TripleZero.unapply("000") -//│ = MatchResult([]) +//│ = MatchResult("000") :expect true "000" is TripleZero diff --git a/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls b/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls index 7d2492b465..f5933f6c5c 100644 --- a/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls +++ b/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls @@ -25,7 +25,7 @@ MatchResult(0) pattern Cross = "X" Cross.unapply("X") -//│ = MatchResult([]) +//│ = MatchResult("X") Cross.unapply("0") //│ = MatchFailure(undefined) diff --git a/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls b/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls index 48795087b9..36ed3a1e0e 100644 --- a/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls @@ -61,43 +61,28 @@ pattern UnsignedByte = 0..< 256 //│ ╔══[ERROR] Name not found: .< //│ ║ l.51: pattern UnsignedByte = 0..< 256 //│ ╙── ^^ -//│ ╔══[ERROR] Unrecognized pattern (operator application) -//│ ║ l.51: pattern UnsignedByte = 0..< 256 -//│ ╙── ^^^^^^^^ :e pattern BadRange = "s"..=0 //│ ╔══[ERROR] The upper and lower bounds of range patterns should be literals of the same type. -//│ ║ l.69: pattern BadRange = "s"..=0 -//│ ╙── ^^^^^^^ -//│ ╔══[ERROR] Incompatible range types: string literal to integer literal -//│ ║ l.69: pattern BadRange = "s"..=0 +//│ ║ l.66: pattern BadRange = "s"..=0 //│ ╙── ^^^^^^^ // It becomes an absurd pattern. 0 is BadRange -//│ = false +//│ = true :e pattern BadRange = 0 ..= "s" //│ ╔══[ERROR] The upper and lower bounds of range patterns should be literals of the same type. -//│ ║ l.82: pattern BadRange = 0 ..= "s" -//│ ╙── ^^^^^^^^^ -//│ ╔══[ERROR] Incompatible range types: integer literal to string literal -//│ ║ l.82: pattern BadRange = 0 ..= "s" +//│ ║ l.76: pattern BadRange = 0 ..= "s" //│ ╙── ^^^^^^^^^ :e pattern BadRange = "yolo" ..= "swag" //│ ╔══[ERROR] String range bounds must have only one character. -//│ ║ l.91: pattern BadRange = "yolo" ..= "swag" -//│ ║ ^^^^^^ -//│ ╟── String range bounds must have only one character. -//│ ║ l.91: pattern BadRange = "yolo" ..= "swag" -//│ ╙── ^^^^^^ -//│ ╔══[ERROR] String range bounds must have only one character. -//│ ║ l.91: pattern BadRange = "yolo" ..= "swag" +//│ ║ l.82: pattern BadRange = "yolo" ..= "swag" //│ ║ ^^^^^^ //│ ╟── String range bounds must have only one character. -//│ ║ l.91: pattern BadRange = "yolo" ..= "swag" +//│ ║ l.82: pattern BadRange = "yolo" ..= "swag" //│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/rp/Simple.mls b/hkmc2/shared/src/test/mlscript/rp/Simple.mls index 9db2fcb089..c160d31077 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Simple.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Simple.mls @@ -3,7 +3,34 @@ import "../../mlscript-compile/Runtime.mls" +:sjs pattern Zero = "0" +//│ JS (unsanitized): +//│ let Zero1; +//│ const Zero$class = class Zero { +//│ constructor() {} +//│ unapply(input) { +//│ if (input === "0") { +//│ return runtime.MatchResult(input) +//│ } else { +//│ return runtime.MatchFailure() +//│ } +//│ } +//│ unapplyStringPrefix(topic) { +//│ let cond, sliced; +//│ cond = runtime.Str.startsWith(topic, "0"); +//│ if (cond === true) { +//│ sliced = runtime.Str.drop(topic, 1); +//│ return runtime.MatchResult([ +//│ sliced +//│ ]) +//│ } else { +//│ return runtime.MatchFailure() +//│ } +//│ } +//│ toString() { return "Zero"; } +//│ }; Zero1 = new Zero$class; +//│ Zero1.class = Zero$class; :expect true Zero.unapply("0") is Runtime.MatchResult diff --git a/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls b/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls index 0a4a70c635..2658572ab6 100644 --- a/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls +++ b/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls @@ -5,7 +5,7 @@ data class Pair[A, B](first: A, second: B) pattern SumPair = Pair(a, b) => a + b fun foo(value) = if value is - SumPair(sum) then print("sum: " + sum) + SumPair as sum then print("sum: " + sum) else print("not a pair") foo of Pair(1, 2) @@ -19,37 +19,50 @@ foo of "hello" //│ > not a pair //│ > not a pair -:fixme pattern BoolLike = true | false | ((Int as x) => x !== 0) -//│ ╔══[ERROR] Class Int does not have a parameter list -//│ ║ l.23: pattern BoolLike = true | false | ((Int as x) => x !== 0) -//│ ╙── ^^^ -fun foo(value) = if value is +fun get_output_using_as(value) = if value is + BoolLike as b then print("" + value + " is equivalent to " + b) + else print("the input " + value + " is not a boolean") + +get_output_using_as of true +get_output_using_as of false +get_output_using_as of 0 +get_output_using_as of 1 +get_output_using_as of 2 +//│ > true is equivalent to true +//│ > false is equivalent to false +//│ > 0 is equivalent to false +//│ > 1 is equivalent to true +//│ > 2 is equivalent to true + +fun get_output_through_param(value) = if value is BoolLike(b) then print("" + value + " is equivalent to " + b) else print("the input " + value + " is not a boolean") -foo of true -foo of false -foo of 0 -foo of 1 -foo of 2 +get_output_through_param of true +get_output_through_param of false +get_output_through_param of 0 +get_output_through_param of 1 +get_output_through_param of 2 //│ > true is equivalent to true //│ > false is equivalent to false //│ > 0 is equivalent to false //│ > 1 is equivalent to true //│ > 2 is equivalent to true -:todo // Similar problem as `EvaluatedTerm`. pattern TrueLike = BoolLike as true :expect true -:fixme true is TrueLike -//│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' -//│ = false +//│ = true + +:expect true +42 is BoolLike +//│ = true -42 is BoolLike(true) +:expect true +42 is TrueLike //│ = true data class Term with @@ -59,40 +72,39 @@ data class Term with Prefix(op: Str, right: Term) // The following pattern generates a lot of code. -// pattern EvaluatedTerm = -// (Literal(value) => value) | -// (Infix("+", EvaluatedTerm as left, EvaluatedTerm as right) => left + right) | -// (Infix("-", EvaluatedTerm as left, EvaluatedTerm as right) => left - right) | -// (Infix("*", EvaluatedTerm as left, EvaluatedTerm as right) => left * right) | -// (Infix("/", EvaluatedTerm as left, EvaluatedTerm as right) => left / right) | -// (Prefix("-", EvaluatedTerm as operand) => -operand) | -// (Prefix("+", EvaluatedTerm as operand) => +operand) - pattern EvaluatedTerm = (Literal(value) => value) | (Infix("+", EvaluatedTerm as left, EvaluatedTerm as right) => left + right) | - (Infix("-", EvaluatedTerm as left, EvaluatedTerm as right) => left - right) + (Infix("-", EvaluatedTerm as left, EvaluatedTerm as right) => left - right) | + (Infix("*", EvaluatedTerm as left, EvaluatedTerm as right) => left * right) | + (Infix("/", EvaluatedTerm as left, EvaluatedTerm as right) => left / right) | + (Prefix("-", EvaluatedTerm as operand) => -operand) | + (Prefix("+", EvaluatedTerm as operand) => +operand) + +// Some helper functions to create `Term`s. +fun wrapLiteral(value) = if value is Int then Literal(value) else value +fun (+:+) add(a, b) = Infix("+", wrapLiteral(a), wrapLiteral(b)) +fun (*:*) mul(a, b) = Infix("*", wrapLiteral(a), wrapLiteral(b)) +fun (-:-) sub(a, b) = Infix("-", wrapLiteral(a), wrapLiteral(b)) +fun (/:/) div(a, b) = Infix("/", wrapLiteral(a), wrapLiteral(b)) + +Literal(1) is EvaluatedTerm(1) +//│ = true -fun eval(term) = if term is - EvaluatedTerm(value) then print("value: " + value) - else print("invalid term") +2 *:* 3 +:+ 1 is EvaluatedTerm(7) +//│ = true -:todo -// This doesn't work because the semantics of `as` is different in `Desugarer` -// and `Translator`. Operator `as` was to bind the LHS pattern to the RHS -// variable, but now it is to match the LHS pattern against the RHS pattern. -eval of Literal(1) -eval of Infix("+", Literal(1), Literal(2)) -eval of Infix("-", Literal(1), Literal(2)) -eval of Infix("*", Literal(1), Literal(2)) -eval of Infix("/", Literal(1), Literal(2)) -eval of Prefix("-", Literal(1)) -//│ > value: 1 -//│ > invalid term -//│ > invalid term -//│ > invalid term -//│ > invalid term -//│ > invalid term +(4 +:+ 5) *:* (6 -:- 2) is EvaluatedTerm(36) +//│ = true + +Prefix("-", 10 +:+ 2 *:* 3) is EvaluatedTerm(-16) +//│ = true + +(8 /:/ 2) +:+ (3 *:* 4) -:- 5 is EvaluatedTerm(11) +//│ = true + +Infix("**", Literal(1), Literal(2)) is EvaluatedTerm +//│ = false pattern DropZ = ((:x, :y, :z)) => (x: x, y: y) @@ -134,9 +146,6 @@ Pair(1, 2) is PairLike :todo pattern TransformTwice = (((Int as x) => x + 4) as y) => y * 2 -//│ ╔══[ERROR] Class Int does not have a parameter list -//│ ║ l.136: pattern TransformTwice = (((Int as x) => x + 4) as y) => y * 2 -//│ ╙── ^^^ 2 is TransformTwice(12) //│ = true @@ -146,3 +155,23 @@ pattern TransformTwice = (((Int as x) => x + 4) as y) => y * 2 17 is TransformTwice(42) //│ = true + +// We can easily define predicative patterns using `as true`. +pattern Positive = ((Int as x) => x > 0) as true + +print of -100 is Positive +print of 0 is Positive +print of 8080 is Positive +//│ > false +//│ > false +//│ > true + +// Hmm, but how to make the pattern return the input value? +Positive.unapply(100) +//│ = MatchResult(true) + +// Just use conjunction patterns. +pattern Positive = (x & (((Int as x) => x > 0) as true)) => x + +Positive.unapply(100) +//│ = MatchResult(100) diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Computation.mls b/hkmc2/shared/src/test/mlscript/rp/examples/Computation.mls new file mode 100644 index 0000000000..3a6de374e6 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/examples/Computation.mls @@ -0,0 +1,88 @@ +:js + +data class Celsius(value: Num) + +data class Fahrenheit(value: Num) + +pattern ToFahrenheit = + ((Celsius(c)) => Fahrenheit((c * 9 / 5) + 32)) | + ((Fahrenheit as f) => f) + +pattern ToCelsius = + ((Fahrenheit(f)) => Celsius((f - 32) * 5 / 9)) | + ((Celsius as c) => c) + +Celsius(0) is ToFahrenheit(Fahrenheit(32)) +//│ = true + + +Fahrenheit(212) is ToCelsius(Celsius(100)) +//│ = true + +Celsius(100) is ToFahrenheit(Fahrenheit(212)) +//│ = true + +Fahrenheit(32) is ToCelsius(Celsius(0)) +//│ = true + +Celsius(-40) is ToFahrenheit(Fahrenheit(-40)) +//│ = true + +Fahrenheit(-40) is ToCelsius(Celsius(-40)) +//│ = true + +pattern HourAM = (0 => 12) | (1 ..= 11) +pattern HourPM = 12 | (((13 ..= 23) as hour) => hour - 12) +pattern Minute = ((0 ..= 59) as minute) => minute.toString().padStart(2, "0") + +// Convert 24-hour time format to 12-hour format with AM/PM +pattern Time12Hour = (( + hour: ((HourAM | HourPM) as hour) & (((HourAM => "AM") | (HourPM => "PM")) as flag), + minute: Minute as minute +)) => hour + ":" + minute + " " + flag + +fun formatTime(ps) = if ps is Time12Hour as time then time + +print of "I had my breakfast at", formatTime of hour: 7, minute: 45 +print of "Let's have lunch at", formatTime of hour: 12, minute: 30 +print of "I plan to have dinner at", formatTime of hour: 18, minute: 45 +print of "I should go to bed before", formatTime of hour: 23, minute: 0 +//│ > I had my breakfast at 7:45 AM +//│ > Let's have lunch at 12:30 PM +//│ > I plan to have dinner at 6:45 PM +//│ > I should go to bed before 11:00 PM + +// This makes use of the chain pattern. It generates 29,232 lines of code! +// pattern Time12Hour = (( +// hour: (( +// ((0 ..= 11) => "AM") | +// ((12 ..= 23) => "PM") +// ) & ( +// (0 => 12) | +// (1 ..= 11) | +// 12 | +// ((13 ..= 23) as h => h - 12) +// )) as [hour, flag], +// minute: Minute as minute +// )) => hour + ":" + minute + " " + flag + +pattern Channel = 0 ..= 255 + +// Convert a color in RGB format to grayscale +pattern Grayscale = + ((r: Channel as r, g: Channel as g, b: Channel as b)) => + Math.round(r * 0.299 + g * 0.587 + b * 0.114) + +fun grayscale(rgb) = if rgb is Grayscale as g then g else -1 + +grayscale of r: 0, g: 0, b: 0 +//│ = 0 + +grayscale of r: 1, g: 1, b: 1 +//│ = 1 + +grayscale of r: 255, g: 255, b: 255 +//│ = 255 + +grayscale of r: 256, g: 255, b: 255 +//│ = -1 diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/ListPredicates.mls b/hkmc2/shared/src/test/mlscript/rp/examples/ListPredicates.mls new file mode 100644 index 0000000000..58c677feeb --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/examples/ListPredicates.mls @@ -0,0 +1,58 @@ +:js + +import "../../../mlscript-compile/Stack.mls" + +open Stack + +fun contains(xs, x) = if xs is + hd :: tl and { hd === x then true, else tl contains(x) } + Nil then false + +let list1 = 1 :: 2 :: 3 :: Nil +//│ list1 = Cons(1, Cons(2, Cons(3, Nil))) + +list1 contains of 3 +//│ = true + +list1 contains of 4 +//│ = false + +// Apply a pattern to a list, returns a list of unique elements if the output of +// each element is unique, and rejects the list otherwise. +pattern Unique(pattern P) = + Nil | ( + ((((P as hd) :: (Unique(pattern P) as tl)) => + let there = tl contains(hd) + [there, if there then tl else hd :: tl]) as [false, list]) => list) + +list1 is Unique(pattern _) +//│ = true + +list1 is Unique(pattern (Num as x) => x.toString().length) +//│ = false + +:todo // This looks legit, but doesn't work. +fun addProperty(obj, k, v) = (...obj, (k): v) +addProperty of (a: 1), "b", 2 +//│ = {a: 1, k: 2} + +// A workaround for the above issue. +fun addProperty(obj, k, v) = + let newObj = Object.fromEntries of [[k, v]] + (...obj, ...newObj) +addProperty of (a: 1), "b", 2 +//│ = {a: 1, b: 2} + +// I forgot the syntax of empty object. +fun emptyObject = Object.fromEntries of [] + +// Convert a list of pairs to a map-like object +pattern ListToObject = (Nil => emptyObject) | (([Str as k, v] :: (ListToObject as rest)) => addProperty(rest, k, v)) + +fun toObject(ps) = if ps is ListToObject as obj then obj else emptyObject + +print of toObject of ["a", 1] :: ["b", 2] :: Nil +//│ > {b: 2, a: 1} + +print of toObject of 0 :: Nil +//│ > {} diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Negation.mls b/hkmc2/shared/src/test/mlscript/rp/examples/Negation.mls new file mode 100644 index 0000000000..0482b5c0ae --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/examples/Negation.mls @@ -0,0 +1,28 @@ +:js + +pattern NonZero = ~0 + +0 is NonZero +//│ = false + +1 is NonZero +//│ = true + +-1 is NonZero +//│ = true + +pattern HomogeneousCoordinate = ((:x, :y, w: ~0 as w)) => (x: x / w, y: y / w) + +(x: 0, y: 0, w: 0) is HomogeneousCoordinate +//│ = false + +(x: 0, y: 0, w: 1) is HomogeneousCoordinate +//│ = true + +fun flatten(xyw) = if xyw is HomogeneousCoordinate as xy then xy + +flatten of x: 0, y: 0, w: 1 +//│ = {x: 0, y: 0} + +flatten of x: 4, y: 4, w: 2 +//│ = {x: 2, y: 2} diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls b/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls new file mode 100644 index 0000000000..dc90dd8497 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls @@ -0,0 +1,86 @@ +:js + +import "../../../mlscript-compile/Option.mls" + +open Option { Some, None } + +// Compute Body Mass Index (BMI) from weight (kg) and height (m) +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) +//│ ║ l.14: (weight: 70, height: 1.75) is CalculateBMI as Rounded(22) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ = false + +pattern RoundedBMI = CalculateBMI as Rounded + +fun bmi(record) = if record is RoundedBMI as value then value + +bmi of weight: 70, height: 1.75 +//│ = 23 + +:re +bmi of weight: 70 +//│ ═══[RUNTIME ERROR] Error: match error + +// This pattern is useful becuase it turns a pattern into a function that +// 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 + +bmi of weight: 70, height: 1.75 +//│ = Some(23) + +bmi of height: 1.75 +//│ = None + +// Calculate the area of different shapes +pattern ShapeArea = + (((:radius)) => Math.PI * radius * radius) | // Circle + (((:width, :height)) => width * height) | // Rectangle + (((:base, :height)) => base * height / 2) // Triangle + +fun area(shape) = if shape is ShapeArea as a then Some(a) else None + +area of (radius: 10) +//│ = Some(314.1592653589793) + +area of (width: 10, height: 20) +//│ = Some(200) + +area of (base: 10, height: 20) +//│ = Some(100) + +// Fields are tested in the same order as they are written in the pattern. +area of (radius: 10, height: 20) +//│ = Some(314.1592653589793) + +area of (width: 10, height: 20, base: 10) +//│ = Some(200) + +// Convert between different number systems +pattern NumberSystem = + (((:digits, radix: "x")) => parseInt(digits, 16)) | + (((: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.75: value is "0" ~ (("x" | "o" | "b") as radix) ~ digits and +//│ ╙── ^ + +// The workaround we can use currently is to use a pattern declaration. +:fixme +pattern IntegerLiteral = + ("0" ~ (("x" | "o" | "b") as radix) ~ digits) => (:radix, :digits) +//│ /!!!\ Uncaught error: java.util.NoSuchElementException: key not found: radix diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/TupleSpread.mls b/hkmc2/shared/src/test/mlscript/rp/examples/TupleSpread.mls new file mode 100644 index 0000000000..acd7c43e33 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/examples/TupleSpread.mls @@ -0,0 +1,95 @@ +:js + +// Extract the first and last elements of a list +pattern FirstLast = ([x, ..._, y] => [x, y]) + +[0, 1, 2] is FirstLast([0, 2]) +//│ = true + +[5, 8] is FirstLast([5, 8]) +//│ = true + +[] is FirstLast +//│ = false + +[1] is FirstLast +//│ = false + +// Test if a list is a palindrome. This example is not very good because it +// outputs a boolean value, rather than rejecting the input. +pattern TestPalindrome = + ([x, ...TestPalindrome as m, y] => m && x == y) | + (([_] | []) => true) | (_ => false) + +[1, 2, 1] is TestPalindrome(true) +//│ = true + +[] is TestPalindrome(true) +//│ = true + +[1] is TestPalindrome(true) +//│ = true + +[1, 2] is TestPalindrome(true) +//│ = false + +// A better palindrome tuple, which matches palindrome sequences exactly. +pattern Palindrome = + (([head, ...Palindrome, last] => head === last) as true) | [_] | [] + +[1, 2, 2, 1] is Palindrome +//│ = true + +[] is Palindrome +//│ = true + +[1] is Palindrome +//│ = true + +[1, 2] is Palindrome +//│ = false + +[1, 2, 3] is Palindrome +//│ = false + +[1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1] is Palindrome +//│ = true + +import "../../../mlscript-compile/Stack.mls" + +open Stack + +pattern StackLike = ([] => Nil) | ([x, ...StackLike as xs] => x :: xs) + +[] is StackLike(Nil) +//│ = true + +[] is StackLike(1 :: Nil) +//│ = false + +[1] is StackLike(1 :: Nil) +//│ = true + +[1, 2, 3, 4, 5] is StackLike(1 :: 2 :: 3 :: 4 :: 5 :: Nil) +//│ = true + +[1, 2, 3, 4, 5] is StackLike(1 :: 2 :: 3 :: 4 :: Nil) +//│ = false + +// Extract elements at even indices in a tuple. +pattern ElementAtEvenIndex = + ([] => []) | + ([x] => [x]) | + ([x, _, ...ElementAtEvenIndex as rest] => [x, ...rest]) + +[] is ElementAtEvenIndex([]) +//│ = true + +[1] is ElementAtEvenIndex([1]) +//│ = true + +[1, 2, 3, 4, 5] is ElementAtEvenIndex([1, 3, 5]) +//│ = true + +[1, 2, 3, 4, 5] is ElementAtEvenIndex([1, 3]) +//│ = false diff --git a/hkmc2/shared/src/test/mlscript/rp/nondeterminism/BitArithmetic.mls b/hkmc2/shared/src/test/mlscript/rp/nondeterminism/BitArithmetic.mls index 8dae116ae0..87e035faf5 100644 --- a/hkmc2/shared/src/test/mlscript/rp/nondeterminism/BitArithmetic.mls +++ b/hkmc2/shared/src/test/mlscript/rp/nondeterminism/BitArithmetic.mls @@ -28,32 +28,56 @@ fun isTruthy(value) = value is @compile Truthy fun isFalsy(value) = value is @compile Falsy +fun isTruthy'(value) = value is Truthy + +fun isFalsy'(value) = value is Falsy + :global :expect true // Test each case of `Truthy`. +// =========================== isTruthy of true //│ = true +isTruthy' of true +//│ = true + isTruthy of true && true //│ = true +isTruthy' of true && true +//│ = true + isTruthy of true || true //│ = true +isTruthy' of true || true +//│ = true + :re isTruthy of true || false //│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' //│ = false +isTruthy' of true || false +//│ = true + isTruthy of false || true //│ = true +isTruthy' of false || true +//│ = true + isTruthy of Not of false //│ = true +isTruthy' of Not of false +//│ = true + // Test each case of `Falsy`. +// ========================== isFalsy of false //│ = true @@ -67,11 +91,23 @@ isFalsy of false && true //│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' //│ = false +isFalsy' of false && true +//│ = true + isFalsy of true && false //│ = true +isFalsy' of true && false +//│ = true + isFalsy of false || false //│ = true +isFalsy' of false || false +//│ = true + isFalsy of Not of true //│ = true + +isFalsy' of Not of true +//│ = true diff --git a/hkmc2/shared/src/test/mlscript/rp/nondeterminism/EvenOddTree.mls b/hkmc2/shared/src/test/mlscript/rp/nondeterminism/EvenOddTree.mls index 6fa119f30f..e138e183a7 100644 --- a/hkmc2/shared/src/test/mlscript/rp/nondeterminism/EvenOddTree.mls +++ b/hkmc2/shared/src/test/mlscript/rp/nondeterminism/EvenOddTree.mls @@ -26,49 +26,87 @@ fun b(lc, rc) = Node(lc, B, rc) :expect true // Test each case of `EvenTree`. +// ============================= B is @compile EvenTree //│ = true +B is EvenTree +//│ = true + not of A is @compile EvenTree //│ = true +not of A is EvenTree +//│ = true + not of a(B, B) is @compile EvenTree //│ = true +not of a(B, B) is EvenTree +//│ = true + a(B, A) is @compile EvenTree //│ = true +a(B, A) is EvenTree +//│ = true + a(A, B) is @compile EvenTree //│ = true +a(A, B) is EvenTree +//│ = true + :re b(A, A) is @compile EvenTree //│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' //│ = false +b(A, A) is EvenTree +//│ = true + :re b(B, B) is @compile EvenTree //│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' //│ = false +b(B, B) is EvenTree +//│ = true + // Test each case of `OddTree`. +// ============================ A is @compile OddTree //│ = true +A is OddTree +//│ = true + a(B, B) is @compile OddTree //│ = true +a(B, B) is OddTree +//│ = true + a(A, A) is @compile OddTree //│ = true +a(A, A) is OddTree +//│ = true + :re b(B, A) is @compile OddTree //│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' //│ = false +b(B, A) is OddTree +//│ = true + :re b(A, B) is @compile OddTree //│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' //│ = false + +b(A, B) is OddTree +//│ = true diff --git a/hkmc2/shared/src/test/mlscript/rp/parametric/ListLike.mls b/hkmc2/shared/src/test/mlscript/rp/parametric/ListLike.mls new file mode 100644 index 0000000000..6c5f23f87a --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/parametric/ListLike.mls @@ -0,0 +1,24 @@ +:js + +import "../../../mlscript-compile/Stack.mls" + +open Stack + +pattern ListLike(pattern T) = Nil | (T :: ListLike(pattern T)) + +fun isOddIntList(xs) = xs is ListLike(pattern ((Int as x) => x % 2 === 1) as true) + +isOddIntList of Nil +//│ = true + +isOddIntList of 1 :: 3 :: Nil +//│ = true + +isOddIntList of 1 :: 2 :: 3 :: Nil +//│ = false + +isOddIntList of 1 :: 71 :: "bruh" :: Nil +//│ = false + +isOddIntList of "hello" :: "world" :: Nil +//│ = false diff --git a/hkmc2/shared/src/test/mlscript/rp/parametric/Nullable.mls b/hkmc2/shared/src/test/mlscript/rp/parametric/Nullable.mls new file mode 100644 index 0000000000..dccf84488f --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/parametric/Nullable.mls @@ -0,0 +1,24 @@ +:js + +pattern Null = null + +null is Null +//│ = true + +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) + +isPositiveOrNull of 0 +//│ = false + +isPositiveOrNull of null +//│ = true + +isPositiveOrNull of 1 +//│ = true + +isPositiveOrNull of -2 +//│ = false diff --git a/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls b/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls index 43460088d2..12ff5eb002 100644 --- a/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls +++ b/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls @@ -69,7 +69,9 @@ 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 'output' pattern D_2 = Box(Bowl | Bowl | Bowl) @@ -139,16 +141,18 @@ fun (::) cons(head, tail) = Pair(head, tail) //│ = true :todo +// Note the location of `null` is missing because of the parse rule doesn't +// attach location information to `UnitLit(true)`. pattern List(a) = null | Pair(a, List) //│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. -//│ ║ l.142: pattern List(a) = null | Pair(a, List) +//│ ║ l.146: pattern List(a) = null | Pair(a, List) //│ ║ ^ //│ ╙── The variable is missing from this sub-pattern. //│ ╔══[ERROR] Name not found: a -//│ ║ l.142: pattern List(a) = null | Pair(a, List) +//│ ║ l.146: pattern List(a) = null | Pair(a, List) //│ ╙── ^ //│ ╔══[ERROR] Name not found: a -//│ ║ l.142: pattern List(a) = null | Pair(a, List) +//│ ║ l.146: pattern List(a) = null | Pair(a, List) //│ ╙── ^ null is @compile List diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls index 12eb0d3133..be06881abd 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls @@ -18,9 +18,6 @@ pattern Foo(val T) = null | T //│ ╔══[ERROR] Name not found: T //│ ║ l.4: pattern Foo(val T) = null | T //│ ╙── ^ -//│ ╔══[ERROR] Name not found: T -//│ ║ l.4: pattern Foo(val T) = null | T -//│ ╙── ^ pattern Foo(pattern T) = null | T diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls index b7de4bd6fd..0bdeb69e63 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls @@ -8,13 +8,9 @@ open Option { Some, None } pattern DropZ = ((:x, :y, :z)) => (x: x, y: y) -:ucs ups -pattern HomogeneousCoordinate = ((:x, :y, w: ~0 as w)) => (x: x / w, y: y / w) -//│ elaborated pattern body: {x: x, y: y, w: ¬0 as w} => { let x; x = builtin:/#0(x#666, w#666); "x": x#0; let y; y = builtin:/#1(y#666, w#666); "y": y#0; } - :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:+#0(hd#666, tl#666) +//│ elaborated pattern body: member:Nil => 0 ∨ member:Cons(member:Int as hd, member:SumList as tl) => builtin:+#0(hd#666, tl#666) // Flatten the nested tuples. Note that it makes use of biased union. pattern Flatten = ([Flatten as hd, ...Flatten as tl] => Array.concat(hd, tl)) | _ @@ -34,31 +30,14 @@ pattern CountTree(pattern P) = pattern Transpose = (null => null) | (([[a, b], [c, d]]) => [[a, c], [b, d]]) //│ elaborated pattern body: null => null ∨ [[a, b], [c, d]] => [[a#666, c#666], [b#666, d#666]] -// Convert a color in RGB format to grayscale -pattern Grayscale = ((:r, :g, :b)) => r * 0.299 + g * 0.587 + b * 0.114 - // 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#666 ∨ [x, ...member:Middle() as m, y] => m#666 +//│ elaborated pattern body: [x] => x#666 ∨ [x, ...member:Middle as m, y] => m#666 // Return the leaves of a tree. pattern Leaves = (null => Nil) | ([x] => x :: Nil) | ([Leaves as left, Leaves as right] => left ::: right) -// Extract the first and last elements of a list -pattern FirstLast = ([x, ..._, y] => [x, y]) - -// Check if a list is a palindrome -:ucs ups -pattern IsPalindrome = ([x, ...IsPalindrome as m, y] => m && x == y) | (([_] | []) => true) | (_ => false) -//│ elaborated pattern body: [x, ...member:IsPalindrome() as m, y] => builtin:&�(m#666, builtin:==#0(x#666, y#666)) ∨ ([_] ∨ []) => true ∨ _ => false - -:ucs ups -// A better `IsPalindrome`, which matches palindrome sequences exactly. -pattern IsPalindrome = - (([first, ...IsPalindrome, last] => first === last) as true) | [_] | [] -//│ elaborated pattern body: ([first, ...member:IsPalindrome(), last] => builtin:===#0(first#666, last#666)) as true ∨ [_] ∨ [] - // Extract the diagonal elements of a 3x3 matrix pattern MatrixDiagonal = ([[a, _, _], [_, b, _], [_, _, c]] => [a, b, c]) @@ -94,64 +73,8 @@ pattern PathToValue(pattern target) = (Node(PathToValue(target) as left, _, _) => [0] ::: left) | (Node(_, _, PathToValue(target) as right) => [1] ::: right) -// Convert a list of pairs to a map-like object -pattern ListToMap = (Nil => ()) | (([Str as k, v] :: ListToMap as rest) => (...rest, (k): v)) - -fun contains(xs, x) = if xs is - hd :: tl and { hd === x then true, else tl contains(x) } - Nil then false - -// Apply a pattern to a list and return a list of unique elements. -pattern Unique(pattern P) = - (Nil => Nil) | - (((P as hd) :: (Unique(P) as tl)) => if tl contains(hd) then tl else hd :: tl) - -// Extract elements at even indices in a tuple. -pattern EvenIndicesTuple = - ([] => []) | - ([x] => [x]) | - ([x, _, ...EvenIndices as rest] => [x, ...rest]) - // Extract elements at even indices in a list. pattern EvenIndicesList = (Nil => Nil) | ((x :: Nil) => x) | ((x :: _ :: EvenIndices as xs) => x :: xs) - -pattern CelsiusToFahrenheit = (Num as c) => c * 9 / 5 + 32 -pattern FahrenheitToCelsius = (Num as f) => (f - 32) * 5/9 - -// Compute Body Mass Index (BMI) from weight (kg) and height (m) -pattern CalculateBMI = ((:weight, :height)) => weight / (height * height) - -pattern HourAM = 0 ..= 12 -pattern HourPM = ((13 ..= 23) as hour) => hour - 12 - -// Convert 24-hour time format to 12-hour format with AM/PM -pattern Time12Hour = (( - hour: ((HourAM | HourPM) as hour) & ((HourAM => "AM" | HourPM => "PM") as flag), - :minute -)) => hour + ":" + minute + " " + flag - -:ucs ups -// This makes use of the chain pattern. -pattern Time12Hour = (( - hour: ( - ((hour as (0 ..= 12)) => ["AM", hour]) | - ((hour as (13 ..= 23)) => ["PM", hour - 12]) - ) as [hour, flag], - :minute -)) => hour + ":" + minute + " " + flag -//│ elaborated pattern body: {hour: ((hour as (0 to 12)) => ["AM", hour#666] ∨ (hour as (13 to 23)) => ["PM", builtin:-#4(hour#666, 12)]) as [hour, flag], minute: minute} => builtin:+#15(builtin:+#14(builtin:+#13(builtin:+#12(hour#666, ":"), minute#666), " "), flag#666) - -// Calculate the area of different shapes -pattern ShapeArea = - (((:radius)) => Math.PI * radius * radius) | // Circle - (((:width, :height)) => width * height) | // Rectangle - (((:base, :height)) => base * height / 2) // Triangle - -// Convert between different number systems -pattern NumberSystem = - (((:value, radix: "binary")) => value.toString(2)) | - (((:value, radix: "hex")) => value.toString(16)) | - (((:value, radix: "octal")) => value.toString(8)) diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls index 2051bcc636..06beca5ebd 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls @@ -16,18 +16,18 @@ 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#666.)Nil‹member:Nil› ∨ member:Pair(T() as hd, member:PlainList(T()) as tl) => (member:Stack#666.)Cons‹member:Cons›(hd#666, tl#666) +//│ elaborated pattern body: null => (member:Stack#666.)Nil‹member:Nil› ∨ member:Pair(T as hd, member:PlainList(T) as tl) => (member:Stack#666.)Cons‹member:Cons›(hd#666, tl#666) :todo :ucs ups // Decide the operator precedence between `as` and `=>`. // This is elaborated into `T as (x => x)`, which might not be what we want. pattern Identity(pattern T) = T as x => x -//│ elaborated pattern body: T() as (x => x#666) +//│ elaborated pattern body: T as (x => x#666) :ucs ups pattern Identity(pattern T) = (T as x) => x -//│ elaborated pattern body: (T() as x) => x#666 +//│ elaborated pattern body: (T as x) => x#666 pattern Identity = x => x @@ -71,52 +71,49 @@ pattern UselessParameter = x //│ ╔══[ERROR] Name not found: x //│ ║ l.61: pattern UselessParameter = x //│ ╙── ^ -//│ ╔══[ERROR] Name not found: x -//│ ║ l.61: 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#666.)Nil‹member:Nil› ∨ [T() as hd, member:PlainList(T()) as tl] => (member:Stack#666.)Cons‹member:Cons›(hd#666, tl#666) +//│ elaborated pattern body: null => (member:Stack#666.)Nil‹member:Nil› ∨ [T as hd, member:PlainList(T) as tl] => (member:Stack#666.)Cons‹member:Cons›(hd#666, tl#666) :fixme [T as hd, PlainList(pattern T) as tl] //│ ╔══[ERROR] Name not found: hd -//│ ║ l.83: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^^ //│ ╔══[ERROR] Illegal type declaration in term position. -//│ ║ l.83: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^ //│ ╔══[ERROR] Name not found: tl -//│ ║ l.83: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^^ //│ ═══[RUNTIME ERROR] TypeError: PlainList3 is not a function :ucs ups pattern Consistent = ((Int as x) | (Str as x)) => x.toString() -//│ elaborated pattern body: (member:Int() as x ∨ member:Str() as x) => x#666.toString() +//│ elaborated pattern body: (member:Int as x ∨ member:Str as x) => x#666.toString() :e pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.100: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() -//│ ║ ^^^ +//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.100: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() -//│ ╙── ^^^^^^^^ +//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.100: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() -//│ ╙── ^ +//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ╙── ^ :e pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() //│ ╔══[ERROR] Name not found: x -//│ ║ l.112: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() +//│ ║ l.109: 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: ¬(member:Int ∨ member:Str) :todo // The precedence of `=>` is higher than `~`? :pt @@ -142,33 +139,33 @@ pattern BadNegated = ~(Int as x) => x :e pattern BadNegated = (~(Int as x)) => x //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.143: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.143: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.143: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^ -//│ elaborated pattern body: ¬(member:Int() as x) => +//│ elaborated pattern body: ¬(member:Int as x) => :ucs ups :e pattern BadNegated = (~Pair(Int, x)) => x //│ ╔══[ERROR] This variable cannot be accessed. -//│ ║ l.157: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x //│ ║ ^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.157: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.157: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^ -//│ elaborated pattern body: ¬member:Pair(member:Int(), x) => +//│ elaborated pattern body: ¬member:Pair(member:Int, x) => :ucs ups pattern DoubleNegated = ~(~Int) -//│ elaborated pattern body: ¬¬member:Int() +//│ elaborated pattern body: ¬¬member:Int :ucs ups pattern Nothing = ~_ @@ -197,13 +194,12 @@ pattern NestedTuple = [[[]]] pattern Digit = "0" ..= "9" pattern Naughty = CanYouSeeMe(n) => n -class CanYouSeeMe(wow: Int) +data class CanYouSeeMe(wow: Int) + -:r fun y = HiddenCorner.m.m.k pattern Naughty = HiddenCorner.m.m.YouCantSeeMe(n) => n + HiddenCorner.m.m.k module HiddenCorner with val m: module HiddenCorner = HiddenCorner class YouCantSeeMe(wow: Int) val k = 0 -//│ Resolved: ( ‹› fun member:y = member:HiddenCorner#666.m‹member:m›.m.k; Pat Naughty ( ‹› fun member:unapply(Param(‹›,scrut,None,Modulefulness(None)), ) = if { else }; ‹› fun member:unapplyStringPrefix(Param(‹›,topic,None,Modulefulness(None)), ) = if { else ($runtime#23.)MatchFailure‹class:MatchFailure›() }; ); Mod HiddenCorner ( ‹› val member:m: member:HiddenCorner#666 = member:HiddenCorner#666; Cls YouCantSeeMeParamList(‹›,List(Param(‹›,wow,Some(Ref(member:Int)),Modulefulness(None))),None) ( let class:YouCantSeeMe.wow; class:YouCantSeeMe.wow = wow#0; ); ‹› val member:k = 0; ); ) diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Precedence.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/Precedence.mls new file mode 100644 index 0000000000..b2c77b05c9 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/Precedence.mls @@ -0,0 +1,53 @@ +:ucs ups + +// The right binding power of `=>` is lower than the left binding power of `|`. +// Therefore, if we want to union two transformations, we should parenthesize +// the second transformation. + +pattern Test = 1 | 2 +//│ elaborated pattern body: 1 ∨ 2 + +pattern Test = 1 | 2 => 3 +//│ elaborated pattern body: 1 ∨ 2 => 3 + +// The following two examples are rejected because `4 => 8` is considered as a +// part of the right hand side term of `1 => 2`. Currently, our implementation +// does not support patterns as lambda parameters. + +:todo +pattern Test = 1 => 2 | 4 => 8 +//│ /!!!\ Uncaught error: scala.MatchError: IntLit(4) (of class hkmc2.syntax.Tree$IntLit) + +:todo +pattern Test = 1 => 2 | (4 => 8) +//│ /!!!\ Uncaught error: scala.MatchError: IntLit(4) (of class hkmc2.syntax.Tree$IntLit) + +pattern Test = (1 => 2) | 4 => 8 +//│ elaborated pattern body: 1 => 2 ∨ 4 => 8 + +// The precedence of `as` is lower than `=>`. + +pattern Test = 0 as x => 2 +//│ elaborated pattern body: 0 as (x => 2) + +pattern Test = (0 as x) => 2 +//│ elaborated pattern body: (0 as x) => 2 + +:e +pattern Test(x) = 0 as x | 1 as x +//│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. +//│ ║ l.37: pattern Test(x) = 0 as x | 1 as x +//│ ║ ^ +//│ ╟── The variable is missing from this sub-pattern. +//│ ║ l.37: pattern Test(x) = 0 as x | 1 as x +//│ ╙── ^ +//│ elaborated pattern body: (0 as (x ∨ 1)) as x + +pattern Test(x) = 0 as x | (1 as x) +//│ elaborated pattern body: 0 as (x ∨ 1 as x) + +pattern Test(x) = 0 as x | (1 as x) +//│ elaborated pattern body: 0 as (x ∨ 1 as x) + +pattern Test(x) = (0 as x) | (1 as x) +//│ elaborated pattern body: 0 as x ∨ 1 as x diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls index a19ecea178..7d353ff11b 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/AliasPattern.mls @@ -16,9 +16,7 @@ fun map(f) = case fun foo = case Some(Some(a as b) as c) as d then [a, b, c, d] //│ Desugared: -//│ > if caseScrut is Option.Some(param0) and $param0 is Option.Some(param0) and +//│ > if caseScrut is Option.Some(param0) as d and $param0 is Option.Some(param0) as c and //│ > let a = $param0#0 //│ > let b = $param0#1 -//│ > let c = $param0#1 -//│ > let d = caseScrut#1 //│ > else [a#666, b#666, c#666, d#666] From 6ca78ca7b3c1063259deef633332795ee77a551e Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 29 Jun 2025 09:38:20 +0800 Subject: [PATCH 10/62] Implement pattern compilation --- .../scala/hkmc2/codegen/js/JSBuilder.scala | 1 + .../scala/hkmc2/semantics/Elaborator.scala | 31 +- .../main/scala/hkmc2/semantics/Split.scala | 34 +- .../main/scala/hkmc2/semantics/Symbol.scala | 12 +- .../src/main/scala/hkmc2/semantics/Term.scala | 2 +- .../hkmc2/semantics/ucs/DeBrujinSplit.scala | 562 ------------------ .../scala/hkmc2/semantics/ucs/Desugarer.scala | 20 +- .../hkmc2/semantics/ucs/DesugaringBase.scala | 27 +- .../hkmc2/semantics/ucs/FlatPattern.scala | 10 +- .../semantics/ucs/HelperExtractors.scala | 34 -- .../hkmc2/semantics/ucs/Normalization.scala | 180 ++---- .../hkmc2/semantics/ucs/PatternStub.scala | 97 --- .../hkmc2/semantics/ucs/Translator.scala | 19 +- .../scala/hkmc2/semantics/ucs/package.scala | 33 + .../scala/hkmc2/semantics/ups/Compiler.scala | 478 +++++++++++++-- .../scala/hkmc2/semantics/ups/Context.scala | 22 + .../hkmc2/semantics/ups/Instantiator.scala | 142 +++++ .../scala/hkmc2/semantics/ups/Pattern.scala | 543 +++++++++-------- .../src/main/scala/hkmc2/syntax/Token.scala | 12 + .../src/main/scala/hkmc2/utils/utils.scala | 38 ++ .../src/test/mlscript-compile/Runtime.mjs | 11 +- .../src/test/mlscript-compile/Runtime.mls | 2 +- .../src/test/mlscript/backlog/ToTriage.mls | 6 +- .../shared/src/test/mlscript/backlog/UCS.mls | 2 +- .../test/mlscript/codegen/FieldSymbols.mls | 1 - hkmc2/shared/src/test/mlscript/llir/Split.mls | 4 +- hkmc2/shared/src/test/mlscript/rp/Future.mls | 143 ++--- .../src/test/mlscript/rp/LocalPatterns.mls | 8 +- .../src/test/mlscript/rp/MatchResult.mls | 10 +- .../src/test/mlscript/rp/RangePatterns.mls | 18 +- hkmc2/shared/src/test/mlscript/rp/Simple.mls | 8 +- .../test/mlscript/rp/SimpleConjunction.mls | 106 ++++ .../src/test/mlscript/rp/SimpleTransform.mls | 63 +- .../test/mlscript/rp/examples/Computation.mls | 65 +- .../test/mlscript/rp/examples/DoubleOrSum.mls | 167 ++++++ .../mlscript/rp/examples/DoubleTripleList.mls | 360 +++++++++++ .../test/mlscript/rp/examples/Extraction.mls | 146 +++++ .../src/test/mlscript/rp/examples/Flatten.mls | 64 ++ .../test/mlscript/rp/examples/Identifier.mls | 55 ++ .../mlscript/rp/examples/ListPredicates.mls | 3 + .../src/test/mlscript/rp/examples/Record.mls | 27 +- .../test/mlscript/rp/examples/TupleSpread.mls | 7 + .../rp/nondeterminism/BitArithmetic.mls | 10 +- .../rp/nondeterminism/EvenOddTree.mls | 16 +- .../rp/parametric/HigherOrderPattern.mls | 14 +- .../test/mlscript/rp/parametric/ListLike.mls | 6 + .../test/mlscript/rp/parametric/Nullable.mls | 54 ++ .../mlscript/rp/specialization/SimpleList.mls | 6 - .../rp/specialization/SimpleLiterals.mls | 16 +- .../test/mlscript/rp/syntax/Declaration.mls | 6 - .../test/mlscript/rp/syntax/PatternBody.mls | 51 +- .../test/mlscript/rp/syntax/WrongArity.mls | 20 +- .../test/mlscript/ucs/patterns/Parameters.mls | 14 +- 53 files changed, 2387 insertions(+), 1399 deletions(-) delete mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala delete mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/HelperExtractors.scala delete mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/PatternStub.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala create mode 100644 hkmc2/shared/src/test/mlscript/rp/SimpleConjunction.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/examples/DoubleOrSum.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/examples/DoubleTripleList.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/examples/Extraction.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/examples/Flatten.mls 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 9a7177d71b..b84d465b5f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -153,6 +153,7 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: case Value.Arr(es) if es.isEmpty => doc"[]" case Value.Arr(es) => doc"[ #{ # ${es.map(argument).mkDocument(doc", # ")} #} # ]" + case Value.Rcd(Nil) => doc"{}" case Value.Rcd(flds) => doc"{ # #{ ${ flds.map: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index a907d8d335..d6678591c5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -212,10 +212,13 @@ object Elaborator: BlockMemberSymbol(id.name, Nil, true) val matchResultClsSymbol = val id = new Ident("MatchResult") - val td = TypeDef(syntax.Cls, App(id, Tup(Ident("captures") :: Nil)), N, N) + val td = TypeDef(syntax.Cls, App(id, Tup(Ident("output") :: Ident("bindings") :: Nil)), N, N) val cs = ClassSymbol(td, id) val flag = FldFlags.empty.copy(value = true) - val ps = PlainParamList(Param(flag, VarSymbol(Ident("captures")), N, Modulefulness(N)(false)) :: Nil) + val ps = PlainParamList( + Param(flag, VarSymbol(Ident("output")), N, Modulefulness(N)(false)) :: + Param(flag, VarSymbol(Ident("bindings")), N, Modulefulness(N)(false)) :: + Nil) cs.defn = S(ClassDef.Parameterized(N, syntax.Cls, cs, BlockMemberSymbol(cs.name, Nil), Nil, ps, N, ObjBody(Blk(Nil, Term.Lit(UnitLit(false)))), N, Nil)) cs @@ -1144,29 +1147,11 @@ extends Importer: case N => raise(WarningReport(msg"Useless pattern binding: $name." -> aliases.head.toLoc :: Nil)) scoped("ucs:ups")(log(s"elaborated pattern body: ${pat.showDbg}")) scoped("ucs:ups:tree")(log(s"elaborated pattern body: ${pat.showAsTree}")) - - // *** BEGIN OBSOLETE CODE *** - td.rhs match - case N => raise(ErrorReport(msg"Pattern definitions must have a body." -> td.toLoc :: Nil)) - case S(tree) => - // // TODO: Implement extraction parameters. - // if extractionParams.nonEmpty then - // raise(ErrorReport(msg"Pattern extraction parameters are not yet supported." -> - // Loc(extractionParams.iterator.map(_.sym)) :: Nil)) - log(s"pattern parameters: ${patternParams.mkString("{ ", ", ", " }")}") - patSym.patternParams = patternParams - val split = ucs.DeBrujinSplit.elaborate(patternParams, tree, this) - scoped("ucs:rp:elaborated"): - log(s"elaborated ${patSym.nme}:\n${split.display}") - patSym.split = split - log(s"pattern body is ${td.rhs}") - // *** END OBSOLETE CODE *** - // Translate the pattern directly into methods that perform matching // using backtracking. val bod = new ucs.Translator(this)( - patSym.patternParams, - Nil, // ps.map(_.params).getOrElse(Nil), // TODO[Luyu]: remove pattern parameters + patternParams, Nil, // TODO: Remove this parameter after we finish + // the pattern translation for string concatenation. td.rhs.getOrElse(die), pat) // `paramsOpt` is set to `N` because we don't want parameters to // appear in the generated class's constructor. @@ -1311,7 +1296,7 @@ extends Importer: def pattern(t: Tree): Ctxl[Pattern] = import ucs.Desugarer.{Ctor, unapply}, Keyword.*, Pattern.*, InvalidReason.* - import ucs.Translator.isInvalidStringBounds, ucs.HelperExtractors.to + import ucs.Translator.isInvalidStringBounds, ucs.extractors.to given TraceLogger = tl /** Elaborate arrow patterns like `p => t`. Meanwhile, report all invalid * variables we found in `p`. */ diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala index dd1dc86387..d6dcddfeb2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala @@ -97,26 +97,44 @@ object Split: * When you want to amend the title of lines, you should use this function. */ def #:(lines: Lines): Lines = lines match - case (0, line) :: lines if lines.forall(_._1 > 0) => (0, s"$prefix $line") :: lines + 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: Split): Str = showSplit("if", s) private def showSplit(prefix: Str, s: Split): Str = + /** 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 + */ 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, (if isTopLevel then "" else "and ") + line) :: tail + 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(term) => - (if isFirst then (0, s"then ${term.showDbg}") else (0, s"else ${term.showDbg}")) :: Nil + case Split.Else(t) => + // (if isFirst then (0, s"then ${t.showDbg}") else (0, s"else ${t.showDbg}")) :: Nil + (if isFirst && !isTopLevel then "" else "else") #: term(t) case Split.End => Nil + 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)) => + s"$sym = if" #: split(splt, true, true) + case stmt => (0, stmt.showDbg) :: Nil + .toList + case t: Statement => (0, t.showDbg) :: Nil def branch(b: Branch, isTopLevel: Bool): Lines = - val Branch(scrutinee, pattern, continuation) = b - val rest = split(continuation, true, isTopLevel) - (s"${scrutinee.sym} is ${pattern.showDbg}" + - (if rest.length > 1 then " and" else s"")) #: rest + val Branch(scrutinee, pattern, consequent) = b + val lines = split(consequent, true, false) + val prefix = s"${scrutinee.sym} is ${pattern.showDbg}" + consequent match + case Split.Else(_) => (prefix + " then") #: lines + case _ => (prefix + " and") #: lines val lines = split(s, true, true) (if prefix.isEmpty then lines else prefix #: lines).toIndentedString + + end display diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 55ae1945f5..f95c23c54c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -225,6 +225,7 @@ case class ErrorSymbol(val nme: Str, tree: Tree)(using State) extends MemberSymb sealed trait ClassLikeSymbol extends Symbol: self: MemberSymbol[? <: ClassDef | ModuleDef] => + val id: Tree.Ident val tree: Tree.TypeDef def subst(using sub: SymbolSubst): ClassLikeSymbol @@ -272,16 +273,7 @@ class PatternSymbol(val id: Tree.Ident, val params: Opt[Tree.Tup], val body: Tre def nme = id.name def toLoc: Option[Loc] = id.toLoc // TODO track source tree of pattern here override def toString: Str = s"pattern:${id.name}" - /** The desugared nameless split. */ - private var _split: Opt[ucs.DeBrujinSplit] = N - def split_=(split: ucs.DeBrujinSplit): Unit = _split = S(split) - def split: ucs.DeBrujinSplit = _split.getOrElse: - lastWords(s"found unelaborated pattern: $nme") - /** The list of pattern parameters, for example, - * `T` in `pattern Nullable(pattern T) = null | T`. - */ - var patternParams: Ls[Param] = Nil - var extractionParams: Ls[Param] = Nil + override def subst(using sub: SymbolSubst): PatternSymbol = sub.mapPatSym(this) class TopLevelSymbol(blockNme: Str)(using State) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 30d6773331..1d9e8d78da 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -304,7 +304,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: 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 Lam(params, body) => s"λ${params.paramSyms.map(_.id).mkString(", ")}. ${body.showDbg}" + case Lam(params, body) => s"λ${params.paramSyms.map(_.id.name).mkString(", ")}. ${body.showDbg}" case Blk(stats, res) => (stats.map(_.showDbg + "; ") :+ (res match { case Lit(Tree.UnitLit(false)) => "" case x => x.showDbg + " " })) .mkString("( ", "", ")") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala deleted file mode 100644 index c2a84a2fcc..0000000000 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DeBrujinSplit.scala +++ /dev/null @@ -1,562 +0,0 @@ -package hkmc2 -package semantics -package ucs - -import mlscript.utils.*, shorthands.* -import Elaborator.{Ctx, ctx, State} -import scala.annotation.tailrec -import collection.immutable.SortedMap -import utils.{TL, tl}, Message.MessageContext - -object DeBrujinSplit: - final val Outermost = 1 - - def elaborate(patternParams: List[Param], - tree: syntax.Tree, - elaborator: Elaborator) - (using Elaborator.Ctx, - Elaborator.State, - Raise): DeBrujinSplit = - import elaborator.tl.*, syntax.Tree, Tree.*, PatternStub.*, HelperExtractors.*, Desugarer.unapply, syntax.Keyword.{as, `=>`, `:`} - type F = (Int, => DeBrujinSplit, => DeBrujinSplit) => DeBrujinSplit - /** Resolve the constructor in the elaborator context. */ - def resolve(ctor: Ident | Sel, params: Ls[Tree]): Opt[F] = - val term = scoped("ucs:mute"): - elaborator.cls(elaborator.term(ctor), inAppPrefix = false) - term.symbol.flatMap(_.asClsLike).map: - case symbol: (ClassSymbol | ModuleSymbol) => - val pattern = ClassLike(ConstructorLike.Symbol(symbol)) - val paramCount = params.length - if pattern.arity == paramCount || paramCount == 0 then - (scrutinee, innermost, alternative) => trace( - pre = s"cls ${pattern.showDbg} <<< $paramCount param(s)", - post = (s: DeBrujinSplit) => s"cls ${pattern.showDbg} >>>\n${s.showDbg}" - ): - val consequence = params.zipWithIndex.foldRight(innermost.increment(paramCount)): - case ((tree, index), inner) => trace( - pre = s"param $index <<<", - post = (s: DeBrujinSplit) => s"param $index >>>\n${s.showDbg}" - ): - go(tree)(paramCount - index, inner, Reject) - Branch(scrutinee, pattern, consequence.bind(paramCount), alternative) - else (_, _, alternative) => - error( - msg"The class `${symbol.nme}` expected ${pattern.arity.toString} arguments." -> symbol.toLoc, - msg"But only ${paramCount.toString} sub-pattern${if paramCount == 1 then " is" else "s are"} given." -> - params.foldLeft[Opt[Loc]](N): - (acc, tree) => acc.fold(tree.toLoc)(_ ++ tree.toLoc |> S.apply)) - alternative - case symbol: PatternSymbol => - val arguments = params.map: param => - (Binder(go(param)(Outermost, Accept(0), Reject)), param) - val pattern = ClassLike(ConstructorLike.Instantiation(symbol, arguments)) - val expectedArity = symbol.patternParams.size - val actualArity = arguments.length - if expectedArity == actualArity then - (scrutinee, innermost, alternative) => trace( - pre = s"pattern ${pattern.showDbg} <<< $actualArity param(s)", - post = (s: DeBrujinSplit) => s"pattern ${pattern.showDbg} >>>\n${s.showDbg}" - ): - val arity = 0 // TODO: reserved for captures - val consequence = innermost.increment(arity) - Branch(scrutinee, pattern, consequence.bind(arity), alternative) - else (_, _, alternative) => - error( - msg"The pattern `${symbol.nme}` expected ${"pattern argument".pluralize(expectedArity, true)}." -> symbol.toLoc, - msg"But ${"argument".pluralize(actualArity, true)} ${if actualArity == 1 then "is" else "are"} given." -> - params.foldLeft[Opt[Loc]](N): - (acc, tree) => acc.fold(tree.toLoc)(_ ++ tree.toLoc |> S.apply)) - alternative - /** Resolve the constructor name in `patternParams` first. Call `resolve` if - * not found. - */ - def dealWithCtor(ctor: Ident | Sel, params: Ls[Tree]): F = ctor match - case Ident(ctorName) => patternParams.find(_.sym.name == ctorName) match - case S(Param(sym = symbol)) => (scrutinee, innermost, alternative) => - log(s"found an input pattern: ${symbol.name}") - val arity = 0 // TODO: fill in the arity - Branch(scrutinee, ClassLike(ConstructorLike.Parameter(symbol)), innermost.increment(arity), alternative) - case N => resolve(ctor, params).getOrElse: (_, _, alternative) => - error(msg"Name not found: ${ctorName}" -> ctor.toLoc) - alternative - case ctor: Sel => resolve(ctor, params).getOrElse: (_, _, alternative) => - error(msg"Name not found: ${ctor.showDbg}" -> ctor.toLoc) - alternative - def go(tree: Tree): F = tree.deparenthesized match - // BEGIN OF TEMPORARY ALLOWANCE - // This allows the pattern `p => t`. - case _ `=>` _ => (_, _, alternative) => alternative - // This allows the pattern `p as id`. - case _ as _ => (_, _, alternative) => alternative - // This allows the pattern `a: p1`. - case _ `:` _ => (_, _, alternative) => alternative - // This allows the pattern `~p`. - case App(Ident("~"), Tup(p :: Nil)) => (_, _, alternative) => alternative - // This allows the pattern `(a: p1, b: p2, ...pn)`. - case Block(_) => (_, _, alternative) => alternative - // This allows the pattern `[p1, p2, ...pn]`. - case Tup(_) => (_, _, alternative) => alternative - // END OF TEMPORARY ALLOWANCE - case lhs or rhs => (scrutinee, consequence, alternative) => trace( - pre = s"or <<<", - post = (_: DeBrujinSplit) => s"or >>>" - ): - val buildLeft = go(lhs) - val buildRight = go(rhs) - val latter = buildRight(scrutinee, consequence, alternative) - buildLeft(scrutinee, consequence, latter) - case lhs ~ rhs => (scrutinee, innermost, alternative) => - Branch(scrutinee, ClassLike(ConstructorLike.StringJoin), innermost.increment(2), alternative) - alternative - case Under() => (_, consequence, _) => consequence - case ctor: (Ident | Sel) => dealWithCtor(ctor, Nil) - case App(Ident("-"), Tup(IntLit(n) :: Nil)) => - Branch(_, Literal(IntLit(-n)), _, _) - case App(Ident("-"), Tup(DecLit(n) :: Nil)) => - Branch(_, Literal(DecLit(-n)), _, _) - // BEGIN TODO: Support range patterns. This is just to suppress the errors. - case (lo: StrLit) to (incl, hi: StrLit) => - (_, _, alternative) => alternative - case (lo: IntLit) to (incl, hi: IntLit) => - (_, _, alternative) => alternative - case (lo: DecLit) to (incl, hi: DecLit) => - (_, _, alternative) => alternative - case (lo: syntax.Literal) to (_, hi: syntax.Literal) => - (_, _, alternative) => alternative - // END TODO: Support range patterns - case App(ctor: (Ident | Sel), Tup(params)) => dealWithCtor(ctor, params) - case OpApp(lhs, op: Ident, rhs :: Nil) => dealWithCtor(op, Ls(lhs, rhs)) - case literal: syntax.Literal => Branch(_, Literal(literal), _, _) - case Tree.TypeDef(syntax.Pat, body, N, N) => go(body) - scoped("ucs:rp:elaborate"): - log(s"tree: ${tree.showDbg}") - Binder(go(tree)(Outermost, Accept(0), Reject)) -end DeBrujinSplit - -import DeBrujinSplit.{Outermost} - -enum DeBrujinSplit: - case Binder(body: DeBrujinSplit) - case Branch(scrutinee: Int, - pattern: PatternStub, - consequent: DeBrujinSplit, - alternative: DeBrujinSplit) - case Accept(outcome: Int) - case Reject - - /** Collect all the patterns matched by the first scrutinee, if any. */ - lazy val firstPatterns: Set[PatternStub] = - def go(split: DeBrujinSplit, target: Int): Set[PatternStub] = - split match - case Binder(body) => go(body, target + 1) - case Branch(scrutinee, pattern, consequent, alternative) => - go(consequent, target) ++ go(alternative, target) ++ - (if scrutinee == target then Set(pattern) else Set()) - case Accept(_) | Reject => Set() - this match - case Binder(body) => go(body, Outermost) - case _ => Set() - - def showDbg: Str = - def go(split: DeBrujinSplit): Str = split match - case Binder(body) => - val bod = go(body) - val shouldIndent = body match - case Binder(_) => false - case _ => bod contains '\n' - "λ " + (if shouldIndent then "\n" + bod.indent(" ") else bod) - case Branch(scrutinee, pattern, consequence, alternative) => - val con = go(consequence) - val alt = go(alternative) - val shouldIndent = consequence match - case Binder(_) => false - case _ => con.contains('\n') - val pat = pattern match - case PatternStub.ClassLike(ConstructorLike.Nested(split)) => - s"split: ${split.showDbg}\n" - case _ => pattern.showDbg + " " - s"$scrutinee is $pat-> " + - (if shouldIndent then "\n" + con.indent(" ") else con) + - (if alt == "reject" then "" else s"\n$alt") - case Accept(outcome) => s"accept $outcome" - case Reject => "reject" - go(this) - - def display: Str = - val freshName = for - size <- (1 to Int.MaxValue).iterator - chars <- ('a' to 'z').combinations(size) - yield chars.mkString - def go(split: DeBrujinSplit, ctx: SortedMap[Int, Str]): Str = split match - case Binder(body) => - val name = freshName.next - val bod = go(body, ctx.mapKeys(_ + 1).toSortedMap + (Outermost -> name)) - val shouldIndent = body match - case Binder(_) => false - case _ => true - name + " => " + (if shouldIndent then "\n" + bod.indent(" ") else bod) - case Branch(scrutinee, pattern, consequence, alternative) => - val con = go(consequence, ctx) - val alt = go(alternative, ctx) - val shouldIndent = consequence match - case Binder(_) => false - case _ => con.contains('\n') - s"${ctx.getOrElse(scrutinee, "?")} is ${pattern.showDbg} -> " + - (if shouldIndent then "\n" + con.indent(" ") else con) + - (if alt == "reject" then "" else s"\n$alt") - case Accept(outcome) => s"accept $outcome" - case Reject => "reject" - go(this, SortedMap()) -end DeBrujinSplit - -import DeBrujinSplit.{Binder, Branch, Accept, Reject} - -case class Subst(entries: SortedMap[Int, Int], offset: Int): - def get(key: Int): Option[Int] = entries.get(key - offset).map(_ + offset) - infix def +(level: Int) = copy(offset = offset + level) - -object Subst: - def from(entries: IterableOnce[(Int, Int)]): Subst = Subst(SortedMap.from(entries), 0) - def apply(entries: (Int, Int)*): Subst = from(entries) - -extension (range: Range) - infix def +(shift: Int): Range = - Range(range.start + shift, range.end + shift, range.step) - - // extension (branch: DeBrujinSplit.Branch) - /** Expand all branches that match the same scrutinee against synonyms. */ - // def expandAll(using tl: TL): DeBrujinSplit = - // import DeBrujinSplit.*, PatternStub.* - // def go(split: DeBrujinSplit, scrutinee: Int): DeBrujinSplit = - // split match - // case Binder(body) => Binder(go(body, scrutinee + 1)) - // case Branch(`scrutinee`, ClassLike(symbol: PatternSymbol), consequence, alternative) => - // val consequence2 = go(consequence, scrutinee) - // val alternative2 = go(alternative, scrutinee) - // symbol.split.expand(scrutinee :: Nil, consequence2) ++ alternative2 - // case split @ Branch(_, _, consequence, alternative) => - // split.copy(consequent = go(consequence, scrutinee), - // alternative = go(alternative, scrutinee)) - // case Accept(_) | Reject => split - // tl.traceSplit(s"expandAll (${branch.scrutinee})", branch): - // go(branch, branch.scrutinee) - -extension (split: DeBrujinSplit) - def traceChange(prefix: => Str)(thunk: => DeBrujinSplit)(using TL) = - tl.trace( - pre = s"$prefix <<<" + { - val dbg = split.showDbg - if dbg.contains('\n') then s"\n$dbg" else s" $dbg"}, - post = (output: DeBrujinSplit) => s"$prefix >>>" + - (if output == split then " (no change)" else s"\n${output.showDbg}") - )(thunk) - - def ++(right: DeBrujinSplit): DeBrujinSplit = - split match - case Binder(body) => ??? // TODO: concatenating binders is ridiculous - case Branch(scrutinee, pattern, consequence, alternative) => - Branch(scrutinee, pattern, consequence, alternative ++ right) - case Accept(outcome) => split - case Reject => right - - /** Count the outermost binders. */ - def arity: Int = - @tailrec def go(split: DeBrujinSplit, n: Int): Int = split match - case Binder(body) => go(body, n + 1) - case _ => n - go(split, 0) - - def freeScrutinees: Set[Int] = - def go(acc: Set[Int], split: DeBrujinSplit, binderCount: Int): Set[Int] = split match - case Binder(body) => go(acc, body, binderCount + 1) - case Branch(scrutinee, _, consequence, alternative) => - val init = if scrutinee > binderCount then acc + scrutinee else acc - go(go(init, consequence, binderCount), alternative, binderCount) - case Accept(_) | Reject => acc - go(Set(), split, 0) - - def unbind: (Int, DeBrujinSplit) = - @tailrec - def go(level: Int, split: DeBrujinSplit): (Int, DeBrujinSplit) = split match - case Binder(body) => go(level + 1, body) - case _ => (level, split) - go(0, split) - - def bind(level: Int): DeBrujinSplit = - (0 until level).foldRight(split)((_, body) => Binder(body)) - - def increment(level: Int): DeBrujinSplit = - def go(split: DeBrujinSplit, binderCount: Int): DeBrujinSplit = split match - case Binder(body) => Binder(go(body, binderCount + 1)) - case split @ Branch(scrutinee, _, consequence, alternative) => - split.copy(scrutinee = if scrutinee > binderCount then scrutinee + level else scrutinee, - consequent = go(consequence, binderCount), - alternative = go(alternative, binderCount)) - case Accept(_) | Reject => split - go(split, 0) - - def decrement(level: Int): DeBrujinSplit = increment(-level) - - def substitute(subst: Subst): DeBrujinSplit = - def go(split: DeBrujinSplit)(using subst: Subst): DeBrujinSplit = split match - case Binder(body) => Binder(go(body)(using subst + 1)) - case Branch(scrutinee, pattern, consequence, alternative) => - val newScrutinee = subst.get(scrutinee).getOrElse(scrutinee) - Branch(newScrutinee, pattern, go(consequence), go(alternative)) - case Accept(outcome) => Accept(outcome) - case Reject => Reject - go(split)(using subst) - - /** Apply a split to variables and replace all accepts with another split. */ - def expand(scrutinees: List[Int], consequence: DeBrujinSplit)(using tl: TL): DeBrujinSplit = - import tl.* - def go(split: DeBrujinSplit, binderCount: Int, subst: Map[Int, Int]): DeBrujinSplit = split match - case Binder(body) => Binder(go(body, binderCount + 1, subst)) - case Branch(scrutinee, pattern, consequent, alternative) => - val newScrutinee = subst.get(scrutinee + binderCount) match - case S(outermostIndex) => outermostIndex + binderCount - case N => scrutinee - Branch(newScrutinee, pattern, - go(consequent, binderCount, subst), - go(alternative, binderCount, subst)) - case Accept(_) => - // We assume there's only one outcome because it's in pattern synonyms. - consequence.increment(binderCount) - case Reject => Reject - val arity = scrutinees.length - split.unbind match - case (`arity`, body) => - go(body, 0, (1 to arity).zip(scrutinees).toMap) - case _ => - // lastWords(s"Arity mismatch in expand: ${split.unbind}") - Reject // TODO: report mismatched arity - - /** To perform a reverse lookup for a term that references a symbol in the current context. */ - def reference(target: ClassSymbol | ModuleSymbol)(using Ctx, State): Opt[Term] = - def go(ctx: Ctx): Opt[Term] = - ctx.env.values.collectFirst: - case elem if elem.symbol.flatMap(_.asClsLike).contains(target) => - elem.ref(target.id) match - case ref: Term.Ref => ref.withIArgs(Nil) - case other => other - .orElse(ctx.parent.flatMap(go)) - go(ctx).map: term => - // If the `target` is a virtual class, then do not select `class`. - target match - case s: ClassSymbol if ctx.builtins.virtualClasses contains s => term - case _ => - Term.SynthSel(term, syntax.Tree.Ident("class"))(S(target)).withIArgs(Nil) - - def toSplit(scrutinees: Vector[() => Term.Ref], - localPatterns: Map[Int, TempSymbol], - outcomes: Map[Opt[Int], Split] - )(using tl: TL)(using Ctx, State, Raise): Split = tl.trace( - pre = s"toSplit <<<\n${split.showDbg}", - post = (s: Split) => s"toSplit >>>\n${Split.display(s)}" - ): - import DeBrujinSplit.*, PatternStub.*, syntax.Tree.Empty, tl.* - scoped("ucs:rp:split"): - val desugaring = new DesugaringBase {} - def go(split: DeBrujinSplit, ctx: Vector[() => Term.Ref]): Split = - split match - case Binder(body) => go(body, ctx) - case Branch(scrutinee, pattern, consequence, alternative) => - log(s"pattern is ${pattern.showDbg}") - lazy val nullaryConsequent = consequence.unbind match - case (0, body) => go(body, ctx) - pattern match - case Literal(value) => - semantics.Branch(ctx(scrutinee - 1)(), FlatPattern.Lit(value)(Nil), nullaryConsequent) ~: go(alternative, ctx) - case ClassLike(ConstructorLike.Symbol(symbol: ClassSymbol)) => - log(s"make temporary symbols for $symbol") - val subSymbols = (1 to symbol.arity).map(i => TempSymbol(N, s"arg_$i")).toList - log(s"class case ${symbol.name} with ${subSymbols.length} sub-scrutinees") - val consequent2 = consequence.unbind match - case (level, body) => // TODO: check level == arity - val ctx2 = subSymbols.reverseIterator.map(s => () => s.ref().withIArgs(Nil)).toVector ++ ctx - log(s"now there are ${ctx2.length} elements in the new context") - go(body, ctx2) - val select = scoped("ucs:sel"): - reference(symbol).getOrElse(Term.Error) - // Here we add a speical case as a workaround: - // If the class is virtual, then we don't make arguments empty. - val arguments = if Elaborator.ctx.builtins.virtualClasses contains symbol then N else S(subSymbols) - val pattern = FlatPattern.ClassLike(select, arguments)(Nil) - semantics.Branch(ctx(scrutinee - 1)(), pattern, consequent2) ~: go(alternative, ctx) - case ClassLike(ConstructorLike.Symbol(symbol: ModuleSymbol)) => - val select = scoped("ucs:sel"): - reference(symbol).getOrElse(Term.Error) - val pattern = FlatPattern.ClassLike(select, N)(Nil) - semantics.Branch(ctx(scrutinee - 1)(), pattern, nullaryConsequent) ~: go(alternative, ctx) - case ClassLike(ConstructorLike.LocalPattern(id)) => - log(s"apply scrutinee $scrutinee to local pattern $id") - desugaring.makeLocalPatternBranch(ctx(scrutinee - 1)(), localPatterns(id), nullaryConsequent)(go(alternative, ctx)) - case ClassLike(ConstructorLike.Nested(split)) => // The arity of embedded splits is always 1. - val innerConsequent = consequence.unbind match - case (0, body) => go(body, ctx) - val nestedOutcomes = Map(N -> Split.End, S(0) -> innerConsequent) - split.toSplit(Vector(ctx(scrutinee - 1)), localPatterns, nestedOutcomes) ~~: go(alternative, ctx) - case Accept(outcome) => outcomes(S(outcome)) - case Reject => outcomes.getOrElse(N, Split.End) - split.unbind match - case (arity, body) if arity == scrutinees.length => - go(split, scrutinees) - case _ => - error(msg"Mismatched arity in split" -> N) - Split.End // TODO: report mismatched arity - - /** To instantiate the body of a pattern synonym. */ - def instantiate(context: Map[VarSymbol, DeBrujinSplit])(using tl: TL): DeBrujinSplit = - import DeBrujinSplit.*, PatternStub.*, ConstructorLike.*, tl.* - def go(split: DeBrujinSplit): DeBrujinSplit = split.traceChange("instantiate"): - split match - case Binder(body) => Binder(go(body)) - case Branch(scrutinee, pattern0 @ ClassLike(Instantiation(symbol, arguments)), consequence, alternative) => - val instantiatedArguments = arguments.map: - case (split, tree) => (go(split), tree) - val pattern = ClassLike(Instantiation(symbol, instantiatedArguments)) - Branch(scrutinee, pattern, go(consequence), go(alternative)) - case Branch(scrutinee, ClassLike(Parameter(symbol)), consequence, alternative) => - context.get(symbol) match - case S(split) => - split.expand(Ls(scrutinee), go(consequence)) ++ go(alternative) - case N => lastWords(s"Pattern parameter not found: ${symbol.nme}") - case Branch(scrutinee, pattern, consequence, alternative) => - Branch(scrutinee, pattern, go(consequence), go(alternative)) - case Accept(outcome) => Accept(outcome) - case Reject => Reject - scoped("ucs:rp:instantiation"): - log(s"pattern parameter context:") - context.foreach: - case (symbol, split) => log(s"${symbol.nme} => ${split.showDbg}") - go(split) - - def normalize(using tl: TL, raise: Raise): (DeBrujinSplit, Map[Int, DeBrujinSplit]) = - import DeBrujinSplit.*, PatternStub.*, ConstructorLike.*, collection.mutable.{Buffer, Map as MutMap}, tl.* - type Expansion = (id: Int, repr: Str, recursive: Bool, normalized: Opt[DeBrujinSplit]) - val expandedPatterns = MutMap.empty[Instantiation, Expansion] - def go(split: DeBrujinSplit): DeBrujinSplit = - scoped("ucs:rp:normalize"): - split match - case Binder(body) => Binder(go(body)) - case Branch(scrutinee, ClassLike(key @ Instantiation(symbol, arguments)), consequent, alternative) => trace( - pre = s"pattern application <<< ${symbol.nme}", - post = (s: DeBrujinSplit) => s"pattern application >>>\n${s.showDbg}" - ): - if arguments.size == symbol.patternParams.size then - val pattern = expandedPatterns.get(key) match - case S((id, repr, recursive, S(normalized))) => - ClassLike(if recursive then LocalPattern(id) else Nested(normalized)) - case S((id, repr, recursive, N)) => - if !recursive then - scoped("ucs:rp:memo"): - log(s"found a recursive pattern: ${symbol.nme}") - expandedPatterns += key -> (id, repr, true, N) - ClassLike(LocalPattern(id)) - case N => - val repr = symbol.nme + arguments.iterator.map(_.tree.showDbg).mkString("(", ", ", ")") - scoped("ucs:rp:memo"): - log(s"add to memo: ${repr}") - expandedPatterns += key -> (expandedPatterns.size, repr, false, N) - val normalized = - val context = symbol.patternParams.iterator.map(_.sym).zip(arguments.iterator.map(_.split)).toMap - val instantiated = symbol.split.instantiate(context) - scoped("ucs:rp:memo"): - log(s"instantiated: ${instantiated.showDbg}") - go(instantiated) - expandedPatterns.get(key) match - case S((id, repr, recursive, N)) => - expandedPatterns += key -> (id, repr, recursive, S(normalized)) - ClassLike(if recursive then LocalPattern(id) else Nested(normalized)) - case S((id, _, _, S(_))) => lastWords(s"the pattern should not be normalized: ${symbol.nme}") - case N => lastWords(s"the pattern should be memoized: ${symbol.nme}") - Branch(scrutinee, pattern, go(consequent), go(alternative)) - else - // TODO: maybe this check is unnecessary as it has been checked in the elaboration. - error( - msg"Pattern `${symbol.nme}` expects ${"pattern argument".pluralize(symbol.patternParams.size, true)}" -> - symbol.patternParams.foldLeft[Opt[Loc]](N): - case (N, param) => param.sym.toLoc - case (S(loc), param) => S(loc ++ param.sym.toLoc), - msg"But ${"pattern argument".pluralize(arguments.size, true)} were given" -> N) - Reject - case split @ Branch(scrutinee, pattern, consequence, alternative) => trace( - pre = s"normalize <<<\n${split.showDbg}", - post = (s: DeBrujinSplit) => s"normalize >>>\n${s.showDbg}" - ): - scoped("ucs:rp:normalize"): - val arity = pattern.arity - val whenTrue = consequence.unbind match - case (level @ (`arity` | 0), consequentBody) => - // The scrutinee handling below is tricky. - val former = trace(s"[Step 1] specialize consequence"): - consequentBody.specialize(scrutinee + level, pattern, 1 to arity) - // We need to increment the level because it is going to be put into a binder. - val latter = trace(s"[Step 2] specialize alternative"): - alternative.specialize(scrutinee, pattern, 1 to arity) - val together = former ++ latter - log(s"[Step 3] concatenate specialized splits\n${together.showDbg}") - val res = trace(s"[Step 4] normalize concatenated splits"): - go(together) - // If the original consequence doesn't have binders, we need to increment the level. - val res2 = if level == arity then res else res.increment(arity) - // log(s"bind with $arity") - res2.bind(arity) - case (_, _) => lastWords("unexpected number of binders") - val whenFalse = - log(s"[Step 5] despecialize alternative") - val despecialized = alternative.despecialize(scrutinee, pattern) - go(despecialized) - split.copy(consequent = whenTrue, alternative = whenFalse) - case Accept(_) | Reject => split - end go - val rootSplit = go(split) - val recursivePatterns = expandedPatterns.iterator.collect: - // Maybe we can store more information about the instantiation. - case (pattern, (id, _, true, S(normalized))) => (id, normalized) - .toMap - scoped("ucs:rp:memo"): - log("memo:") - expandedPatterns.values.foreach: - case (id, repr, true, S(normalized)) => log(s"$id $repr =>\n${normalized.display}") - case _ => () - (rootSplit, recursivePatterns) - - def specialize(scrutinee: Int, pattern: PatternStub, parameters: Range)(using tl: TL): DeBrujinSplit = - import PatternStub.*, ConstructorLike.*, tl.* - require(parameters.length == pattern.arity) - def go(split: DeBrujinSplit)(using target: Int, parameters: Range): DeBrujinSplit = - split match - case Binder(body) => - log("go into the binder") - Binder(go(body)(using target + 1, parameters + 1)) - case Branch(`target`, `pattern`, consequence, alternative) => - val (level, body) = consequence.unbind - log(s"consequence:\n${consequence.showDbg}") - log(s"unbound consequence:\n${body.showDbg}") - val substedConsequent = if level == 0 then body else body.substitute(Subst.from((1 to level).zip(parameters))) - if level == 0 || level == pattern.arity then - body ++ go(alternative)(using target, parameters) - else - lastWords("unexpected number of binders") - case Branch(`target`, pattern, consequence, alternative) => - log(s"found ${pattern.showDbg}: remove the consequent") - go(alternative) - case split @ Branch(_, _, consequence, alternative) => - split.copy(consequent = go(consequence), - alternative = go(alternative)) - case Accept(_) | Reject => split - split.traceChange(s"S+ $scrutinee is ${pattern.showDbg}#${pattern.arity}${parameters.mkString("(", ", ", ")")}"): - go(split)(using scrutinee, parameters) - - def despecialize(scrutinee: Int, pattern: PatternStub)(using TL): DeBrujinSplit = - def go(split: DeBrujinSplit)(using target: Int): DeBrujinSplit = - split match - case Binder(body) => Binder(go(body)(using target + 1)) - case Branch(`target`, `pattern`, _, alternative) => go(alternative) - case split @ Branch(_, _, consequent, alternative) => - split.copy(consequent = go(consequent), - alternative = go(alternative)) - case Accept(_) | Reject => split - split.traceChange(s"S- $scrutinee is ${pattern.showDbg}#${pattern.arity}"): - go(split)(using scrutinee) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala index bc0b5b81fb..88622584de 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala @@ -453,13 +453,6 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten val scrutinees = scrutSymbol.getSubScrutinees(args.size) val matches = scrutinees.iterator.zip(args).map: case (symbol, tree) => - // BEGIN OBSOLETE CODE - val argument = tree match - case TypeDef(syntax.Pat, body, N, N) => S(DeBrujinSplit.elaborate(Nil, body, elaborator)) - case td @ TypeDef(k = syntax.Pat) => - error(msg"Ill-formed pattern argument" -> td.toLoc); N - case _ => N - // END OBSOLETE CODE // 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 @@ -467,11 +460,12 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten val pattern = tree match case TypeDef(syntax.Pat, body, N, N) => val pattern = elaborator.pattern(body) - S(new Translator(elaborator).translateAnonymousPattern(Nil, Nil, pattern)) + val term = new Translator(elaborator).translateAnonymousPattern(Nil, Nil, pattern) + S((pattern, term)) case td @ TypeDef(k = syntax.Pat) => error(msg"Ill-formed pattern argument" -> td.toLoc); N case _ => N - Argument(symbol, tree, argument, pattern) + Argument(symbol, tree, pattern) .toList Branch( ref, @@ -539,7 +533,7 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten ref, FlatPattern.Tuple(lead.length + rest.fold(0)(_._2.length), rest.isDefined)(output), // The outermost is a tuple, so pattern arguments are not possible. - wrap(subMatches(matches.map { case (s, t) => Argument(s, t, N, N) }, sequel)(Split.End)(ctx)) + wrap(subMatches(matches.map { case (s, t) => Argument(s, t, N) }, sequel)(Split.End)(ctx)) ) ~: fallback // Negative numeric literals case App(Ident("-"), Tup(IntLit(value) :: Nil)) => fallback => ctx => @@ -635,9 +629,9 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten post = (r: Split) => s"subMatches >>> ${r.showDbg}" ): sequel(ctx) - case Argument(_, Under(), _, _) :: rest => subMatches(rest, sequel) // Skip wildcards - case Argument(_, _, S(_), _) :: rest => subMatches(rest, sequel) // Skip pattern arguments - case Argument(scrutinee, tree, _, _) :: rest => fallback => trace( + case Argument(_, Under(), _) :: rest => subMatches(rest, sequel) // Skip wildcards + case Argument(_, _, S(_)) :: rest => subMatches(rest, sequel) // Skip pattern arguments + case Argument(scrutinee, tree, _) :: rest => fallback => trace( pre = s"subMatches (nested) <<< $scrutinee is $tree", post = (r: Sequel) => s"subMatches (nested) >>>" ): diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala index 09f275bb71..5e5d93847d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala @@ -3,7 +3,7 @@ package semantics package ucs import mlscript.utils.*, shorthands.* -import syntax.Tree.*, Elaborator.{Ctx, ctx}, Elaborator.State +import syntax.Tree.*, Elaborator.{Ctx, State, ctx} /** Contains some helpers that makes UCS desugaring easier. */ trait DesugaringBase(using Ctx, State): @@ -15,11 +15,19 @@ trait DesugaringBase(using Ctx, State): protected final def sel(p: Term, k: Str, s: FieldSymbol): Term.SynthSel = sel(p, Ident(k): Ident, s) protected final def int(i: Int) = Term.Lit(IntLit(BigInt(i))) protected final def str(s: Str) = Term.Lit(StrLit(s)) + protected final def `null` = Term.Lit(UnitLit(true)) protected final def fld(t: Term) = Fld(FldFlags.empty, t, N) protected final def tup(xs: Fld*): Term.Tup = Term.Tup(xs.toList)(Tup(Nil)) protected final def app(l: Term, r: Term, label: Str): Term.App = app(l, r, FlowSymbol(label)) protected final def app(l: Term, r: Term, s: FlowSymbol): Term.App = (Term.App(l, r)(App(Dummy, Dummy), N, s): Term.App).withIArgs(Nil) + protected final def rcd(fields: RcdField*): Term.Rcd = Term.Rcd(fields.toList) + + protected final def splitLet(sym: BlockLocalSymbol, term: Term)(inner: Split): Split = + Split.Let(sym, term, inner) + + protected final def param = Param(FldFlags.empty, _, N, Modulefulness.none) + protected final def paramList(params: Param*) = PlainParamList(params.toList) private lazy val runtimeRef: Term.Ref = State.runtimeSymbol.ref().withIArgs(Nil) @@ -82,11 +90,17 @@ trait DesugaringBase(using Ctx, State): val s = TempSymbol(N, dbgName) Split.Let(s, cond, Branch(s.ref(), inner) ~: Split.End) - protected final def makeMatchResult(captures: Term) = - app(matchResultClass, tup(fld(captures)), "result of `MatchResult`") + protected final def makeMatchResult(output: Term) = + app(matchResultClass, tup(fld(output), fld(rcd())), "result of `MatchResult`") + + protected final def makeMatchResult(output: Term, bindings: Term) = + app(matchResultClass, tup(fld(output), fld(bindings)), "result of `MatchResult`") + + protected final def makeMatchResult(output: Term, fields: Ls[RcdField | RcdSpread]) = + app(matchResultClass, tup(fld(output), fld(Term.Rcd(fields))), "result of `MatchResult`") - protected final def makeMatchFailure = - app(matchFailureClass, tup(), "result of `MatchFailure`") + protected final def makeMatchFailure(errors: Term = Term.Lit(UnitLit(true))) = + app(matchFailureClass, tup(fld(errors)), "result of `MatchFailure`") /** Make a `Branch` that calls `Pattern` symbols' `unapply` functions. */ def makeLocalPatternBranch( @@ -144,9 +158,10 @@ trait DesugaringBase(using Ctx, State): tempLet("matchResult", call): resultSymbol => // let `matchResult` be the return value val argSym = TempSymbol(N, "arg") + val bindingsSymbol = TempSymbol(N, "bindings") // let `arg` be the first element of `matchResult` Branch( resultSymbol.ref().withIArgs(Nil), - matchResultPattern(S(argSym :: Nil)), + matchResultPattern(S(argSym :: bindingsSymbol :: Nil)), Split.Let(postfixSymbol, callTupleGet(argSym.ref().withIArgs(Nil), 0, "postfix"), inner) ) ~: fallback 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 045ed9e696..46ac7efe61 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -5,7 +5,6 @@ package ucs import mlscript.utils.*, shorthands.* import syntax.*, Tree.Ident import Elaborator.{Ctx, ctx, State} -import DeBrujinSplit.* import collection.mutable.Buffer import FlatPattern.* @@ -33,7 +32,7 @@ enum FlatPattern extends AutoLocated: def subTerms: Ls[Term] = this match case p: ClassLike => p.constructor :: (p.mode match case MatchMode.Default => p.arguments.fold(Nil): - _.iterator.flatMap(_.pattern).toList + _.iterator.flatMap(_.pattern.map(_.term)).toList case _: MatchMode.StringPrefix => Nil case MatchMode.Annotated(annotation) => annotation :: Nil) case _: (Lit | Tuple | Record) => Nil @@ -78,16 +77,15 @@ object FlatPattern: final case class Argument( scrutinee: BlockLocalSymbol, tree: Tree, - split: Opt[DeBrujinSplit], - pattern: Opt[Term.Rcd] + pattern: Opt[(pattern: Pattern, term: Term.Rcd)] ) extends Located: override def toLoc: Opt[Loc] = tree.toLoc object Argument: def apply(scrutinee: BlockLocalSymbol, tree: Tree): Argument = - Argument(scrutinee, tree, N, N) + Argument(scrutinee, tree, N) def apply(scrutinee: BlockLocalSymbol): Argument = - Argument(scrutinee, Tree.Dummy, N, N) + Argument(scrutinee, Tree.Dummy, N) /** A class-like pattern whose symbol is resolved to a class. */ object Class: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/HelperExtractors.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/HelperExtractors.scala deleted file mode 100644 index 5bb6e923b1..0000000000 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/HelperExtractors.scala +++ /dev/null @@ -1,34 +0,0 @@ -package hkmc2 -package semantics -package ucs - -import syntax.Tree, Tree.* -import mlscript.utils.*, shorthands.* - -/** A set of extractors to help elaborate tree. */ -object HelperExtractors: - /** A helper extractor for matching the tree of `x | y`. */ - object or: - infix def unapply(tree: Tree): Opt[(Tree, Tree)] = tree match - case OpApp(lhs, Ident("|"), rhs :: Nil) => S(lhs, rhs) - case _ => N - - /** 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. - */ - object to: - infix def unapply(tree: Tree): Opt[(Tree, (Bool, Tree))] = tree match - 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 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 0744f780c7..6b1d9f0222 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -8,6 +8,7 @@ import Message.MessageContext import Elaborator.{Ctx, State, ctx} import utils.* import FlatPattern.Argument +import ups.Instantiator class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBase: import Normalization.*, Mode.*, FlatPattern.MatchMode @@ -184,20 +185,20 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // 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, alternative) + normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, pattern.output, consequent, normalizeImpl(alternative)) case MatchMode.StringPrefix(prefix, postfix) => - normalizeStringPrefixPattern(scrutinee, pat, ctor, postfix, consequent, alternative) + normalizeStringPrefixPattern(scrutinee, pat, ctor, postfix, consequent, normalizeImpl(alternative)) case MatchMode.Annotated(annotation) => annotation.symbol match case S(symbol) if symbol === ctx.builtins.annotations.compile => - normalizeCompiledPattern(scrutinee, pat, ctor, argsOpt, mode, consequent, alternative) + 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, alternative) + 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, alternative) + normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, pattern.output, consequent, normalizeImpl(alternative)) case Split.Let(v, _, tail) if vs has v => log(s"LET: SKIP already declared scrutinee $v") normalizeImpl(tail) @@ -226,18 +227,19 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas case S(paramList) => argsOpt match case S(args) => // Check the number of parameters is correct. - val n = args.size.toString - val m = paramList.params.size.toString - if n != m then - val argsLoc = Loc(args) + 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 $n" -> argsLoc + msg"The constructor does not take any arguments but found ${ + "argument" countBy args.size}." -> loc else - msg"mismatched arity: expect $m, found $n" -> argsLoc + 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(_, Tree.Under(), _, _)) => true + case (_, Argument(_, Tree.Under(), _)) => true case (Param(flags, sym, _, _), arg) if !flags.value => 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, @@ -247,11 +249,11 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas case _ => true // If patterns are more than parameters, or one of parameters is // incessible, we cannot make the branch. - .foldLeft(n <= m)(_ && _) + .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".pluralize(args.size, true, false)}" -> Loc(args)) + 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 => @@ -262,7 +264,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas true case S(args) => error(msg"Class ${ctorSymbol.name} does not have a parameter list" -> ctorTerm.toLoc, - msg"but the pattern has ${"sub-pattern".pluralize(args.size, true, false)}" -> Loc(args)) + msg"but the pattern has ${"sub-pattern" countBy args.size}" -> Loc(args)) false case N => true @@ -312,12 +314,15 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // No need to destruct the result. Branch(resultSymbol.safeRef, matchResultPattern(N), consequent) ~: alternative else - val extractionSymbol = TempSymbol(N, "extraction") - Branch(resultSymbol.safeRef, matchResultPattern(S(extractionSymbol :: Nil)), - aliasOutputSymbols(extractionSymbol.safeRef, outputSymbols, consequent) + 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) + /** Normalize splits whose leading branch matches a pattern and does not have + * a `@compile` annotation. */ private def normalizeExtractorPattern( scrutinee: Term.Ref, patternSymbol: PatternSymbol, @@ -332,16 +337,16 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas log: allArgsOpt.fold(Iterator.empty[Str]): _.iterator.map: - case Argument(scrutinee, _, _, N) => s"extraction: ${scrutinee.nme}" - case Argument(scrutinee, _, _, S(pattern)) => s"pattern: ${scrutinee.nme} = ${pattern.showDbg}" + case Argument(scrutinee, _, N) => s"extraction: ${scrutinee.nme}" + case Argument(scrutinee, _, S((_, term))) => s"pattern: ${scrutinee.nme} = ${term.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(scrutinee, _, _, N) => Left(scrutinee) - case Argument(scrutinee, _, _, S(pattern)) => Right((scrutinee, pattern)) + case Argument(scrutinee, _, N) => Left(scrutinee) + case Argument(scrutinee, _, S(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.ref().withIArgs(Nil) |> fld) :+ fld(scrutinee) @@ -349,7 +354,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // Create a split that binds the pattern arguments. def bindPatternArguments(split: Split): Split = patternArguments.foldRight(split): - case ((sym, rcd), innerSplit) => Split.Let(sym, rcd, innerSplit) + case ((sym, (_, rcd)), innerSplit) => Split.Let(sym, rcd, innerSplit) val split = bindPatternArguments(tempLet("matchResult", unapplyCall): resultSymbol => extractionArgsOpt match case N => @@ -357,8 +362,9 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // No need to destruct the result. Branch(resultSymbol.safeRef, matchResultPattern(N), consequent) ~: alternative else - val extractionSymbol = TempSymbol(N, "extraction") - Branch(resultSymbol.safeRef, matchResultPattern(S(extractionSymbol :: Nil)), + 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) => @@ -368,6 +374,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // 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 @@ -376,7 +383,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // // 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 :: Nil)), + Branch(resultSymbol.safeRef, matchResultPattern(S(extractionSymbol :: bindingsSymbol :: Nil)), aliasOutputSymbols(extractionSymbol.safeRef, outputSymbols, makeTupleBranch(extractionSymbol.safeRef, extractionArgs, consequent, Split.End)) ) ~: alternative @@ -390,14 +397,16 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // 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 :: Nil)), + 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"mismatched arity: expect ${extractionParams.size}, found ${extractionArgs.size}" -> Loc(extractionArgs)) + 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)) @@ -419,95 +428,38 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas symbol: PatternSymbol, ctorTerm: Term, argsOpt: Opt[Ls[FlatPattern.Argument]], - mode: MatchMode, + outputSymbols: Ls[BlockLocalSymbol], consequent: Split, alternative: Split, )(using VarSet): Split = scoped("ucs:rp"): - log(s"SYNONYM: ${scrutinee.showDbg} is $symbol") - import DeBrujinSplit.*, PatternStub.* - // The reason why we comment the pattern arguments number check is that - // it has been checked during elaboration, as the old pattern compilation - // scheme still resolves symbols. The new pattern compilation scheme, which - // will be implemented in the near future, should not do this. - val arguments = argsOpt match - case S(args) => - val patternArgs = args.collect: - case Argument(_, pattern, S(split), _) => (split, pattern) - // if symbol.patternParams.size != patternArgs.size then error( - // msg"Pattern `${symbol.nme}` expects ${"pattern argument".pluralize(symbol.patternParams.size, true)}" -> - // Loc(symbol.patternParams.iterator.map(_.sym)), - // msg"But ${"pattern argument".pluralize(patternArgs.size, true)} were given" -> Loc(args.iterator.map(_.pattern))) - patternArgs - case N => - // if symbol.patternParams.size > 0 then error( - // msg"Pattern `${symbol.nme}` expects ${"pattern argument".pluralize(symbol.patternParams.size, true)}" -> - // Loc(symbol.patternParams.iterator.map(_.sym)), - // msg"But no arguments were given" -> ctorTerm.toLoc) - Nil - val mainSplit = Binder: - Branch( - scrutinee = Outermost, - // Here we run into a problem: during elaboration, we don't know whether - // a constructor will be resolved to a pattern symbol. So we don't know - // whether we should elaborate its arguments into terms or de Bruijn - // splits. What's worse, `Desugarer` treats pattern arguments as - // sub-patterns and expands them... For example: - // ``` - // pattern Nullable(pattern A) = null | A - // 0 is @compile Nullable(Int) - // ``` - // My solution is to add `pattern` before each pattern argument. - // ``` - // 0 is @compile Nullable(pattern Int) - // ``` - pattern = ClassLike(ConstructorLike.Instantiation(symbol, arguments)), - consequent = Accept(42), - alternative = Reject - ) - log(s"the initial split:\n${mainSplit.display}") - val (normalizedMainSplit, indexSplitMap) = scoped("ucs:rpn"): - mainSplit.normalize - log(s"the normalized main split:\n${normalizedMainSplit.display}") - // The entry in the local pattern map. - val indexSplitSymbolMap = indexSplitMap.map: - case (index, split) => (index, (split, TempSymbol(N, s"match$index"))) - val idSymbolMap = indexSplitSymbolMap.map(_ -> _._2) - val compiledMainSplit = normalizedMainSplit.toSplit( - scrutinees = Vector(() => scrutinee), - localPatterns = idSymbolMap, - outcomes = Map(S(42) -> consequent), - ) - log(s"the compiled main split:\n${Split.display(compiledMainSplit)}") - // Insert local pattern bindings before the split. - val compiled = indexSplitSymbolMap.foldRight(normalizeImpl(compiledMainSplit ++ alternative)): - case ((index, (split, symbol)), inner) => - val definition = - log(s"making definition for ${split.display}") - import syntax.{Fun, Keyword, ParamBind, Tree}, Tree.Ident - // The memorized splits may have free variables. We will count - // the number of free variables, bind them, and substitute them - // with the new indices. - val paramSymbols = (1 to split.arity).map: i => - VarSymbol(Ident(s"param$i")) - .toVector - val paramList = PlainParamList: - paramSymbols.iterator.map(Param(FldFlags.empty, _, N, Modulefulness.none)).toList - val success = Split.Else(makeMatchResult(Term.Tup(Nil)(Tree.Tup(Nil)))) - val failure = Split.Else(makeMatchFailure) - val bodySplit = scoped("ucs:rp:split"): - val bodySplit = split.toSplit( - scrutinees = paramSymbols.map(symbol => () => symbol.safeRef), - localPatterns = idSymbolMap, - outcomes = Map(S(0) -> success, N -> failure) - ) ++ Split.Else(makeMatchFailure) - log(s"the compiled local pattern $index:\n${Split.display(bodySplit)}") - bodySplit - val funcBody: Term = Term.IfLike(Keyword.`if`, bodySplit) - Term.Lam(paramList, funcBody) - Split.Let(symbol, definition, inner) - scoped("ucs:compiled"): - log(s"the compiled split:\n${compiledMainSplit.toString()}") - compiled + import ups.* + + // Instantiate the pattern and all patterns used in it. + val instantiator = new Instantiator + val patternArguments = argsOpt.fold(Nil)(_.collect: + case Argument(_, _, S((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`. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/PatternStub.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/PatternStub.scala deleted file mode 100644 index 5fffb2b1f1..0000000000 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/PatternStub.scala +++ /dev/null @@ -1,97 +0,0 @@ -package hkmc2 -package semantics -package ucs - -import collection.immutable.NumericRange -import mlscript.utils.*, shorthands.* -import syntax.Tree - -enum ConstructorLike: - case Symbol(symbol: ClassSymbol | ModuleSymbol) - /** "Virtual" constructor for string joining operator `~`. */ - case StringJoin - /** Describe the size of tuples. If `infinite` is `false`, it represents - * fixed-size tuples. Otherwise, it represents tuples with at least `size`. - */ - case TupleCapacity(size: Int, infinite: Bool) - case Instantiation(symbol: PatternSymbol, arguments: List[(split: DeBrujinSplit, tree: Tree)]) - case LocalPattern(id: Int) - /** This case represents pattern parameters, which only make sense within the - * body of the pattern declaration. - */ - case Parameter(symbol: VarSymbol) - /** - * This case represents a nested split, where the arity of the split must be 1 - * (there is exactly one `Binder` at the top level). The split must not have - * free variables. - */ - case Nested(split: DeBrujinSplit) - - lazy val arity: Int = this match - case Symbol(symbol: ClassSymbol) => symbol.arity - case Symbol(symbol: ModuleSymbol) => 0 - case StringJoin => 2 - case TupleCapacity(size, infinite) => ??? - // Note that `arguments` here are higher-order patterns. The `arity` method - // computes the number of extraction parameters, which we have not support - // yet. Therefore, it's temporarily set to 0. - case Instantiation(symbol, arguments) => 0 - case LocalPattern(id) => 0 - // Specifying the arity of pattern parameters is not supported for now. - // Therefore, we temporarily assume the arity of a parameter is 0. - case Parameter(symbol) => 0 - case Nested(_) => 0 - - def showDbg: Str = this match - case Symbol(symbol: ClassSymbol) => symbol.toString // TODO: display arity - case Symbol(symbol: ModuleSymbol) => symbol.toString - case StringJoin => "~" - case TupleCapacity(size, true) => s"tuple:$size+" - case TupleCapacity(size, false) => s"tuple:$size" - case Instantiation(symbol, arguments) => - val content = if arguments.isEmpty then "" else - arguments.iterator.map(_.split.showDbg.indent(" ")).mkString("\n", "\n", "\n") - s"instantiated:${symbol.nme}($content)" - case LocalPattern(id) => s"local:$id" - case Parameter(symbol) => s"parameter:${symbol.nme}" - case Nested(split) => "" - -/** `PatternStub` is a simplified representation of `semantics.Pattern`. It - * excludes terms and symbols which can break the uniqueness of the pattern. - */ -enum PatternStub: - /** Match the current scrutinee with a literal. */ - case Literal(value: syntax.Literal) - - /** Match the current scrutinee with a range of characters. */ - case CharClass(range: NumericRange[Char]) - - /** Match the current scrutinee with a class-like symbol. If the class-like - * symbol has extractions, each extraction has to match the corresponding - * `PartialSplit` stored in `subSplits`. - */ - case ClassLike(constructor: ConstructorLike) - - /** Match the current scrutinee unconditionally. */ - case Wildcard - - lazy val arity: Int = this match - case Literal(_) => 0 - case CharClass(_) => 0 - case ClassLike(symbol) => symbol.arity - case Wildcard => 0 - - def display: Str = s"$showDbg ($arity)" - - def showDbg: Str = this match - case Literal(value) => value.idStr - case CharClass(range) => range.isInclusive match - case true => s"'${range.start}' to '${range.end}'" - case false => s"'${range.start}' until '${range.end}'" - case ClassLike(symbol) => symbol.showDbg - case Wildcard => "_" - -object PatternStub: - object CharClass: - def apply(start: Char, end: Char, inclusive: Bool): PatternStub = - if inclusive then CharClass(start to end) else CharClass(start until end) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala index c2b786dae4..9efd5f6556 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala @@ -4,20 +4,19 @@ package ucs import mlscript.utils.*, shorthands.* import Message.MessageContext -import Split.display, ucs.Normalization -import syntax.{Fun, Keyword, ParamBind, Tree}, Tree.*, Keyword.{`as`, `=>`} -import scala.collection.mutable.{Buffer, Set as MutSet} +import Split.display, Desugarer.unapply, extractors.* +import syntax.{Fun, Keyword, Tree}, Tree.*, Keyword.{`as`, `=>`} +import scala.collection.mutable.Buffer import Elaborator.{Ctx, State, ctx} -import Desugarer.unapply object Translator: /** 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"String range bounds must have only one character." -> lo.toLoc + ds += msg"The lower bound of character ranges must be a single character." -> lo.toLoc if hi.value.length != 1 then - ds += msg"String range bounds must have only one character." -> hi.toLoc + ds += msg"The upper bound of character ranges must be a single character." -> hi.toLoc if ds.nonEmpty then error(ds.toSeq*) ds.nonEmpty @@ -79,7 +78,7 @@ import Translator.* * perform pattern matching on terms described by the pattern. */ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends DesugaringBase: - import elaborator.term, elaborator.tl.*, HelperExtractors.*, FlatPattern.MatchMode + import elaborator.term, elaborator.tl.*, FlatPattern.MatchMode import Pattern.* private type CaptureMap = Map[Param, Term.Ref] @@ -170,7 +169,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De argumentOutput, // TODO: Combine `outerOutput` and `argumentOutput` outerBindings ++ argumentBindings), Split.End) - val theArgument = FlatPattern.Argument(subScrutinee, Tree.Empty().withLocOf(argument), N, N) + val theArgument = FlatPattern.Argument(subScrutinee, Tree.Empty().withLocOf(argument), N) (theArgument :: theArguments, makeThisSplit) .mapFirst(S(_)) // For pattern arguments for higher-order patterns, we generate the @@ -178,7 +177,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De val arguments0 = patternArguments.iterator.zipWithIndex.map: (pattern, index) => val patternSymbol = TempSymbol(N, s"patternArgument$index$$") val patternObject = translateAnonymousPattern(Nil, Nil, pattern) - FlatPattern.Argument(patternSymbol, Tree.Empty().withLocOf(pattern), N, S(patternObject)) + FlatPattern.Argument(patternSymbol, Tree.Empty().withLocOf(pattern), S((pattern, patternObject))) .toList val theArguments = arguments1.fold(if arguments0.isEmpty then N else S(arguments0)): case arguments => S(arguments0 ::: arguments) @@ -554,7 +553,7 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De Split.Else(makeMatchResult(Term.Tup(head :: fields)(Tup(Nil)))) /** Failed matctching result. */ - private def failure: Split = Split.Else(makeMatchFailure) + private def failure: Split = Split.Else(makeMatchFailure()) private def errorSplit: Split = Split.Else(Term.Error) 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 ced005efb1..0b020a0fb7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/package.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/package.scala @@ -10,10 +10,43 @@ package object ucs: def warn(using Line, FileName, Name, Raise)(msgs: (Message, Option[Loc])*): Unit = raise(WarningReport(msgs.toList)) + def bug(using Line, FileName, Name, Raise)(msgs: (Message, Option[Loc])*): Unit = + raise(InternalError(msgs.toList)) + extension (symbol: BlockLocalSymbol) /** Create a `Ref` that does not have any implicit arguments. We need this * function because we generate a lot of `Ref`s after implicit resolution. * Writing `.withIArgs(Nil)` is too verbose. */ def safeRef: Term.Ref = symbol.ref().withIArgs(Nil) + + /** 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 + + /** 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. + */ + object to: + infix def unapply(tree: Tree): Opt[(Tree, (Bool, Tree))] = tree match + 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 36de0ecb55..c65eb6d7b0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -3,59 +3,447 @@ package semantics package ups -import mlscript.utils.shorthands.* +import mlscript.utils.*, shorthands.* -import syntax.Tree, Tree.Ident -import codegen.{Block, Case, Match, End} -import Pattern.Head +import syntax.{Keyword, LetBind, Tree}, Tree.{DecLit, Ident, IntLit, StrLit, UnitLit} +import Term.{Blk, IfLike, Rcd, Ref, SynthSel} +import Pattern.{Instantiation, Head} +import Elaborator.{Ctx, State, ctx}, utils.TL +import ucs.{DesugaringBase as Base, FlatPattern, safeRef} -import collection.mutable.Map as MutMap -import collection.immutable.{Set, Map} -import hkmc2.codegen.Value.Rcd -import hkmc2.codegen.RcdArg +import collection.mutable.{Queue, Map as MutMap}, collection.immutable.{Set, Map} -class Compiler(using Elaborator.State, Raise): - - private type Label = Int - - var labelMap: MutMap[Pattern, Label] = MutMap() - - var multiMatchers: MutMap[Set[Label], BlockLocalSymbol] = MutMap() - var implementations: MutMap[BlockLocalSymbol, Block] = MutMap() - - extension (pattern: Pattern) - +class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Base: + import Compiler.*, tl.* + + extension (label: Label) + /** This decides the the field name of each label in the match record. */ + def asFieldName: Str = s"p_$label" + + extension (pattern: Pat) + /** Get or create a label for the pattern. */ def label: Label = labelMap.getOrElseUpdate(pattern, labelMap.size) - - def buildMatchFunction(patterns: Set[Pattern]): BlockLocalSymbol = + + extension (field: Ident | Int) + def showDbg: Str = field match + case id: Ident => id.name + case index: Int => s"p_$index" + /** Convert the field name to an `Ident`. */ + def asIdent: Ident = field match + case id: Ident => id + case index: Int => Ident(index.toString) + + 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 sym: ClassLikeSymbol => + val target = reference(sym).getOrElse(Term.Error) + FlatPattern.ClassLike(target, N)(Nil) + def showDbg: Str = head match + case lit: syntax.Literal => lit.idStr + case sym: ClassLikeSymbol => sym.nme + + extension (patterns: Set[(Label, ExPat)]) + /** Specialize a set of patterns. Also display them in the debug log. */ + def specializeSet(head: Opt[Head]): Set[(Label, SpPat)] = + log(s"Specialized patterns of ${head.fold("the case without tags")(_.showDbg)}:") + patterns.map: (label, pattern) => + val spec = pattern.specialize(head) + val simp = spec.simplify + log(s"\u2022 Label $label") + log(s" \u2023 Expanded: ${spec.showDbg}") + log(s" \u2023 Simplified: ${simp.showDbg}") + (label, simp) + + val labelMap: MutMap[Pat, Label] = MutMap() + + val multiMatchers: MutMap[Set[Label], BlockLocalSymbol] = MutMap.empty + + /** The built multi-matcher functions. */ + val implementations: MutMap[BlockLocalSymbol, (ParamList, Term)] = MutMap.empty + + val buildQueue: Queue[(BlockLocalSymbol, Set[Pat])] = Queue.empty + + /** Build a matcher function that matches a single pattern. This function + * should be applied to the pattern that is considered as the entry point.*/ + def buildMatcher(pattern: Pat): ((BlockLocalSymbol, Str), Ls[Implementation]) = scoped("ucs:compiler"): + val entryPointSymbol = buildMultiMatcher(Set(pattern)) + while buildQueue.nonEmpty do + val (symbol, patterns) = buildQueue.dequeue() + implementations += (symbol -> buildMultiMatcherBody(patterns)) + (entryPointSymbol, pattern.label.asFieldName) -> implementations.iterator.map: + case (symbol, (paramList, term)) => (symbol, paramList, term) + .toList + + /** Build a multi-matcher function and returns the local symbol that we can + * use to call it. The built function definition is stored in the map + * `implementations`. */ + def buildMultiMatcher(patterns: Set[Pat]): BlockLocalSymbol = + // Get or create the label for each pattern. Multi-matchers are identified + // by the set of labels (orders are not important). val labels = patterns.map(_.label) - multiMatchers.get(labels) match - case Some(f) => f - case None => - val f = TempSymbol(N, s"multimatcher_${multiMatchers.size}") + multiMatchers.get(labels).getOrElse: + val f = TempSymbol(N, makeMultiMatcherName(patterns)) multiMatchers += (labels -> f) - val expandedPatterns = patterns.map(p => (p.label, p.expand())) - val heads = expandedPatterns.flatMap((_, p) => p.heads).toList - val scrut = TempSymbol(N, s"scrut") - val branches = heads.map: head => - val specialized = expandedPatterns.map((l, p) => (l, p.specialize(Some(head)))) - val branch = multiMatcherBranch(specialized, scrut) - head match - case Term.Lit(lit) => (Case.Lit(lit), branch) - case sym: ClassSymbol => (Case.Cls(sym, ???), branch) - val els = - val specialized = expandedPatterns.map((l, p) => (l, p.specialize(None))) - multiMatcherBranch(specialized, scrut) - implementations += (f -> Match(/* scrut */???, branches, Some(els), ???)) - f - - def multiMatcherBranch(patterns: Set[(Label, Pattern)], scrut: BlockLocalSymbol): Block = + buildQueue enqueue (f -> patterns) + f // Return the symbol of the built function. + + /** Build the body of a multi-matcher function. The memoization is done by + * `buildMultiMatcher`. */ + def buildMultiMatcherBody(patterns: Set[Pat]): (ParamList, Term) = trace( + pre = s"buildMultiMatcherBody: ${ + patterns.iterator.map: pattern => + s"${pattern.showDbg} => ${pattern.label}" + .mkString("{", ", ", "}")}" + ): + val expandedPatterns = patterns.map(p => (p.label, p.expand())) + val heads = expandedPatterns.flatMap((_, p) => p.heads).toList + // This is the parameter of the current multi-matcher. + val scrutinee = VarSymbol(Ident("input")) + // Assemble branches for constructors and literals. + val branches = heads.map: head => + // Weird. Removing type annotations caused type errors. + val specialized = expandedPatterns.specializeSet(S(head)) + val consequent = Split.Else(multiMatcherBranch(specialized, scrutinee)) + Branch(scrutinee.safeRef, head.toFlatPattern, consequent) + // Assemble the default branch. + val default = + // Weird. Removing type annotations caused type errors. + val specialized = expandedPatterns.specializeSet(N) + 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) + log(s"Multi-matcher body:\n${Split.display(topmostSplit)}") + (paramList(param(scrutinee)), bodyTerm) + + def multiMatcherBranch( + patterns: Set[(Label, SpPat)], + scrutinee: BlockLocalSymbol + ): Blk = trace( + pre = s"multiMatcherBranch: scrutinee = ${scrutinee} | patterns = ${ + patterns.iterator.map: (label, pattern) => + s"${pattern.showDbg} => ${label}" + .mkString("{", ", ", "}")}" + ): val labels = patterns.map((l, _) => l) val fields = patterns.flatMap((_, p) => p.fields) - val subScrutineeVars = Map.from(fields.map(id => id -> TempSymbol(N, s"$scrut.$id"))) - val bindings = fields.map: field => + log(s"fields: ${fields.iterator.map(_.showDbg).mkString("{", ", ", "}")}") + val subScrutinees = Map.from(fields.map(id => id -> VarSymbol(id.asIdent))) + // The default record value for each sub-scrutinee. It is only used in the + // bindings of fields. + val emptyRecordSymbol = TempSymbol(N, s"emptyRecord$$") + val emptyRecord = Rcd: + patterns.map: (label, _) => + RcdField(str(label.asFieldName), makeMatchFailure(str("empty"))) + .toList + // Let bindings that bind the sub-scrutinee to the result of each matcher. + val bindings = subScrutinees.iterator.flatMap: (field, subScrutineeVar) => val subPatterns = patterns.flatMap((_, p) => p.collectSubPatterns(field)) - val f = buildMatchFunction(subPatterns) + log(s"subPattern for field ${field.showDbg}: ${ + subPatterns.iterator.map(_.showDbg).mkString("{", ", ", "}")}") + val subMatcherSymbol = buildMultiMatcher(subPatterns) + val conditional = + // 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 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)) + LetDecl(subScrutineeVar, Nil) :: DefineVar(subScrutineeVar, conditional) :: Nil + .toList + // If there are no bindings, we do not need to create the empty record. + val bindings2 = if bindings.isEmpty then Nil else + LetDecl(emptyRecordSymbol, Nil) :: + DefineVar(emptyRecordSymbol, emptyRecord) :: bindings + // For each pattern, we compile a split and bind the result to a variable. + // The variable will be a field of the output record. + val z = (Nil: Ls[Statement], Nil: Ls[RcdField]) + val (tests, recordFields) = patterns.iterator.foldLeft(z): + case ((stmts, fields), (label, pattern)) => + val symbol = TempSymbol(N, label.asFieldName + "$") + val makeSplit = completePattern(pattern, scrutinee, subScrutinees, Nil) + val split = makeSplit( + // There's no transform in the topmost pattern. So, we just make and + // return a `MatchResult` instance. + makeConsequent = (outputSymbol, bindings) => Split.Else: + makeMatchResult(outputSymbol.use, bindings.use), + // 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) + // 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) + // Lastly, we return the output record. + Blk(bindings2 ::: tests.reverse, Rcd(recordFields.reverse)) + + import Pattern.* + + type Usable = BlockLocalSymbol | Term + + /** The bindings can be a `Term` or a `TempSymbol`. */ + type MakeConsequent = (output: Usable, bindings: Usable) => Split + + /** A function that makes a split in matcher functions. The first argument + * indicates the transform that should be applied to bindings at the + * innermost split. If it is `None`, then the innermost split should just + * return the bindings through `MatchResult`. The second argument is the + * alternative split. It is not a global fallback split. + */ + type MakeSplit = (makeConsequent: MakeConsequent, alternative: Split) => Split + + extension (symbolOrTerm: Usable) + def use: Term = symbolOrTerm match + case symbol: BlockLocalSymbol => symbol.safeRef + case term: Term => term + + /** Create the innermost `Else` split based on whether we have a transform + * term or not. + * @param output The default output of the pattern. It will not be evaluated + * if there is a transform term. + */ + private def completeMatchResult( + transformOpt: Opt[TempSymbol], + output: => Term, + bindings: => Term + ): Split = transformOpt match + // If no transform is provided, we just return the current scrutinee and + // the bindings through `MatchResult`. + case N => Split.Else: + makeMatchResult(output, bindings) + case S(transform) => + val resultSymbol = TempSymbol(N, "transformResult") + val transformTerm = app(transform.safeRef, tup(fld(bindings)), "the transform's result") + Split.Let(resultSymbol, transformTerm, Split.Else( + makeMatchResult(resultSymbol.safeRef))) + + def completePattern( + pattern: SpPat, // This is actually a `SpPat`. + scrutinee: BlockLocalSymbol, + subScrutinees: Map[Ident | Int, BlockLocalSymbol], + aliases: Ls[VarSymbol] + ): MakeSplit = trace(pre = s"completePattern: ${pattern.showDbg}"): + pattern match + case Record(fields) => + // Input: a record pattern made of several fields. + // Output: a record, each field of which is the output of the + // corresponding field's pattern. + // Construct a conjunction of tests for each field. + val makeMakeSplit = fields.iterator.foldRight( + (fields: Ls[RcdField], bindingsSymbols: Ls[TempSymbol]) => + ((makeConsequent, alternative) => + log(s"current bindings: " + aliases.map(_.name).mkString("{", ", ", "}")) + // The current pattern's bindings. + val currentBindings = aliases.map: + alias => RcdField(str(alias.name), scrutinee.safeRef) + // Here, we need to merge the current bindings with previously + // accumulated bindings. Some special cases here are to make the + // generated code more concise and efficient. + val bindings = bindingsSymbols match + case Nil => if currentBindings.isEmpty then rcd() else Rcd(currentBindings) + case bindingsSymbol :: Nil => + if currentBindings.isEmpty then bindingsSymbol.safeRef + else Rcd(RcdSpread(bindingsSymbol.safeRef) :: currentBindings) + case _ => + // Spread the previously accumulated bindings. + val spreads = bindingsSymbols.reverseIterator.map: + _.safeRef |> RcdSpread.apply + .toList + // Append the current bindings to the spreads. + Rcd(spreads ::: currentBindings) + makeConsequent(Rcd(fields.reverse), bindings) + ): MakeSplit + ): + case ((field, pattern), makeInnerSplit) => + val label = pattern.label + val target = sel(subScrutinees(field).safeRef, label.asFieldName) + // This is the symbol for `MatchResult`. + val resultSymbol = TempSymbol(N, s"result$label$$") + // This is the symbol for the output of the pattern. + val outputSymbol = TempSymbol(N, s"output$label$$") + val outputField = RcdField(str(field.name), outputSymbol.safeRef) + // This is the bindings of the current field. + val fieldAliases = pattern.aliases + val fieldBindingsSymbol = TempSymbol(N, "fieldBindings") + val fieldBindingsTerm = Rcd(fieldAliases.map: + alias => RcdField(str(alias.name), outputSymbol.safeRef)) + (outputFields: Ls[RcdField], bindingsSymbols: Ls[TempSymbol]) => + ((makeConsequent, alternative) => + val bindingsSymbol = TempSymbol(N, "bindings") + Split.Let(resultSymbol, target, Branch( + scrutinee = resultSymbol.safeRef, + pattern = matchResultPattern(S(outputSymbol :: bindingsSymbol :: Nil)), + continuation = Split.Let( + fieldBindingsSymbol, + fieldBindingsTerm, + makeInnerSplit( + outputField :: outputFields, + fieldBindingsSymbol :: bindingsSymbol :: bindingsSymbols + )(makeConsequent, Split.End) + ) + ) ~: alternative)): MakeSplit + makeMakeSplit(Nil, Nil) + case Tuple(leading, spread, trailing) => + // Think about how to handle the spread pattern. ??? - ??? + // The wildcard case always succeeds. Thus, the `alternative` is not used. + case Or(Nil) => (makeConsequent, _) => + // Do forget to add the aliases of the current pattern to bindings. + val bindings = aliases.map: + alias => RcdField(str(alias.name), scrutinee.safeRef) + makeConsequent(scrutinee, Rcd(bindings)) + // The never case should always fail. + case And(Nil) => (_, _) => Split.Else(makeMatchFailure(str("never"))) + // The disjunction case should check the result from each pattern in order. + case Or(patterns) => + // Make those functions first so that symbols are allocated top-down. + val functions = patterns.map: + completePattern(_, scrutinee, subScrutinees, aliases) + (makeConsequent, alternative) => functions.foldRight(alternative): + case (makeSplit, innerSplit) => makeSplit(makeConsequent, innerSplit) + // The conjunction case should check all results from patterns and only + // return `MatchResult` if all patterns succeed. + case And(patterns) => + val functions = patterns.map: + completePattern(_, scrutinee, subScrutinees, aliases) + val makeMakeSplit = functions.foldRight( + (allOutputs: Ls[Usable], allBindings: Ls[Usable]) => ( + (makeConsequent, alternative) => + // Here, we need to make a tuple of all output values of patterns. + // Then we merge the bindings of all patterns. + val outputSymbol = TempSymbol(N, "combinedOutput") + val outputTerm = tup(allOutputs.reverseIterator.map(_.use |> fld).toSeq*) + val bindingsSymbol = TempSymbol(N, "combinedBindings") + // I think the bindings do not need to be reversed. + val bindingsTerm = Rcd(allBindings.map: + binding => RcdSpread(binding.use)) + splitLet(outputSymbol, outputTerm) <|: + splitLet(bindingsSymbol, bindingsTerm) <|: + makeConsequent(outputSymbol, bindingsSymbol) + ): MakeSplit + ): (makeSplit, makeInnerMakeSplit) => + (accOutputs: Ls[Usable], accBindings: Ls[Usable]) => ( + (makeConsequent, alternative) => makeSplit( + // Get the output and bindings of the current pattern. + makeConsequent = (output, bindings) => + makeInnerMakeSplit(output :: accOutputs, bindings :: accBindings) + (makeConsequent, Split.End), + alternative = alternative) + ): MakeSplit + makeMakeSplit(Nil, Nil) + case Not(pattern) => ??? + case Rename(pattern, name) => + // We should add those fields to a context. + completePattern(pattern, scrutinee, subScrutinees, name :: aliases) + case Extract(pattern, 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. + val letBindings = pattern.symbols.flatMap: symbol => + LetDecl(symbol, Nil) :: + DefineVar(symbol, sel(bindingsSymbol.safeRef, symbol.name)) :: Nil + val makeSplit = completePattern(pattern, scrutinee, subScrutinees, Nil) + (makeConsequent, alternative) => Split.Let( + sym = transformSymbol, + term = Term.Lam(params, Blk(letBindings, term)), + tail = makeSplit( + // The `outputSymbol` is the output of `pattern`. + // vvvvvvvvvvvv + makeConsequent = (outputSymbol, bindings) => + // Apply the transform to the bindings. + val transformTerm = app(transformSymbol.safeRef, + tup(fld(bindings.use)), "the transform's result") + // Bind the transformation result to a new output symbol. + val resultSymbol = TempSymbol(N, "transformResult") + // Don't forget that current pattern may also have aliases which are + // available in some outer transform patterns. + val currentBindingsSymbol = TempSymbol(N, "bindings") + val currentBindings = Rcd(aliases.map: + alias => RcdField(str(alias.name), resultSymbol.safeRef)) + Split.Let(resultSymbol, transformTerm, + Split.Let(currentBindingsSymbol, currentBindings, + makeConsequent(resultSymbol, currentBindingsSymbol))), + alternative = alternative)) + + /** Make a human-readable name for patterns that only have class patterns. If + * the patterns are too complicated, we just use the counter. */ + def makeMultiMatcherName(patterns: Set[Pat]): Str = + "matcher" + + patterns.iterator.mapOption(_.shortName()).fold(multiMatchers.size.toString): + _.reverse.mkString("__", "_", "") + + "$" + +object Compiler: + type Label = Int + + /** A multi-matcher implementation. */ + type Implementation = (BlockLocalSymbol, ParamList, Term) + + /** Perform a reverse lookup for a term that references a symbol in the + * current context. */ + def reference(symbol: ClassLikeSymbol)(using tl: TL)(using Ctx, State): Opt[Term] = + /** To make lowering happy about the terms. */ + def fillImplicitArgs(term: Term): Term = term match + case ref: Ref => ref.withIArgs(Nil) + case sel: SynthSel => + fillImplicitArgs(sel.prefix) + sel.withIArgs(Nil) + case other => other + def go(ctx: Ctx): Opt[Term] = + ctx.env.values.collectFirst: + case elem if elem.symbol.flatMap(_.asClsLike).contains(symbol) => + fillImplicitArgs(elem.ref(symbol.id)) + .orElse(ctx.parent.flatMap(go)) + go(ctx).map: term => + // If the `symbol` is a virtual class, then do not select `class`. + symbol match + case s: ClassSymbol if !(ctx.builtins.virtualClasses contains s) => + SynthSel(term, Ident("class"))(S(s)).withIArgs(Nil) + case _: (ClassSymbol | ModuleSymbol) => term + + import Pattern.* + + extension [X](xs: Iterator[X]) + /** Apply a function to each element of the iterator. If any of the calls + * returns `None`, then the whole function returns `None`. */ + def mapOption[Y](f: X => Opt[Y]): Opt[Ls[Y]] = + xs.foldLeft(S(Nil): Opt[Ls[Y]]): + case (S(acc), x) => f(x) match + case S(y) => S(y :: acc) + case N => N + case (N, _) => N + + extension (pattern: Pat) + /** Make a human-readable short name using Unicode letters, which are + * allowed in in identifiers in the generated code, for patterns. */ + def shortName(prec: Int = 0): Opt[Str] = pattern match + case Literal(IntLit(n)) => S((if n < 0 then "\u30FC" else "") + n.toString) + case Literal(StrLit(s)) => S(s"str${s.length}") + case Literal(UnitLit(true)) => S("null") + case Literal(UnitLit(false)) => S("undefined") + case ClassLike(sym, arguments) => arguments.fold(S(sym.nme)): arguments => + arguments.iterator.mapOption: + case (_, pat) => pat.shortName(0) + .map(_.reverse.mkString(s"${sym.nme}\u02BF", "_", "\u02BE")) + case Synonym(Instantiation(symbol, patterns)) => + if patterns.isEmpty then S(symbol.nme) else + patterns.iterator.mapOption(_.shortName(0)).map: + // Go's trick: https://news.ycombinator.com/item?id=14276891 + _.reverse.mkString(s"${symbol.nme}\u1438", "_", "\u1433") + case Or(Nil) => S("\uA4D5") + case Or(patterns) => patterns.iterator.mapOption(_.shortName(1)).map: + _.reverse.mkString( + if prec > 0 then "\u02BF" else "", + "\u01C1", // LATIN LETTER LATERAL CLICK + if prec > 0 then "\u02BE" else "") + case And(Nil) => S("\u0422") + case _ => N diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala new file mode 100644 index 0000000000..2891886e6b --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala @@ -0,0 +1,22 @@ +package hkmc2 +package semantics +package ups + +import mlscript.utils.*, shorthands.* +import Pattern.{Instantiation, Never} +import Message.MessageContext, ucs.bug +import sourcecode.{FileName, Line, Name} + +class Context(val definitions: Map[Pattern.Instantiation, Pat]): + def get(instantiation: Instantiation)(using Raise): Pat = + definitions.get(instantiation) match + case Some(pattern) => pattern + case None => + bug(msg"The pattern is not instantiated." -> instantiation.toLoc) + Never + +object Context: + extension (instantiation: Instantiation) + /** Get the body of the instantiated pattern definition. */ + def body(using Context, Raise): Pat = + summon[Context].get(instantiation) \ No newline at end of file diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala new file mode 100644 index 0000000000..dd63fc9128 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Instantiator.scala @@ -0,0 +1,142 @@ +package hkmc2 +package semantics +package ups + +import mlscript.utils.*, shorthands.* +import Elaborator.{Ctx, State, ctx} +import Message.MessageContext, ucs.{error, warn} +import semantics.Pattern as SP, Pattern.* +import syntax.Tree, Tree.{StrLit, Ident, IntLit} +import collection.mutable.{Map as MutMap, Queue as MutQueue} +import utils.TL +import scala.collection.immutable.SeqMap + +object Instantiator: + type Subst = Map[VarSymbol, Pat] + +class Instantiator(using tl: TL)(using Ctx, State, Raise): + import tl.* + + /** The map contains all instantiated patterns. The key is a pattern symbol + * paired with fully instantiated patterns as arguments. The value is the + * pattern symbol's definition with all parameters have been substituted. + * If the value is `None`, the pattern has not been instantiated yet. */ + val progress: MutMap[Instantiation, Opt[Pat]] = MutMap.empty + + /** We instantiate patterns in a breadth-first manner. The queue contains all + * patterns that need to be instantiated. */ + val thingsToDo: MutQueue[Instantiation] = MutQueue.empty + + /** Instantiate a pattern and patterns used in it. */ + def apply( + symbol: PatternSymbol, + arguments: Ls[SP], + useSiteLoc: Opt[Loc] + ): (Pat, Context) = scoped("ucs:instantiation"): + val entryPoint = Instantiation(symbol, arguments.map(instantiate(_)(using Map.empty)))(useSiteLoc) + val result = schedule(entryPoint) + while thingsToDo.nonEmpty do + val instantiation = thingsToDo.dequeue() + val defn = instantiation.symbol.defn.get + // Check if the number of pattern parameters and pattern arguments match. + if defn.patternParams.size != instantiation.arguments.size then + val parameterCount = "pattern parameter" countBy defn.patternParams.size + val argumentCount = "pattern argument" countBy instantiation.arguments.size + error(msg"Pattern `${instantiation.symbol.nme}` has $parameterCount." -> Loc(defn.patternParams), + msg"But $argumentCount were provided." -> instantiation.toLoc) + // The entire pattern will not be instantiated. Use never instead. + progress += (instantiation -> S(Pattern.Never)) + else + val subst = defn.patternParams.iterator.map(_.sym).zip(instantiation.arguments).toMap + log("Instantiating " + instantiation.showDbg) + val instantiated = instantiate(defn.pattern)(using subst) + log(s"Instantiated ${instantiation.showDbg}\n" + + s"arity = ${instantiation.symbol.defn.get.patternParams.size}\n" + + s"arguments = ${instantiation.arguments.iterator.map(_.showDbg).mkString(", ")}\n" + + s"instantiated = ${instantiated.showDbg}") + // Add the instantiated pattern to the progress map. + progress += (instantiation -> S(instantiated)) + // Finally, return the synonym representing the entry point pattern and all + // instantiated pattern definitions. + val definitions = progress.view.mapValues: + _.getOrElse(lastWords("The pattern is expected to be instantiated.")) + .toMap + (Synonym(result), Context(definitions)) + + /** Add the instantiation to the queue if it has not been instantiated yet. */ + def schedule(instantiation: Instantiation): Instantiation = + if !progress.contains(instantiation) then + progress += (instantiation -> N) + thingsToDo.enqueue(instantiation) + else + log(s"Already instantiated ${instantiation.showDbg}") + 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 + // Look up the corresponding pattern from the substitution. + case S(symbol: VarSymbol) => subst(symbol) + case S(symbol) => symbol.asClsOrMod.orElse(symbol.asPat) match + case S(symbol: ClassSymbol) => + val keyedArguments = symbol.defn.get.paramsOpt match + case S(ParamList(_, params, _)) => arguments match + case S(arguments) => + if params.size != arguments.size then + error(msg"Class `${symbol.nme}` has ${params.size} parameters." -> Loc(params), + msg"But ${arguments.size} arguments were provided." -> Loc(arguments)) + S(params.iterator.zip(arguments).flatMap: + case (param, argument) if param.flags.value => + S(param.sym.id -> instantiate(argument)) + case (param, argument) => + error(msg"Parameter `${param.sym.nme}` is not accessible." -> param.toLoc) + N + .to(SeqMap)) + case N => N // The class has parameters but no arguments are provided. + case N => arguments match + case N => N // No arguments are provided. + case S(arguments) => + error(msg"Class `${symbol.nme}` has no parameters." -> Loc(arguments)) + N + ClassLike(symbol, keyedArguments) + case S(symbol: ModuleSymbol) => + arguments match + case N => ClassLike(symbol, N) + case S(arguments) => error( + 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) + 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) + case SP.Negation(pattern) => Not(instantiate(pattern)) + case SP.Wildcard() => Wildcard + case SP.Literal(literal) => Literal(literal) + case SP.Range(lower, upper, rightInclusive) => + // Currently, we expand the range pattern into a list of literals. After + // the `where` clause or chain patterns are implemented, we could directly + // expand the range pattern into a range test. + (lower, upper) match + case (StrLit(lower), StrLit(upper)) => + Or((lower.head to upper.head).map(c => Literal(StrLit(c.toString))).toList) + case (IntLit(lower), IntLit(upper)) => + Or((lower to upper).map(i => Literal(IntLit(i))).toList) + case _ => + error(msg"Range patterns are not supported in pattern compilation." -> pattern.toLoc) + Never + 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.Chain(first, second) => + error(msg"Pattern chaining is not supported in pattern compilation." -> pattern.toLoc) + Never + case alias @ SP.Alias(pattern, id) => alias.symbolOption match + 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) 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 f5d76d765f..ac3e892714 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala @@ -3,66 +3,94 @@ package semantics package ups -import mlscript.utils.AnyOps -import mlscript.utils.shorthands.* - +import mlscript.utils.*, shorthands.* +import semantics.Pattern as SP import syntax.Tree, Tree.Ident import Message.MessageContext - - -enum Pattern: - import Pattern.{Wildcard, Never, Head} - - case Lit(lit: Term.Lit) - - /** Represents a constructor or a class, - * possibly with arguments. - * @param sym the class symbol corresponding to the class or the constructor - * @param arguments is None if no arguments were provided (as - * in ``Foo``, and is Some if there were arguments provided, even if - * it is an empty argument list, e.g. ``Bar()``) - * - * the list contains the patterns in the order with which they were given - * along with the corresponding identifier, even if it was not present before. - * e.g. if we have a class defintion ``class L = Nil | Cons(hd: Int, tl: L)`` - * then the pattern ``Cons(foo, bar)`` is expected to be - * ``ClassLike(sym=Cons, arguments=Som(List((hd, foo), tl, bar)))`` - */ - case ClassLike( - sym: ClassSymbol, - arguments: Opt[Ls[(Ident, Pattern)]] - ) - - case Record(entries: Map[Ident, Pattern]) - - /** @param entries is the ordered list of patterns, from left to right - * @param strict is true if we matchexactly these fields, and no more, - * it is false, if we are allowed to have more fields. - * - * Tuple([p0, ..., p(n-1)], true) <~> {0:p0, ..., n-1: p(n-1)} & Not({n:_}) - * Tuple([p0, ..., p(n-1)], false) <~> {0:p0, ..., n-1: p(n-1)} - */ - case Tuple(entries: List[Pattern], strict: Bool) - - case And(patterns: List[Pattern]) - - case Or(patterns: List[Pattern]) - - case Not(pattern: Pattern) - - case Rename(pattern: Pattern, name: VarSymbol) - - case Extract(pattern: Pattern, term: Term) - - /** Represents a pattern Synonym, - * possibly with arguments. - * @param sym the pattern symbol corresponding to the class or the constructor - * @param arguments is None if no arguments were provided (as - * in ``Foo``, and is Some if there were arguments provided, even if - * it is an empty argument list, e.g. ``Bar()``) - */ - case Synonym(sym: PatternSymbol, params: Opt[Ls[Pattern]]) - +import ucs.{error, warn} +import Context.* +import hkmc2.semantics.ups.Pattern.NonCompositional +import scala.collection.immutable.SeqMap + +private[ups] object Kind: + sealed trait Specialized extends Expanded + sealed trait Expanded extends Complete + sealed trait Complete + +/** This class `Pattern` with its case classes are the internal representation + * of patterns used in pattern compilation. The most important difference + * between these and those in the `semantics` package are as follows. + * + * 1. All symbols in this abstract data type are resolved, including bindings + * used in pattern transformations. Ill-formed bindings have been rejected + * during the elaboration stage. + * 2. Higher-order patterns have been instantiated. Diverging patterns have + * been rejected during instantiation. Therefore, all symbols in this abstract + * data type are guaranteed to be first-order. + */ +sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: + import Pattern.* + + infix def and[L >: K <: Kind.Complete](that: Pattern[L]): Pattern[L] = this match + case And(patterns) => that match + case And(patterns2) => And(patterns ++ patterns2) + case _ => And[L](patterns appended that) + case _ => that match + case And(patterns) => And(this :: patterns) + case _ => And[L](this :: that :: Nil) + + infix def or[L >: K <: Kind.Complete](that: Pattern[L]): Pattern[L] = this match + case Or(patterns) => that match + case Or(patterns2) => Or(patterns ++ patterns2) + case _ => Or[L](patterns appended that) + case _ => that match + case Or(patterns) => Or[L](this :: patterns) + case _ => Or[L](this :: that :: Nil) + + // TODO: Associate with locations. + protected def children: List[Located] = this match + case Literal(lit) => lit :: Nil + 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 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 Synonym(pattern) => pattern.symbol :: pattern.arguments + + lazy val symbols: Ls[VarSymbol] = this match + case Literal(lit) => Nil + 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 Synonym(_) => Nil + case And(patterns) => patterns.flatMap(_.symbols) + case Or(patterns) => + // We have checked that bindings are consistent in the elaboration stage. + // Inconsistent bindings are not associated with symbols. + patterns.find(_ != Never).fold(Nil)(_.symbols) + case Not(_) => Nil + case Rename(pattern, name) => name :: pattern.symbols + 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 + * not defined at a node, the node is left unchanged. + * @param f The partial function to apply to each node. + */ + def map[L <: Kind.Complete](f: NonCompositional[K] => Pattern[L]): Pattern[L] = this match + case p: NonCompositional[K] => f(p) + case And(patterns) => And[L](patterns.map(_.map(f))) + 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) + /** A simplified reduce for ``Pattern``. * It is designed to be used when we want to * compute a single value, starting from the leaves. @@ -70,219 +98,242 @@ enum Pattern: * The function must provide all the other base cases, and how * a list is merged (for ``And`` and ``Or`` nodes) */ - def reduce[A](f: (Lit | ClassLike | Record | Tuple | List[A]) => A): A = this match - case p: (Lit | ClassLike | Record | Tuple) => f(p) - case And(patterns) => f(patterns.map(_.reduce(f))) - case Or(patterns) => f(patterns.map(_.reduce(f))) - case Not(pattern) => pattern.reduce(f) - case Rename(pattern, _) => pattern.reduce(f) - case Extract(pattern, _) => pattern.reduce(f) - case Synonym(_, params) => ??? // TODO call on the body - - def heads: Set[Head] = this.reduce: - case Lit(lit) => Set(lit) + def reduce[A](merge: List[A] => A)(f: PartialFunction[Pattern[K], A]): A = this match + case p: NonCompositional[K] => + if f.isDefinedAt(p) then f(p) else merge(Nil) + case And(patterns) => merge(patterns.map(_.reduce(merge)(f))) + 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) + + def heads: Set[Head] = reduce[Set[Head]](_.toSet.flatten): + case Literal(lit) => Set(lit) case ClassLike(sym, _) => Set(sym) - case _: (Record | Tuple) => Set() - case ls: List[Set[Head]] => ls.toSet.flatten - def fields: Set[Ident | Int] = this.reduce: - case _: (Lit | ClassLike) => Set() + def fields: Set[Ident | Int] = reduce[Set[Ident | Int]](_.toSet.flatten): case Record(entries) => entries.keys.toSet[Ident | Int] - case Tuple(entries, strict) => - val n = entries.size + case Tuple(leading, spread, trailing) => + val n = leading.size + trailing.size val subfields = Range(0, n).toSet[Ident | Int] // if this is strict, then a condition is imposed on field n - if strict then subfields + n else subfields - case ls: List[Set[Ident | Int]] => ls.toSet.flatten + if spread.isEmpty then subfields + n else subfields - def collectSubPatterns(field: Ident | Int): Set[Pattern] = field match - case id: Ident => this.reduce: - case Lit(_) => Set() + def collectSubPatterns(field: Ident | Int): Set[Pat] = field match + case id: Ident => this.reduce[Set[Pat]](_.toSet.flatten): // TODO: raise a warning case ClassLike(sym, arguments) => /* TODO:raise a warning */ arguments match case None => Set() case Some(arguments) => arguments.find((id1, _) => id === id).map((_, p) => p).toSet case Record(entries) => entries.get(id).toSet - case Tuple(entries, strict) => Set() - case ls: List[Set[Pattern]] => ls.toSet.flatten - case n : Int => this.reduce: - case _: (Lit | ClassLike) => /* TODO : riase a warning */ Set() - case Record(_) => Set() - case Tuple(entries, strict) => entries.lift(n).toSet - case ls: List[Set[Pattern]] => ls.toSet.flatten - - // old versions without reduce - - // def heads: Set[Term.Lit | ClassSymbol] = this match - // case _: (Record | Tuple) => Set() - // case Lit(lit) => Set(lit) - // case ClassLike(sym, _) => Set(sym) - // case And(patterns) => patterns.toSet.flatMap(_.heads) - // case Or(patterns) => patterns.toSet.flatMap(_.heads) - // case Not(pattern) => pattern.heads - // case Rename(pattern, _) => pattern.heads - // case Extract(pattern, _) => pattern.heads - // case Synonym(sym, params) => ??? // TODO : raise a warning - - // def fields: Set[Ident | Int] = this match - // case _: (Lit | ClassLike) => Set() - // // TODO : raise a warning - // case Record(entries) => entries.keys.toSet[Ident | Int] - // case Tuple(entries, strict) => - // val n = entries.size - // val subfields = Range(0, n).toSet[Ident | Int] - // // if this is strict, then a condition is imposed on field n - // if strict then subfields + n else subfields - // case And(patterns) => patterns.toSet.flatMap(_.fields) - // case Or(patterns) => patterns.toSet.flatMap(_.fields) - // case Not(pattern) => pattern.fields - // case Rename(pattern, _) => pattern.fields - // case Extract(pattern, _) => pattern.fields - // case Synonym(sym, params) => ??? // TODO : raise a warning - - // def collectSubPatterns(id: Ident): Set[Pattern] = this match - // case Lit(_) => Set() - // // TODO: raise a warning - // case ClassLike(sym, arguments) => /* TODO:raise a warning */ arguments match - // case None => Set() - // case Some(arguments) => - // arguments.find((id1, _) => id === id).map((_, p) => p) match - // case None => Set() - // case Some(value) => Set(value) - // case Record(entries) => entries.get(id).toSet - // case Tuple(entries, strict) => Set() - // case And(patterns) => patterns.toSet.flatMap(_.collectSubPatterns(id)) - // case Or(patterns) => patterns.toSet.flatMap(_.collectSubPatterns(id)) - // case Not(pattern) => pattern.collectSubPatterns(id) - // case Rename(pattern, _) => pattern.collectSubPatterns(id) - // case Extract(pattern, _) => pattern.collectSubPatterns(id) - // case Synonym(sym, params) => ??? - - // def collectSubPatterns(n: Int): Set[Pattern] = this match - // case _: (Lit | ClassLike) => /* TODO : riase a warning */ Set() - // case Record(_) => Set() - // case Tuple(entries, strict) => entries.lift(n).toSet - // case And(patterns) => patterns.toSet.flatMap(_.collectSubPatterns(n)) - // case Or(patterns) => patterns.toSet.flatMap(_.collectSubPatterns(n)) - // case Not(pattern) => pattern.collectSubPatterns(n) - // case Rename(pattern, _) => pattern.collectSubPatterns(n) - // case Extract(pattern, _) => pattern.collectSubPatterns(n) - // case Synonym(sym, params) => ??? - - def simplify: Pattern = this match - case _: Lit => this + case Tuple(leading, spread, trailing) => Set() + case n : Int => this.reduce[Set[Pat]](_.toSet.flatten): + case Tuple(leading, spread, trailing) => trailing.lift(n).toSet + + /** Simplify the pattern by removing `Never` patterns. */ + def simplify: Pattern[K] = this match + case _: Literal => this case ClassLike(sym, arguments) => ClassLike(sym, arguments.map(_.map((id, p) => (id, p.simplify)))) 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(entries, strict) => - val simplify = entries.map(_.simplify) - if simplify.contains(Never) then Never else Tuple(simplify, strict) + case Tuple(leading, spread, trailing) => + val leading2 = leading.map(_.simplify) + val spread2 = spread.map(_.simplify) + val trailing2 = trailing.map(_.simplify) + if leading2.contains(Never) || spread2.contains(Never) || trailing2.contains(Never) + then Never else Tuple(leading2, spread2, trailing2) case And(patterns) => - val simplify = patterns.map(_.simplify) - // we cannot simplify wildcard, because we still return the scrutinee - if simplify.contains(Never) then Never else And(simplify) + // TODO: Complete the simplification logic here. + val simplified = patterns.foldRight(Nil: Ls[Pattern[K]]): + case (p, acc) => p.simplify match + case `Wildcard` => acc match + case `Wildcard` :: _ => acc // One consecutive wildcard is enough. + case acc => Wildcard :: acc + case simplified => simplified :: acc + if simplified contains Never then Never else simplified.foldSingleton(And.apply)(identity) case Or(patterns) => - def simplifyOr(patterns: List[Pattern]): List[Pattern] = patterns match - case Nil => Nil - case p :: tl => p.simplify match - case Never => simplifyOr(tl) - case Wildcard => Wildcard :: Nil - case pat => pat :: simplifyOr(tl) - Or(simplifyOr(patterns)) + patterns.foldRight(Nil: Ls[Pattern[K]]): + case (p, acc) => p.simplify match + case Never => acc match + // The list should be a list of non-`Never` patterns or a singleton + // list of `Never` pattern. + case Nil => Never :: Nil + case Never :: Nil | _ => acc + // Should we discard the accumulated patterns? + // case `Wildcard` => Wildcard :: Nil // Discard the following patterns. + case `Wildcard` => acc match + case `Never` :: Nil => Wildcard :: Nil + case `Wildcard` :: _ => acc // One consecutive wildcard is enough. + case acc => Wildcard :: acc + case pat => acc match + case Nil | Never :: Nil => pat :: Nil + case _ => pat :: acc + .foldSingleton(Or.apply)(identity) case Not(pattern) => Not(pattern.simplify) case Rename(pattern, name) => Rename(pattern.simplify, name) - case Extract(pattern, term) => Extract(pattern.simplify, term) - case Synonym(sym, params) => ??? - - def map(f: (Lit | ClassLike | Record | Tuple | Synonym) => Pattern): Pattern = this match - case p :(Lit | ClassLike | Record | Tuple | Synonym) => f(p) - case And(patterns) => And(patterns.map(_.map(f))) - case Or(patterns) => Or(patterns.map(_.map(f))) - case Not(pattern) => Not(pattern.map(f)) - case Rename(pattern, name) => Rename(pattern.map(f), name) - case Extract(pattern, term) => Extract(pattern.map(f), term) - - def specialize(lit: Term.Lit): Pattern = this.map: - case Lit(lit1) => - if lit1 === lit then Wildcard else Never - case ClassLike(_, _) => Never - case Record(_) => Never - // TODO : are we sure that a literal can't have fields? - case Tuple(Nil, false) => ??? - case Tuple(_, _) => Never - case Synonym(sym, params) => ??? - - def specialize(cons: ClassSymbol): Pattern = this.map: - case Lit(_) => Never + case Extract(pattern, term) => pattern.simplify match + case `Never` => Never // Lift up `Never`. Let the caller reduce it. + case simplified => Extract(simplified, term) + case Synonym(sym) => this + + /** Expand the pattern by replacing any top-level synonym with its body. + * @return the expanded pattern named "ExPat" in the paper. */ + def expand(alreadyExpanded: Set[Instantiation] = Set())(using Context, Raise): ExPat = map: + case Synonym(instantiation) => + if alreadyExpanded contains instantiation then + error(msg"Expanding this pattern leads to an infinite loop." -> instantiation.toLoc) + Never // Infinite recursive patterns are considered as never patterns. + else + // Otherwise, we expand the instantiated pattern definition's body. + instantiation.body.expand(alreadyExpanded + instantiation) + // I don't know how to make the GADT type checking work here. + // case pattern: NonCompositional[K] => pattern // Noop. + // case pattern: ExPat => pattern // Noop. + case pattern: ClassLike => pattern + case pattern: Record => pattern + case pattern: Tuple => pattern + case pattern: Literal => pattern + + /** Unwrap `Rename` patterns until we reach a non-`Rename` pattern. Collect + * the symbols on the way. Maybe we can make `Rename` a property of each + * pattern. */ + def aliases: Ls[VarSymbol] = this match + case Rename(pattern, name) => name :: pattern.aliases + case _: Pattern[K] => Nil + + def showDbg: Str = this match + case Literal(lit) => lit.idStr case ClassLike(sym, arguments) => - if sym === cons then Wildcard else Never - case Record(_) => this - case Tuple(Nil, false) => ??? - case Tuple(_, _) => Never - case Synonym(sym, params) => ??? - - def specialize(head: Option[Head]): Pattern = head match - case Some(h: Term.Lit) => this.specialize(h) - case Some(h: ClassSymbol) => this.specialize(h) - case None => this.map: - case _: (Lit | ClassLike) => Never - case _: (Record | Tuple) => this - case Synonym(sym, params) => ??? - - def expand(alreadyExpanded: Set[PatternSymbol] = Set())(using Raise): Pattern = this.map: - case _: (Lit | ClassLike | Record | Tuple) => this - case Synonym(sym, params) if sym in alreadyExpanded => - raise(ErrorReport(msg"expanding ${sym.nme} leads to an infinite loop." -> sym.toLoc :: Nil)) - this - case Synonym(sym, params) => params match - case None => sym.defn match - case None => - raise(ErrorReport(msg"No definition found for pattern synonym ${sym.nme}" -> sym.toLoc :: Nil)) - this - case Some(defn) => ??? - // TODO : transform the body into what we want - case Some(_) => - raise(ErrorReport(msg"Higher order patterns are not supported yet." -> sym.toLoc :: Nil)) - this - - // old version without map + val argumentsText = arguments.fold(""): + _.iterator.map((id, p) => s"${id.name}: ${p.showDbg}").mkString(" { ", ", ", " }") + s"${sym.nme}$argumentsText" + case Record(entries) => + 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 And(Nil) => "\u22A5" + case And(pattern :: Nil) => pattern.showDbg + case And(patterns) => + patterns.map(_.showDbg).mkString("(", " \u2227 ", ")") + case Or(Nil) => "\u22A4" + case Or(pattern :: Nil) => pattern.showDbg + case Or(patterns) => + patterns.map(_.showDbg).mkString("(", " \u2228 ", ")") + 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 Synonym(sym) => sym.showDbg - // def specialize(lit: Term.Lit): Pattern = this match - // case Lit(lit1) => - // if lit1 === lit then Wildcard else Never - // case ClassLike(_, _) => Never - // case Record(_) => Never - // // TODO : are we sure that a literal can't have fields? - // case Tuple(Nil, false) => ??? - // case Tuple(_, _) => Never - // case And(patterns) => And(patterns.map(_.specialize(lit))) - // case Or(patterns) => Or(patterns.map(_.specialize(lit))) - // case Not(pattern) => Not(pattern.specialize(lit)) - // case Rename(pattern, name) => Rename(pattern.specialize(lit), name) - // case Extract(pattern, term) => Extract(pattern.specialize(lit), term) - // case Synonym(sym, params) => Synonym(sym, params.map(_.map(_.specialize(lit)))) +object Pattern: + sealed trait NonCompositional[+K <: Kind.Complete] extends Pattern[K] + + final case class Literal(lit: syntax.Literal) extends NonCompositional[Kind.Expanded] - // def specialize(cons: ClassSymbol): Pattern = this match - // case Lit(_) => Never - // case ClassLike(sym, arguments) => - // if sym === cons then Wildcard else Never - // case Record(_) => this - // case Tuple(Nil, false) => ??? - // case Tuple(_, _) => Never - // case And(patterns) => And(patterns.map(_.specialize(cons))) - // case Or(patterns) => Or(patterns.map(_.specialize(cons))) - // case Not(pattern) => Not(pattern.specialize(cons)) - // case Rename(pattern, name) => Rename(pattern.specialize(cons), name) - // case Extract(pattern, term) => Extract(pattern.specialize(cons), term) - // case Synonym(sym, params) => Synonym(sym, params.map(_.map(_.specialize(cons)))) + /** Represents a constructor or a class, possibly with arguments. + * @param sym the class symbol corresponding to the class or the constructor + * @param arguments is None if no arguments were provided (as + * in ``Foo``, and is Some if there were arguments provided, even if + * it is an empty argument list, e.g. ``Bar()``) + * + * the list contains the patterns in the order with which they were given + * along with the corresponding identifier, even if it was not present before. + * e.g. if we have a class defintion ``class L = Nil | Cons(hd: Int, tl: L)`` + * then the pattern ``Cons(foo, bar)`` is expected to be + * ``ClassLike(sym=Cons, arguments=Some(List((hd, foo), tl, bar)))`` + */ + final case class ClassLike( + sym: ClassLikeSymbol, + arguments: Opt[SeqMap[Ident, Pat]] + ) extends NonCompositional[Kind.Expanded] -object Pattern: + final case class Record( + entries: SeqMap[Ident, Pat] + ) extends NonCompositional[Kind.Specialized] + /** @param entries is the ordered list of patterns, from left to right + * @param strict is true if we matchexactly these fields, and no more, + * it is false, if we are allowed to have more fields. + * + * Tuple([p0, ..., p(n-1)], true) <~> {0:p0, ..., n-1: p(n-1)} & Not({n:_}) + * Tuple([p0, ..., p(n-1)], false) <~> {0:p0, ..., n-1: p(n-1)} + */ + final case class Tuple( + leading: List[Pat], + spread: Opt[Pat], + trailing: List[Pat] + ) extends NonCompositional[Kind.Specialized] + + /** Represents a pattern synonym. + * @param sym the pattern symbol corresponding + */ + final case class Synonym(pattern: Instantiation) extends NonCompositional[Kind.Complete] + + // * Pattern kinds propagate through the following cases. + final case class And[+K <: Kind.Complete](patterns: List[Pattern[K]]) extends Pattern[K] + 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] + val Wildcard = Or(Nil) - + val Never = And(Nil) - - type Head = Term.Lit | ClassSymbol + + type Head = syntax.Literal | ClassLikeSymbol + + /** A representation of fully instantiated first-order patterns. */ + final case class Instantiation( + symbol: PatternSymbol, + arguments: Ls[Pat] + )(val toLoc: Opt[Loc]) extends Located: + def showDbg: Str = + val argumentsText = if arguments.isEmpty then "" else + arguments.iterator.map(_.showDbg).mkString("[", ", ", "]") + s"${symbol.nme}$argumentsText" + +type Pat = Pattern[Kind.Complete] + +type ExPat = Pattern[Kind.Expanded] + +type SpPat = Pattern[Kind.Specialized] + +import Pattern.* + +extension (pattern: ExPat) + /** Modifies the pattern under the assumption that the scrutinee matches the + * given literal. We decide to not care about literals with fields. (For + * example, wrapped primitives with properties in JavaScript.) */ + def specialize(lit: syntax.Literal): SpPat = pattern.map: + case Literal(`lit`) => Wildcard + case _: (Literal | ClassLike) => Never + case pattern: (Record | Tuple) => pattern + + /** Modifies the pattern under the assumption that the scrutinee matches the + * given class. */ + def specialize(symbol: ClassLikeSymbol): SpPat = pattern.map: + case Literal(_) => Never + /** Note that `ClassLike` corresponds the syntax sugar of class patterns + * intersected with record patterns. So, we need to leave the arguments + * part unchanged. If there's no arguments, we return `Wildcard`. */ + case ClassLike(`symbol`, arguments) => + arguments.fold(Wildcard)(_ |> Record.apply) + case ClassLike(_, _) => Never + case pattern: (Record | Tuple) => pattern + + /** Modifies the pattern under the assumption that the scrutinee matches the + * given literal or class. */ + def specialize(head: Option[Head]): SpPat = head match + case Some(h: syntax.Literal) => pattern.specialize(h) + case Some(h: ClassLikeSymbol) => pattern.specialize(h) + case None => pattern.map: + case _: (Literal | ClassLike) => Never + case pattern: (Record | Tuple) => pattern diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala index 78cb69d313..f406590559 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala @@ -52,6 +52,18 @@ final case class COMMENT(text: String) extends Token with Stroken final case class SUSPENSION(dotDotDot: Bool) extends Token with Stroken final case class ESC_IDENT(name: String) extends Token with Stroken +/* + + +(foo + bar) + +(foo INDENT bar) DEINDENT + +Bracket[Round]{foo Bracket[INDENT]{bar}} + + +*/ sealed abstract class BracketKind: import BracketKind._ diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala index 24347838f3..f5e6923802 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala @@ -102,3 +102,41 @@ extension [A](self: Opt[A]) else S(v2) case N => N +extension [A](ls: Ls[A]) + /** Apply a special function for lists with one element. */ + def foldSingleton[B](no: Ls[A] => B)(yes: A => B): B = ls match + case x :: Nil => yes(x) + case Nil | _ :: _ => no(ls) + +extension (n: Int) + /** Converts a number to its English word representation. */ + def spelled: String = n match + case 0 => "zero" case 1 => "one" case 2 => "two" + case 3 => "three" case 4 => "four" case 5 => "five" + case 6 => "six" case 7 => "seven" case 8 => "eight" + case 9 => "nine" case _ => n.toString + +extension (str: String) + /** Converts a singular noun to its plural form using English pluralization + * rules. The rules should be updated as needed. */ + def pluralize: String = + // This is not a complete list (nor is it intended to be). + val irregularPlurals = Map("datum" -> "data", "index" -> "indices") + val ves = Set("proof") // Add more as needed. + val o = Set("zero") // Add more as needed. + irregularPlurals.get(str).getOrElse: + // -s, -sh, -ch, -x, -z -> +es + if str.matches(".*[sxz]$") || str.endsWith("sh") || str.endsWith("ch") then str + "es" + // -[^aeiou]y -> -ies + else if str.matches(".*[^aeiou]y$") then str.dropRight(1) + "ies" + // -f -> -ves (with some exceptions) + else if str.endsWith("f") && !ves.contains(str) then str.dropRight(1) + "ves" + // -fe -> -ves + else if str.endsWith("fe") then str.dropRight(2) + "ves" + // -o -> -es (with some exceptions) + else if str.endsWith("o") && !o.contains(str) then str.dropRight(1) + "es" + else str + "s" + + /** Formats a number and a noun as a human-readable string. */ + infix def countBy(n: Int): String = + s"${n.spelled} ${if n === 1 then str else str.toLowerCase.pluralize}" diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index b0f3c5b80e..cc022f8c04 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -39,14 +39,15 @@ let Runtime1; } toString() { return "EffectHandle(" + "" + ")"; } }; - this.MatchResult = function MatchResult(captures1) { - return new MatchResult.class(captures1); + this.MatchResult = function MatchResult(output1, bindings1) { + return new MatchResult.class(output1, bindings1); }; this.MatchResult.class = class MatchResult { - constructor(captures) { - this.captures = captures; + constructor(output, bindings) { + this.output = output; + this.bindings = bindings; } - toString() { return "MatchResult(" + runtime.render(this.captures) + ")"; } + toString() { return "MatchResult(" + runtime.render(this.output) + ", " + runtime.render(this.bindings) + ")"; } }; this.MatchFailure = function MatchFailure(errors1) { return new MatchFailure.class(errors1); diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index 1c5450f2ea..88a459839e 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -52,7 +52,7 @@ fun try(f) = // For `pattern` definitions -data class MatchResult(captures) +data class MatchResult(output, bindings) data class MatchFailure(errors) // For pattern matching on tuples diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index b9d196c032..79ec89fcdb 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -368,19 +368,19 @@ if Foo(1, 2, 3) is Foo(...args) then args //│ ╔══[ERROR] Unrecognized pattern (spread) //│ ║ l.367: if Foo(1, 2, 3) is Foo(...args) then args //│ ╙── ^^^^ -//│ ╔══[ERROR] the constructor does not take any arguments but found 1 +//│ ╔══[ERROR] The constructor does not take any arguments but found one argument. //│ ║ l.367: 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 3 +//│ ╔══[ERROR] The constructor does not take any arguments but found three arguments. //│ ║ l.376: 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 1 +//│ ╔══[ERROR] The constructor does not take any arguments but found one argument. //│ ║ l.382: if Foo(1, 2, 3) is Foo(arg) then arg //│ ╙── ^^^ //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/backlog/UCS.mls b/hkmc2/shared/src/test/mlscript/backlog/UCS.mls index 016a04b970..ddc0f814c0 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/UCS.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/UCS.mls @@ -183,7 +183,7 @@ let foo = //│ ╔══[ERROR] Unrecognized pattern (spread) //│ ║ l.182: Tuple(...elements) then elements //│ ╙── ^^^^^^^^ -//│ ╔══[ERROR] mismatched arity: expect 3, found 1 +//│ ╔══[ERROR] Expected three arguments, but found only one argument. //│ ║ l.182: Tuple(...elements) then elements //│ ╙── ^^^^^^^^ //│ foo = [function foo] diff --git a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls index d91243f11a..7629547a0c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls @@ -93,7 +93,6 @@ case //│ Argument: //│ scrutinee = $param0 //│ tree = Ident of "a" -//│ split = N //│ pattern = N //│ mode = Default //│ refined = false diff --git a/hkmc2/shared/src/test/mlscript/llir/Split.mls b/hkmc2/shared/src/test/mlscript/llir/Split.mls index af0f4ad158..0f74c6069c 100644 --- a/hkmc2/shared/src/test/mlscript/llir/Split.mls +++ b/hkmc2/shared/src/test/mlscript/llir/Split.mls @@ -26,6 +26,8 @@ map_sum(x => x, 200) //│ 20100 :intl +:todo +// Works fine with `~hkmc2DiffTests/Test/run` but not with `hkmc2AllTests/test`. abstract class Iter[T] object Done data class Yield(n) @@ -52,4 +54,4 @@ fun map_sum(f, n) = let it = map(f, dec(n)) in fold(0, it) map_sum(x => x, 200) //│ //│ Interpreted: -//│ 20100 +//│ /!!!\ Uncaught error: java.lang.StackOverflowError diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/rp/Future.mls index f07bf94757..b0f97da6cc 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Future.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Future.mls @@ -12,46 +12,55 @@ pattern Rep0[A] = "" | A ~ Rep0[A] //│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] //│ ╙── ^ + +:fixme +pattern Foo1[A]: ... +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. + +:fixme +pattern Foo2[pattern A]: ... +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. + + :todo pattern Rep0(pattern A, B, C)(head) = "" | (A as head) ~ Rep0[A] //│ ╔══[ERROR] Multiple parameter lists are not supported for this definition. -//│ ║ l.8: pattern Rep0(pattern A, B, C)(head) = -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.9: "" | (A as head) ~ Rep0[A] -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.26: pattern Rep0(pattern A, B, C)(head) = +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.27: "" | (A as head) ~ Rep0[A] +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.9: "" | (A as head) ~ Rep0[A] -//│ ╙── ^^^^^^^ +//│ ║ l.27: "" | (A as head) ~ Rep0[A] +//│ ╙── ^^^^^^^ //│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. -//│ ║ l.9: "" | (A as head) ~ Rep0[A] -//│ ║ ^^^^ +//│ ║ l.27: "" | (A as head) ~ Rep0[A] +//│ ║ ^^^^ //│ ╟── The variable is missing from this sub-pattern. -//│ ║ l.9: "" | (A as head) ~ Rep0[A] -//│ ╙── ^^ -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: no symbol was assigned to variable `head` at Some(Loc(51,55,Future.mls:+8)) +//│ ║ l.27: "" | (A as head) ~ Rep0[A] +//│ ╙── ^^ +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: no symbol was assigned to variable `head` at Some(Loc(51,55,Future.mls:+26)) :todo // Pattern extractions via aliases. pattern Email(name, domain) = (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╔══[ERROR] Duplicate pattern variable. -//│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.46: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ║ ^^^^^^^^^^ //│ ╟── The previous definition is as follows. -//│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.46: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^ //│ ╔══[WARNING] Useless pattern binding: Identifier. -//│ ║ l.19: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.46: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^ -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: no symbol was assigned to variable `Identifier` at Some(Loc(33,43,Future.mls:+18)) +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: no symbol was assigned to variable `Identifier` at Some(Loc(33,43,Future.mls:+45)) :todo // View patterns pattern GreaterThan(value) = case n and n > value then n //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.33: n and n > value then n +//│ ║ l.60: n and n > value then n //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(InfixApp(Ident(n),keyword 'and',OpApp(Ident(n),Ident(>),List(Ident(value)))),keyword 'then',Ident(n))))) (of class hkmc2.syntax.Tree$Case) :todo // Normal view pattern @@ -63,11 +72,11 @@ fun foo(x) = if x is Unit then .... Arrow(...) then .... //│ ╔══[ERROR] Unrecognized pattern split (infix operator). -//│ ║ l.42: view as +//│ ║ l.71: view as //│ ║ ^^^^^^^ -//│ ║ l.43: Unit then .... +//│ ║ l.72: Unit then .... //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ║ l.44: Arrow(...) then .... +//│ ║ l.73: Arrow(...) then .... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -85,18 +94,17 @@ 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.62: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ║ l.91: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^ //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.62: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ║ l.91: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^^^^ -//│ /!!!\ Uncaught error: scala.MatchError: Jux(StrLit(a),Ident(to)) (of class hkmc2.syntax.Tree$Jux) :todo pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.73: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╙── ^^^ +//│ ║ l.104: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) +//│ ╙── ^^^ :todo pattern LineSep(pattern P) = case @@ -105,14 +113,12 @@ pattern LineSep(pattern P) = case "" then nd :: Nil "\n" ~ LineSep(P, t) then nd :: t //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.80: "" then Nil -//│ ║ ^^^^^^^^^^^ -//│ ║ l.81: L(...nd) ~ -//│ ║ ^^^^^^^^^^^^ -//│ ║ l.82: "" then nd :: Nil -//│ ╙── ^^^^ -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(StrLit(),keyword 'then',Ident(Nil)), OpApp(App(Ident(L),Tup(List(Spread(keyword '...',Some(Loc(52,55,Future.mls:+79)),Some(Ident(nd)))))),Ident(~),List(Block(List(InfixApp(StrLit(),keyword 'then',OpApp(Ident(nd),Ident(::),List(Ident(Nil)))), InfixApp(OpApp(StrLit( -//│ ),Ident(~),List(App(Ident(LineSep),Tup(List(Ident(P), Ident(t)))))),keyword 'then',OpApp(Ident(nd),Ident(::),List(Ident(t))))))))))) (of class hkmc2.syntax.Tree$Case) +//│ ║ l.111: "" then Nil +//│ ║ ^^^^^^^^^^^ +//│ ║ l.112: L(...nd) ~ +//│ ║ ^^^^^^^^^^^^ +//│ ║ l.113: "" then nd :: Nil +//│ ╙── ^^^^ :todo pattern Lines(pattern L) = case @@ -125,29 +131,29 @@ pattern Lines(pattern L) = case :todo if input is Lines of Email then //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.96: if input is Lines of Email then -//│ ╙── ^ +//│ ║ l.132: if input is Lines of Email then +//│ ╙── ^ //│ ╔══[ERROR] Unexpected record property pattern. -//│ ║ l.89: "" then [] -//│ ║ ^^^^^^^^^^ -//│ ║ l.90: pattern Tail = case -//│ ║ ^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.91: "" then [] -//│ ║ ^^^^^^^^^^^^^^ -//│ ║ l.92: "\n" ~ (Lines of L) as t then t -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.93: L as h ~ Tail as t then [h, ...t] -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.94: -//│ ║ ^^^ -//│ ║ l.95: :todo -//│ ╙── ^^^^^ +//│ ║ l.125: "" then [] +//│ ║ ^^^^^^^^^^ +//│ ║ l.126: pattern Tail = case +//│ ║ ^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.127: "" then [] +//│ ║ ^^^^^^^^^^^^^^ +//│ ║ l.128: "\n" ~ (Lines of L) as t then t +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.129: L as h ~ Tail as t then [h, ...t] +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.130: +//│ ║ ^^^ +//│ ║ l.131: :todo +//│ ╙── ^^^^^ //│ ╔══[ERROR] Name not found: input -//│ ║ l.96: if input is Lines of Email then -//│ ╙── ^^^^^ +//│ ║ l.132: if input is Lines of Email then +//│ ╙── ^^^^^ //│ ╔══[ERROR] Name not found: Email -//│ ║ l.96: if input is Lines of Email then -//│ ╙── ^^^^^ +//│ ║ l.132: if input is Lines of Email then +//│ ╙── ^^^^^ :todo pattern Opt(pattern P) = case @@ -157,56 +163,53 @@ pattern Opt(pattern P) = case :todo pattern Email(name, domain) = ... //│ ╔══[ERROR] Unexpected record property pattern. -//│ ║ l.124: P(value) then (Some(value)) +//│ ║ l.160: P(value) then (Some(value)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.125: "" then (None) +//│ ║ l.161: "" then (None) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.126: +//│ ║ l.162: //│ ║ ^^^ -//│ ║ l.127: :todo +//│ ║ l.163: :todo //│ ╙── ^^^^^ :todo if input is Opt(Email, Some((n, d))) then ... //│ ╔══[ERROR] Name not found: input -//│ ║ l.140: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.176: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^^ //│ ╔══[ERROR] Name not found: Some -//│ ║ l.140: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.176: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^ //│ ╔══[ERROR] invalid record field pattern -//│ ║ l.140: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.176: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^ -//│ ═══[ERROR] mismatched arity: expect 0, found 2 +//│ ═══[ERROR] Expected zero arguments, but found two arguments. :todo pattern Opt(pattern P) = case P(...values) then (Some(values)) "" then (None) //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.154: P(...values) then (Some(values)) +//│ ║ l.190: P(...values) then (Some(values)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.155: "" then (None) +//│ ║ l.191: "" then (None) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+153)),Some(Ident(values)))))),keyword 'then',Bra(Round,App(Ident(Some),Tup(List(Ident(values)))))), InfixApp(StrLit(),keyword 'then',Bra(Round,Ident(None)))))) (of class hkmc2.syntax.Tree$Case) :todo pattern Opt(pattern P) = case P(...values) then Some(values) "" then None //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.160: P(...values) then Some(values) +//│ ║ l.200: P(...values) then Some(values) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.161: "" then None +//│ ║ l.201: "" then None //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ -//│ /!!!\ Uncaught error: scala.MatchError: Case(None,Block(List(InfixApp(App(Ident(P),Tup(List(Spread(keyword '...',Some(Loc(34,37,Future.mls:+159)),Some(Ident(values)))))),keyword 'then',App(Ident(Some),Tup(List(Ident(values))))), InfixApp(StrLit(),keyword 'then',Ident(None))))) (of class hkmc2.syntax.Tree$Case) :todo pattern Digits = "0" to "9" ~ (Digits | "") //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.165: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.209: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^ //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.165: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.209: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^^^^ -//│ /!!!\ Uncaught error: scala.MatchError: Jux(StrLit(0),Ident(to)) (of class hkmc2.syntax.Tree$Jux) diff --git a/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls b/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls index a6504a31a0..331473f0cf 100644 --- a/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls @@ -17,7 +17,7 @@ Playground.Zero //│ = Zero Playground.Zero.unapply("0") -//│ = MatchResult("0") +//│ = MatchResult("0", {}) :expect true "0" is Playground.Zero @@ -30,7 +30,7 @@ Playground.DoubleZero //│ = DoubleZero Playground.DoubleZero.unapply("00") -//│ = MatchResult("00") +//│ = MatchResult("00", {}) :expect true "00" is Playground.DoubleZero @@ -43,7 +43,7 @@ Playground.ZeroOne //│ = ZeroOne Playground.ZeroOne.unapply("01") -//│ = MatchResult("01") +//│ = MatchResult("01", {}) :expect true "01" is Playground.ZeroOne @@ -58,7 +58,7 @@ TripleZero //│ = TripleZero TripleZero.unapply("000") -//│ = MatchResult("000") +//│ = MatchResult("000", {}) :expect true "000" is TripleZero diff --git a/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls b/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls index f5933f6c5c..00cec0fa51 100644 --- a/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls +++ b/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls @@ -5,10 +5,10 @@ import "../../mlscript-compile/Runtime.mls" open Runtime { MatchResult, MatchFailure } -MatchResult(0) -//│ = MatchResult(0) +MatchResult(0, ()) +//│ = MatchResult(0, ()) -if MatchResult(0) is MatchResult(x) then x +if MatchResult(0) is MatchResult(x, _) then x //│ = 0 MatchFailure("oops") @@ -25,10 +25,10 @@ MatchResult(0) pattern Cross = "X" Cross.unapply("X") -//│ = MatchResult("X") +//│ = MatchResult("X", {}) Cross.unapply("0") -//│ = MatchFailure(undefined) +//│ = MatchFailure(null) :sjs fun foo(x) = x is Cross diff --git a/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls b/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls index 36ed3a1e0e..0e3b6e4c26 100644 --- a/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls @@ -55,17 +55,11 @@ pattern UnsignedByte = 0..< 256 //│ ╔══[ERROR] Name not found: .< //│ ║ l.51: pattern UnsignedByte = 0..< 256 //│ ╙── ^^ -//│ ╔══[ERROR] Name not found: .< -//│ ║ l.51: pattern UnsignedByte = 0..< 256 -//│ ╙── ^^ -//│ ╔══[ERROR] Name not found: .< -//│ ║ l.51: pattern UnsignedByte = 0..< 256 -//│ ╙── ^^ :e pattern BadRange = "s"..=0 //│ ╔══[ERROR] The upper and lower bounds of range patterns should be literals of the same type. -//│ ║ l.66: pattern BadRange = "s"..=0 +//│ ║ l.60: pattern BadRange = "s"..=0 //│ ╙── ^^^^^^^ // It becomes an absurd pattern. @@ -75,14 +69,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.76: pattern BadRange = 0 ..= "s" +//│ ║ l.70: pattern BadRange = 0 ..= "s" //│ ╙── ^^^^^^^^^ :e pattern BadRange = "yolo" ..= "swag" -//│ ╔══[ERROR] String range bounds must have only one character. -//│ ║ l.82: pattern BadRange = "yolo" ..= "swag" +//│ ╔══[ERROR] The lower bound of character ranges must be a single character. +//│ ║ l.76: pattern BadRange = "yolo" ..= "swag" //│ ║ ^^^^^^ -//│ ╟── String range bounds must have only one character. -//│ ║ l.82: pattern BadRange = "yolo" ..= "swag" +//│ ╟── The upper bound of character ranges must be a single character. +//│ ║ l.76: pattern BadRange = "yolo" ..= "swag" //│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/rp/Simple.mls b/hkmc2/shared/src/test/mlscript/rp/Simple.mls index c160d31077..33837c5734 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Simple.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Simple.mls @@ -11,9 +11,9 @@ pattern Zero = "0" //│ constructor() {} //│ unapply(input) { //│ if (input === "0") { -//│ return runtime.MatchResult(input) +//│ return runtime.MatchResult(input, {}) //│ } else { -//│ return runtime.MatchFailure() +//│ return runtime.MatchFailure(null) //│ } //│ } //│ unapplyStringPrefix(topic) { @@ -23,9 +23,9 @@ pattern Zero = "0" //│ sliced = runtime.Str.drop(topic, 1); //│ return runtime.MatchResult([ //│ sliced -//│ ]) +//│ ], {}) //│ } else { -//│ return runtime.MatchFailure() +//│ return runtime.MatchFailure(null) //│ } //│ } //│ toString() { return "Zero"; } diff --git a/hkmc2/shared/src/test/mlscript/rp/SimpleConjunction.mls b/hkmc2/shared/src/test/mlscript/rp/SimpleConjunction.mls new file mode 100644 index 0000000000..9f30150729 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/SimpleConjunction.mls @@ -0,0 +1,106 @@ +:js + +import "../../mlscript-compile/Option.mls" + +open Option { Some, None } + +open annotations + +pattern OverlappedIntervals = (0 ..= 2) & (1 ..= 3) + +fun test(n) = n is @compile OverlappedIntervals + +print of "0:", test of 0 +print of "1:", test of 1 +print of "2:", test of 2 +print of "3:", test of 3 +print of "42:", test of 42 +//│ > 0: false +//│ > 1: true +//│ > 2: true +//│ > 3: false +//│ > 42: false + +fun show(n) = if n is (@compile OverlappedIntervals) as v then Some(v) else None + +print of "0:", show of 0 +print of "1:", show of 1 +print of "2:", show of 2 +print of "3:", show of 3 +print of "42:", show of 42 +//│ > 0: None +//│ > 1: Some(1) +//│ > 2: Some(2) +//│ > 3: None +//│ > 42: None + +data class A(x: Int) +data class B(x: Int) + +pattern BothTransformedConjunction = + (((0 ..= 2) as x) => A(x)) & + (((1 ..= 3) as y) => B(y)) + +fun show(n) = if n is (@compile BothTransformedConjunction) as v then Some(v) else None + +print of "0:", show of 0 +print of "1:", show of 1 +print of "2:", show of 2 +print of "3:", show of 3 +print of "42:", show of 42 +//│ > 0: None +//│ > 1: Some([A(1), B(1)]) +//│ > 2: Some([A(2), B(2)]) +//│ > 3: None +//│ > 42: None + +pattern RightTransformedConjunction = + (0 ..= 2) & + (((1 ..= 3) as y) => B(y)) + +fun show(n) = if n is (@compile RightTransformedConjunction) as v then Some(v) else None + +print of "0:", show of 0 +print of "1:", show of 1 +print of "2:", show of 2 +print of "3:", show of 3 +print of "42:", show of 42 +//│ > 0: None +//│ > 1: Some([1, B(1)]) +//│ > 2: Some([2, B(2)]) +//│ > 3: None +//│ > 42: None + +pattern LeftTransformedConjunction = + (((0 ..= 2) as x) => A(x)) & + (1 ..= 3) + +fun show(n) = if n is (@compile LeftTransformedConjunction) as v then Some(v) else None + +print of "0:", show of 0 +print of "1:", show of 1 +print of "2:", show of 2 +print of "3:", show of 3 +print of "42:", show of 42 +//│ > 0: None +//│ > 1: Some([A(1), 1]) +//│ > 2: Some([A(2), 2]) +//│ > 3: None +//│ > 42: None + +// We can statically reduce this pattern to bottom. +pattern ImpossibleIntervals = (0 ..= 2) & (3 ..= 4) + +// From the generated code we can see that all cases lead to match failure. +fun test(n) = n is @compile ImpossibleIntervals + +// This pattern is equivalent to bottom but we can't statically reduce it. +pattern UnionRecords = (a: 1, b: 2) & (a: 3, b: 4) + +fun test(rcd) = rcd is @compile UnionRecords + +test of (a: 1, b: 2) +//│ = false + +test of (a: 3, b: 4) +//│ = false diff --git a/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls b/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls index 2658572ab6..55775f71f4 100644 --- a/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls +++ b/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls @@ -1,5 +1,11 @@ :js +import "../../mlscript-compile/Option.mls" + +open Option { Some, None } + +open annotations + data class Pair[A, B](first: A, second: B) pattern SumPair = Pair(a, b) => a + b @@ -36,6 +42,21 @@ get_output_using_as of 2 //│ > 1 is equivalent to true //│ > 2 is equivalent to true +fun get_output_using_as'(value) = if value is + (@compile BoolLike) as b then print("" + value + " is equivalent to " + b) + else print("the input " + value + " is not a boolean") + +get_output_using_as' of true +get_output_using_as' of false +get_output_using_as' of 0 +get_output_using_as' of 1 +get_output_using_as' of 2 +//│ > true is equivalent to true +//│ > false is equivalent to false +//│ > 0 is equivalent to false +//│ > 1 is equivalent to true +//│ > 2 is equivalent to true + fun get_output_through_param(value) = if value is BoolLike(b) then print("" + value + " is equivalent to " + b) else print("the input " + value + " is not a boolean") @@ -106,6 +127,31 @@ Prefix("-", 10 +:+ 2 *:* 3) is EvaluatedTerm(-16) Infix("**", Literal(1), Literal(2)) is EvaluatedTerm //│ = false +fun evaluate(term) = if term is + (@compile EvaluatedTerm) as v then Some(v) + else None + +evaluate of Literal(1) +//│ = Some(1) + +evaluate of Literal(1) +//│ = Some(1) + +evaluate of 2 *:* 3 +:+ 1 +//│ = Some(7) + +evaluate of (4 +:+ 5) *:* (6 -:- 2) +//│ = Some(36) + +evaluate of Prefix("-", 10 +:+ 2 *:* 3) +//│ = Some(-16) + +evaluate of (8 /:/ 2) +:+ (3 *:* 4) -:- 5 +//│ = Some(11) + +evaluate of Infix("**", Literal(1), Literal(2)) +//│ = None + pattern DropZ = ((:x, :y, :z)) => (x: x, y: y) (x: 1, y: 2, z: 3) is DropZ((x: 1, y: 2)) @@ -144,7 +190,6 @@ pattern PairLike = [_, _] | Pair(_, _) Pair(1, 2) is PairLike //│ = true -:todo pattern TransformTwice = (((Int as x) => x + 4) as y) => y * 2 2 is TransformTwice(12) @@ -156,6 +201,18 @@ pattern TransformTwice = (((Int as x) => x + 4) as y) => y * 2 17 is TransformTwice(42) //│ = true +fun testTransformTwice(x) = + if x is (@compile TransformTwice) as v then Some(v) else None + +testTransformTwice of 100 +//│ = Some(208) + +testTransformTwice of 2 +//│ = Some(12) + +testTransformTwice of 17 +//│ = Some(42) + // We can easily define predicative patterns using `as true`. pattern Positive = ((Int as x) => x > 0) as true @@ -168,10 +225,10 @@ print of 8080 is Positive // Hmm, but how to make the pattern return the input value? Positive.unapply(100) -//│ = MatchResult(true) +//│ = MatchResult(true, {}) // Just use conjunction patterns. pattern Positive = (x & (((Int as x) => x > 0) as true)) => x Positive.unapply(100) -//│ = MatchResult(100) +//│ = MatchResult(100, {}) diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Computation.mls b/hkmc2/shared/src/test/mlscript/rp/examples/Computation.mls index 3a6de374e6..a66694e10d 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/Computation.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/Computation.mls @@ -1,5 +1,7 @@ :js +open annotations + data class Celsius(value: Num) data class Fahrenheit(value: Num) @@ -15,7 +17,6 @@ pattern ToCelsius = Celsius(0) is ToFahrenheit(Fahrenheit(32)) //│ = true - Fahrenheit(212) is ToCelsius(Celsius(100)) //│ = true @@ -31,6 +32,33 @@ Celsius(-40) is ToFahrenheit(Fahrenheit(-40)) Fahrenheit(-40) is ToCelsius(Celsius(-40)) //│ = true +fun toFahrenheit(c) = + if c is (@compile ToFahrenheit) as output then output else "What's this?" + +toFahrenheit of Celsius(100) +//│ = Fahrenheit(212) + +toFahrenheit of Fahrenheit(100) +//│ = Fahrenheit(100) + +toFahrenheit of Celsius(-40) +//│ = Fahrenheit(-40) + +toFahrenheit of "Cheers!" +//│ = "What's this?" + +fun toCelsius(f) = + if f is (@compile ToCelsius) as output then output else "What's this?" + +toCelsius of Fahrenheit(100) +//│ = Celsius(37.77777777777778) + +toCelsius of Celsius(100) +//│ = Celsius(100) + +toCelsius of "Cheers!" +//│ = "What's this?" + pattern HourAM = (0 => 12) | (1 ..= 11) pattern HourPM = 12 | (((13 ..= 23) as hour) => hour - 12) pattern Minute = ((0 ..= 59) as minute) => minute.toString().padStart(2, "0") @@ -53,18 +81,29 @@ print of "I should go to bed before", formatTime of hour: 23, minute: 0 //│ > I should go to bed before 11:00 PM // This makes use of the chain pattern. It generates 29,232 lines of code! -// pattern Time12Hour = (( -// hour: (( -// ((0 ..= 11) => "AM") | -// ((12 ..= 23) => "PM") -// ) & ( -// (0 => 12) | -// (1 ..= 11) | -// 12 | -// ((13 ..= 23) as h => h - 12) -// )) as [hour, flag], -// minute: Minute as minute -// )) => hour + ":" + minute + " " + flag +pattern Time12Hour = (( + hour: (( + ((0 ..= 11) => "AM") | + ((12 ..= 23) => "PM") + ) & ( + (0 => 12) | + (1 ..= 11) | + 12 | + ((13 ..= 23) as h => h - 12) + )) as [hour, flag], + minute: Minute as minute +)) => hour + ":" + minute + " " + flag + +fun formatTime(ps) = if ps is Time12Hour as time then time + +print of "I had my breakfast at", formatTime of hour: 7, minute: 45 +print of "Let's have lunch at", formatTime of hour: 12, minute: 30 +print of "I plan to have dinner at", formatTime of hour: 18, minute: 45 +print of "I should go to bed before", formatTime of hour: 23, minute: 0 +//│ > I had my breakfast at AM:45 7 +//│ > Let's have lunch at PM:30 12 +//│ > I plan to have dinner at PM:45 6 +//│ > I should go to bed before PM:00 11 pattern Channel = 0 ..= 255 diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/DoubleOrSum.mls b/hkmc2/shared/src/test/mlscript/rp/examples/DoubleOrSum.mls new file mode 100644 index 0000000000..b5eb09ec46 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/examples/DoubleOrSum.mls @@ -0,0 +1,167 @@ +:js + +open annotations + +import "../../../mlscript-compile/Stack.mls" +import "../../../mlscript-compile/Iter.mls" +import "../../../mlscript-compile/Option.mls" + +open Stack +open Option { Some, None } + +fun list(...elements) = elements Iter.toStack() + +// This is a helper function to print the addition of two numbers. +fun (++) addWithPrint(x, y) = + let result = x + y + print of "" + x + " + " + y + " = " + result + result + +pattern DoubleList = Nil | (Int :: Int :: DoubleList) + +pattern SumList = (Nil => 0) | (((Int as hd) :: (SumList as tl)) => hd ++ tl) + +Nil is SumList(0) +//│ = true + +let _1234 = list(1, 2, 3, 4) +let _1234567 = list(1, 2, 3, 4, 5, 6, 7) +//│ _1234 = Cons(1, Cons(2, Cons(3, Cons(4, Nil)))) +//│ _1234567 = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Cons(7, Nil))))))) + +_1234 is SumList(10) +//│ > 4 + 0 = 4 +//│ > 3 + 4 = 7 +//│ > 2 + 7 = 9 +//│ > 1 + 9 = 10 +//│ = true + +_1234567 is SumList(28) +//│ > 7 + 0 = 7 +//│ > 6 + 7 = 13 +//│ > 5 + 13 = 18 +//│ > 4 + 18 = 22 +//│ > 3 + 22 = 25 +//│ > 2 + 25 = 27 +//│ > 1 + 27 = 28 +//│ = true + +_1234567 is SumList(42) +//│ > 7 + 0 = 7 +//│ > 6 + 7 = 13 +//│ > 5 + 13 = 18 +//│ > 4 + 18 = 22 +//│ > 3 + 22 = 25 +//│ > 2 + 25 = 27 +//│ > 1 + 27 = 28 +//│ = false + +_1234 is DoubleList +//│ = true + +_1234567 is DoubleList +//│ = false + +fun sum(xs) = if xs is SumList as output then Some(output) else None + +sum of _1234 +//│ > 4 + 0 = 4 +//│ > 3 + 4 = 7 +//│ > 2 + 7 = 9 +//│ > 1 + 9 = 10 +//│ = Some(10) + +sum of _1234567 +//│ > 7 + 0 = 7 +//│ > 6 + 7 = 13 +//│ > 5 + 13 = 18 +//│ > 4 + 18 = 22 +//│ > 3 + 22 = 25 +//│ > 2 + 25 = 27 +//│ > 1 + 27 = 28 +//│ = Some(28) + +sum of Nil +//│ = Some(0) + +// Try to make a disjunction of two patterns. + +pattern SumOrDouble = SumList | DoubleList + +fun isSumOrDouble__translated(xs) = if xs is SumOrDouble as output then output else "noop" + +isSumOrDouble__translated of _1234 +//│ > 4 + 0 = 4 +//│ > 3 + 4 = 7 +//│ > 2 + 7 = 9 +//│ > 1 + 9 = 10 +//│ = 10 + +isSumOrDouble__translated of _1234567 +//│ > 7 + 0 = 7 +//│ > 6 + 7 = 13 +//│ > 5 + 13 = 18 +//│ > 4 + 18 = 22 +//│ > 3 + 22 = 25 +//│ > 2 + 25 = 27 +//│ > 1 + 27 = 28 +//│ = 28 + +isSumOrDouble__translated of Nil +//│ = 0 + +fun isSumOrDouble__compiled(xs) = if xs is (@compile SumOrDouble) as output then output else "noop" + +isSumOrDouble__compiled of _1234 +//│ > 4 + 0 = 4 +//│ > 3 + 4 = 7 +//│ > 2 + 7 = 9 +//│ > 1 + 9 = 10 +//│ = 10 + +isSumOrDouble__compiled of _1234567 +//│ > 7 + 0 = 7 +//│ > 6 + 7 = 13 +//│ > 5 + 13 = 18 +//│ > 4 + 18 = 22 +//│ > 3 + 22 = 25 +//│ > 2 + 25 = 27 +//│ > 1 + 27 = 28 +//│ = 28 + +isSumOrDouble__compiled of Nil +//│ = 0 + +// Swap the order of two patterns. + +pattern DoubleOrSum = DoubleList | SumList + +fun isDoubleOrSum__translated(xs) = if xs is DoubleOrSum as output then output else "noop" + +:todo // Fix the return value of the translation. +isDoubleOrSum__translated of _1234 +//│ = Nil + +isDoubleOrSum__translated of list(1, 2, 3) +//│ > 3 + 0 = 3 +//│ > 2 + 3 = 5 +//│ > 1 + 5 = 6 +//│ = 6 + +fun isDoubleOrSum__compiled(xs) = if xs is (@compile DoubleOrSum) as output then output else "noop" + +isDoubleOrSum__compiled of _1234 +//│ > 4 + 0 = 4 +//│ > 3 + 4 = 7 +//│ > 2 + 7 = 9 +//│ = {head: 1, tail: {head: 2, tail: {head: 3, tail: {head: 4, tail: Nil}}}} + +isDoubleOrSum__compiled of _1234567 +//│ > 7 + 0 = 7 +//│ > 6 + 7 = 13 +//│ > 5 + 13 = 18 +//│ > 4 + 18 = 22 +//│ > 3 + 22 = 25 +//│ > 2 + 25 = 27 +//│ > 1 + 27 = 28 +//│ = 28 diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/DoubleTripleList.mls b/hkmc2/shared/src/test/mlscript/rp/examples/DoubleTripleList.mls new file mode 100644 index 0000000000..6fdb6489ca --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/examples/DoubleTripleList.mls @@ -0,0 +1,360 @@ +:js + +open annotations + +import "../../../mlscript-compile/Stack.mls" +import "../../../mlscript-compile/Iter.mls" + +open Stack + +fun list(...elements) = elements Iter.toStack() + +// The example of lists whose length is a multiple of two. + +// Note that the parentheses are necessary. +pattern DoubleList = Nil | (Int :: Int :: DoubleList) + +list(1, 2, 3, 4) is DoubleList +//│ = true + +list(1, 2, 3) is DoubleList +//│ = false + +Nil is DoubleList +//│ = true + +fun isDoubleList(xs) = xs is @compile DoubleList + +isDoubleList of list(1, 2, 3, 4) +//│ = true + +isDoubleList of list(1, 2, 3) +//│ = false + +isDoubleList of Nil +//│ = true + +// The example of lists whose length is a multiple of three. + +// Note that the parentheses are necessary. +pattern TripleList = Nil | (Int :: Int :: Int :: TripleList) + +list(1, 2, 3, 4, 5, 6) is TripleList +//│ = true + +list(1, 2, 3) is TripleList +//│ = true + +list(1, 2, 3, 4) is TripleList +//│ = false + +Nil is TripleList +//│ = true + +fun isTripleList(xs) = xs is @compile TripleList + +isTripleList of list(1, 2, 3, 4, 5, 6) +//│ = true + +isTripleList of list(1, 2, 3) +//│ = true + +isTripleList of list(1, 2, 3, 4) +//│ = false + +isTripleList of Nil +//│ = true + +pattern DoubleTripleList = DoubleList | TripleList + +// Make a `Cons` object and print a message whenever the `head` or `tail` +// property is accessed. +let doLog = false +fun (::) makeObservableCons(x, xs) = + let cons = Cons(x, xs) + let repr = "[" + (cons Iter.fromStack() Iter.joined of ", ") + "]" + Object.defineProperty of + cons + "head" + enumerable: true + configurable: false + get: () => + if doLog do print("access the `head` of " + repr) + x + Object.defineProperty of + cons + "tail" + enumerable: true + configurable: false + get: () => + if doLog do print("access the `tail` of " + repr) + xs + cons +//│ doLog = false + +fun withLog(f) = + set doLog = true + val result = f() + set doLog = false + result + +let testList = 1 :: 2 :: Nil +//│ testList = Cons(1, Cons(2, Nil)) + +withLog of () => + testList.head + testList.tail + () +//│ > access the `head` of [1, 2] +//│ > access the `tail` of [1, 2] + +fun list(...elements) = elements Iter.rightFolded of Nil, (::) + +fun isDoubleTripleList__compiled(xs) = withLog of + () => xs is @compile DoubleTripleList + +isDoubleTripleList__compiled of list(1, 2, 3) +//│ > access the `head` of [1, 2, 3] +//│ > access the `tail` of [1, 2, 3] +//│ > access the `head` of [2, 3] +//│ > access the `tail` of [2, 3] +//│ > access the `head` of [3] +//│ > access the `tail` of [3] +//│ = true + +isDoubleTripleList__compiled of list(1, 2, 3, 4) +//│ > access the `head` of [1, 2, 3, 4] +//│ > access the `tail` of [1, 2, 3, 4] +//│ > access the `head` of [2, 3, 4] +//│ > access the `tail` of [2, 3, 4] +//│ > access the `head` of [3, 4] +//│ > access the `tail` of [3, 4] +//│ > access the `head` of [4] +//│ > access the `tail` of [4] +//│ = true + +isDoubleTripleList__compiled of list(1, 2, 3, 4, 5) +//│ > access the `head` of [1, 2, 3, 4, 5] +//│ > access the `tail` of [1, 2, 3, 4, 5] +//│ > access the `head` of [2, 3, 4, 5] +//│ > access the `tail` of [2, 3, 4, 5] +//│ > access the `head` of [3, 4, 5] +//│ > access the `tail` of [3, 4, 5] +//│ > access the `head` of [4, 5] +//│ > access the `tail` of [4, 5] +//│ > access the `head` of [5] +//│ > access the `tail` of [5] +//│ = false + +isDoubleTripleList__compiled of list(1, 2, 3, 4, 5, 6, 7, 8, 9) +//│ > access the `head` of [1, 2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [1, 2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `head` of [2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `head` of [3, 4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [3, 4, 5, 6, 7, 8, 9] +//│ > access the `head` of [4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [4, 5, 6, 7, 8, 9] +//│ > access the `head` of [5, 6, 7, 8, 9] +//│ > access the `tail` of [5, 6, 7, 8, 9] +//│ > access the `head` of [6, 7, 8, 9] +//│ > access the `tail` of [6, 7, 8, 9] +//│ > access the `head` of [7, 8, 9] +//│ > access the `tail` of [7, 8, 9] +//│ > access the `head` of [8, 9] +//│ > access the `tail` of [8, 9] +//│ > access the `head` of [9] +//│ > access the `tail` of [9] +//│ = true + +isDoubleTripleList__compiled of Nil +//│ = true + +fun isDoubleTripleList__translated(xs) = + set doLog = true + let result = xs is DoubleTripleList + set doLog = false + result + +// It does not backtracking because the `DoubleList` is tested first. +isDoubleTripleList__translated of list(1, 2, 3, 4) +//│ > access the `head` of [1, 2, 3, 4] +//│ > access the `tail` of [1, 2, 3, 4] +//│ > access the `head` of [2, 3, 4] +//│ > access the `tail` of [2, 3, 4] +//│ > access the `head` of [3, 4] +//│ > access the `tail` of [3, 4] +//│ > access the `head` of [4] +//│ > access the `tail` of [4] +//│ = true + +// It does backtracking when `DoubleList` does not match. +isDoubleTripleList__translated of list(1, 2, 3) +//│ > access the `head` of [1, 2, 3] +//│ > access the `tail` of [1, 2, 3] +//│ > access the `head` of [2, 3] +//│ > access the `tail` of [2, 3] +//│ > access the `head` of [3] +//│ > access the `tail` of [3] +//│ > access the `head` of [1, 2, 3] +//│ > access the `tail` of [1, 2, 3] +//│ > access the `head` of [2, 3] +//│ > access the `tail` of [2, 3] +//│ > access the `head` of [3] +//│ > access the `tail` of [3] +//│ = true + +isDoubleTripleList__translated of list(1, 2, 3, 4, 5, 6, 7, 8, 9) +//│ > access the `head` of [1, 2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [1, 2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `head` of [2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `head` of [3, 4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [3, 4, 5, 6, 7, 8, 9] +//│ > access the `head` of [4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [4, 5, 6, 7, 8, 9] +//│ > access the `head` of [5, 6, 7, 8, 9] +//│ > access the `tail` of [5, 6, 7, 8, 9] +//│ > access the `head` of [6, 7, 8, 9] +//│ > access the `tail` of [6, 7, 8, 9] +//│ > access the `head` of [7, 8, 9] +//│ > access the `tail` of [7, 8, 9] +//│ > access the `head` of [8, 9] +//│ > access the `tail` of [8, 9] +//│ > access the `head` of [9] +//│ > access the `tail` of [9] +//│ > access the `head` of [1, 2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [1, 2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `head` of [2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [2, 3, 4, 5, 6, 7, 8, 9] +//│ > access the `head` of [3, 4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [3, 4, 5, 6, 7, 8, 9] +//│ > access the `head` of [4, 5, 6, 7, 8, 9] +//│ > access the `tail` of [4, 5, 6, 7, 8, 9] +//│ > access the `head` of [5, 6, 7, 8, 9] +//│ > access the `tail` of [5, 6, 7, 8, 9] +//│ > access the `head` of [6, 7, 8, 9] +//│ > access the `tail` of [6, 7, 8, 9] +//│ > access the `head` of [7, 8, 9] +//│ > access the `tail` of [7, 8, 9] +//│ > access the `head` of [8, 9] +//│ > access the `tail` of [8, 9] +//│ > access the `head` of [9] +//│ > access the `tail` of [9] +//│ = true + +pattern DoubleAndTripleList = DoubleList & TripleList + +fun isDoubleAndTriple__translated(xs) = withLog of + () => xs is DoubleAndTripleList + +isDoubleAndTriple__translated of list(8, 9) +//│ > access the `head` of [8, 9] +//│ > access the `tail` of [8, 9] +//│ > access the `head` of [9] +//│ > access the `tail` of [9] +//│ > access the `head` of [8, 9] +//│ > access the `tail` of [8, 9] +//│ > access the `head` of [9] +//│ > access the `tail` of [9] +//│ = false + +isDoubleAndTriple__translated of list(1, 2, 3) +//│ > access the `head` of [1, 2, 3] +//│ > access the `tail` of [1, 2, 3] +//│ > access the `head` of [2, 3] +//│ > access the `tail` of [2, 3] +//│ > access the `head` of [3] +//│ > access the `tail` of [3] +//│ = false + +isDoubleAndTriple__translated of list(4, 3, 2, 1) +//│ > access the `head` of [4, 3, 2, 1] +//│ > access the `tail` of [4, 3, 2, 1] +//│ > access the `head` of [3, 2, 1] +//│ > access the `tail` of [3, 2, 1] +//│ > access the `head` of [2, 1] +//│ > access the `tail` of [2, 1] +//│ > access the `head` of [1] +//│ > access the `tail` of [1] +//│ > access the `head` of [4, 3, 2, 1] +//│ > access the `tail` of [4, 3, 2, 1] +//│ > access the `head` of [3, 2, 1] +//│ > access the `tail` of [3, 2, 1] +//│ > access the `head` of [2, 1] +//│ > access the `tail` of [2, 1] +//│ > access the `head` of [1] +//│ > access the `tail` of [1] +//│ = false + +isDoubleAndTriple__translated of list(1, 2, 3, 4, 5, 6) +//│ > access the `head` of [1, 2, 3, 4, 5, 6] +//│ > access the `tail` of [1, 2, 3, 4, 5, 6] +//│ > access the `head` of [2, 3, 4, 5, 6] +//│ > access the `tail` of [2, 3, 4, 5, 6] +//│ > access the `head` of [3, 4, 5, 6] +//│ > access the `tail` of [3, 4, 5, 6] +//│ > access the `head` of [4, 5, 6] +//│ > access the `tail` of [4, 5, 6] +//│ > access the `head` of [5, 6] +//│ > access the `tail` of [5, 6] +//│ > access the `head` of [6] +//│ > access the `tail` of [6] +//│ > access the `head` of [1, 2, 3, 4, 5, 6] +//│ > access the `tail` of [1, 2, 3, 4, 5, 6] +//│ > access the `head` of [2, 3, 4, 5, 6] +//│ > access the `tail` of [2, 3, 4, 5, 6] +//│ > access the `head` of [3, 4, 5, 6] +//│ > access the `tail` of [3, 4, 5, 6] +//│ > access the `head` of [4, 5, 6] +//│ > access the `tail` of [4, 5, 6] +//│ > access the `head` of [5, 6] +//│ > access the `tail` of [5, 6] +//│ > access the `head` of [6] +//│ > access the `tail` of [6] +//│ = true + +fun isDoubleAndTriple__compiled(xs) = withLog of + () => xs is @compile DoubleAndTripleList + +isDoubleAndTriple__compiled of list(8, 9) +//│ > access the `head` of [8, 9] +//│ > access the `tail` of [8, 9] +//│ > access the `head` of [9] +//│ > access the `tail` of [9] +//│ = false + +isDoubleAndTriple__compiled of list(1, 2, 3) +//│ > access the `head` of [1, 2, 3] +//│ > access the `tail` of [1, 2, 3] +//│ > access the `head` of [2, 3] +//│ > access the `tail` of [2, 3] +//│ > access the `head` of [3] +//│ > access the `tail` of [3] +//│ = false + +isDoubleAndTriple__compiled of list(4, 3, 2, 1) +//│ > access the `head` of [4, 3, 2, 1] +//│ > access the `tail` of [4, 3, 2, 1] +//│ > access the `head` of [3, 2, 1] +//│ > access the `tail` of [3, 2, 1] +//│ > access the `head` of [2, 1] +//│ > access the `tail` of [2, 1] +//│ > access the `head` of [1] +//│ > access the `tail` of [1] +//│ = false + +isDoubleAndTriple__compiled of list(1, 2, 3, 4, 5, 6) +//│ > access the `head` of [1, 2, 3, 4, 5, 6] +//│ > access the `tail` of [1, 2, 3, 4, 5, 6] +//│ > access the `head` of [2, 3, 4, 5, 6] +//│ > access the `tail` of [2, 3, 4, 5, 6] +//│ > access the `head` of [3, 4, 5, 6] +//│ > access the `tail` of [3, 4, 5, 6] +//│ > access the `head` of [4, 5, 6] +//│ > access the `tail` of [4, 5, 6] +//│ > access the `head` of [5, 6] +//│ > access the `tail` of [5, 6] +//│ > access the `head` of [6] +//│ > access the `tail` of [6] +//│ = true diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Extraction.mls b/hkmc2/shared/src/test/mlscript/rp/examples/Extraction.mls new file mode 100644 index 0000000000..3ff2af4369 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/examples/Extraction.mls @@ -0,0 +1,146 @@ +:js + +import "../../../mlscript-compile/Option.mls" + +open Option { Some, None } +open annotations + +pattern Itch = Some(Some(Some(Some(Some(Some(Some(42))))))) + +fun isItch(x) = x is @compile Itch + +:expect true +isItch of Some(Some(Some(Some(Some(Some(Some(42))))))) +//│ = true + +:expect false +isItch of Some(Some(Some(Some(Some(Some(Some(Some(42)))))))) +//│ = false + +:expect false +isItch of 42 +//│ = false + +pattern Itch' = Some(Some(Some(Some(Some("scratch" as x))))) => x + +fun action(x) = + if x is (@compile Itch') as output then + "Just " + output + "!" + else + "I don't know." + +action of Some(Some(Some(Some(Some("scratch"))))) +//│ = "Just scratch!" + +// Test the semantics of biased disjunction. + +pattern BiasedOr1 = Some(1 | Some(_)) + +fun biasedOr1(x) = x is @compile BiasedOr1 + +biasedOr1 of Some(1) +//│ = true + +biasedOr1 of Some(42) +//│ = false + +biasedOr1 of Some(Some(2)) +//│ = true + +biasedOr1 of Some(Some("hello")) +//│ = true + +biasedOr1 of Some(Some(Some(Some(Some(Some(Some(Some(42)))))))) +//│ = true + +// Make it a bit more complicated: biased disjunction with transformations. + +pattern BiasedOr2 = Some(Some(x) => x + 1) | Some(x => x + 2) + +fun biasedOr2__compiled(x) = if x is (@compile BiasedOr2) as output then output + +fun biasedOr2__translated(x) = if x is BiasedOr2(output) then output + +biasedOr2__compiled of Some(1) +//│ = {value: 3} + +biasedOr2__translated of Some(1) +//│ = 3 + +biasedOr2__compiled of Some(Some(1)) +//│ = {value: 2} + +biasedOr2__translated of Some(Some(1)) +//│ = 2 + +// Comments: I think the output from the compiled pattern is semantically +// correct, but people might expect the output from the translated pattern. +// The problem is that we can't synthesize a record with the class tags. + +data class Pair[A, B](first: A, second: B) + +pattern XorPair = Pair(None, Some(_)) | Pair(Some(_), None) + +fun xorPair(x) = if x is (@compile XorPair) as output then output else "failed" + +xorPair of Pair(None, None) +//│ = "failed" + +xorPair of Pair(None, Some(1)) +//│ = {first: None, second: {value: 1}} + +xorPair of Pair(Some(56), None) +//│ = {first: {value: 56}, second: None} + +data class Triplet[A, B, C](first: A, second: B, third: C) + +pattern ExactlyOneNone = + Triplet(None, Some(_), Some(_)) | + Triplet(Some(_), None, Some(_)) | + Triplet(Some(_), Some(_), None ) + +fun exactlyOneNone(x) = + if x is (@compile ExactlyOneNone) as output then output else "failed" + +fun exactlyOneNone__translated(x) = + if x is ExactlyOneNone(output) then output else "failed" + +exactlyOneNone of Triplet(None, Some(1), Some(2)) +//│ = {first: None, second: {value: 1}, third: {value: 2}} + +exactlyOneNone of Triplet(Some(1), None, Some(2)) +//│ = {first: {value: 1}, second: None, third: {value: 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. + +exactlyOneNone__translated of Triplet(None, Some(1), Some(100)) +//│ = 100 + +exactlyOneNone__translated of Triplet(Some(1), None, Some(400)) +//│ = 400 + +exactlyOneNone__translated of Triplet(Some(1), Some(2), None) +//│ = None + +:expect "failed" +exactlyOneNone of Triplet(None, Some(1), None) +//│ = "failed" + +:expect "failed" +exactlyOneNone of Triplet(None, None, Some(1)) +//│ = "failed" + +:expect "failed" +exactlyOneNone of Triplet(Some(1), None, None) +//│ = "failed" + +:expect "failed" +exactlyOneNone of Triplet(None, None, None) +//│ = "failed" + +:expect "failed" +exactlyOneNone of Triplet(Some(1), Some(2), Some(3)) +//│ = "failed" diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Flatten.mls b/hkmc2/shared/src/test/mlscript/rp/examples/Flatten.mls new file mode 100644 index 0000000000..90dfd50b35 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/examples/Flatten.mls @@ -0,0 +1,64 @@ +:js + +open annotations + +import "../../../mlscript-compile/Stack.mls" +import "../../../mlscript-compile/Iter.mls" + +open Stack + +fun list(...elements) = elements Iter.toStack() + +// This pattern can flatten nested lists. It tries to flatten the head and +// concatenate the output with the tail. If the head cannot be flattened, it +// would just append the head to the flattened tail. +pattern Flatten = + (Nil => Nil) | + (((Flatten as hd) :: (Flatten as tl)) => hd ::: tl) | + ((hd :: (Flatten as tl)) => hd :: tl) + +fun flatten__translated(xs) = + if xs is Flatten as ys then + print of "Flattened: [" + (ys Iter.fromStack() Iter.joined(", ")) + "]" + else "Cannot flatten: " + xs + +flatten__translated of Nil +//│ > Flattened: [] + +flatten__translated of list(1) +//│ > Flattened: [1] + +flatten__translated of list(1, 2, 3, 4) +//│ > Flattened: [1, 2, 3, 4] + +flatten__translated of list(list(1, 2), list(3, 4)) +//│ > Flattened: [1, 2, 3, 4] + +flatten__translated of list(list(list(list(1, 2, 3, 4)))) +//│ > Flattened: [1, 2, 3, 4] + +flatten__translated of list(list(1, 2), list(list(3, 4), list(list(5, 6)))) +//│ > Flattened: [1, 2, 3, 4, 5, 6] + +fun flatten__compiled(xs) = + if xs is (@compile Flatten) as ys then + print of "Flattened: [" + (ys Iter.fromStack() Iter.joined(", ")) + "]" + else "Cannot flatten: " + xs + +flatten__compiled of Nil +//│ > Flattened: [] + +flatten__compiled of list(1) +//│ > Flattened: [1] + +flatten__compiled of list(1, 2, 3, 4) +//│ > Flattened: [1, 2, 3, 4] + +flatten__compiled of list(list(1, 2), list(3, 4)) +//│ > Flattened: [1, 2, 3, 4] + +flatten__compiled of list(list(list(list(1, 2, 3, 4)))) +//│ > Flattened: [1, 2, 3, 4] + +flatten__compiled of list(list(1, 2), list(list(3, 4), list(list(5, 6)))) +//│ > Flattened: [1, 2, 3, 4, 5, 6] diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Identifier.mls b/hkmc2/shared/src/test/mlscript/rp/examples/Identifier.mls index e0a5863ca9..ce67f4f512 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/Identifier.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/Identifier.mls @@ -1,5 +1,11 @@ :js +open annotations + +fun check(pred, what) = Array.from(length: 10).forEach of (_, i, _) => + let n = i.toString() + print of n, (if pred(n) then "is" else "is not"), what + pattern Zero = "0" :expect true @@ -8,6 +14,21 @@ pattern Zero = "0" pattern Binary = "0" | "1" +// Currently, we expand range patterns into disjunction. +fun isBinary(x) = x is @compile Binary + +check of isBinary, "binary" +//│ > 0 is binary +//│ > 1 is binary +//│ > 2 is not binary +//│ > 3 is not binary +//│ > 4 is not binary +//│ > 5 is not binary +//│ > 6 is not binary +//│ > 7 is not binary +//│ > 8 is not binary +//│ > 9 is not binary + :expect false "2" is Binary //│ = false @@ -26,6 +47,20 @@ pattern Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" "a" is Digit //│ = false +fun isDigit(x) = x is @compile Digit + +check of isDigit, "digit" +//│ > 0 is digit +//│ > 1 is digit +//│ > 2 is digit +//│ > 3 is digit +//│ > 4 is digit +//│ > 5 is digit +//│ > 6 is digit +//│ > 7 is digit +//│ > 8 is digit +//│ > 9 is digit + pattern Lower = "a"..="z" :expect true @@ -72,6 +107,16 @@ pattern Letter = Lower | Upper "9" is Letter //│ = false +// Inspect the generated code. The use of `Map` makes the expanded disjunction +// out of order. I wonder if we should use a `SeqMap` for literals. +fun isLetter(x) = x is @compile Letter + +isLetter of "a" +//│ = true + +isLetter of "0" +//│ = false + pattern Word = Letter ~ (Word | "") :expect false @@ -90,6 +135,16 @@ pattern Word = Letter ~ (Word | "") "b0rked" is Word //│ = false +:e +fun isWord(x) = x is @compile Word +//│ ╔══[ERROR] String concatenation is not supported in pattern compilation. +//│ ║ l.120: pattern Word = Letter ~ (Word | "") +//│ ╙── ^^^^^^^^^^^^^^^^^^^ + +// Unsupported patterns are equivalent to `Never`. +isWord of "pattern" +//│ = false + pattern ManyDigits = ("0" ..= "9") ~ (ManyDigits | "") :expect true diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/ListPredicates.mls b/hkmc2/shared/src/test/mlscript/rp/examples/ListPredicates.mls index 58c677feeb..de675212f5 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/ListPredicates.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/ListPredicates.mls @@ -1,6 +1,9 @@ :js +open annotations + import "../../../mlscript-compile/Stack.mls" +import "../../../mlscript-compile/Iter.mls" open Stack diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls b/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls index dc90dd8497..4a58bf7c8f 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls @@ -1,5 +1,7 @@ :js +open annotations + import "../../../mlscript-compile/Option.mls" open Option { Some, None } @@ -13,7 +15,7 @@ pattern Rounded = (Num as value) => Math.round(value) // Note: This needs refactoring the desugarer. (weight: 70, height: 1.75) is CalculateBMI as Rounded(22) //│ ╔══[ERROR] Unrecognized pattern (infix operator) -//│ ║ l.14: (weight: 70, height: 1.75) is CalculateBMI as Rounded(22) +//│ ║ l.16: (weight: 70, height: 1.75) is CalculateBMI as Rounded(22) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = false @@ -34,6 +36,13 @@ pattern MatchOrNone(pattern P) = ((P as x) => Some(x)) | (_ => None) fun bmi(record) = if record is MatchOrNone(pattern 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 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ + bmi of weight: 70, height: 1.75 //│ = Some(23) @@ -64,6 +73,20 @@ area of (radius: 10, height: 20) area of (width: 10, height: 20, base: 10) //│ = Some(200) +fun area__compiled(shape) = if shape is (@compile ShapeArea) as a then Some(a) else None + +area__compiled of (radius: 10) +//│ = Some(314.1592653589793) + +area__compiled of (width: 10, height: 20) +//│ = Some(200) + +area__compiled of (base: 10, height: 20) +//│ = Some(100) + +area__compiled of (width: 10, height: 20, base: 10) +//│ = Some(200) + // Convert between different number systems pattern NumberSystem = (((:digits, radix: "x")) => parseInt(digits, 16)) | @@ -76,7 +99,7 @@ fun parseIntegerLiteral(value) = if (:radix, :digits) is NumberSystem as value then Some(value) else None //│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.75: value is "0" ~ (("x" | "o" | "b") as radix) ~ digits and +//│ ║ l.98: value is "0" ~ (("x" | "o" | "b") as radix) ~ digits and //│ ╙── ^ // The workaround we can use currently is to use a pattern declaration. diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/TupleSpread.mls b/hkmc2/shared/src/test/mlscript/rp/examples/TupleSpread.mls index acd7c43e33..fe567a4534 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/TupleSpread.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/TupleSpread.mls @@ -73,6 +73,13 @@ pattern StackLike = ([] => Nil) | ([x, ...StackLike as xs] => x :: xs) [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) +//│ ║ l.77: [1, 2, 3, 4, 5] is StackLike as (1 :: 2 :: 3 :: 4 :: 5 :: Nil) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ = false + [1, 2, 3, 4, 5] is StackLike(1 :: 2 :: 3 :: 4 :: Nil) //│ = false diff --git a/hkmc2/shared/src/test/mlscript/rp/nondeterminism/BitArithmetic.mls b/hkmc2/shared/src/test/mlscript/rp/nondeterminism/BitArithmetic.mls index 87e035faf5..1efda8e4d1 100644 --- a/hkmc2/shared/src/test/mlscript/rp/nondeterminism/BitArithmetic.mls +++ b/hkmc2/shared/src/test/mlscript/rp/nondeterminism/BitArithmetic.mls @@ -56,10 +56,8 @@ isTruthy of true || true isTruthy' of true || true //│ = true -:re isTruthy of true || false -//│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' -//│ = false +//│ = true isTruthy' of true || false //│ = true @@ -85,11 +83,9 @@ isFalsy of false isFalsy of false && false //│ = true -:re -// The compilation scheme does not handle non-determinism at all at the moment. +// This example requires the ability to handle non-determinism. isFalsy of false && true -//│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' -//│ = false +//│ = true isFalsy' of false && true //│ = true diff --git a/hkmc2/shared/src/test/mlscript/rp/nondeterminism/EvenOddTree.mls b/hkmc2/shared/src/test/mlscript/rp/nondeterminism/EvenOddTree.mls index e138e183a7..821b947bb0 100644 --- a/hkmc2/shared/src/test/mlscript/rp/nondeterminism/EvenOddTree.mls +++ b/hkmc2/shared/src/test/mlscript/rp/nondeterminism/EvenOddTree.mls @@ -58,18 +58,14 @@ a(A, B) is @compile EvenTree a(A, B) is EvenTree //│ = true -:re b(A, A) is @compile EvenTree -//│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' -//│ = false +//│ = true b(A, A) is EvenTree //│ = true -:re b(B, B) is @compile EvenTree -//│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' -//│ = false +//│ = true b(B, B) is EvenTree //│ = true @@ -95,18 +91,14 @@ a(A, A) is @compile OddTree a(A, A) is OddTree //│ = true -:re b(B, A) is @compile OddTree -//│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' -//│ = false +//│ = true b(B, A) is OddTree //│ = true -:re b(A, B) is @compile OddTree -//│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' -//│ = false +//│ = true b(A, B) is OddTree //│ = true diff --git a/hkmc2/shared/src/test/mlscript/rp/parametric/HigherOrderPattern.mls b/hkmc2/shared/src/test/mlscript/rp/parametric/HigherOrderPattern.mls index e5d8f73241..0f0c1be3a3 100644 --- a/hkmc2/shared/src/test/mlscript/rp/parametric/HigherOrderPattern.mls +++ b/hkmc2/shared/src/test/mlscript/rp/parametric/HigherOrderPattern.mls @@ -8,18 +8,22 @@ object A :e null is @compile Nullable -//│ ╔══[ERROR] Pattern `Nullable` expects 1 pattern argument +//│ ╔══[ERROR] Pattern `Nullable` has one pattern parameter. //│ ║ l.5: pattern Nullable(pattern T) = null | T //│ ║ ^ -//│ ╙── But 0 pattern arguments were given +//│ ╟── But zero pattern arguments were provided. +//│ ║ l.10: null is @compile Nullable +//│ ╙── ^^^^^^^^ //│ = false :e null is @compile Nullable(pattern A, pattern A) -//│ ╔══[ERROR] Pattern `Nullable` expects 1 pattern argument +//│ ╔══[ERROR] Pattern `Nullable` has one pattern parameter. //│ ║ l.5: pattern Nullable(pattern T) = null | T //│ ║ ^ -//│ ╙── But 2 pattern arguments were given +//│ ╟── But two pattern arguments were provided. +//│ ║ l.20: null is @compile Nullable(pattern A, pattern A) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ = false pattern Nullable(pattern T) = null | T @@ -35,7 +39,7 @@ A is @compile Nullable(pattern A) data class Pair[A, B](val first: A, val second: B) -pattern Stack(pattern T) = null | Pair(T, Stack(T)) +pattern Stack(pattern T) = null | Pair(T, Stack(pattern T)) null is @compile Stack(pattern A) //│ = true diff --git a/hkmc2/shared/src/test/mlscript/rp/parametric/ListLike.mls b/hkmc2/shared/src/test/mlscript/rp/parametric/ListLike.mls index 6c5f23f87a..c4b2cf2380 100644 --- a/hkmc2/shared/src/test/mlscript/rp/parametric/ListLike.mls +++ b/hkmc2/shared/src/test/mlscript/rp/parametric/ListLike.mls @@ -1,5 +1,7 @@ :js +open annotations + import "../../../mlscript-compile/Stack.mls" open Stack @@ -22,3 +24,7 @@ isOddIntList of 1 :: 71 :: "bruh" :: Nil isOddIntList of "hello" :: "world" :: Nil //│ = false + +pattern Nullable(pattern T) = null | T + +fun isNullableIntList(xs) = xs is @compile ListLike(pattern Nullable(pattern Int)) diff --git a/hkmc2/shared/src/test/mlscript/rp/parametric/Nullable.mls b/hkmc2/shared/src/test/mlscript/rp/parametric/Nullable.mls index dccf84488f..316ed5e38a 100644 --- a/hkmc2/shared/src/test/mlscript/rp/parametric/Nullable.mls +++ b/hkmc2/shared/src/test/mlscript/rp/parametric/Nullable.mls @@ -1,5 +1,7 @@ :js +open annotations + pattern Null = null null is Null @@ -22,3 +24,55 @@ isPositiveOrNull of 1 isPositiveOrNull of -2 //│ = false + +// 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 + +mightBeInt of 42 +//│ = 42 + +mightBeInt of null +//│ = null + +:re +mightBeInt of "hello" +//│ ═══[RUNTIME ERROR] Error: match error + +import "../../../mlscript-compile/Option.mls" + +open Option { Some, None } + +pattern Optional(pattern T) = (null => None) | ((T as x) => Some(x)) + +null is Optional(pattern Int, None) +//│ = true + +42 is Optional(pattern Int, Some(42)) +//│ = true + +null is Optional(pattern Int, Some(42)) +//│ = false + +:e +// Test the error message. +fun toIntOption(x) = if x is (@compile Optional) as o then o +//│ ╔══[ERROR] Pattern `Optional` has one pattern parameter. +//│ ║ l.47: pattern Optional(pattern T) = (null => None) | ((T as x) => Some(x)) +//│ ║ ^ +//│ ╟── But zero pattern arguments were provided. +//│ ║ 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 + +toIntOption of 42 +//│ = Some(42) + +toIntOption of null +//│ = None + +:re +toIntOption of "hello" +//│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls b/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls index 12ff5eb002..a5846ef2cf 100644 --- a/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls +++ b/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls @@ -148,12 +148,6 @@ pattern List(a) = null | Pair(a, List) //│ ║ l.146: pattern List(a) = null | Pair(a, List) //│ ║ ^ //│ ╙── The variable is missing from this sub-pattern. -//│ ╔══[ERROR] Name not found: a -//│ ║ l.146: pattern List(a) = null | Pair(a, List) -//│ ╙── ^ -//│ ╔══[ERROR] Name not found: a -//│ ║ l.146: pattern List(a) = null | Pair(a, List) -//│ ╙── ^ null is @compile List //│ = true diff --git a/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleLiterals.mls b/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleLiterals.mls index da22f82dbd..00d579bb8c 100644 --- a/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleLiterals.mls +++ b/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleLiterals.mls @@ -23,28 +23,24 @@ fun checkZeroOneTwo(x) = if x is //│ ║ l.14: @compile Two then 2 //│ ╙── ^^^^^^^^^^^^^^^^^^ //│ Normalized: -//│ > if then +//│ > if else import "../../../mlscript-compile/Option.mls" open Option -:ucs normalized fun checkSomeZeroOneTwo(x) = if x is Some(@compile Zero) then 0 Some(@compile One) then 1 Some(@compile Two) then 2 else () -//│ Normalized: -//│ > if -//│ > x is Option.Some.class(param0) and -//│ > $param0 is "0" then 0 -//│ > $param0 is "1" then 1 -//│ > $param0 is "2" then 2 -//│ > else () -//│ > else () pattern ManyZero = ("0" ~ (ManyZero | "")) | "" +:e +:todo let res = "1" is @compile ManyZero +//│ ╔══[ERROR] String concatenation is not supported in pattern compilation. +//│ ║ l.37: pattern ManyZero = ("0" ~ (ManyZero | "")) | "" +//│ ╙── ^^^^^^^^^^^^^^^^^^^^ //│ res = false diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls index be06881abd..03867b6dc1 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls @@ -12,12 +12,6 @@ pattern Foo(val T) = null | T //│ ╔══[ERROR] Name not found: T //│ ║ l.4: pattern Foo(val T) = null | T //│ ╙── ^ -//│ ╔══[ERROR] Name not found: T -//│ ║ l.4: pattern Foo(val T) = null | T -//│ ╙── ^ -//│ ╔══[ERROR] Name not found: T -//│ ║ l.4: pattern Foo(val T) = null | T -//│ ╙── ^ pattern Foo(pattern T) = null | T diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls index 06beca5ebd..bf86c00d28 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls @@ -40,36 +40,17 @@ pattern PairLike = [fst, snd] => Pair(fst, snd) //│ elaborated pattern body: [fst, snd] => member:Pair#666(fst#666, snd#666) :ucs ups -:e // The errors are from the old pattern compilation. pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] //│ elaborated pattern body: member:Pair(fst, snd) ∨ [fst, snd] -//│ ╔══[ERROR] Name not found: snd -//│ ║ l.44: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] -//│ ╙── ^^^ -//│ ╔══[ERROR] Name not found: snd -//│ ║ l.44: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] -//│ ╙── ^^^ -//│ ╔══[ERROR] Name not found: fst -//│ ║ l.44: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] -//│ ╙── ^^^ -//│ ╔══[ERROR] Name not found: fst -//│ ║ l.44: pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] -//│ ╙── ^^^ :w :e // The errors are from the old pattern translation and compilation. pattern UselessParameter = x //│ ╔══[WARNING] Useless pattern binding: x. -//│ ║ l.61: pattern UselessParameter = x +//│ ║ l.48: pattern UselessParameter = x //│ ╙── ^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.61: pattern UselessParameter = x -//│ ╙── ^ -//│ ╔══[ERROR] Name not found: x -//│ ║ l.61: pattern UselessParameter = x -//│ ╙── ^ -//│ ╔══[ERROR] Name not found: x -//│ ║ l.61: pattern UselessParameter = x +//│ ║ l.48: pattern UselessParameter = x //│ ╙── ^ :ucs ups @@ -79,13 +60,13 @@ pattern PlainList(pattern T) = (null => Nil) | ([T as hd, PlainList(T) as tl] => :fixme [T as hd, PlainList(pattern T) as tl] //│ ╔══[ERROR] Name not found: hd -//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.61: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^^ //│ ╔══[ERROR] Illegal type declaration in term position. -//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.61: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^ //│ ╔══[ERROR] Name not found: tl -//│ ║ l.80: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.61: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^^ //│ ═══[RUNTIME ERROR] TypeError: PlainList3 is not a function @@ -96,20 +77,20 @@ pattern Consistent = ((Int as x) | (Str as x)) => x.toString() :e pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.78: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.78: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.97: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.78: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╙── ^ :e pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() //│ ╔══[ERROR] Name not found: x -//│ ║ l.109: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() -//│ ╙── ^ +//│ ║ l.90: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() +//│ ╙── ^ :ucs ups pattern Negated = ~(Int | Str) @@ -139,13 +120,13 @@ pattern BadNegated = ~(Int as x) => x :e pattern BadNegated = (~(Int as x)) => x //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.121: pattern BadNegated = (~(Int as x)) => x //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.121: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.140: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.121: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^ //│ elaborated pattern body: ¬(member:Int as x) => @@ -153,13 +134,13 @@ pattern BadNegated = (~(Int as x)) => x :e pattern BadNegated = (~Pair(Int, x)) => x //│ ╔══[ERROR] This variable cannot be accessed. -//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.135: pattern BadNegated = (~Pair(Int, x)) => x //│ ║ ^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.135: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.154: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.135: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^ //│ elaborated pattern body: ¬member:Pair(member:Int, x) => diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArity.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArity.mls index 4d1e50a7d3..48e831245b 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArity.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArity.mls @@ -1,19 +1,15 @@ +:js + data class Pair[A, B](val first: A, val second: B) :e pattern LessArity = Pair("meow") -//│ ╔══[ERROR] The class `Pair` expected 2 arguments. -//│ ║ l.1: data class Pair[A, B](val first: A, val second: B) -//│ ║ ^^^^ -//│ ╟── But only 1 sub-pattern is given. -//│ ║ l.4: pattern LessArity = Pair("meow") +//│ ╔══[ERROR] Expected two arguments, but found only one argument. +//│ ║ l.6: pattern LessArity = Pair("meow") //│ ╙── ^^^^^^ :e -pattern GreaterArity = Pair("meow", "woof", "moo") -//│ ╔══[ERROR] The class `Pair` expected 2 arguments. -//│ ║ l.1: data class Pair[A, B](val first: A, val second: B) -//│ ║ ^^^^ -//│ ╟── But only 3 sub-patterns are given. -//│ ║ l.13: pattern GreaterArity = Pair("meow", "woof", "moo") -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ +pattern MoreArity = Pair("meow", "woof", "moo") +//│ ╔══[ERROR] Expected two arguments, but found three arguments. +//│ ║ l.12: pattern MoreArity = Pair("meow", "woof", "moo") +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls index 480633a640..c382185af8 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Parameters.mls @@ -79,14 +79,16 @@ fun test(x) = if x is Test(p, q, r, s, t, u) then "ok" :e fun test(x) = if x is Test(_, _, _) then "ok" -//│ ═══[ERROR] mismatched arity: expect 6, found 3 +//│ ╔══[ERROR] Expected six arguments, but found only three arguments. +//│ ║ l.81: fun test(x) = if x is Test(_, _, _) then "ok" +//│ ╙── ^^^^ fun test(x) = if x is Test(_, _, _, _, _, _) then "ok" :e fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" //│ ╔══[ERROR] This pattern cannot be matched -//│ ║ l.87: fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" +//│ ║ l.89: fun test(x) = if x is Test(_, _, _, a, _, _) then "ok" //│ ║ ^ //│ ╟── because the corresponding parameter `d` is not publicly accessible //│ ║ l.25: class Test(a, b, c, d, e, f) @@ -112,19 +114,19 @@ object A :e fun foo(x) = if x is A() then 0 //│ ╔══[ERROR] `A` is an object. -//│ ║ l.110: object A +//│ ║ l.112: object A //│ ║ ^ //│ ╟── Its pattern cannot have an argument list. -//│ ║ l.113: fun foo(x) = if x is A() then 0 +//│ ║ l.115: fun foo(x) = if x is A() then 0 //│ ╙── ^^^ :e fun foo(x) = if x is A(y) then y //│ ╔══[ERROR] `A` is an object. -//│ ║ l.110: object A +//│ ║ l.112: object A //│ ║ ^ //│ ╟── Its pattern cannot have arguments. -//│ ║ l.122: fun foo(x) = if x is A(y) then y +//│ ║ l.124: fun foo(x) = if x is A(y) then y //│ ╙── ^^^^ 0 is Foo From b19fe3124d3f9c75b4228bc8aa11785bb4fb50b5 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:43:57 +0800 Subject: [PATCH 11/62] Revise the wording Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 8ca7fbce8f..91323e13ab 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -311,7 +311,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): case _: sem.BuiltinSymbol => true case sym: sem.BlockMemberSymbol => sym.trmImplTree.fold(sym.clsTree.isDefined)(_.k is syntax.Fun) - // Do not perform safe check on `MatchResult` and `MatchFailure`. + // Do not perform safety check on `MatchResult` and `MatchFailure`. case sym => (sym is State.matchResultClsSymbol) || (sym is State.matchFailureClsSymbol) def conclude(fr: Path) = From e5c2438e1ed93dcc18730c1e842a2ebd62555a94 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:44:50 +0800 Subject: [PATCH 12/62] Add a blank line at the end of the file Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala index 2891886e6b..1fe83c0b9d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala @@ -19,4 +19,4 @@ object Context: extension (instantiation: Instantiation) /** Get the body of the instantiated pattern definition. */ def body(using Context, Raise): Pat = - summon[Context].get(instantiation) \ No newline at end of file + summon[Context].get(instantiation) From 89269e3060992968c281ece42a59a42cd3b75199 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:47:01 +0800 Subject: [PATCH 13/62] Delete the comment that was accidentally committed --- hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala index f406590559..78cb69d313 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala @@ -52,18 +52,6 @@ final case class COMMENT(text: String) extends Token with Stroken final case class SUSPENSION(dotDotDot: Bool) extends Token with Stroken final case class ESC_IDENT(name: String) extends Token with Stroken -/* - - -(foo - bar) - -(foo INDENT bar) DEINDENT - -Bracket[Round]{foo Bracket[INDENT]{bar}} - - -*/ sealed abstract class BracketKind: import BracketKind._ From b392c4a5272e325371ef22ce27da97d1c3f19ff8 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:25:29 +0800 Subject: [PATCH 14/62] Improve `pluralize` --- .../src/main/scala/hkmc2/utils/utils.scala | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala index f5e6923802..781a1eb503 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala @@ -116,26 +116,36 @@ extension (n: Int) case 6 => "six" case 7 => "seven" case 8 => "eight" case 9 => "nine" case _ => n.toString +object English: + // These lists are not complete (nor are they intended to be). Please add + // necessary words as needed. + val irregularPlurals = Map("datum" -> "data", "index" -> "indices") + val ves = Set("proof") + val o = Set("zero") + extension (str: String) /** Converts a singular noun to its plural form using English pluralization * rules. The rules should be updated as needed. */ - def pluralize: String = - // This is not a complete list (nor is it intended to be). - val irregularPlurals = Map("datum" -> "data", "index" -> "indices") - val ves = Set("proof") // Add more as needed. - val o = Set("zero") // Add more as needed. - irregularPlurals.get(str).getOrElse: - // -s, -sh, -ch, -x, -z -> +es - if str.matches(".*[sxz]$") || str.endsWith("sh") || str.endsWith("ch") then str + "es" - // -[^aeiou]y -> -ies - else if str.matches(".*[^aeiou]y$") then str.dropRight(1) + "ies" - // -f -> -ves (with some exceptions) - else if str.endsWith("f") && !ves.contains(str) then str.dropRight(1) + "ves" - // -fe -> -ves - else if str.endsWith("fe") then str.dropRight(2) + "ves" - // -o -> -es (with some exceptions) - else if str.endsWith("o") && !o.contains(str) then str.dropRight(1) + "es" - else str + "s" + def pluralize: String = English.irregularPlurals.get(str).getOrElse: + str.lift(str.size - 1) match + // -s, -sh, -ch, -x, -z -> +es; e.g., bus -> buses, box -> boxes + case S('s' | 'x' | 'z') => str + "es" + // -sh, -ch -> +es; e.g., brush -> brushes, church -> churches + case S('h') if str.lift(str.size - 2).exists(c => (c is 's') || (c is 'c')) => + str + "es" + // -o -> +es (with some exceptions); e.g., potato -> potatoes + case S('o') if !English.o.contains(str) => str + "es" + // -[^aeiou]y -> -ies; e.g., city -> cities, but not toy -> toys + case S('y') => str.lift(str.size - 2) match + case S('a' | 'e' | 'i' | 'o' | 'u') => str + "s" // Vowel before 'y' + case S(_) | N => str.dropRight(1) + "ies" // Consonant before 'y' + // -f -> -ves (with exceptions); e.g., leaf -> leaves + case S('f') if !English.ves.contains(str) => str.dropRight(1) + "ves" + case S('e') => str.lift(str.size - 2) match + // -fe -> -ves; e.g., knife -> knives + case S('f') => str.dropRight(2) + "ves" + case S(_) | N => str + "s" // Default case: just add 's' + case S(_) | N => str + "s" // Default case: just add 's' /** Formats a number and a noun as a human-readable string. */ infix def countBy(n: Int): String = From e3d0fddcc5bec3ebe45d34b2872aa5da1c245d25 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:29:18 +0800 Subject: [PATCH 15/62] Remove a `:todo` flag --- hkmc2/shared/src/test/mlscript/rp/JoinPatterns.mls | 1 - 1 file changed, 1 deletion(-) diff --git a/hkmc2/shared/src/test/mlscript/rp/JoinPatterns.mls b/hkmc2/shared/src/test/mlscript/rp/JoinPatterns.mls index d9843f044b..7a7f52f25b 100644 --- a/hkmc2/shared/src/test/mlscript/rp/JoinPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/JoinPatterns.mls @@ -22,7 +22,6 @@ pattern SimpleJoin = ("abc" | "xyz") ~ ("def" | "") "abcxyzdef" is SimpleJoin //│ = false -:todo pattern Exponential = ("a" | "b") ~ ("c" | "d") ~ ("e" | "f") // ~ ("g" | "h") ~ ("i" | "j") ~ ("k" | "l") ~ ("m" | "n") ~ ("o" | "p") ~ ("q" | "r") ~ ("s" | "t") ~ ("u" | "v") ~ ("w" | "x") ~ ("y" | "z") :expect true From bcac1bd90c6102c92d292aae54592656c0071e6b Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 30 Jun 2025 17:32:46 +0800 Subject: [PATCH 16/62] Add examples of DNF and CNF --- .../src/test/mlscript/rp/examples/DnfCnf.mls | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls b/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls new file mode 100644 index 0000000000..a14e2b89df --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls @@ -0,0 +1,126 @@ +:js + +open annotations + +data class Formula with + constructor + Var(name: Str) + Pred(name: Str, arg: Formula) + And(left: Formula, right: Formula) + Or(left: Formula, right: Formula) + +pattern Dnf = Or(Dnf, Dnf) | Atoms +pattern Atoms = And(Atoms, Atoms) | Atom +pattern Atom = Pred | Var + +fun isDnf(x) = x is @compile Dnf + +let f1 = Var("x") +let f2 = Pred("P", Var("y")) +//│ f1 = Var("x") +//│ f2 = Pred("P", Var("y")) + +f1 is Dnf +//│ = true + +isDnf of f1 +//│ = true + +f2 is Dnf +//│ = true + +isDnf of f2 +//│ = true + +let f3 = And(f1, f2) +//│ f3 = And(Var("x"), Pred("P", Var("y"))) + +f3 is Dnf +//│ = true + +isDnf of f3 +//│ = true + +let f4 = Or(f3, Pred("Q", Var("z"))) +//│ f4 = Or(And(Var("x"), Pred("P", Var("y"))), Pred("Q", Var("z"))) + +f4 is Dnf +//│ = true + +isDnf of f4 +//│ = true + +let f5 = Or(And(Pred("A", Var("a")), Pred("B", Var("b"))), Pred("C", Var("c"))) +//│ f5 = Or(And(Pred("A", Var("a")), Pred("B", Var("b"))), Pred("C", Var("c"))) + +f5 is Dnf +//│ = true + +isDnf of f5 +//│ = true + +let cnf1 = And(Or(Var("x"), Var("y")), Pred("P", Var("z"))) +//│ cnf1 = And(Or(Var("x"), Var("y")), Pred("P", Var("z"))) + +let cnf2 = And(Or(Var("a"), Var("b")), Or(Pred("P", Var("c")), Var("d"))) +//│ cnf2 = And(Or(Var("a"), Var("b")), Or(Pred("P", Var("c")), Var("d"))) + +let cnf3 = And(And(Or(Var("x"), Pred("Q", Var("y"))), Or(Var("z"), Var("w"))), Or(Pred("R", Var("u")), Var("v"))) +//│ cnf3 = And(And(Or(Var("x"), Pred("Q", Var("y"))), Or(Var("z"), Var("w"))), Or(Pred("R", Var("u")), Var("v"))) + +let cnf4 = And(Or(And(Var("m"), Var("n")), Var("o")), Or(Pred("S", Var("p")), And(Var("q"), Var("r")))) +//│ cnf4 = And(Or(And(Var("m"), Var("n")), Var("o")), Or(Pred("S", Var("p")), And(Var("q"), Var("r")))) + +let cnf5 = And(Or(Var("a"), Or(Var("b"), Var("c"))), And(Or(Pred("T", Var("d")), Var("e")), Or(Var("f"), Var("g")))) +//│ cnf5 = And(Or(Var("a"), Or(Var("b"), Var("c"))), And(Or(Pred("T", Var("d")), Var("e")), Or(Var("f"), Var("g")))) + +let cnf6 = And(Or(Var("x"), And(Var("y"), Or(Var("z"), Var("w")))), Or(Pred("U", Var("t")), Var("s"))) +//│ cnf6 = And(Or(Var("x"), And(Var("y"), Or(Var("z"), Var("w")))), Or(Pred("U", Var("t")), Var("s"))) + +print of cnf1 is Dnf +print of cnf2 is Dnf +print of cnf3 is Dnf +print of cnf4 is Dnf +print of cnf5 is Dnf +print of cnf6 is Dnf +//│ > false +//│ > false +//│ > false +//│ > false +//│ > 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 Atom'(pattern P) = Pred(_, P) | Var + +pattern DeepDnf = Dnf'(pattern DeepDnf) + +fun isDeepDnf(f) = f is @compile DeepDnf + +print of isDeepDnf of f1 +print of isDeepDnf of f2 +print of isDeepDnf of f3 +print of isDeepDnf of f4 +print of isDeepDnf of f5 +//│ > true +//│ > true +//│ > true +//│ > true +//│ > true + +print of isDeepDnf of cnf1 +print of isDeepDnf of cnf2 +print of isDeepDnf of cnf3 +print of isDeepDnf of cnf4 +print of isDeepDnf of cnf5 +print of isDeepDnf of cnf6 +//│ > false +//│ > false +//│ > false +//│ > false +//│ > false +//│ > false + +pattern Cnf'(pattern P) = And(Cnf'(pattern P), Cnf'(pattern P)) +pattern DnfOrCnf = Dnf'(DnfOrCnf) | Cnf'(DnfOrCnf) From 575bdcf4e351f4ce5d7f6ce9dc084960af30bd8a Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 30 Jun 2025 23:21:31 +0800 Subject: [PATCH 17/62] Further improve `pluralize` --- .../src/main/scala/hkmc2/utils/utils.scala | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala index 781a1eb503..1cead72c96 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala @@ -126,26 +126,25 @@ object English: extension (str: String) /** Converts a singular noun to its plural form using English pluralization * rules. The rules should be updated as needed. */ - def pluralize: String = English.irregularPlurals.get(str).getOrElse: - str.lift(str.size - 1) match + def pluralize: String = English.irregularPlurals.getOrElse(str, + if str.isEmpty then str else str(str.size - 1) match // -s, -sh, -ch, -x, -z -> +es; e.g., bus -> buses, box -> boxes - case S('s' | 'x' | 'z') => str + "es" + case 's' | 'x' | 'z' => str + "es" // -sh, -ch -> +es; e.g., brush -> brushes, church -> churches - case S('h') if str.lift(str.size - 2).exists(c => (c is 's') || (c is 'c')) => + case 'h' if str.size > 1 && (str(str.size - 2) === 's' || str(str.size - 2) === 'c') => str + "es" // -o -> +es (with some exceptions); e.g., potato -> potatoes - case S('o') if !English.o.contains(str) => str + "es" + case 'o' if !English.o.contains(str) => str + "es" // -[^aeiou]y -> -ies; e.g., city -> cities, but not toy -> toys - case S('y') => str.lift(str.size - 2) match - case S('a' | 'e' | 'i' | 'o' | 'u') => str + "s" // Vowel before 'y' - case S(_) | N => str.dropRight(1) + "ies" // Consonant before 'y' + case 'y' if str.size > 1 => str(str.size - 2) match + case 'a' | 'e' | 'i' | 'o' | 'u' => str + "s" // Vowel before 'y' + case _ => str.dropRight(1) + "ies" // Consonant before 'y' // -f -> -ves (with exceptions); e.g., leaf -> leaves - case S('f') if !English.ves.contains(str) => str.dropRight(1) + "ves" - case S('e') => str.lift(str.size - 2) match - // -fe -> -ves; e.g., knife -> knives - case S('f') => str.dropRight(2) + "ves" - case S(_) | N => str + "s" // Default case: just add 's' - case S(_) | N => str + "s" // Default case: just add 's' + case 'f' if !English.ves.contains(str) => str.dropRight(1) + "ves" + // -fe -> -ves; e.g., knife -> knives + case 'e' if str.size > 1 && str(str.size - 2) === 'f' => str.dropRight(2) + "ves" + // Default case: just add 's' + case _ => str + "s") /** Formats a number and a noun as a human-readable string. */ infix def countBy(n: Int): String = From 78ea61cc53cbee9b1dbbb2533b0e97cb4187c314 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 30 Jun 2025 23:28:58 +0800 Subject: [PATCH 18/62] Delete a self-talking comment --- hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index d6678591c5..3edbb4a461 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1109,7 +1109,6 @@ extends Importer: case Pat => val patSym = td.symbol.asInstanceOf[PatternSymbol] // TODO improve `asInstanceOf` val owner = ctx.outer.inner - // OK. It seems that the context after `nestInner` already have all parameters. newCtx.nestInner(patSym).givenIn: // Pattern definition should not have a body like class definition. assert(body.isEmpty) From 163f09141224ef14778b8a410ee8a1d5851fbc02 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 4 Aug 2025 03:00:17 +0800 Subject: [PATCH 19/62] Remove useless classes from `Term.mls` --- .../shared/src/test/mlscript-compile/Term.mls | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Term.mls b/hkmc2/shared/src/test/mlscript-compile/Term.mls index f2bc046b41..beafaf54b6 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Term.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Term.mls @@ -13,37 +13,11 @@ module Term with... class Symbol(val name: Str) type Literal = null | undefined | Str | Int | Num | Bool - -class ConstructorLike with - constructor - SymbolCons(val symbol: Symbol) - StringJoin - TupleCapacity(val size: Int, val inf: Bool) - Instantiation(val symbol: Symbol, val args: Array[DebrujinSplit]) - LocalPattern(val id: Int) - Parameter(val symbol: Symbol) - Nested(val split: DebrujinSplit) - -class PatternStub with - constructor - LiteralStub(val value: Literal) - CharClass(val start: Str, val end: Str, val inclusive: Bool) - ClassLikeStub(val cons: ConstructorLike) - Wildcard - -class DebrujinSplit with - constructor - Binder(val body: DebrujinSplit) - DebrujinBranch(val scrutinee: Int, val ptrn, val consequent: DebrujinSplit, val alternative: DebrujinSplit) - Accept(val outcome: Int) - Reject - class Pattern with constructor LitPattern(val lit: Literal) Var(val sym: Symbol) ClassLike(val sym: Symbol, val trm: Term, val parameters: Array[Symbol] | null) - Synonym(val symbol: Symbol, val patternArguments: Array[DebrujinSplit]) Tuple(val size: Int, val inf: Bool) Record(val entities: Array[[Str, Symbol]]) From 837994c6c305ceb8b27466ff5f9e727a13aa9d6f Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 4 Aug 2025 03:09:19 +0800 Subject: [PATCH 20/62] Rename `Translator` as `NaiveCompiler` --- .../scala/hkmc2/semantics/Elaborator.scala | 4 ++-- .../scala/hkmc2/semantics/ucs/Desugarer.scala | 2 +- .../{Translator.scala => NaiveCompiler.scala} | 10 +++++----- .../test/mlscript/rp/examples/DoubleOrSum.mls | 14 +++++++------- .../mlscript/rp/examples/DoubleTripleList.mls | 18 +++++++++--------- .../test/mlscript/rp/examples/Extraction.mls | 14 +++++++------- .../src/test/mlscript/rp/examples/Flatten.mls | 14 +++++++------- 7 files changed, 38 insertions(+), 38 deletions(-) rename hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/{Translator.scala => NaiveCompiler.scala} (99%) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 4bc023b48a..9719b01333 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1174,7 +1174,7 @@ extends Importer: scoped("ucs:ups:tree")(log(s"elaborated pattern body: ${pat.showAsTree}")) // Translate the pattern directly into methods that perform matching // using backtracking. - val bod = new ucs.Translator(this)( + val bod = new ucs.NaiveCompiler(this)( patternParams, Nil, // TODO: Remove this parameter after we finish // the pattern translation for string concatenation. td.rhs.getOrElse(die), pat) @@ -1314,7 +1314,7 @@ extends Importer: def pattern(t: Tree): Ctxl[Pattern] = import ucs.Desugarer.{Ctor, unapply}, Keyword.*, Pattern.*, InvalidReason.* - import ucs.Translator.isInvalidStringBounds, ucs.extractors.to + import ucs.NaiveCompiler.isInvalidStringBounds, ucs.extractors.to given TraceLogger = tl /** Elaborate arrow patterns like `p => t`. Meanwhile, report all invalid * variables we found in `p`. */ diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala index 5f249adee0..cae2609b59 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala @@ -465,7 +465,7 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten val pattern = tree match case TypeDef(syntax.Pat, body, N) => val pattern = elaborator.pattern(body) - val term = new Translator(elaborator).translateAnonymousPattern(Nil, Nil, pattern) + val term = new NaiveCompiler(elaborator).translateAnonymousPattern(Nil, Nil, pattern) S((pattern, term)) case td @ TypeDef(k = syntax.Pat) => error(msg"Ill-formed pattern argument" -> td.toLoc); N diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala similarity index 99% rename from hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala rename to hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala index 9efd5f6556..b27c7830b1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala @@ -9,7 +9,7 @@ import syntax.{Fun, Keyword, Tree}, Tree.*, Keyword.{`as`, `=>`} import scala.collection.mutable.Buffer import Elaborator.{Ctx, State, ctx} -object Translator: +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])] @@ -72,12 +72,12 @@ object Translator: val rejectPrefixSplit: MakePrefixSplit = (_, alternative) => alternative -import Translator.* +import NaiveCompiler.* /** This class translates a tree describing a pattern into functions that can * perform pattern matching on terms described by the pattern. */ -class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends DesugaringBase: +class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends DesugaringBase: import elaborator.term, elaborator.tl.*, FlatPattern.MatchMode import Pattern.* @@ -586,8 +586,8 @@ class Translator(val elaborator: Elaborator)(using State, Ctx, Raise) extends De * old `body` parameter are mixed. */ def apply(patternParams: Ls[Param], params: Ls[Param], body: Tree, pattern: Pattern): Ls[TermDefinition] = trace( - pre = s"Translator <<< ${params.mkString(", ")} $body", - post = (blk: Ls[TermDefinition]) => s"Translator >>> $blk" + pre = s"NaiveCompiler <<< ${params.mkString(", ")} $body", + post = (blk: Ls[TermDefinition]) => s"NaiveCompiler >>> $blk" ): val unapply = scoped("ucs:translation"): val inputSymbol = VarSymbol(Ident("input")) diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/DoubleOrSum.mls b/hkmc2/shared/src/test/mlscript/rp/examples/DoubleOrSum.mls index b5eb09ec46..081196aa03 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/DoubleOrSum.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/DoubleOrSum.mls @@ -88,16 +88,16 @@ sum of Nil pattern SumOrDouble = SumList | DoubleList -fun isSumOrDouble__translated(xs) = if xs is SumOrDouble as output then output else "noop" +fun isSumOrDouble__naive(xs) = if xs is SumOrDouble as output then output else "noop" -isSumOrDouble__translated of _1234 +isSumOrDouble__naive of _1234 //│ > 4 + 0 = 4 //│ > 3 + 4 = 7 //│ > 2 + 7 = 9 //│ > 1 + 9 = 10 //│ = 10 -isSumOrDouble__translated of _1234567 +isSumOrDouble__naive of _1234567 //│ > 7 + 0 = 7 //│ > 6 + 7 = 13 //│ > 5 + 13 = 18 @@ -107,7 +107,7 @@ isSumOrDouble__translated of _1234567 //│ > 1 + 27 = 28 //│ = 28 -isSumOrDouble__translated of Nil +isSumOrDouble__naive of Nil //│ = 0 fun isSumOrDouble__compiled(xs) = if xs is (@compile SumOrDouble) as output then output else "noop" @@ -136,13 +136,13 @@ isSumOrDouble__compiled of Nil pattern DoubleOrSum = DoubleList | SumList -fun isDoubleOrSum__translated(xs) = if xs is DoubleOrSum as output then output else "noop" +fun isDoubleOrSum__naive(xs) = if xs is DoubleOrSum as output then output else "noop" :todo // Fix the return value of the translation. -isDoubleOrSum__translated of _1234 +isDoubleOrSum__naive of _1234 //│ = Nil -isDoubleOrSum__translated of list(1, 2, 3) +isDoubleOrSum__naive of list(1, 2, 3) //│ > 3 + 0 = 3 //│ > 2 + 3 = 5 //│ > 1 + 5 = 6 diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/DoubleTripleList.mls b/hkmc2/shared/src/test/mlscript/rp/examples/DoubleTripleList.mls index 6fdb6489ca..e8d40e7eed 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/DoubleTripleList.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/DoubleTripleList.mls @@ -170,14 +170,14 @@ isDoubleTripleList__compiled of list(1, 2, 3, 4, 5, 6, 7, 8, 9) isDoubleTripleList__compiled of Nil //│ = true -fun isDoubleTripleList__translated(xs) = +fun isDoubleTripleList__naive(xs) = set doLog = true let result = xs is DoubleTripleList set doLog = false result // It does not backtracking because the `DoubleList` is tested first. -isDoubleTripleList__translated of list(1, 2, 3, 4) +isDoubleTripleList__naive of list(1, 2, 3, 4) //│ > access the `head` of [1, 2, 3, 4] //│ > access the `tail` of [1, 2, 3, 4] //│ > access the `head` of [2, 3, 4] @@ -189,7 +189,7 @@ isDoubleTripleList__translated of list(1, 2, 3, 4) //│ = true // It does backtracking when `DoubleList` does not match. -isDoubleTripleList__translated of list(1, 2, 3) +isDoubleTripleList__naive of list(1, 2, 3) //│ > access the `head` of [1, 2, 3] //│ > access the `tail` of [1, 2, 3] //│ > access the `head` of [2, 3] @@ -204,7 +204,7 @@ isDoubleTripleList__translated of list(1, 2, 3) //│ > access the `tail` of [3] //│ = true -isDoubleTripleList__translated of list(1, 2, 3, 4, 5, 6, 7, 8, 9) +isDoubleTripleList__naive of list(1, 2, 3, 4, 5, 6, 7, 8, 9) //│ > access the `head` of [1, 2, 3, 4, 5, 6, 7, 8, 9] //│ > access the `tail` of [1, 2, 3, 4, 5, 6, 7, 8, 9] //│ > access the `head` of [2, 3, 4, 5, 6, 7, 8, 9] @@ -245,10 +245,10 @@ isDoubleTripleList__translated of list(1, 2, 3, 4, 5, 6, 7, 8, 9) pattern DoubleAndTripleList = DoubleList & TripleList -fun isDoubleAndTriple__translated(xs) = withLog of +fun isDoubleAndTriple__naive(xs) = withLog of () => xs is DoubleAndTripleList -isDoubleAndTriple__translated of list(8, 9) +isDoubleAndTriple__naive of list(8, 9) //│ > access the `head` of [8, 9] //│ > access the `tail` of [8, 9] //│ > access the `head` of [9] @@ -259,7 +259,7 @@ isDoubleAndTriple__translated of list(8, 9) //│ > access the `tail` of [9] //│ = false -isDoubleAndTriple__translated of list(1, 2, 3) +isDoubleAndTriple__naive of list(1, 2, 3) //│ > access the `head` of [1, 2, 3] //│ > access the `tail` of [1, 2, 3] //│ > access the `head` of [2, 3] @@ -268,7 +268,7 @@ isDoubleAndTriple__translated of list(1, 2, 3) //│ > access the `tail` of [3] //│ = false -isDoubleAndTriple__translated of list(4, 3, 2, 1) +isDoubleAndTriple__naive of list(4, 3, 2, 1) //│ > access the `head` of [4, 3, 2, 1] //│ > access the `tail` of [4, 3, 2, 1] //│ > access the `head` of [3, 2, 1] @@ -287,7 +287,7 @@ isDoubleAndTriple__translated of list(4, 3, 2, 1) //│ > access the `tail` of [1] //│ = false -isDoubleAndTriple__translated of list(1, 2, 3, 4, 5, 6) +isDoubleAndTriple__naive of list(1, 2, 3, 4, 5, 6) //│ > access the `head` of [1, 2, 3, 4, 5, 6] //│ > access the `tail` of [1, 2, 3, 4, 5, 6] //│ > access the `head` of [2, 3, 4, 5, 6] diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Extraction.mls b/hkmc2/shared/src/test/mlscript/rp/examples/Extraction.mls index 3ff2af4369..47e9615f91 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/Extraction.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/Extraction.mls @@ -59,18 +59,18 @@ pattern BiasedOr2 = Some(Some(x) => x + 1) | Some(x => x + 2) fun biasedOr2__compiled(x) = if x is (@compile BiasedOr2) as output then output -fun biasedOr2__translated(x) = if x is BiasedOr2(output) then output +fun biasedOr2__naive(x) = if x is BiasedOr2(output) then output biasedOr2__compiled of Some(1) //│ = {value: 3} -biasedOr2__translated of Some(1) +biasedOr2__naive of Some(1) //│ = 3 biasedOr2__compiled of Some(Some(1)) //│ = {value: 2} -biasedOr2__translated of Some(Some(1)) +biasedOr2__naive of Some(Some(1)) //│ = 2 // Comments: I think the output from the compiled pattern is semantically @@ -102,7 +102,7 @@ pattern ExactlyOneNone = fun exactlyOneNone(x) = if x is (@compile ExactlyOneNone) as output then output else "failed" -fun exactlyOneNone__translated(x) = +fun exactlyOneNone__naive(x) = if x is ExactlyOneNone(output) then output else "failed" exactlyOneNone of Triplet(None, Some(1), Some(2)) @@ -116,13 +116,13 @@ exactlyOneNone of Triplet(Some(1), Some(2), None) // It seems that the return value of the translation is not correct. -exactlyOneNone__translated of Triplet(None, Some(1), Some(100)) +exactlyOneNone__naive of Triplet(None, Some(1), Some(100)) //│ = 100 -exactlyOneNone__translated of Triplet(Some(1), None, Some(400)) +exactlyOneNone__naive of Triplet(Some(1), None, Some(400)) //│ = 400 -exactlyOneNone__translated of Triplet(Some(1), Some(2), None) +exactlyOneNone__naive of Triplet(Some(1), Some(2), None) //│ = None :expect "failed" diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Flatten.mls b/hkmc2/shared/src/test/mlscript/rp/examples/Flatten.mls index 90dfd50b35..01fba03b71 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/Flatten.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/Flatten.mls @@ -17,27 +17,27 @@ pattern Flatten = (((Flatten as hd) :: (Flatten as tl)) => hd ::: tl) | ((hd :: (Flatten as tl)) => hd :: tl) -fun flatten__translated(xs) = +fun flatten__naive(xs) = if xs is Flatten as ys then print of "Flattened: [" + (ys Iter.fromStack() Iter.joined(", ")) + "]" else "Cannot flatten: " + xs -flatten__translated of Nil +flatten__naive of Nil //│ > Flattened: [] -flatten__translated of list(1) +flatten__naive of list(1) //│ > Flattened: [1] -flatten__translated of list(1, 2, 3, 4) +flatten__naive of list(1, 2, 3, 4) //│ > Flattened: [1, 2, 3, 4] -flatten__translated of list(list(1, 2), list(3, 4)) +flatten__naive of list(list(1, 2), list(3, 4)) //│ > Flattened: [1, 2, 3, 4] -flatten__translated of list(list(list(list(1, 2, 3, 4)))) +flatten__naive of list(list(list(list(1, 2, 3, 4)))) //│ > Flattened: [1, 2, 3, 4] -flatten__translated of list(list(1, 2), list(list(3, 4), list(list(5, 6)))) +flatten__naive of list(list(1, 2), list(list(3, 4), list(list(5, 6)))) //│ > Flattened: [1, 2, 3, 4, 5, 6] fun flatten__compiled(xs) = From d6137a6fe0c5e115f169229981e3ea8a715777c5 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Mon, 4 Aug 2025 15:06:11 +0800 Subject: [PATCH 21/62] Fix desugaring derp --- hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala index fa5b15018e..070cdeee1a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala @@ -13,7 +13,7 @@ trait BlockImpl(using Elaborator.State): val desugStmts = def desug(stmts: Ls[Tree]): Ls[Tree] = stmts match - case PossiblyAnnotated(anns, syntax.Desugared(td: TypeDef)) :: stmts => + case syntax.Desugared(PossiblyAnnotated(anns, td: TypeDef)) :: stmts => val ctors = td.withPart.toList.flatMap: case Block(sts) => sts.flatMap: case Constructor(Block(ctors)) => ctors From e5696404438b1b8d6b5aed658941f95ce86553c3 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:43:23 +0800 Subject: [PATCH 22/62] Very minor cleanups --- .../shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala | 2 -- .../src/main/scala/hkmc2/semantics/ucs/Normalization.scala | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) 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 46ac7efe61..4855822c65 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -108,8 +108,6 @@ object FlatPattern: case Default /** Call `unapplyStringPrefix` instead of `unapply`. */ case StringPrefix(prefix: TempSymbol, postfix: TempSymbol) - /** Call `unapplyTuplePrefix` instead of `unapply`. */ - case TuplePrefix(prefix: TempSymbol, postfix: TempSymbol) /** The pattern is annotated. The normalization will intepret the pattern * matching behavior based on the resolved symbol */ 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 6b1d9f0222..0743d0ef9e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -354,7 +354,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // Create a split that binds the pattern arguments. def bindPatternArguments(split: Split): Split = patternArguments.foldRight(split): - case ((sym, (_, rcd)), innerSplit) => Split.Let(sym, rcd, innerSplit) + case ((sym, arg), innerSplit) => Split.Let(sym, arg.term, innerSplit) val split = bindPatternArguments(tempLet("matchResult", unapplyCall): resultSymbol => extractionArgsOpt match case N => From e027628ae9d11dad02b014f49da0bdd59dacdf76 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 08:43:15 +0800 Subject: [PATCH 23/62] Remove old naive pattern compilation completely --- .../scala/hkmc2/semantics/Elaborator.scala | 6 +- .../main/scala/hkmc2/semantics/Pattern.scala | 3 +- .../scala/hkmc2/semantics/ucs/Desugarer.scala | 6 +- .../hkmc2/semantics/ucs/DesugaringBase.scala | 20 -- .../hkmc2/semantics/ucs/FlatPattern.scala | 3 - .../hkmc2/semantics/ucs/NaiveCompiler.scala | 295 ++++++------------ .../hkmc2/semantics/ucs/Normalization.scala | 31 +- hkmc2/shared/src/test/mlscript/rp/Future.mls | 78 +++-- hkmc2/shared/src/test/mlscript/rp/Simple.mls | 14 +- .../src/test/mlscript/rp/SimpleTransform.mls | 18 ++ .../src/test/mlscript/rp/examples/Record.mls | 19 +- .../test/mlscript/rp/syntax/Declaration.mls | 3 - .../test/mlscript/rp/syntax/PatternBody.mls | 32 +- 13 files changed, 224 insertions(+), 304 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 0cd77b9969..16bf37b09e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1184,10 +1184,8 @@ extends Importer: scoped("ucs:ups:tree")(log(s"elaborated pattern body: ${pat.showAsTree}")) // Translate the pattern directly into methods that perform matching // using backtracking. - val bod = new ucs.NaiveCompiler(this)( - patternParams, Nil, // TODO: Remove this parameter after we finish - // the pattern translation for string concatenation. - td.rhs.getOrElse(die), pat) + val compiler = new ucs.NaiveCompiler(using tl) + val bod = compiler.compilePattern(patternParams, extractionParams, pat) // `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, N, diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index 8f03e1ed30..7f5038fa2c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -61,7 +61,8 @@ object Pattern: .toSeq /** Get all symbols for the variables. */ - def symbols: Ls[VarSymbol] = varMap.iterator.map(_._2.head.symbol).toList + def symbols: Ls[VarSymbol] = + varMap.iterator.flatMap(_._2.head.symbolOption).toList /** Add a single variable to the variable set. */ def +(alias: Pattern.Alias): Variables = diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala index 6eb0d9b87e..8015756a2c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala @@ -28,7 +28,7 @@ object Desugarer: end Desugarer class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) extends DesugaringBase: - import Desugarer.*, elaborator.term, elaborator.subterm, elaborator.tl.* + import Desugarer.*, elaborator.term, elaborator.subterm, elaborator.tl, tl.* given Ordering[Loc] = Ordering.by: loc => (loc.spanStart, loc.spanEnd) @@ -465,8 +465,8 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten val pattern = tree match case TypeDef(syntax.Pat, body, N) => val pattern = elaborator.pattern(body) - val term = new NaiveCompiler(elaborator).translateAnonymousPattern(Nil, Nil, pattern) - S((pattern, term)) + val compiler = new NaiveCompiler(using tl) + S((pattern, compiler.compileAnonymousPattern(Nil, Nil, pattern))) case td @ TypeDef(k = syntax.Pat) => error(msg"Ill-formed pattern argument" -> td.toLoc); N case _ => N diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala index 6006ec905d..6d8fc8a95c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala @@ -146,23 +146,3 @@ trait DesugaringBase(using Ctx, State): 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 - - /** Make a `Branch` that calls `Pattern` symbols' `unapplyStringPrefix` functions. */ - protected final def makeUnapplyStringPrefixBranch( - scrut: => Term.Ref, - clsTerm: Term, - postfixSymbol: TempSymbol, - inner: => Split, - method: Str = "unapplyStringPrefix" - )(fallback: Split): Split = - val call = app(sel(clsTerm, method), tup(fld(scrut)), s"result of $method") - tempLet("matchResult", call): resultSymbol => - // let `matchResult` be the return value - val argSym = TempSymbol(N, "arg") - val bindingsSymbol = TempSymbol(N, "bindings") - // let `arg` be the first element of `matchResult` - Branch( - resultSymbol.ref().withIArgs(Nil), - matchResultPattern(S(argSym :: bindingsSymbol :: Nil)), - Split.Let(postfixSymbol, callTupleGet(argSym.ref().withIArgs(Nil), 0, "postfix"), inner) - ) ~: fallback 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 4855822c65..afac86afcb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -69,9 +69,6 @@ object FlatPattern: * * @param scrutinee the symbol representing the scrutinee * @param tree the original `Tree` for making error messages - * @param split is for the old pattern compilation. **TODO(ucs/rp)**: Replace - * with suitable representation when implementing the new pattern - * compilation. * @param pattern is for the new pattern compilation and translation. */ final case class Argument( diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala index b27c7830b1..5584c7278c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala @@ -7,7 +7,7 @@ import Message.MessageContext import Split.display, Desugarer.unapply, extractors.* import syntax.{Fun, Keyword, Tree}, Tree.*, Keyword.{`as`, `=>`} import scala.collection.mutable.Buffer -import Elaborator.{Ctx, State, ctx} +import Elaborator.{Ctx, State, ctx}, utils.TL object NaiveCompiler: /** String range bounds must be single characters. */ @@ -74,22 +74,13 @@ object NaiveCompiler: import NaiveCompiler.* -/** This class translates a tree describing a pattern into functions that can - * perform pattern matching on terms described by the pattern. - */ -class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends DesugaringBase: - import elaborator.term, elaborator.tl.*, FlatPattern.MatchMode - import Pattern.* - - private type CaptureMap = Map[Param, Term.Ref] - - private type Inner = CaptureMap => Split - - private type PrefixInner = (CaptureMap, Scrut) => Split +/** 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 DesugaringBase: + import tl.*, FlatPattern.MatchMode, Pattern.* private lazy val lteq = State.builtinOpsMap("<=") private lazy val lt = State.builtinOpsMap("<") - private lazy val eq = State.builtinOpsMap("===") private def makeRangeTest(scrut: Scrut, lo: syntax.Literal, hi: syntax.Literal, rightInclusive: Bool, innerSplit: Split) = def scrutFld = fld(scrut()) @@ -98,14 +89,6 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends val test2 = app(upperOp.ref(), tup(scrutFld, fld(Term.Lit(hi))), "isLessThanUpper") plainTest(test1, "isGreaterThanLower")(plainTest(test2, "isLessThanUpper")(innerSplit)) - @deprecated("Remove after we finished the new pattern translation.") - private def makeRange(scrut: Scrut, lo: syntax.Literal, hi: syntax.Literal, rightInclusive: Bool, inner: Inner) = - def scrutFld = fld(scrut()) - val test1 = app(lteq.ref(), tup(fld(Term.Lit(lo)), scrutFld), "gtLo") - val upperOp = if rightInclusive then lteq else lt - val test2 = app(upperOp.ref(), tup(scrutFld, fld(Term.Lit(hi))), "ltHi") - plainTest(test1, "gtLo")(plainTest(test2, "ltHi")(inner(Map.empty))) - /** Create a pattern object that contains the given pattern. */ def makeAnonymousPatternObject( name: Str, @@ -128,7 +111,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends case ((element, index), (subScrutinees, makeInnerSplit)) => val subScrutinee = makeSubScrutineeSymbol(index) val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(subScrutinee.toScrut, element, Nil /* TODO */)( + makeMatchSplit(subScrutinee.toScrut, element)( (elementOutput, elementBindings) => makeInnerSplit( elementOutput, // TODO: Combine `outerOutput` and `elementOutput` outerBindings ++ elementBindings), @@ -145,11 +128,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends * its symbol exists in this list before binding it. But I'm * not sure whether this check is redundant. */ - private def makeMatchSplit( - scrutinee: Scrut, - pattern: Pattern, - allowedBindings: Ls[VarSymbol] - ): MakeSplit = + private def makeMatchSplit(scrutinee: Scrut, pattern: Pattern): MakeSplit = import Pattern.* pattern match case Constructor(target, patternArguments, arguments) => (makeConsequent, alternative) => @@ -164,7 +143,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends case ((argument, index), (theArguments, makeInnerSplit)) => val subScrutinee = TempSymbol(N, s"argument$index$$") val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(subScrutinee.toScrut, argument, allowedBindings)( + makeMatchSplit(subScrutinee.toScrut, argument)( (argumentOutput, argumentBindings) => makeInnerSplit( argumentOutput, // TODO: Combine `outerOutput` and `argumentOutput` outerBindings ++ argumentBindings), @@ -176,7 +155,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends // inline objects with `unapply` and `unapplyStringPrefix` methods. val arguments0 = patternArguments.iterator.zipWithIndex.map: (pattern, index) => val patternSymbol = TempSymbol(N, s"patternArgument$index$$") - val patternObject = translateAnonymousPattern(Nil, Nil, pattern) + val patternObject = compileAnonymousPattern(Nil, Nil, pattern) FlatPattern.Argument(patternSymbol, Tree.Empty().withLocOf(pattern), S((pattern, patternObject))) .toList val theArguments = arguments1.fold(if arguments0.isEmpty then N else S(arguments0)): @@ -192,10 +171,10 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends val consequent = makeChainedConsequent(outputSymbol.toScrut, Map.empty) Branch(scrutinee(), FlatPattern.ClassLike(target, theArguments, MatchMode.Default, false)(Tree.Dummy, outputSymbol :: Nil), consequent) ~: alternative case Composition(true, left, right) => - makeMatchSplit(scrutinee, left, allowedBindings) | makeMatchSplit(scrutinee, right, allowedBindings) + makeMatchSplit(scrutinee, left) | makeMatchSplit(scrutinee, right) case Composition(false, left, right) => (makeConsequent, alternative) => - makeMatchSplit(scrutinee, left, allowedBindings)( - (leftOutput, leftBindings) => makeMatchSplit(scrutinee, right, allowedBindings)( + makeMatchSplit(scrutinee, left)( + (leftOutput, leftBindings) => makeMatchSplit(scrutinee, right)( (rightOutput, rightBindings) => val tupleIdent = Ident("tupledResults") val tupleSymbol = TempSymbol(N, "tupledResults") @@ -212,7 +191,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends val outputSymbol = TempSymbol(N, "negationOutput") // The place where the diagnostic information should be stored. val outputTerm = scrutinee() - makeMatchSplit(scrutinee, pattern, allowedBindings)( + makeMatchSplit(scrutinee, pattern)( (_output, _bindings) => alternative, // The output and bindings are discarded. Split.Let(outputSymbol, outputTerm, makeConsequent(() => outputSymbol.ref(), Map.empty) ~~: alternative) ) @@ -224,11 +203,13 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends makeRangeTest(scrutinee, lower, upper, rightInclusive, makeConsequent(scrutinee, Map.empty)) ~~: alternative case Concatenation(left, right) => (makeConsequent, alternative) => makeStringPrefixMatchSplit(scrutinee, left)( - (consumedOutput, remainingOutput, bindings) => - makeMatchSplit(remainingOutput, right, allowedBindings)( + (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, bindings) => makeConsequent(scrutinee, bindings) ~~: alternative, + (_postfixOutput, bindingsFromRemaining) => makeConsequent( + scrutinee, bindingsFromConsumed ++ bindingsFromRemaining + ) ~~: alternative, alternative ), alternative @@ -241,7 +222,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends case ((element, index), (subScrutinees, makeInnerSplit)) => val subScrutinee = TempSymbol(N, s"element$index$$") val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(subScrutinee.toScrut, element, allowedBindings)( + makeMatchSplit(subScrutinee.toScrut, element)( (elementOutput, elementBindings) => makeInnerSplit( elementOutput, // TODO: Combine `outerOutput` and `elementOutput` outerBindings ++ elementBindings), @@ -254,7 +235,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends index => TempSymbol(N, s"lastElement$index$$") val spreadSubScrutinee = TempSymbol(N, "middleElements") val makeConsequent1: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(spreadSubScrutinee.toScrut, spread, allowedBindings)( + makeMatchSplit(spreadSubScrutinee.toScrut, spread)( (spreadOutput, spreadBindings) => makeConsequent0( spreadOutput, // TODO: Combine `outerOutput` and `spreadOutput` outerBindings ++ spreadBindings), @@ -270,7 +251,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends case (((key, pattern), index), (fields, makeInnerSplit)) => val subScrutinee = TempSymbol(N, s"field_${key.name}$$") val makeThisSplit: MakeConsequent = (outerOutput, outerBindings) => - makeMatchSplit(subScrutinee.toScrut, pattern, allowedBindings)( + makeMatchSplit(subScrutinee.toScrut, pattern)( (fieldOutput, fieldBindings) => makeInnerSplit( fieldOutput, // TODO: Combine `outerOutput` and `fieldOutput` outerBindings ++ fieldBindings), @@ -281,8 +262,8 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends val consequent = makeChainedConsequent(scrutinee, Map.empty) Branch(scrutinee(), FlatPattern.Record(entries)(Nil), consequent) ~: alternative case Chain(first, second) => (makeConsequent, alternative) => - makeMatchSplit(scrutinee, first, allowedBindings)( - (firstOutput, firstBindings) => makeMatchSplit(firstOutput, second, allowedBindings)( + makeMatchSplit(scrutinee, first)( + (firstOutput, firstBindings) => makeMatchSplit(firstOutput, second)( (secondOutput, secondBindings) => makeConsequent(secondOutput, firstBindings ++ secondBindings), alternative), alternative) @@ -291,9 +272,9 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends // reported errors. case N => log(s"pattern ${pattern.showDbg} doesn't have an alias symbol: ${id.name}") - makeMatchSplit(scrutinee, pattern, allowedBindings) + makeMatchSplit(scrutinee, pattern) case S(symbol) => (makeConsequent, alternative) => - makeMatchSplit(scrutinee, pattern, allowedBindings)( + makeMatchSplit(scrutinee, pattern)( (output, bindings) => makeConsequent(output, bindings + (symbol -> output)), alternative) @@ -319,7 +300,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends // 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, symbols)( + 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) => @@ -337,21 +318,32 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends private def makeStringPrefixMatchSplit( scrutinee: Scrut, pattern: Pattern, - ): MakePrefixSplit = pattern match - case Constructor(target, patternArguments, arguments) => (makeConsequent, alternative) => + )(using Raise): MakePrefixSplit = pattern match + case Constructor(target, patternArguments, arguments) => // TODO: Handle `patternArguments` and `arguments` accordingly. + // // This case is very different from the `Constructor` case in - // `makeMatchSplit` because `target` can only be a pattern. Currently, - // I have not figured out how to handle `arguments`. So, let me just - // ignore them for now. - val outputSymbol = TempSymbol(N, "output") // Denotes the pattern's output. - val remainingSymbol = TempSymbol(N, "remaining") // Denotes the remaining value. - val consequent = makeConsequent(outputSymbol.toScrut, remainingSymbol.toScrut, Map.empty) - val mode = MatchMode.StringPrefix(outputSymbol, remainingSymbol) - // `Normalization.normalizeStringPrefixPattern` is responsible for - // declaring the symbols we created here. - val pattern = FlatPattern.ClassLike(target, N, mode, false)(Tree.Dummy, outputSymbol :: Nil) - Branch(scrutinee(), pattern, consequent) ~: alternative + // `makeMatchSplit` because we know the `scrutinee` is a string. + // Hence, the match is acceptable only if `target` is a pattern that + // also matches a string. If `target` is a class or object, we should + // directly reject. + // + // However, we do not know whether `target` is a pattern or not until + // the lowering stage. As discussed, we will move `NaiveCompiler` to the + // lowering stage. + (makeConsequent, alternative) => + val outputSymbol = TempSymbol(N, "output") // Denotes the pattern's output. + val remainingSymbol = TempSymbol(N, "remaining") // Denotes the remaining value. + // This is just a temporary solution. After moving `NaiveCompiler` to + // the lowering stage, it should be implemented correctly. + val argumentVariables = arguments.fold(Map.empty: BindingMap): + _.foldLeft(Map.empty: BindingMap): + case (acc, pattern) => acc ++ pattern.variables.symbols.map: symbol => + symbol -> symbol.toScrut + log(s"argumentVariables of ${pattern.showDbg} are ${argumentVariables.keys.map(_.nme).mkString(", ")}") + val consequent = makeConsequent(outputSymbol.toScrut, remainingSymbol.toScrut, argumentVariables) + val mode = MatchMode.StringPrefix(outputSymbol, remainingSymbol) + Branch(scrutinee(), FlatPattern.ClassLike(target, N, mode, false)(Tree.Dummy, outputSymbol :: Nil), consequent) ~: alternative case Composition(true, left, right) => val makeLeft = makeStringPrefixMatchSplit(scrutinee, left) val makeRight = makeStringPrefixMatchSplit(scrutinee, right) @@ -364,7 +356,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends // prefix and retry `right`. // TODO: Implement the correct backtracking behavior. makeStringPrefixMatchSplit(scrutinee, left)( - (leftOutput, leftRemains, leftBindings) => makeMatchSplit(scrutinee, right, Nil /* TODO */)( + (leftOutput, leftRemains, leftBindings) => makeMatchSplit(scrutinee, right)( (rightOutput, rightBindings) => val productSymbol = TempSymbol(N, "product") val productTerm = tup(leftOutput() |> fld, rightOutput() |> fld) @@ -372,7 +364,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends productSymbol.toScrut, leftRemains, leftBindings ++ rightBindings)), alternative), alternative) - case Negation(pattern) => (makeConsequent, 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 @@ -381,7 +373,7 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends // 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. @@ -442,17 +434,23 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends // continuation. makeStringPrefixMatchSplit(scrutinee, first)( (firstOutput, firstRemains, firstBindings) => - makeMatchSplit(firstOutput, second, Nil /* TODO */)( + makeMatchSplit(firstOutput, second)( (secondOutput, secondBindings) => makeConsequent(secondOutput, firstRemains, firstBindings ++ secondBindings), alternative), alternative) - case alias @ Alias(pattern, id) => (makeConsequent, alternative) => - // TODO: Duplicate code with the `Alias` case in `makeMatchSplit`. - makeStringPrefixMatchSplit(scrutinee, pattern)( - (output, remains, bindings) => - makeConsequent(output, remains, bindings + (alias.symbol -> output)), - 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 @@ -474,102 +472,31 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends Split.Let(resultSymbol, resultTerm, makeConsequent(resultSymbol.toScrut, remains, Map.empty)), alternative)) - /** Generate a split that consumes the prefix of the scrutinee. */ - private def stringPrefix(scrut: Scrut, pat: Tree, inner: PrefixInner)(using Raise): Split = trace( - pre = s"stringPrefix <<< $pat", - post = (split: Split) => s"stringPrefix >>> $split" - ): - pat.deparenthesized match - case lhs or rhs => stringPrefix(scrut, lhs, inner) ~~: stringPrefix(scrut, rhs, inner) - case (lo: StrLit) to (incl, hi: StrLit) => if isInvalidStringBounds(lo, hi) then failure else - val emptyTest = app(eq.ref(), tup(fld(scrut()), fld(str(""))), "test empty") - val headTerm = callStringGet(scrut(), 0, "head") - val tailTerm = callStringDrop(scrut(), 1, "tail") - plainTest(emptyTest, "emptyTest")(failure) ~~: - tempLet("head", headTerm): headSym => - tempLet("tail", tailTerm): tailSym => - makeRange(() => headSym.ref(), lo, hi, incl, captures => - inner(Map.empty, () => tailSym.ref())) - case (lo: IntLit) to (incl, hi: IntLit) => Split.End - case (lo: DecLit) to (incl, hi: DecLit) => Split.End - case (lo: syntax.Literal) to (_, hi: syntax.Literal) => - error(msg"Incompatible range types: ${lo.describe} to ${hi.describe}" -> pat.toLoc) - errorSplit - case lit @ StrLit(value) => - plainTest(callStringStartsWith(scrut(), Term.Lit(lit), "startsWith")): - tempLet("sliced", callStringDrop(scrut(), value.length, "sliced")): slicedSym => - inner(Map.empty, () => slicedSym.ref()) - case prefix ~ postfix => - stringPrefix(scrut, prefix, (captures1, postfixScrut1) => - stringPrefix(postfixScrut1, postfix, (captures2, postfixScrut2) => - inner(captures2 ++ captures1, postfixScrut2))) - case Under() => inner(Map.empty, scrut) // TODO: check if this is correct - case ctor @ (_: Ident | _: Sel) => - val ctorTrm = term(ctor) - val prefixSymbol = new TempSymbol(N, "prefix") - val postfixSymbol = new TempSymbol(N, "postfix") - val mode = MatchMode.StringPrefix(prefixSymbol, postfixSymbol) - val pattern = FlatPattern.ClassLike(ctorTrm, N, mode, false)(ctor, Nil) - Branch(scrut(), pattern, inner(Map.empty, () => postfixSymbol.ref())) ~: Split.End - case pat => - error(msg"Unrecognized pattern (${pat.describe})" -> pat.toLoc) - errorSplit - - /** Create a function that compiles the resulting term of each case. It checks - * the captured references and sort them in the order of parameters. - */ - private def success(params: Ls[Param]): Inner = - val paramIndexMap = params.zipWithIndex.toMap - captures => trace( - pre = s"success <<< ${params.iterator.map(_.sym).mkString(", ")}", - post = (split: Split) => s"success >>> ${display(split)}" - ): - require(captures.forall(_._1 |> paramIndexMap.contains)) - if captures.size != params.size then - // TODO: report uncaptured parameters and add tests after captures/extraction is done - error(msg"Unmatched number of captures and parameters." -> N) - Split.Else(Term.Error) - else - val fields = captures.toList.sortBy(_._1 |> paramIndexMap).map: - case (_, ref) => Fld(FldFlags.empty, ref, N) - Split.Else(makeMatchResult(Term.Tup(fields)(Tup(Nil)))) - - /* The successful matching result used in prefix matching functions. */ - private def prefixSuccess(params: Ls[Param]): PrefixInner = - val paramIndexMap = params.zipWithIndex.toMap - (captures, postfixScrut) => trace( - pre = s"prefixSuccess <<< ${params.iterator.map(_.sym).mkString(", ")}", - post = (split: Split) => s"prefixSuccess >>> ${display(split)}" - ): - require(captures.forall(_._1 |> paramIndexMap.contains)) - if captures.size != params.size then - // TODO: report uncaptured parameters - error(msg"Unmatched number of captures and parameters." -> N) - Split.Else(Term.Error) - else - val fields = captures.toList.sortBy(_._1 |> paramIndexMap).map: - case (_, ref) => Fld(FldFlags.empty, ref, N) - val head = Fld(FldFlags.empty, postfixScrut(), N) - Split.Else(makeMatchResult(Term.Tup(head :: fields)(Tup(Nil)))) - - /** Failed matctching result. */ + /** 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()) private def errorSplit: Split = Split.Else(Term.Error) - /** Create a function definition from the given UCS splits. + /** 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 makeMatcher(name: Str, patternParameters: List[Param], scrut: VarSymbol, topmost: Split): TermDefinition = + private def makeMethod( + owner: Opt[PatternSymbol], + name: Str, + patternParameters: List[Param], + scrut: VarSymbol, + topmost: Split + ): TermDefinition = 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) - val body = Term.IfLike(Keyword.`if`, topmost) - val res = FlowSymbol(s"the return value of $name") - TermDefinition(N, Fun, sym, ps :: Nil, N, N, S(body), res, TermDefFlags.empty, Modulefulness.none, Nil) + TermDefinition(owner, Fun, sym, ps :: Nil, N, N, S(Term.IfLike(Keyword.`if`, topmost)), + FlowSymbol(s"‹unapply-result›"), TermDefFlags.empty, Modulefulness.none, Nil) /** Translate a list of extractor/matching functions for the given pattern. * There are currently two functions: `unapply` and `unapplyStringPrefix`. @@ -585,40 +512,32 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends * `Pattern` class. Now the new `pattern` parameter and the * old `body` parameter are mixed. */ - def apply(patternParams: Ls[Param], params: Ls[Param], body: Tree, pattern: Pattern): Ls[TermDefinition] = trace( - pre = s"NaiveCompiler <<< ${params.mkString(", ")} $body", - post = (blk: Ls[TermDefinition]) => s"NaiveCompiler >>> $blk" + def compilePattern(patternParams: Ls[Param], params: Ls[Param], pattern: Pattern): Ls[TermDefinition] = trace( + pre = s"compilePattern <<< ${params.mkString(", ")}", + post = (blk: Ls[TermDefinition]) => s"compilePattern >>> $blk" ): val unapply = scoped("ucs:translation"): val inputSymbol = VarSymbol(Ident("input")) - val topmost = makeMatchSplit( - scrutinee = () => inputSymbol.ref().withIArgs(Nil), - pattern = pattern, - allowedBindings = Nil // TODO: pass proper bindings - )( - (output, bindings) => Split.Else(makeMatchResult(output())), - failure - ) + val topmost = makeMatchSplit(inputSymbol.toScrut, pattern) + ((output, bindings) => Split.Else(makeMatchResult(output())), failure) log(s"Translated `unapply`: ${display(topmost)}") - makeMatcher("unapply", patternParams, inputSymbol, topmost) + makeMethod(N, "unapply", patternParams, inputSymbol, topmost) val unapplyStringPrefix = scoped("ucs:cp"): - // We don't report errors here because they are already reported in the - // translation of `unapply` function. + // We don't report errors here because they have been already reported in + // the translation of `unapply` function. given Raise = Function.const(()) - val scrutSym = VarSymbol(Ident("topic")) - stringPrefix(() => scrutSym.ref(), body, prefixSuccess(params)) match - case Split.Else(Term.Error) => - makeMatcher("unapplyStringPrefix", patternParams, scrutSym, failure) - case split => - val topmost = split ~~: failure - log(s"Translated `unapplyStringPrefix`: ${display(topmost)}") - makeMatcher("unapplyStringPrefix", patternParams, scrutSym, topmost) + 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 `unapply`: ${display(topmost)}") + makeMethod(N, "unapplyStringPrefix", patternParams, inputSymbol, topmost) unapply :: unapplyStringPrefix :: Nil /** Translate an anonymous pattern. They are usually pattern arguments. */ - def translateAnonymousPattern(patternParams: Ls[Param], params: Ls[Param], pattern: Pattern): Term.Rcd = trace( - pre = s"translateAnonymousPattern <<< $pattern", - post = (blk: Term.Rcd) => s"translateAnonymousPattern >>> $blk" + def compileAnonymousPattern(patternParams: Ls[Param], params: Ls[Param], pattern: Pattern): Term.Rcd = trace( + pre = s"compileAnonymousPattern <<< $pattern", + post = (blk: Term.Rcd) => s"compileAnonymousPattern >>> $blk" ): // We should apply an optimization to avoid generating unnecessary objects. // If the pattern is a constructor pattern, we can just reference the @@ -626,26 +545,8 @@ class NaiveCompiler(val elaborator: Elaborator)(using State, Ctx, Raise) extends // stage that we can know the `target` refers to a pattern or not. val unapplyStmts = scoped("ucs:translation"): val inputSymbol = VarSymbol(Ident("input")) - val topmost = makeMatchSplit( - scrutinee = () => inputSymbol.ref().withIArgs(Nil), - pattern = pattern, - allowedBindings = Nil // TODO: pass proper bindings - )( - (output, bindings) => Split.Else(makeMatchResult(output())), - failure - ) + val topmost = makeMatchSplit(inputSymbol.toScrut, pattern) + ((output, bindings) => Split.Else(makeMatchResult(output())), failure) log(s"Translated `unapply`: ${display(topmost)}") makeAnonymousPatternObject("unapply", patternParams, inputSymbol, topmost) - // val stmts2 = scoped("ucs:cp"): - // // We don't report errors here because they are already reported in the - // // translation of `unapply` function. - // given Raise = Function.const(()) - // val scrutSym = VarSymbol(Ident("input")) - // stringPrefix(() => scrutSym.ref(), body, prefixSuccess(params)) match - // case Split.Else(Term.Error) => - // makeAnonymousPatternObject("unapplyStringPrefix", patternParams, scrutSym, failure) - // case split => - // val topmost = split ~~: failure - // log(s"Translated `unapplyStringPrefix`: ${display(topmost)}") - // makeAnonymousPatternObject("unapplyStringPrefix", patternParams, scrutSym, topmost) Term.Rcd(unapplyStmts) 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 0743d0ef9e..313c5c4546 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -152,7 +152,14 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // is in a pattern translation. if argsOpt.fold(false)(_.nonEmpty) then error(msg"Pattern parameters cannot be applied." -> ctor.toLoc) - normalizeExtractorPatternParameter(scrutinee, ctor, pattern.output, consequent, alternative) + mode match + case MatchMode.Default => + normalizeExtractorPatternParameter(scrutinee, ctor, pattern.output, consequent, alternative) + case MatchMode.StringPrefix(prefix, postfix) => + normalizeStringPrefixPattern(scrutinee, ctor, postfix, 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) @@ -187,7 +194,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas case MatchMode.Default => normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, pattern.output, consequent, normalizeImpl(alternative)) case MatchMode.StringPrefix(prefix, postfix) => - normalizeStringPrefixPattern(scrutinee, pat, ctor, postfix, consequent, normalizeImpl(alternative)) + normalizeStringPrefixPattern(scrutinee, ctor, postfix, 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)) @@ -333,7 +340,6 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas alternative: Split, )(using VarSet): Split = scoped("ucs:np"): - log(s"patternSymbol: ${patternSymbol.nme}") log: allArgsOpt.fold(Iterator.empty[Str]): _.iterator.map: @@ -414,13 +420,26 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas private def normalizeStringPrefixPattern( scrutinee: Term.Ref, - patternSymbol: PatternSymbol, ctorTerm: Term, - postfixSymbol: TempSymbol, + remainingSymbol: TempSymbol, + outputSymbols: Ls[BlockLocalSymbol], consequent: Split, alternative: Split, )(using VarSet): Split = - normalize(makeUnapplyStringPrefixBranch(scrutinee, ctorTerm, postfixSymbol, consequent)(alternative)) + val method = "unapplyStringPrefix" + val call = app(sel(ctorTerm, method), tup(fld(scrutinee)), s"result of $method") + val split = tempLet("matchResult", call): resultSymbol => + // let `matchResult` be the return value + val argSym = TempSymbol(N, "arg") + val bindingsSymbol = TempSymbol(N, "bindings") + // let `arg` be the first element of `matchResult` + Branch( + resultSymbol.ref().withIArgs(Nil), + matchResultPattern(S(argSym :: bindingsSymbol :: Nil)), + aliasOutputSymbols(resultSymbol.safeRef, outputSymbols, + Split.Let(remainingSymbol, callTupleGet(argSym.ref().withIArgs(Nil), 1, "postfix"), consequent)) + ) ~: alternative + normalize(split) // Note: This function will be overhauled in the new pattern compilation scheme. private def normalizeCompiledPattern( diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/rp/Future.mls index b0f97da6cc..777b3d1145 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Future.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Future.mls @@ -39,27 +39,25 @@ pattern Rep0(pattern A, B, C)(head) = //│ ╟── The variable is missing from this sub-pattern. //│ ║ l.27: "" | (A as head) ~ Rep0[A] //│ ╙── ^^ -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: no symbol was assigned to variable `head` at Some(Loc(51,55,Future.mls:+26)) :todo // Pattern extractions via aliases. pattern Email(name, domain) = (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╔══[ERROR] Duplicate pattern variable. -//│ ║ l.46: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.45: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ║ ^^^^^^^^^^ //│ ╟── The previous definition is as follows. -//│ ║ l.46: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.45: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^ //│ ╔══[WARNING] Useless pattern binding: Identifier. -//│ ║ l.46: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.45: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^ -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: no symbol was assigned to variable `Identifier` at Some(Loc(33,43,Future.mls:+45)) :todo // View patterns pattern GreaterThan(value) = case n and n > value then n //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.60: n and n > value then n +//│ ║ l.58: n and n > value then n //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ :todo @@ -72,11 +70,11 @@ fun foo(x) = if x is Unit then .... Arrow(...) then .... //│ ╔══[ERROR] Unrecognized pattern split (infix operator). -//│ ║ l.71: view as +//│ ║ l.69: view as //│ ║ ^^^^^^^ -//│ ║ l.72: Unit then .... +//│ ║ l.70: Unit then .... //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ║ l.73: Arrow(...) then .... +//│ ║ l.71: Arrow(...) then .... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -94,16 +92,19 @@ 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.91: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ║ l.89: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^ //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.91: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ║ l.89: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^^^^ :todo pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.104: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) +//│ ║ l.102: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) +//│ ╙── ^^^ +//│ ╔══[ERROR] Cannot use this reference as a pattern +//│ ║ l.102: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ╙── ^^^ :todo @@ -113,11 +114,11 @@ pattern LineSep(pattern P) = case "" then nd :: Nil "\n" ~ LineSep(P, t) then nd :: t //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.111: "" then Nil +//│ ║ l.112: "" then Nil //│ ║ ^^^^^^^^^^^ -//│ ║ l.112: L(...nd) ~ +//│ ║ l.113: L(...nd) ~ //│ ║ ^^^^^^^^^^^^ -//│ ║ l.113: "" then nd :: Nil +//│ ║ l.114: "" then nd :: Nil //│ ╙── ^^^^ :todo @@ -131,29 +132,26 @@ pattern Lines(pattern L) = case :todo if input is Lines of Email then //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.132: if input is Lines of Email then +//│ ║ l.133: if input is Lines of Email then //│ ╙── ^ //│ ╔══[ERROR] Unexpected record property pattern. -//│ ║ l.125: "" then [] +//│ ║ l.126: "" then [] //│ ║ ^^^^^^^^^^ -//│ ║ l.126: pattern Tail = case +//│ ║ l.127: pattern Tail = case //│ ║ ^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.127: "" then [] +//│ ║ l.128: "" then [] //│ ║ ^^^^^^^^^^^^^^ -//│ ║ l.128: "\n" ~ (Lines of L) as t then t +//│ ║ l.129: "\n" ~ (Lines of L) as t then t //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.129: L as h ~ Tail as t then [h, ...t] +//│ ║ l.130: L as h ~ Tail as t then [h, ...t] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.130: +//│ ║ l.131: //│ ║ ^^^ -//│ ║ l.131: :todo +//│ ║ l.132: :todo //│ ╙── ^^^^^ //│ ╔══[ERROR] Name not found: input -//│ ║ l.132: if input is Lines of Email then +//│ ║ l.133: if input is Lines of Email then //│ ╙── ^^^^^ -//│ ╔══[ERROR] Name not found: Email -//│ ║ l.132: if input is Lines of Email then -//│ ╙── ^^^^^ :todo pattern Opt(pattern P) = case @@ -163,25 +161,25 @@ pattern Opt(pattern P) = case :todo pattern Email(name, domain) = ... //│ ╔══[ERROR] Unexpected record property pattern. -//│ ║ l.160: P(value) then (Some(value)) +//│ ║ l.158: P(value) then (Some(value)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.161: "" then (None) +//│ ║ l.159: "" then (None) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.162: +//│ ║ l.160: //│ ║ ^^^ -//│ ║ l.163: :todo +//│ ║ l.161: :todo //│ ╙── ^^^^^ :todo if input is Opt(Email, Some((n, d))) then ... //│ ╔══[ERROR] Name not found: input -//│ ║ l.176: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.174: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^^ //│ ╔══[ERROR] Name not found: Some -//│ ║ l.176: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.174: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^ //│ ╔══[ERROR] invalid record field pattern -//│ ║ l.176: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.174: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^ //│ ═══[ERROR] Expected zero arguments, but found two arguments. @@ -190,9 +188,9 @@ pattern Opt(pattern P) = case P(...values) then (Some(values)) "" then (None) //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.190: P(...values) then (Some(values)) +//│ ║ l.188: P(...values) then (Some(values)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.191: "" then (None) +//│ ║ l.189: "" then (None) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ :todo @@ -200,16 +198,16 @@ pattern Opt(pattern P) = case P(...values) then Some(values) "" then None //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.200: P(...values) then Some(values) +//│ ║ l.198: P(...values) then Some(values) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.201: "" then None +//│ ║ l.199: "" then None //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ :todo pattern Digits = "0" to "9" ~ (Digits | "") //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.209: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.207: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^ //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.209: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.207: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/rp/Simple.mls b/hkmc2/shared/src/test/mlscript/rp/Simple.mls index 1d64fdb545..12c4f57299 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Simple.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Simple.mls @@ -16,13 +16,15 @@ pattern Zero = "0" //│ return runtime.MatchFailure(null) //│ } //│ } -//│ unapplyStringPrefix(topic) { -//│ let cond, sliced; -//│ cond = runtime.Str.startsWith(topic, "0"); -//│ if (cond === true) { -//│ sliced = runtime.Str.drop(topic, 1); +//│ unapplyStringPrefix(input1) { +//│ let isLeading, consumed, remains; +//│ isLeading = runtime.Str.startsWith(input1, "0"); +//│ if (isLeading === true) { +//│ consumed = runtime.Str.take(input1, 1); +//│ remains = runtime.Str.drop(input1, 1); //│ return runtime.MatchResult([ -//│ sliced +//│ consumed, +//│ remains //│ ], {}) //│ } else { //│ return runtime.MatchFailure(null) diff --git a/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls b/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls index 28103889a3..7a25a3d079 100644 --- a/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls +++ b/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls @@ -232,3 +232,21 @@ pattern Positive = (x & (((Int as x) => x > 0) as true)) => x Positive.unapply(100) //│ = MatchResult(100, {}) + +// ____ _ _ +// / ___|| |_ _ __(_)_ __ __ _ +// \___ \| __| '__| | '_ \ / _` | +// ___) | |_| | | | | | | (_| | +// |____/ \__|_| |_|_| |_|\__, | +// |___/ +// ============================== + +pattern HelloWorld = + (("hello" as a) ~ " " ~ ("world" as b)) => + "the user said " + a + " to the " + b + +"hello world" is HelloWorld +//│ = true + +if "hello world" is HelloWorld as x then x +//│ = "the user said hello to the world" diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls b/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls index 4a58bf7c8f..cec0e929c8 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls @@ -102,8 +102,21 @@ fun parseIntegerLiteral(value) = if //│ ║ l.98: value is "0" ~ (("x" | "o" | "b") as radix) ~ digits and //│ ╙── ^ -// The workaround we can use currently is to use a pattern declaration. -:fixme pattern IntegerLiteral = ("0" ~ (("x" | "o" | "b") as radix) ~ digits) => (:radix, :digits) -//│ /!!!\ Uncaught error: java.util.NoSuchElementException: key not found: radix + +fun parseIntegerLiteral(value) = if + value is IntegerLiteral as output then Some(output) + else None + +parseIntegerLiteral of "0x1234" +//│ = Some({radix: "x", digits: "1234"}) + +parseIntegerLiteral of "0b0000111100001111" +//│ = Some({radix: "b", digits: "0000111100001111"}) + +parseIntegerLiteral of "0o673264673" +//│ = Some({radix: "o", digits: "673264673"}) + +parseIntegerLiteral of "0k1234" +//│ = None diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls index 03867b6dc1..7b4e8256b7 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls @@ -9,9 +9,6 @@ pattern Foo(val T) = null | T //│ ║ l.4: pattern Foo(val T) = null | T //│ ║ ^ //│ ╙── The variable is missing from this sub-pattern. -//│ ╔══[ERROR] Name not found: T -//│ ║ l.4: pattern Foo(val T) = null | T -//│ ╙── ^ pattern Foo(pattern T) = null | T diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls index 970377767d..6636205ee7 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls @@ -44,13 +44,9 @@ pattern PairLike(fst, snd) = Pair(fst, snd) | [fst, snd] //│ elaborated pattern body: member:Pair(fst, snd) ∨ [fst, snd] :w -:e // The errors are from the old pattern translation and compilation. pattern UselessParameter = x //│ ╔══[WARNING] Useless pattern binding: x. -//│ ║ l.48: pattern UselessParameter = x -//│ ╙── ^ -//│ ╔══[ERROR] Name not found: x -//│ ║ l.48: pattern UselessParameter = x +//│ ║ l.47: pattern UselessParameter = x //│ ╙── ^ :ucs ups @@ -60,13 +56,13 @@ pattern PlainList(pattern T) = (null => Nil) | ([T as hd, PlainList(T) as tl] => :fixme [T as hd, PlainList(pattern T) as tl] //│ ╔══[ERROR] Name not found: hd -//│ ║ l.61: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.57: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^^ //│ ╔══[ERROR] Illegal type declaration in term position. -//│ ║ l.61: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.57: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^ //│ ╔══[ERROR] Name not found: tl -//│ ║ l.61: [T as hd, PlainList(pattern T) as tl] +//│ ║ l.57: [T as hd, PlainList(pattern T) as tl] //│ ╙── ^^ //│ ═══[RUNTIME ERROR] TypeError: PlainList3 is not a function @@ -77,19 +73,19 @@ pattern Consistent = ((Int as x) | (Str as x)) => x.toString() :e pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.78: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.74: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.78: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.74: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.78: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() +//│ ║ l.74: pattern Inconsistent = ~(~(Int as x) | Str) => x.toString() //│ ╙── ^ :e pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() //│ ╔══[ERROR] Name not found: x -//│ ║ l.90: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() +//│ ║ l.86: pattern Inconsistent = (~(~((Int as x) => x)) | Str) => x.toString() //│ ╙── ^ :ucs ups @@ -119,13 +115,13 @@ pattern BadNegated = ~(Int as x) => x :e pattern BadNegated = (~(Int as x)) => x //│ ╔══[ERROR] This pattern cannot be bound. -//│ ║ l.120: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.116: pattern BadNegated = (~(Int as x)) => x //│ ║ ^^^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.120: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.116: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.120: pattern BadNegated = (~(Int as x)) => x +//│ ║ l.116: pattern BadNegated = (~(Int as x)) => x //│ ╙── ^ //│ elaborated pattern body: ¬(member:Int as x) => @@ -133,13 +129,13 @@ pattern BadNegated = (~(Int as x)) => x :e pattern BadNegated = (~Pair(Int, x)) => x //│ ╔══[ERROR] This variable cannot be accessed. -//│ ║ l.134: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.130: pattern BadNegated = (~Pair(Int, x)) => x //│ ║ ^ //│ ╟── Because the pattern it belongs to is negated. -//│ ║ l.134: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.130: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^^^^^^^^^^^ //│ ╔══[ERROR] Name not found: x -//│ ║ l.134: pattern BadNegated = (~Pair(Int, x)) => x +//│ ║ l.130: pattern BadNegated = (~Pair(Int, x)) => x //│ ╙── ^ //│ elaborated pattern body: ¬member:Pair(member:Int, x) => From f209f8fe08a10f1126d4b85c3660d0df20baee12 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 09:19:15 +0800 Subject: [PATCH 24/62] Call `NaiveCompiler` from `Lowering` --- .../main/scala/hkmc2/codegen/Lowering.scala | 19 +++++++++- .../scala/hkmc2/semantics/Elaborator.scala | 7 +--- .../src/main/scala/hkmc2/semantics/Term.scala | 14 ++++--- .../hkmc2/semantics/ucs/DesugaringBase.scala | 6 +-- .../hkmc2/semantics/ucs/NaiveCompiler.scala | 38 ++++++++++--------- .../scala/hkmc2/semantics/ucs/package.scala | 2 +- 6 files changed, 52 insertions(+), 34 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index ac82639386..5127be9c08 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -183,7 +183,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx): blockImpl(stats, res)(k) case cls: ClassLikeDef => reportAnnotations(cls, cls.extraAnnotations) - val (mtds, publicFlds, privateFlds, ctor) = gatherMembers(cls.body) + val (mtds, publicFlds, privateFlds, ctor) = cls match + case pd: PatternDef => compilePatternMethods(pd) + case _ => gatherMembers(cls.body) cls.ext match case N => Define(ClsLikeDefn(cls.owner, cls.sym, cls.bsym, cls.kind, cls.paramsOpt, cls.auxParams, N, @@ -812,6 +814,21 @@ class Lowering()(using Config, TL, Raise, State, Ctx): case t => t (mtds, publicFlds, privateFlds, ctor) + /** Compile the pattern definition into `unapply` and `unapplyStringPrefix` + * methods using the `NaiveCompiler`, 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], Ls[TermSymbol], Block) = + val compiler = new ucs.NaiveCompiler + 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) + (mtds, Nil, Nil, End()) + def args(elems: Ls[Elem])(k: Ls[Arg] => Block)(using Subst): Block = val as = elems.map: case sem.Fld(sem.FldFlags.benign(), value, N) => R(N -> value) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 16bf37b09e..14c81a0817 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1182,15 +1182,10 @@ extends Importer: case N => raise(WarningReport(msg"Useless pattern binding: $name." -> aliases.head.toLoc :: Nil)) scoped("ucs:ups")(log(s"elaborated pattern body: ${pat.showDbg}")) scoped("ucs:ups:tree")(log(s"elaborated pattern body: ${pat.showAsTree}")) - // Translate the pattern directly into methods that perform matching - // using backtracking. - val compiler = new ucs.NaiveCompiler(using tl) - val bod = compiler.compilePattern(patternParams, extractionParams, pat) // `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, N, - patternParams, extractionParams, pat, Nil, - ObjBody(Blk(bod, Term.Lit(UnitLit(false)))), annotations) + patternParams, extractionParams, pat, annotations) patSym.defn = S(pd) pd case k: (Mod.type | Obj.type) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index b4df339a62..e297fe3bff 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -504,17 +504,21 @@ case class PatternDef( /** The extraction parameters, for example, `x` in * `pattern PairLike(x, y) = [x, y] | Pair(x, y)`. */ extractionParams: Ls[Param], - /** The elaborated pattern right-hand side. */ + /** The elaborated pattern on the right-hand side, for example, + * `[x, y] | Pair(x, y)` in `pattern PairLike(x, y) = [x, y] | Pair(x, y)`. + */ pattern: Pattern, - // Here, `ObjBody` contains methods `unapply` and `unapplyStringPrefix`, - // which are generated from the pattern definition. - auxParams: Ls[ParamList], - body: ObjBody, annotations: Ls[Annot], ) extends ClassLikeDef: self => val kind: ClsLikeKind = Pat val ext: Opt[New] = N + /** Each pattern definition should contain two methods: `unapply` and + * `unapplyStringPrefix`, which are generated in `Lowering`. Hence, there + * is no need to make `body` an parameter. */ + val body: ObjBody = ObjBody(Blk(Nil, Term.Lit(syntax.Tree.UnitLit(false)))) + /** Pattern definitions can only have one parameter list. */ + val auxParams: Ls[ParamList] = Nil sealed abstract class ClassDef extends ClassLikeDef: diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala index 6d8fc8a95c..a6e6b178ce 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala @@ -89,7 +89,7 @@ trait DesugaringBase(using Ctx, State): protected final def plainTest(cond: Term, dbgName: Str = "cond")(inner: => Split): Split = val s = TempSymbol(N, dbgName) - Split.Let(s, cond, Branch(s.ref(), inner) ~: Split.End) + Split.Let(s, cond, Branch(s.safeRef, inner) ~: Split.End) protected final def makeMatchResult(output: Term) = app(matchResultClass, tup(fld(output), fld(rcd())), "result of `MatchResult`") @@ -109,9 +109,9 @@ trait DesugaringBase(using Ctx, State): localPatternSymbol: BlockLocalSymbol, inner: => Split, )(fallback: Split): Split = - val call = app(localPatternSymbol.ref().withIArgs(Nil), tup(fld(scrut)), s"result of ${localPatternSymbol.nme}") + val call = app(localPatternSymbol.safeRef, tup(fld(scrut)), s"result of ${localPatternSymbol.nme}") tempLet("matchResult", call): resultSymbol => - Branch(resultSymbol.ref().withIArgs(Nil), matchResultPattern(N), inner) ~: fallback + Branch(resultSymbol.safeRef, matchResultPattern(N), inner) ~: fallback protected final def makeTupleBranch( scrut: => Term.Ref, diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala index 5584c7278c..341d7c3984 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala @@ -26,7 +26,7 @@ object NaiveCompiler: type Scrut = () => Term.Ref extension (symbol: BlockLocalSymbol) - def toScrut: Scrut = () => symbol.ref().withIArgs(Nil) + def toScrut: Scrut = () => symbol.safeRef type BindingMap = Map[VarSymbol, Scrut] @@ -84,9 +84,9 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas private def makeRangeTest(scrut: Scrut, lo: syntax.Literal, hi: syntax.Literal, rightInclusive: Bool, innerSplit: Split) = def scrutFld = fld(scrut()) - val test1 = app(lteq.ref(), tup(fld(Term.Lit(lo)), scrutFld), "isGreaterThanLower") + val test1 = app(lteq.safeRef, tup(fld(Term.Lit(lo)), scrutFld), "isGreaterThanLower") val upperOp = if rightInclusive then lteq else lt - val test2 = app(upperOp.ref(), tup(scrutFld, fld(Term.Lit(hi))), "isLessThanUpper") + val test2 = app(upperOp.safeRef, tup(scrutFld, fld(Term.Lit(hi))), "isLessThanUpper") plainTest(test1, "isGreaterThanLower")(plainTest(test2, "isLessThanUpper")(innerSplit)) /** Create a pattern object that contains the given pattern. */ @@ -102,7 +102,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas 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.ref()) + val field = RcdField(Term.Lit(StrLit(name)), fieldSymbol.safeRef) decl :: defineVar :: field :: Nil extension (patterns: Ls[Pattern]) @@ -179,7 +179,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas val tupleIdent = Ident("tupledResults") val tupleSymbol = TempSymbol(N, "tupledResults") val tupleTerm = tup(leftOutput() |> fld, rightOutput() |> fld) - Split.Let(tupleSymbol, tupleTerm, makeConsequent(() => tupleSymbol.ref(), leftBindings ++ rightBindings) ~~: alternative), + Split.Let(tupleSymbol, tupleTerm, makeConsequent(() => tupleSymbol.safeRef, leftBindings ++ rightBindings) ~~: alternative), alternative), alternative) case Negation(pattern) => (makeConsequent, alternative) => @@ -193,7 +193,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas val outputTerm = scrutinee() makeMatchSplit(scrutinee, pattern)( (_output, _bindings) => alternative, // The output and bindings are discarded. - Split.Let(outputSymbol, outputTerm, makeConsequent(() => outputSymbol.ref(), Map.empty) ~~: alternative) + 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) @@ -307,7 +307,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas 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.ref(), tup(arguments*), "the transform's result") + 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)) @@ -379,7 +379,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas // 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.ref(), N)(Nil), + Branch(scrutinee(), FlatPattern.ClassLike(ctx.builtins.Str.safeRef, N)(Nil), Split.Let(emptyStringSymbol, str(""), makeConsequent(scrutinee, emptyStringSymbol.toScrut, Map.empty)) ) ~: alternative @@ -394,7 +394,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas val remainsSymbol = TempSymbol(N, "remains") val remainsTerm = callStringDrop(scrutinee(), prefix.value.length, "the remaining input") Split.Let(isLeadingSymbol, isLeadingTerm, - Branch(isLeadingSymbol.ref(), + Branch(isLeadingSymbol.safeRef, Split.Let(outputSymbol, outputTerm, Split.Let(remainsSymbol, remainsTerm, makeConsequent(outputSymbol.toScrut, remainsSymbol.toScrut, Map.empty))) @@ -406,9 +406,9 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas val stringHeadSymbol = TempSymbol(N, "stringHead") val stringTailSymbol = TempSymbol(N, "stringTail") val nonEmptySymbol = TempSymbol(N, "nonEmpty") - val nonEmptyTerm = app(this.lt.ref(), tup(fld(int(0)), fld(sel(scrutinee(), "length"))), "string is not empty") + 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.ref(), + Branch(nonEmptySymbol.safeRef, Split.Let(stringHeadSymbol, callStringGet(scrutinee(), 0, "head"), Split.Let(stringTailSymbol, callStringDrop(scrutinee(), 1, "tail"), makeRangeTest(stringHeadSymbol.toScrut, lower, upper, rightInclusive, @@ -467,7 +467,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas // 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.ref(), tup(arguments*), "the transform's result") + 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)) @@ -512,26 +512,28 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas * `Pattern` class. Now the new `pattern` parameter and the * old `body` parameter are mixed. */ - def compilePattern(patternParams: Ls[Param], params: Ls[Param], pattern: Pattern): Ls[TermDefinition] = trace( - pre = s"compilePattern <<< ${params.mkString(", ")}", + 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, pattern) + val topmost = makeMatchSplit(inputSymbol.toScrut, pd.pattern) ((output, bindings) => Split.Else(makeMatchResult(output())), failure) log(s"Translated `unapply`: ${display(topmost)}") - makeMethod(N, "unapply", patternParams, inputSymbol, topmost) + 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, pattern) + val topmost = makeStringPrefixMatchSplit(inputSymbol.toScrut, pd.pattern) ((consumedOutput, remainingOutput, bindings) => Split.Else: makeMatchResult(tup(fld(consumedOutput()), fld(remainingOutput()))), failure) log(s"Translated `unapply`: ${display(topmost)}") - makeMethod(N, "unapplyStringPrefix", patternParams, inputSymbol, topmost) + makeMethod(N, "unapplyStringPrefix", pd.patternParams, inputSymbol, topmost) unapply :: unapplyStringPrefix :: Nil /** Translate an anonymous pattern. They are usually pattern arguments. */ 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 0b020a0fb7..c99b757ab3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/package.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/package.scala @@ -13,7 +13,7 @@ package object ucs: def bug(using Line, FileName, Name, Raise)(msgs: (Message, Option[Loc])*): Unit = raise(InternalError(msgs.toList)) - extension (symbol: BlockLocalSymbol) + extension (symbol: Symbol) /** Create a `Ref` that does not have any implicit arguments. We need this * function because we generate a lot of `Ref`s after implicit resolution. * Writing `.withIArgs(Nil)` is too verbose. From 16cce58cb0c58522dedd61c47ff9a29677cd7457 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 09:31:34 +0800 Subject: [PATCH 25/62] Move `NaiveCompiler` into the `ups` package --- .../main/scala/hkmc2/codegen/Lowering.scala | 2 +- .../scala/hkmc2/semantics/Elaborator.scala | 2 +- .../scala/hkmc2/semantics/ucs/Desugarer.scala | 2 +- .../{ucs => ups}/NaiveCompiler.scala | 23 ++++++++----------- 4 files changed, 12 insertions(+), 17 deletions(-) rename hkmc2/shared/src/main/scala/hkmc2/semantics/{ucs => ups}/NaiveCompiler.scala (97%) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 5127be9c08..befb11e6ff 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -820,7 +820,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): def compilePatternMethods(defn: PatternDef)(using Subst): // The return type is intended to be consistent with `gatherMembers` (Ls[FunDefn], Ls[BlockMemberSymbol], Ls[TermSymbol], Block) = - val compiler = new ucs.NaiveCompiler + val compiler = new ups.NaiveCompiler val methods = compiler.compilePattern(defn) val mtds = methods .flatMap: td => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 14c81a0817..4bd82a3780 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1317,7 +1317,7 @@ extends Importer: def pattern(t: Tree): Ctxl[Pattern] = import ucs.Desugarer.{Ctor, unapply}, Keyword.*, Pattern.*, InvalidReason.* - import ucs.NaiveCompiler.isInvalidStringBounds, ucs.extractors.to + import ups.NaiveCompiler.isInvalidStringBounds, ucs.extractors.to given TraceLogger = tl /** Elaborate arrow patterns like `p => t`. Meanwhile, report all invalid * variables we found in `p`. */ diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala index 8015756a2c..dd529cc7cc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala @@ -465,7 +465,7 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten val pattern = tree match case TypeDef(syntax.Pat, body, N) => val pattern = elaborator.pattern(body) - val compiler = new NaiveCompiler(using tl) + val compiler = new ups.NaiveCompiler(using tl) S((pattern, compiler.compileAnonymousPattern(Nil, Nil, pattern))) case td @ TypeDef(k = syntax.Pat) => error(msg"Ill-formed pattern argument" -> td.toLoc); N diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala similarity index 97% rename from hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala rename to hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala index 341d7c3984..c04222b1bb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/NaiveCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala @@ -1,13 +1,14 @@ package hkmc2 package semantics -package ucs +package ups import mlscript.utils.*, shorthands.* import Message.MessageContext -import Split.display, Desugarer.unapply, extractors.* -import syntax.{Fun, Keyword, Tree}, Tree.*, Keyword.{`as`, `=>`} +import Split.display, ucs.{DesugaringBase, 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. */ @@ -77,7 +78,7 @@ 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 DesugaringBase: - import tl.*, FlatPattern.MatchMode, Pattern.* + import tl.*, FlatPattern.MatchMode, SP.* private lazy val lteq = State.builtinOpsMap("<=") private lazy val lt = State.builtinOpsMap("<") @@ -105,7 +106,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas val field = RcdField(Term.Lit(StrLit(name)), fieldSymbol.safeRef) decl :: defineVar :: field :: Nil - extension (patterns: Ls[Pattern]) + extension (patterns: Ls[SP]) def folded(z: (Ls[TempSymbol], MakeConsequent))(makeSubScrutineeSymbol: Int => TempSymbol) = patterns.iterator.zipWithIndex.foldRight(z): case ((element, index), (subScrutinees, makeInnerSplit)) => @@ -122,14 +123,8 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas * 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. - * - * @param bindings Variables that can be bound in this pattern. Currently, - * when we encounter an `Alias` pattern, we check whether - * its symbol exists in this list before binding it. But I'm - * not sure whether this check is redundant. */ - private def makeMatchSplit(scrutinee: Scrut, pattern: Pattern): MakeSplit = - import Pattern.* + 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 @@ -317,7 +312,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas * @return The return value is a function that builds the split. */ private def makeStringPrefixMatchSplit( scrutinee: Scrut, - pattern: Pattern, + pattern: SP, )(using Raise): MakePrefixSplit = pattern match case Constructor(target, patternArguments, arguments) => // TODO: Handle `patternArguments` and `arguments` accordingly. @@ -537,7 +532,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas unapply :: unapplyStringPrefix :: Nil /** Translate an anonymous pattern. They are usually pattern arguments. */ - def compileAnonymousPattern(patternParams: Ls[Param], params: Ls[Param], pattern: Pattern): Term.Rcd = trace( + def compileAnonymousPattern(patternParams: Ls[Param], params: Ls[Param], pattern: SP): Term.Rcd = trace( pre = s"compileAnonymousPattern <<< $pattern", post = (blk: Term.Rcd) => s"compileAnonymousPattern >>> $blk" ): From 22f70b9ec8f304847bdf228702691c490c65cb43 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:36:43 +0800 Subject: [PATCH 26/62] Group tests about string patterns --- hkmc2/shared/src/test/mlscript/rp/Simple.mls | 69 ------------------- .../mlscript/rp/{ => regex}/EmptyString.mls | 1 + .../rp/{examples => regex}/Identifier.mls | 0 .../rp/{examples => regex}/Number.mls | 0 .../test/mlscript/rp/regex/Simplification.mls | 44 ++++++++++++ .../test/mlscript/rp/regex/TailRepetition.mls | 49 +++++++++++++ 6 files changed, 94 insertions(+), 69 deletions(-) delete mode 100644 hkmc2/shared/src/test/mlscript/rp/Simple.mls rename hkmc2/shared/src/test/mlscript/rp/{ => regex}/EmptyString.mls (97%) rename hkmc2/shared/src/test/mlscript/rp/{examples => regex}/Identifier.mls (100%) rename hkmc2/shared/src/test/mlscript/rp/{examples => regex}/Number.mls (100%) create mode 100644 hkmc2/shared/src/test/mlscript/rp/regex/Simplification.mls create mode 100644 hkmc2/shared/src/test/mlscript/rp/regex/TailRepetition.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/Simple.mls b/hkmc2/shared/src/test/mlscript/rp/Simple.mls deleted file mode 100644 index 19af9a0261..0000000000 --- a/hkmc2/shared/src/test/mlscript/rp/Simple.mls +++ /dev/null @@ -1,69 +0,0 @@ -:js - -import "../../mlscript-compile/Runtime.mls" - - -:sjs -pattern Zero = "0" -//│ JS (unsanitized): -//│ let Zero1; -//│ const Zero$class = class Zero { -//│ constructor() {} -//│ unapply(input) { -//│ if (input === "0") { -//│ return runtime.MatchResult(input, globalThis.Object.freeze({})) -//│ } else { -//│ return runtime.MatchFailure(null) -//│ } -//│ } -//│ unapplyStringPrefix(input1) { -//│ let isLeading, consumed, remains; -//│ isLeading = runtime.Str.startsWith(input1, "0"); -//│ if (isLeading === true) { -//│ consumed = runtime.Str.take(input1, 1); -//│ remains = runtime.Str.drop(input1, 1); -//│ return runtime.MatchResult(globalThis.Object.freeze([ consumed, remains ]), globalThis.Object.freeze({})) -//│ } else { -//│ return runtime.MatchFailure(null) -//│ } -//│ } -//│ toString() { return runtime.render(this); } -//│ static [definitionMetadata] = ["pattern", "Zero"]; -//│ }; Zero1 = globalThis.Object.freeze(new Zero$class); - -Zero -//│ = pattern Zero - -:expect true -Zero.unapply("0") is Runtime.MatchResult -//│ = true - -:expect true -"0" is Zero -//│ = true - -pattern ManyZeros = "0" ~ (ManyZeros | "") - -:expect false -"" is ManyZeros -//│ = false - -:expect true -"0" is ManyZeros -//│ = true - -:expect true -"000" is ManyZeros -//│ = true - -:expect false -"0001" is ManyZeros -//│ = false - -:expect false -"1" is ManyZeros -//│ = false - -:expect false -"1000" is ManyZeros -//│ = false diff --git a/hkmc2/shared/src/test/mlscript/rp/EmptyString.mls b/hkmc2/shared/src/test/mlscript/rp/regex/EmptyString.mls similarity index 97% rename from hkmc2/shared/src/test/mlscript/rp/EmptyString.mls rename to hkmc2/shared/src/test/mlscript/rp/regex/EmptyString.mls index a94c409697..c04ae45652 100644 --- a/hkmc2/shared/src/test/mlscript/rp/EmptyString.mls +++ b/hkmc2/shared/src/test/mlscript/rp/regex/EmptyString.mls @@ -3,6 +3,7 @@ pattern Oops = "" ~ (Oops | "") :re +:todo "" is Oops //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Identifier.mls b/hkmc2/shared/src/test/mlscript/rp/regex/Identifier.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/Identifier.mls rename to hkmc2/shared/src/test/mlscript/rp/regex/Identifier.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Number.mls b/hkmc2/shared/src/test/mlscript/rp/regex/Number.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/Number.mls rename to hkmc2/shared/src/test/mlscript/rp/regex/Number.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/Simplification.mls b/hkmc2/shared/src/test/mlscript/rp/regex/Simplification.mls new file mode 100644 index 0000000000..2f4bebc00d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/regex/Simplification.mls @@ -0,0 +1,44 @@ +:js + +pattern X = "x" + +"x" is X +//│ = true + +pattern Xs = X ~ (Xs | "") + +"xxxxxxx" is Xs +//│ = true + +pattern Xss = Xs ~ (Xss | "") + +"xxxxxxxxxxxxxx" is Xss +//│ = true + +pattern XsX = Xs ~ "x" + +// In the following tests, the left-hand side of concatenation should not +// eagerly consume all letters. We should compile these string patterns even in +// naive compilation, rather than just transliterate them. + +:expect true +:fixme +"xxxxxxxxxxxxxx" is XsX +//│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' +//│ = false + +pattern XssX = Xss ~ "x" + +:expect true +:fixme +"xxxxxxxx" is XssX +//│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' +//│ = false + +pattern XsXs = Xs ~ Xs + +:expect true +:fixme +"xxxxxx" is XsXs +//│ ═══[RUNTIME ERROR] Expected: 'true', got: 'false' +//│ = false diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/TailRepetition.mls b/hkmc2/shared/src/test/mlscript/rp/regex/TailRepetition.mls new file mode 100644 index 0000000000..29eda87608 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/regex/TailRepetition.mls @@ -0,0 +1,49 @@ +:js + +import "../../../mlscript-compile/Runtime.mls" + + +pattern Zero = "0" + +Zero +//│ = pattern Zero + +:expect true +Zero.unapply("0") is Runtime.MatchResult +//│ = true + +:expect true +"0" is Zero +//│ = true + +pattern ManyZeros = "0" ~ (ManyZeros | "") + +:expect false +"" is ManyZeros +//│ = false + +:expect true +"0" is ManyZeros +//│ = true + +:expect true +"000" is ManyZeros +//│ = true + +:expect false +"0001" is ManyZeros +//│ = false + +:expect false +"1" is ManyZeros +//│ = false + +:expect false +"1000" is ManyZeros +//│ = false + +pattern Rep(pattern S) = S ~ (Rep(pattern S) | "") + +:todo +"00000" is Rep(pattern "0") +//│ ═══[RUNTIME ERROR] TypeError: S.unapplyStringPrefix is not a function From e1b66aeeffe4c2e22098b145d89cc2c8ad3ad1d6 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:03:19 +0800 Subject: [PATCH 27/62] Fix string patterns and add tests --- .../hkmc2/semantics/ucs/DesugaringBase.scala | 4 +- .../hkmc2/semantics/ucs/FlatPattern.scala | 3 +- .../hkmc2/semantics/ucs/Normalization.scala | 56 ++++++++----- .../hkmc2/semantics/ups/NaiveCompiler.scala | 59 +++++++++----- .../test/mlscript/rp/regex/EmailAddress.mls | 79 +++++++++++++++++++ .../test/mlscript/rp/regex/TailRepetition.mls | 3 +- 6 files changed, 158 insertions(+), 46 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/rp/regex/EmailAddress.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala index d4bec36a24..4bd11dc649 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala @@ -17,7 +17,9 @@ trait DesugaringBase(using Ctx, State): protected final def str(s: Str) = Term.Lit(StrLit(s)) protected final def `null` = Term.Lit(UnitLit(true)) protected final def fld(t: Term) = Fld(FldFlags.empty, t, N) - protected final def tup(xs: Fld*): Term.Tup = Term.Tup(xs.toList)(Tup(Nil)) + protected final def tup(xs: List[Term]): Term.Tup = + Term.Tup(xs.iterator.map(fld).toList)(DummyTup) + protected final def tup(xs: Fld*): Term.Tup = Term.Tup(xs.toList)(DummyTup) protected final def app(l: Term, r: Term, label: Str): Term.App = app(l, r, FlowSymbol(label)) protected final def app(l: Term, r: Term, s: FlowSymbol): Term.App = (Term.App(l, r)(App(Dummy, Dummy), N, s): Term.App).withIArgs(Nil) 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 afac86afcb..6c050d365d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -64,8 +64,7 @@ enum FlatPattern extends AutoLocated: output.iterator.map(s => s.nme).mkStringOr("as ", " as ", "", "") object FlatPattern: - /** Represent the type of arguments in `ClassLike` patterns. This type alias - * is used to reduce repetition in the code. + /** Represent the type of arguments in `ClassLike` patterns. * * @param scrutinee the symbol representing the scrutinee * @param tree the original `Tree` for making error messages 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 78ba2907cb..4daddb16f3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -155,8 +155,9 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas mode match case MatchMode.Default => normalizeExtractorPatternParameter(scrutinee, ctor, pattern.output, consequent, alternative) - case MatchMode.StringPrefix(prefix, postfix) => - normalizeStringPrefixPattern(scrutinee, ctor, postfix, 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) @@ -193,8 +194,8 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // should properly handle the pattern arguments. case MatchMode.Default => normalizeExtractorPattern(scrutinee, pat, ctor, argsOpt, pattern.output, consequent, normalizeImpl(alternative)) - case MatchMode.StringPrefix(prefix, postfix) => - normalizeStringPrefixPattern(scrutinee, ctor, postfix, 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)) @@ -328,6 +329,14 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas ) ~: alternative normalize(split) + /** Create a split that binds the pattern arguments. */ + def bindPatternArguments( + patternArguments: List[(BlockLocalSymbol, (pattern: Pattern, term: Term.Rcd))], + split: Split + ): Split = + patternArguments.foldRight(split): + case ((sym, arg), innerSplit) => Split.Let(sym, arg.term, innerSplit) + /** Normalize splits whose leading branch matches a pattern and does not have * a `@compile` annotation. */ private def normalizeExtractorPattern( @@ -355,13 +364,9 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas case Argument(scrutinee, _, S(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.ref().withIArgs(Nil) |> fld) :+ fld(scrutinee) + val unapplyArgs = patternArguments.map(_._1.safeRef |> fld) :+ fld(scrutinee) val unapplyCall = app(sel(ctorTerm, "unapply").withIArgs(Nil), tup(unapplyArgs*), s"result of unapply") - // Create a split that binds the pattern arguments. - def bindPatternArguments(split: Split): Split = - patternArguments.foldRight(split): - case ((sym, arg), innerSplit) => Split.Let(sym, arg.term, innerSplit) - val split = bindPatternArguments(tempLet("matchResult", unapplyCall): resultSymbol => + val split = bindPatternArguments(patternArguments, tempLet("matchResult", unapplyCall): resultSymbol => extractionArgsOpt match case N => if outputSymbols.isEmpty then @@ -421,25 +426,36 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas private def normalizeStringPrefixPattern( scrutinee: Term.Ref, ctorTerm: Term, - remainingSymbol: TempSymbol, + allArgsOpt: Opt[Ls[FlatPattern.Argument]], + stringPrefix: MatchMode.StringPrefix, outputSymbols: Ls[BlockLocalSymbol], consequent: Split, alternative: Split, - )(using VarSet): Split = - val method = "unapplyStringPrefix" - val call = app(sel(ctorTerm, method), tup(fld(scrutinee)), s"result of $method") + )(using VarSet): Split = trace( + pre = s"normalizeStringPrefixPattern <<< ${ctorTerm.showDbg}", + post = (r: Split) => s"normalizeStringPrefixPattern >>> ${Split.display(r)}" + ): + val patternArguments = allArgsOpt.fold(Nil): + _.collect: + case Argument(symbol, _, S(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 argSym = TempSymbol(N, "arg") + val outputSymbol = TempSymbol(N, "arg") val bindingsSymbol = TempSymbol(N, "bindings") - // let `arg` be the first element of `matchResult` Branch( - resultSymbol.ref().withIArgs(Nil), - matchResultPattern(S(argSym :: bindingsSymbol :: Nil)), + resultSymbol.safeRef, + matchResultPattern(S(outputSymbol :: bindingsSymbol :: Nil)), aliasOutputSymbols(resultSymbol.safeRef, outputSymbols, - Split.Let(remainingSymbol, callTupleGet(argSym.ref().withIArgs(Nil), 1, "postfix"), consequent)) + // 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(split) + normalize(bindPatternArguments(patternArguments, split)) // Note: This function will be overhauled in the new pattern compilation scheme. private def normalizeCompiledPattern( diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala index e62f465111..d2807fe298 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala @@ -90,22 +90,6 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas val test2 = app(upperOp.safeRef, tup(scrutFld, fld(Term.Lit(hi))), "isLessThanUpper") plainTest(test1, "isGreaterThanLower")(plainTest(test2, "isLessThanUpper")(innerSplit)) - /** Create a pattern object that contains the given pattern. */ - def makeAnonymousPatternObject( - 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 - extension (patterns: Ls[SP]) def folded(z: (Ls[TempSymbol], MakeConsequent))(makeSubScrutineeSymbol: Int => TempSymbol) = patterns.iterator.zipWithIndex.foldRight(z): @@ -338,7 +322,13 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas log(s"argumentVariables of ${pattern.showDbg} are ${argumentVariables.keys.map(_.nme).mkString(", ")}") val consequent = makeConsequent(outputSymbol.toScrut, remainingSymbol.toScrut, argumentVariables) val mode = MatchMode.StringPrefix(outputSymbol, remainingSymbol) - Branch(scrutinee(), FlatPattern.ClassLike(target, N, mode, false)(Tree.Dummy, outputSymbol :: Nil), consequent) ~: alternative + val theArguments = patternArguments.iterator.zipWithIndex.map: (pattern, index) => + val patternSymbol = TempSymbol(N, s"patternArgument$index$$") + val patternObject = compileAnonymousPattern(Nil, Nil, pattern) + FlatPattern.Argument(patternSymbol, Tree.Empty().withLocOf(pattern), S((pattern, patternObject))) + .toList + val thePattern = FlatPattern.ClassLike(target, S(theArguments), mode, false)(Tree.Dummy, Nil) + Branch(scrutinee(), thePattern, consequent) ~: alternative case Composition(true, left, right) => val makeLeft = makeStringPrefixMatchSplit(scrutinee, left) val makeRight = makeStringPrefixMatchSplit(scrutinee, right) @@ -529,10 +519,27 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas val topmost = makeStringPrefixMatchSplit(inputSymbol.toScrut, pd.pattern) ((consumedOutput, remainingOutput, bindings) => Split.Else: makeMatchResult(tup(fld(consumedOutput()), fld(remainingOutput()))), failure) - log(s"Translated `unapply`: ${display(topmost)}") + log(s"Translated `unapplyStringPrefix`: ${display(topmost)}") 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.Rcd = trace( pre = s"compileAnonymousPattern <<< $pattern", @@ -542,10 +549,20 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas // If the pattern is a constructor pattern, we can just reference the // `target` term. Currently, we don't do this because it is until resolution // stage that we can know the `target` refers to a pattern or not. - val unapplyStmts = scoped("ucs:translation"): + 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`: ${display(topmost)}") - makeAnonymousPatternObject("unapply", patternParams, inputSymbol, topmost) - Term.Rcd(false, unapplyStmts) + 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`: ${display(topmost)}") + makeUnapplyRecordStatements("unapplyStringPrefix", patternParams, inputSymbol, topmost) + Term.Rcd(false, unapply ::: unapplyStringPrefix) diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/EmailAddress.mls b/hkmc2/shared/src/test/mlscript/rp/regex/EmailAddress.mls new file mode 100644 index 0000000000..4449e7c90b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/regex/EmailAddress.mls @@ -0,0 +1,79 @@ +:js + +import "../../../mlscript-compile/Char.mls" +import "../../../mlscript-compile/Iter.mls" +import "../../../mlscript-compile/Stack.mls" + +// 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 UserName = Rep1(pattern UserNameLetter) + +:expect true +"john.doe" is UserName +//│ = true + +:expect true +"jane_doe123" is UserName +//│ = true + +:expect true +"user+name%test" is UserName +//│ = true + +:expect false +"invalid@email!" is UserName +//│ = false + +:expect false +"" is UserName +//│ = false + +:todo +// Nested pattern arguments seem problematic. +pattern DomainSuffix = Rep1(pattern ("." ~ Char.Letter ~ Rep1(pattern Char.Letter))) + +pattern DomainSuffixFragment = "." ~ Char.Letter ~ Rep1(pattern Char.Letter) + +[".com", ".hk", ".fr", ".cn"] + Iter.mapping of _ is DomainSuffixFragment + Iter.folded of true, _ && _ +//│ = true + +[".", ".a", "com"] + Iter.mapping of _ is DomainSuffixFragment + Iter.folded of false, _ || _ +//│ = false + +pattern DomainSuffix = Rep1(pattern DomainSuffixFragment) + +pattern DomainNameLetter = Char.Letter | Char.Digit | "-" + +pattern DomainName = Rep1(pattern DomainNameLetter) + +"google" is DomainName +//│ = true + +pattern Domain = DomainName ~ DomainSuffix + +"google.com" is Domain +//│ = true + +pattern Email = UserName ~ "@" ~ Domain + +[ + "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" +] + Iter.mapping of _ is Email + Iter.folded of true, _ && _ +//│ = true diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/TailRepetition.mls b/hkmc2/shared/src/test/mlscript/rp/regex/TailRepetition.mls index 29eda87608..5cb2e71cc7 100644 --- a/hkmc2/shared/src/test/mlscript/rp/regex/TailRepetition.mls +++ b/hkmc2/shared/src/test/mlscript/rp/regex/TailRepetition.mls @@ -44,6 +44,5 @@ pattern ManyZeros = "0" ~ (ManyZeros | "") pattern Rep(pattern S) = S ~ (Rep(pattern S) | "") -:todo "00000" is Rep(pattern "0") -//│ ═══[RUNTIME ERROR] TypeError: S.unapplyStringPrefix is not a function +//│ = true From b873b2cff3bdecb2b365bfc3a2c4ed37aebddea9 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:25:25 +0800 Subject: [PATCH 28/62] Clean up the pattern compiler --- .../scala/hkmc2/semantics/ups/Compiler.scala | 37 +++++++++++-------- .../scala/hkmc2/semantics/ups/Context.scala | 8 +++- 2 files changed, 28 insertions(+), 17 deletions(-) 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 adaf7bde9f..aae821b8c2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -10,10 +10,14 @@ import Term.{Blk, IfLike, Rcd, Ref, SynthSel} import Pattern.{Instantiation, Head} import Elaborator.{Ctx, State, ctx}, utils.TL import ucs.{DesugaringBase as Base, FlatPattern, safeRef} +import Message.MessageContext, ucs.error import collection.mutable.{Queue, Map as MutMap}, collection.immutable.{Set, Map} - +/** The compiler for pattern definitions. It compiles instantiated patterns into + * a few matcher functions. Each matcher function matches a set of patterns + * and returns a record that contains the results of each pattern. + */ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Base: import Compiler.*, tl.* @@ -184,24 +188,23 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Bas import Pattern.* + /** Represent things that can be used as expressions in consequents. */ type Usable = BlockLocalSymbol | Term + extension (usable: Usable) + def use: Term = usable match + case symbol: BlockLocalSymbol => symbol.safeRef + case term: Term => term + /** The bindings can be a `Term` or a `TempSymbol`. */ type MakeConsequent = (output: Usable, bindings: Usable) => Split /** A function that makes a split in matcher functions. The first argument - * indicates the transform that should be applied to bindings at the - * innermost split. If it is `None`, then the innermost split should just - * return the bindings through `MatchResult`. The second argument is the - * alternative split. It is not a global fallback split. + * is the function that makes the innermost split. The second argument is the + * alternative split. Note that the alternative is not a fallback. */ type MakeSplit = (makeConsequent: MakeConsequent, alternative: Split) => Split - extension (symbolOrTerm: Usable) - def use: Term = symbolOrTerm match - case symbol: BlockLocalSymbol => symbol.safeRef - case term: Term => term - /** Create the innermost `Else` split based on whether we have a transform * term or not. * @param output The default output of the pattern. It will not be evaluated @@ -223,7 +226,7 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Bas makeMatchResult(resultSymbol.safeRef))) def completePattern( - pattern: SpPat, // This is actually a `SpPat`. + pattern: SpPat, scrutinee: BlockLocalSymbol, subScrutinees: Map[Ident | Int, BlockLocalSymbol], aliases: Ls[VarSymbol] @@ -288,9 +291,10 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Bas ) ) ~: alternative)): MakeSplit makeMakeSplit(Nil, Nil) - case Tuple(leading, spread, trailing) => - // Think about how to handle the spread pattern. - ??? + case Tuple(leading, spread, trailing) => (_, _) => + // 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"))) // The wildcard case always succeeds. Thus, the `alternative` is not used. case Or(Nil) => (makeConsequent, _) => // Do forget to add the aliases of the current pattern to bindings. @@ -336,7 +340,10 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Bas alternative = alternative) ): MakeSplit makeMakeSplit(Nil, Nil) - case Not(pattern) => ??? + case Not(pattern) => (_, _) => + // TODO: Think about how to handle negation patterns. + error(msg"Negation patterns are not supported yet." -> pattern.toLoc) + Split.Else(makeMatchFailure(str("unsupported negation pattern"))) case Rename(pattern, name) => // We should add those fields to a context. completePattern(pattern, scrutinee, subScrutinees, name :: aliases) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala index 1fe83c0b9d..7ba05ccdec 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Context.scala @@ -7,6 +7,11 @@ import Pattern.{Instantiation, Never} import Message.MessageContext, ucs.bug import sourcecode.{FileName, Line, Name} +/** Before pattern compilation, we monomorphize higher-order patterns into + * first-order patterns. `Context` records the correspondence between each + * `Pattern.Instantiation` and the resulting `Pat` after monomorphization, + * to avoid redundant work. + */ class Context(val definitions: Map[Pattern.Instantiation, Pat]): def get(instantiation: Instantiation)(using Raise): Pat = definitions.get(instantiation) match @@ -18,5 +23,4 @@ class Context(val definitions: Map[Pattern.Instantiation, Pat]): object Context: extension (instantiation: Instantiation) /** Get the body of the instantiated pattern definition. */ - def body(using Context, Raise): Pat = - summon[Context].get(instantiation) + def body(using Context, Raise): Pat = summon[Context].get(instantiation) From 6cee1c3d9a54ed0ede75163ef2f382a1dcddd288 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:56:27 +0800 Subject: [PATCH 29/62] Fix Unicode characters --- .../scala/hkmc2/semantics/ups/Compiler.scala | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) 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 aae821b8c2..2550a181ab 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -429,28 +429,51 @@ object Compiler: case N => N case (N, _) => N + /** Canadian Syllabics Pa */ + val FAKE_LEFT_ANGLE = "ᐸ" + /** Canadian Syllabics Na */ + val FAKE_RIGHT_ANGLE = "ᐳ" + + // I learned the characters above from Go's trick: + // https://news.ycombinator.com/item?id=14276891 + // and I introduced more symbols by following the trick. + + /** Katakana-Hiragana Prolonged Sound Mark */ + val FAKE_MINUS = "ー" + /** Lisu Letter Ta */ + val FAKE_TOP = "ꓔ" + /** Lisu Letter Tha */ + val FAKE_BOTTOM = "ꓕ" + /** Lisu Letter Tone Na Po */ + val FAKE_COMMA = "ꓹ" + /** Modifier Letter Left Half Ring */ + val FAKE_LEFT_PAREN = "ʿ" + /** Modifier Letter Right Half Ring */ + val FAKE_RIGHT_PAREN = "ʾ" + /** Latin Letter Lateral Click */ + val FAKE_BAR = "ǁ" + extension (pattern: Pat) /** Make a human-readable short name using Unicode letters, which are * allowed in in identifiers in the generated code, for patterns. */ def shortName(prec: Int = 0): Opt[Str] = pattern match - case Literal(IntLit(n)) => S((if n < 0 then "\u30FC" else "") + n.toString) + case Literal(IntLit(n)) => S((if n < 0 then FAKE_MINUS else "") + n.toString) case Literal(StrLit(s)) => S(s"str${s.length}") case Literal(UnitLit(true)) => S("null") case Literal(UnitLit(false)) => S("undefined") case ClassLike(sym, arguments) => arguments.fold(S(sym.nme)): arguments => arguments.iterator.mapOption: case (_, pat) => pat.shortName(0) - .map(_.reverse.mkString(s"${sym.nme}\u02BF", "_", "\u02BE")) + .map(_.reverse.mkString(sym.nme + FAKE_LEFT_PAREN, FAKE_COMMA, FAKE_RIGHT_PAREN)) case Synonym(Instantiation(symbol, patterns)) => if patterns.isEmpty then S(symbol.nme) else patterns.iterator.mapOption(_.shortName(0)).map: - // Go's trick: https://news.ycombinator.com/item?id=14276891 - _.reverse.mkString(s"${symbol.nme}\u1438", "_", "\u1433") - case Or(Nil) => S("\uA4D5") + _.reverse.mkString(symbol.nme + FAKE_LEFT_ANGLE, FAKE_COMMA, FAKE_RIGHT_ANGLE) + case Or(Nil) => S(FAKE_TOP) case Or(patterns) => patterns.iterator.mapOption(_.shortName(1)).map: _.reverse.mkString( - if prec > 0 then "\u02BF" else "", - "\u01C1", // LATIN LETTER LATERAL CLICK - if prec > 0 then "\u02BE" else "") - case And(Nil) => S("\u0422") + if prec > 0 then FAKE_LEFT_PAREN else "", + FAKE_BAR, + if prec > 0 then FAKE_RIGHT_PAREN else "") + case And(Nil) => S(FAKE_BOTTOM) case _ => N From 850d87455a3212a6007d2e06eb9fe2d06a5fab1f Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 19:42:11 +0800 Subject: [PATCH 30/62] Add spaces on both sides of the `::` operator Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/main/scala/hkmc2/Message.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/Message.scala b/hkmc2/shared/src/main/scala/hkmc2/Message.scala index e9053088b8..c4fbe0ca0a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Message.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Message.scala @@ -29,9 +29,9 @@ object Message: sealed abstract class Bit final case class Text(str: Str) extends Bit final case class Code(ty: TypeLike) extends Bit - implicit def fromType(ty: TypeLike): Message = Message(Code(ty)::Nil) - implicit def fromStr(str: Str): Message = Message(Text(str)::Nil) - implicit def fromInt(int: Int): Message = Message(Text(int.toString)::Nil) + implicit def fromType(ty: TypeLike): Message = Message(Code(ty) :: Nil) + implicit def fromStr(str: Str): Message = Message(Text(str) :: Nil) + implicit def fromInt(int: Int): Message = Message(Text(int.toString) :: Nil) implicit class MessageContext(private val ctx: StringContext): def msg(inserted: Message*): Message = From e8c6c44bd17dcb812a90250cc64a8c9177459847 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:08:46 +0800 Subject: [PATCH 31/62] Correct the wording in an error message Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 183a33febc..ad168f9c4d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1178,7 +1178,7 @@ extends Importer: // All flags are `false`. case p @ Param(flags = FldFlags(false, false, false, false)) => S(p) case Param(flags, sym, _, _) => - raise(ErrorReport(msg"Unexpected pattern parameter ${sym.name} with flags ${flags.show}" -> sym.toLoc :: Nil)) + raise(ErrorReport(msg"Unexpected pattern parameter ${sym.name} with modifiers: ${flags.show}" -> sym.toLoc :: Nil)) N .partition(_.flags.pat) log(s"`${patSym.nme}`'s pattern parameters: ${patternParams.mkString("[", ", ", "]")}") From 406637b4a87a2ca95095e527bf04127e04392763 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:09:15 +0800 Subject: [PATCH 32/62] Correct the grammar in a comment Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index ad168f9c4d..d21e2b40de 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1191,7 +1191,7 @@ extends Importer: val pat = pattern(rhs)(using ctx ++ patternParams.iterator.map(p => p.sym.name -> p.sym)) // Report all invalid variables we found in the top-level pattern. pat.variables.report - // Note that the remaining variables have not been bounded to any + // Note that the remaining variables have not been bound to any // `VarSymbol` yet. Thus, we need to pair them with the extraction // parameters. We only report warnings for unbounded variables // because they are harmless. From 4a7639c1dcc9982c2efc13b8d886f3f820c8052f Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:17:27 +0800 Subject: [PATCH 33/62] Amend test changes --- hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls index 7b4e8256b7..dd01243bc2 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls @@ -2,7 +2,7 @@ :e pattern Foo(val T) = null | T -//│ ╔══[ERROR] Unexpected pattern parameter T with flags val +//│ ╔══[ERROR] Unexpected pattern parameter T with modifiers: val //│ ║ l.4: pattern Foo(val T) = null | T //│ ╙── ^ //│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. From 20467e56b1e2eb5871a33c3097e260a171eb57f1 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:17:35 +0800 Subject: [PATCH 34/62] Remove a use of default arguments --- hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala | 2 +- hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 2550a181ab..06b82b7588 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -102,7 +102,7 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Bas s"${pattern.showDbg} => ${pattern.label}" .mkString("{", ", ", "}")}" ): - val expandedPatterns = patterns.map(p => (p.label, p.expand())) + val expandedPatterns = patterns.map(p => (p.label, p.expand(Set.empty))) val heads = expandedPatterns.flatMap((_, p) => p.heads).toList // This is the parameter of the current multi-matcher. val scrutinee = VarSymbol(Ident("input")) 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 ac3e892714..56ad6b6256 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala @@ -181,7 +181,7 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: /** Expand the pattern by replacing any top-level synonym with its body. * @return the expanded pattern named "ExPat" in the paper. */ - def expand(alreadyExpanded: Set[Instantiation] = Set())(using Context, Raise): ExPat = map: + def expand(alreadyExpanded: Set[Instantiation])(using Context, Raise): ExPat = map: case Synonym(instantiation) => if alreadyExpanded contains instantiation then error(msg"Expanding this pattern leads to an infinite loop." -> instantiation.toLoc) From 0196de575c4455ba274f448f51a7a249b31acab0 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:42:10 +0800 Subject: [PATCH 35/62] Stop compiling pattern arguments in the `Desugarer` --- .../main/scala/hkmc2/semantics/Pattern.scala | 5 +- .../scala/hkmc2/semantics/ucs/Desugarer.scala | 36 +++---- .../hkmc2/semantics/ucs/DesugaringBase.scala | 4 +- .../hkmc2/semantics/ucs/FlatPattern.scala | 43 ++++++--- .../hkmc2/semantics/ucs/Normalization.scala | 48 ++++++---- .../scala/hkmc2/semantics/ups/Compiler.scala | 3 +- .../hkmc2/semantics/ups/NaiveCompiler.scala | 94 +++++++++++-------- .../test/mlscript/codegen/FieldSymbols.mls | 3 +- hkmc2/shared/src/test/mlscript/rp/Future.mls | 92 +++++++++--------- .../src/test/mlscript/rp/examples/DnfCnf.mls | 2 +- 10 files changed, 178 insertions(+), 152 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index 7f5038fa2c..0a50303862 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -289,8 +289,9 @@ enum Pattern extends AutoLocated: def showDbg: Str = this match case Constructor(target, patternArguments, arguments) => val targetText = target.symbol.fold(target.showDbg)(_.toString()) - val patternArgumentsText = patternArguments.iterator.map(_.showDbg) - .map("pattern " + _).mkStringOr("(", ", ", ")", "") + 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" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala index dd529cc7cc..df6fd79362 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala @@ -457,20 +457,14 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten )(fallback: Split): Sequel = ctx => val scrutinees = scrutSymbol.getSubScrutinees(args.size) val matches = scrutinees.iterator.zip(args).map: - case (symbol, tree) => + 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. - val pattern = tree match - case TypeDef(syntax.Pat, body, N) => - val pattern = elaborator.pattern(body) - val compiler = new ups.NaiveCompiler(using tl) - S((pattern, compiler.compileAnonymousPattern(Nil, Nil, pattern))) - case td @ TypeDef(k = syntax.Pat) => - error(msg"Ill-formed pattern argument" -> td.toLoc); N - case _ => N - Argument(symbol, tree, pattern) + // 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, @@ -514,12 +508,12 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten val (wrapRest, restMatches) = rest match case S((kw, rest, last)) => val (wrapLast, reversedLastMatches) = last.reverseIterator.zipWithIndex - .foldLeft[(Split => Split, Ls[(BlockLocalSymbol, Tree)])]((identity, Nil)): + .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, (sym, pat) :: matches) + (wrap, Argument(sym, pat) :: matches) val lastMatches = reversedLastMatches.reverse val sliceFn = kw match case Keyword.`..` => tupleLazySlice @@ -530,21 +524,21 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten val sym = TempSymbol(N, "rest") val wrap = (split: Split) => Split.Let(sym, app(sliceFn, tup(fld(ref), fld(int(lead.length)), fld(int(last.length))), sym), wrapLast(split)) - (wrap, (sym, pat) :: lastMatches) + (wrap, Argument(sym, pat) :: lastMatches) case N => (identity: Split => Split, Nil) - val (wrap, matches) = lead.zipWithIndex.foldRight((wrapRest, restMatches)): - case ((pat, i), (wrapInner, matches)) => + 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, (sym, pat) :: matches) + (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(matches.map { case (s, t) => Argument(s, t, N) }, sequel)(Split.End)(ctx)) + wrap(subMatches(arguments, sequel)(Split.End)(ctx)) ) ~: fallback // Negative numeric literals case App(Ident("-"), Tup(IntLit(value) :: Nil)) => fallback => ctx => @@ -651,9 +645,9 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten post = (r: Split) => s"subMatches >>> ${r.showDbg}" ): sequel(ctx) - case Argument(_, Under(), _) :: rest => subMatches(rest, sequel) // Skip wildcards - case Argument(_, _, S(_)) :: rest => subMatches(rest, sequel) // Skip pattern arguments - case Argument(scrutinee, tree, _) :: rest => fallback => trace( + 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) >>>" ): diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala index 4bd11dc649..b08e79314c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala @@ -39,7 +39,7 @@ trait DesugaringBase(using Ctx, State): /** 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)(Nil) + FlatPattern.ClassLike(sel(matchResultClass, "class", State.matchResultClsSymbol), parameters) /** Make a term that looks like `runtime.MatchFailure` with its symbol. */ protected lazy val matchFailureClass = @@ -47,7 +47,7 @@ trait DesugaringBase(using Ctx, State): /** 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)(Nil) + FlatPattern.ClassLike(sel(matchFailureClass, "class", State.matchFailureClsSymbol), parameters) protected lazy val tupleSlice = sel(sel(runtimeRef, "Tuple"), "slice") protected lazy val tupleLazySlice = sel(sel(runtimeRef, "Tuple"), "lazySlice") 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 6c050d365d..60d2a019cb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/FlatPattern.scala @@ -8,6 +8,7 @@ import Elaborator.{Ctx, ctx, State} import collection.mutable.Buffer import FlatPattern.* +import hkmc2.codegen.Block /** Flat patterns for pattern matching */ enum FlatPattern extends AutoLocated: @@ -25,6 +26,12 @@ enum FlatPattern extends AutoLocated: var refined: Bool )(val tree: Tree, val output: Ls[BlockLocalSymbol]) + 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)(val output: Ls[BlockLocalSymbol]) case Record(entries: List[(Ident -> BlockLocalSymbol)])(val output: Ls[BlockLocalSymbol]) @@ -32,7 +39,10 @@ enum FlatPattern extends AutoLocated: def subTerms: Ls[Term] = this match case p: ClassLike => p.constructor :: (p.mode match case MatchMode.Default => p.arguments.fold(Nil): - _.iterator.flatMap(_.pattern.map(_.term)).toList + _.iterator.flatMap: + case Argument.Term(_, _) => Nil + case Argument.Pattern(_, pattern) => pattern.subTerms + .toList case _: MatchMode.StringPrefix => Nil case MatchMode.Annotated(annotation) => annotation :: Nil) case _: (Lit | Tuple | Record) => Nil @@ -64,24 +74,27 @@ enum FlatPattern extends AutoLocated: output.iterator.map(s => s.nme).mkStringOr("as ", " as ", "", "") object FlatPattern: - /** Represent the type of arguments in `ClassLike` patterns. + /** 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. */ - final case class Argument( - scrutinee: BlockLocalSymbol, - tree: Tree, - pattern: Opt[(pattern: Pattern, term: Term.Rcd)] - ) extends Located: - override def toLoc: Opt[Loc] = tree.toLoc + 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 = - Argument(scrutinee, tree, N) - def apply(scrutinee: BlockLocalSymbol): Argument = - Argument(scrutinee, Tree.Dummy, N) + 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: @@ -110,5 +123,7 @@ object FlatPattern: case Annotated(annotation: Term) object ClassLike: - def apply(constructor: Term, arguments: Opt[Ls[BlockLocalSymbol]])(output: Ls[BlockLocalSymbol]): ClassLike = - ClassLike(constructor, arguments.map(_.map(Argument(_))), MatchMode.Default, false)(Tree.Dummy, output) + 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) 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 4daddb16f3..ed1b35a725 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -9,6 +9,7 @@ import Elaborator.{Ctx, State, ctx} import utils.* import FlatPattern.Argument import ups.Instantiator +import hkmc2.semantics.ups.NaiveCompiler class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBase: import Normalization.*, Mode.*, FlatPattern.MatchMode @@ -146,10 +147,6 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas ctor.symbol match case S(symbol: VarSymbol) => symbol.decl match case S(param @ Param(flags = FldFlags(pat = true))) => - // We're missing two checks here. The first one is to make - // sure that the pattern parameter is accessible from the - // context. The second one is to make sure that the current - // is in a pattern translation. if argsOpt.fold(false)(_.nonEmpty) then error(msg"Pattern parameters cannot be applied." -> ctor.toLoc) mode match @@ -172,7 +169,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas normalizeImpl(alternative) case S(S(cls: ClassSymbol)) => validateMatchMode(ctor, cls, mode) - if validateClassPattern(ctor, cls, argsOpt) then // TODO(ucs): deduplicate [1] + 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) @@ -224,7 +221,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas private def validateClassPattern( ctorTerm: Term, ctorSymbol: ClassSymbol, - argsOpt: Opt[Ls[FlatPattern.Argument]] + argsOpt: Opt[Ls[FlatPattern.Argument.Term]] ): Bool = // Obtain the `classHead` used for error reporting and the parameter list // from the class definitions. @@ -247,7 +244,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas }${"argument" countBy args.size}." -> loc // Check the fields are accessible. paramList.params.iterator.zip(args).map: - case (_, Argument(_, Tree.Under(), _)) => true + 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, @@ -290,6 +287,15 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas 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"Pattern argument `${pattern.showDbg}` cannot be used here." -> pattern.toLoc); N + /** Warn about inappropriate annotations used on class or object patterns. */ private def validateMatchMode( ctorTerm: Term, @@ -330,12 +336,15 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas normalize(split) /** Create a split that binds the pattern arguments. */ - def bindPatternArguments( - patternArguments: List[(BlockLocalSymbol, (pattern: Pattern, term: Term.Rcd))], + def buildPatternArguments( + patternArguments: List[(BlockLocalSymbol, Pattern)], split: Split ): Split = + val compiler = new NaiveCompiler patternArguments.foldRight(split): - case ((sym, arg), innerSplit) => Split.Let(sym, arg.term, innerSplit) + case ((symbol, pattern), innerSplit) => + 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. */ @@ -352,21 +361,21 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas log: allArgsOpt.fold(Iterator.empty[Str]): _.iterator.map: - case Argument(scrutinee, _, N) => s"extraction: ${scrutinee.nme}" - case Argument(scrutinee, _, S((_, term))) => s"pattern: ${scrutinee.nme} = ${term.showDbg}" + 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(scrutinee, _, N) => Left(scrutinee) - case Argument(scrutinee, _, S(pattern)) => Right((scrutinee, pattern)) + 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").withIArgs(Nil), tup(unapplyArgs*), s"result of unapply") - val split = bindPatternArguments(patternArguments, tempLet("matchResult", unapplyCall): resultSymbol => + val split = buildPatternArguments(patternArguments, tempLet("matchResult", unapplyCall): resultSymbol => extractionArgsOpt match case N => if outputSymbols.isEmpty then @@ -435,9 +444,8 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas pre = s"normalizeStringPrefixPattern <<< ${ctorTerm.showDbg}", post = (r: Split) => s"normalizeStringPrefixPattern >>> ${Split.display(r)}" ): - val patternArguments = allArgsOpt.fold(Nil): - _.collect: - case Argument(symbol, _, S(pattern)) => symbol -> pattern + 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) @@ -455,7 +463,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas Split.Let(stringPrefix.prefix, callTupleGet(outputSymbol.safeRef, 0, "prefix"), Split.Let(stringPrefix.postfix, callTupleGet(outputSymbol.safeRef, 1, "postfix"), consequent))) ) ~: alternative - normalize(bindPatternArguments(patternArguments, split)) + normalize(buildPatternArguments(patternArguments, split)) // Note: This function will be overhauled in the new pattern compilation scheme. private def normalizeCompiledPattern( @@ -472,7 +480,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas // Instantiate the pattern and all patterns used in it. val instantiator = new Instantiator val patternArguments = argsOpt.fold(Nil)(_.collect: - case Argument(_, _, S((pattern, _))) => pattern) + case Argument.Pattern(_, pattern) => pattern) val (synonym, context) = instantiator(symbol, patternArguments, Loc(ctorTerm :: patternArguments)) // Initate the compilation. val compiler = new Compiler(using context) 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 06b82b7588..b3aea68710 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -43,8 +43,7 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Bas def toFlatPattern: FlatPattern = head match case lit: syntax.Literal => FlatPattern.Lit(lit)(Nil) case sym: ClassLikeSymbol => - val target = reference(sym).getOrElse(Term.Error) - FlatPattern.ClassLike(target, N)(Nil) + FlatPattern.ClassLike(reference(sym).getOrElse(Term.Error), N, Nil) def showDbg: Str = head match case lit: syntax.Literal => lit.idStr case sym: ClassLikeSymbol => sym.nme diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala index d2807fe298..67df3fbce4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala @@ -127,15 +127,13 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas argumentOutput, // TODO: Combine `outerOutput` and `argumentOutput` outerBindings ++ argumentBindings), Split.End) - val theArgument = FlatPattern.Argument(subScrutinee, Tree.Empty().withLocOf(argument), N) + 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) => - val patternSymbol = TempSymbol(N, s"patternArgument$index$$") - val patternObject = compileAnonymousPattern(Nil, Nil, pattern) - FlatPattern.Argument(patternSymbol, Tree.Empty().withLocOf(pattern), S((pattern, patternObject))) + 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) @@ -148,7 +146,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas // pattern or not. val outputSymbol = TempSymbol(N, "output") val consequent = makeChainedConsequent(outputSymbol.toScrut, Map.empty) - Branch(scrutinee(), FlatPattern.ClassLike(target, theArguments, MatchMode.Default, false)(Tree.Dummy, outputSymbol :: Nil), consequent) ~: alternative + 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) => @@ -181,6 +179,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas 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)( @@ -298,37 +297,56 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas scrutinee: Scrut, pattern: SP, )(using Raise): MakePrefixSplit = pattern match - case Constructor(target, patternArguments, arguments) => - // TODO: Handle `patternArguments` and `arguments` accordingly. - // - // This case is very different from the `Constructor` case in - // `makeMatchSplit` because we know the `scrutinee` is a string. - // Hence, the match is acceptable only if `target` is a pattern that - // also matches a string. If `target` is a class or object, we should - // directly reject. - // - // However, we do not know whether `target` is a pattern or not until - // the lowering stage. As discussed, we will move `NaiveCompiler` to the - // lowering stage. - (makeConsequent, alternative) => - val outputSymbol = TempSymbol(N, "output") // Denotes the pattern's output. - val remainingSymbol = TempSymbol(N, "remaining") // Denotes the remaining value. - // This is just a temporary solution. After moving `NaiveCompiler` to - // the lowering stage, it should be implemented correctly. - val argumentVariables = arguments.fold(Map.empty: BindingMap): - _.foldLeft(Map.empty: BindingMap): - case (acc, pattern) => acc ++ pattern.variables.symbols.map: symbol => - symbol -> symbol.toScrut - log(s"argumentVariables of ${pattern.showDbg} are ${argumentVariables.keys.map(_.nme).mkString(", ")}") - val consequent = makeConsequent(outputSymbol.toScrut, remainingSymbol.toScrut, argumentVariables) - val mode = MatchMode.StringPrefix(outputSymbol, remainingSymbol) - val theArguments = patternArguments.iterator.zipWithIndex.map: (pattern, index) => - val patternSymbol = TempSymbol(N, s"patternArgument$index$$") - val patternObject = compileAnonymousPattern(Nil, Nil, pattern) - FlatPattern.Argument(patternSymbol, Tree.Empty().withLocOf(pattern), S((pattern, patternObject))) - .toList - val thePattern = FlatPattern.ClassLike(target, S(theArguments), mode, false)(Tree.Dummy, Nil) - Branch(scrutinee(), thePattern, consequent) ~: alternative + 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) @@ -364,7 +382,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas // 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), + Branch(scrutinee(), FlatPattern.ClassLike(ctx.builtins.Str.safeRef, N, Nil), Split.Let(emptyStringSymbol, str(""), makeConsequent(scrutinee, emptyStringSymbol.toScrut, Map.empty)) ) ~: alternative @@ -461,8 +479,6 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas * error messages and pass them to the function. */ private def failure: Split = Split.Else(makeMatchFailure()) - private def errorSplit: Split = Split.Else(Term.Error) - /** 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. diff --git a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls index 7d7061be8d..2a66f0a1e0 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls @@ -90,10 +90,9 @@ case //│ sym = member:Foo //│ iargsLs = N //│ arguments = S of Ls of -//│ Argument: +//│ Term: //│ scrutinee = $param0 //│ tree = Ident of "a" -//│ pattern = N //│ mode = Default //│ refined = false //│ continuation = Let: diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/rp/Future.mls index 777b3d1145..e56e3ba654 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Future.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Future.mls @@ -5,12 +5,6 @@ pattern Rep0[A] = "" | A ~ Rep0[A] //│ ╔══[ERROR] Found an unrecognizable pattern. //│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] //│ ╙── ^^^^^^^ -//│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] -//│ ╙── ^ -//│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] -//│ ╙── ^ :fixme @@ -26,38 +20,38 @@ pattern Foo2[pattern A]: ... pattern Rep0(pattern A, B, C)(head) = "" | (A as head) ~ Rep0[A] //│ ╔══[ERROR] Multiple parameter lists are not supported for this definition. -//│ ║ l.26: pattern Rep0(pattern A, B, C)(head) = +//│ ║ l.20: pattern Rep0(pattern A, B, C)(head) = //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.27: "" | (A as head) ~ Rep0[A] +//│ ║ l.21: "" | (A as head) ~ Rep0[A] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.27: "" | (A as head) ~ Rep0[A] +//│ ║ l.21: "" | (A as head) ~ Rep0[A] //│ ╙── ^^^^^^^ //│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. -//│ ║ l.27: "" | (A as head) ~ Rep0[A] +//│ ║ l.21: "" | (A as head) ~ Rep0[A] //│ ║ ^^^^ //│ ╟── The variable is missing from this sub-pattern. -//│ ║ l.27: "" | (A as head) ~ Rep0[A] +//│ ║ l.21: "" | (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.45: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.39: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ║ ^^^^^^^^^^ //│ ╟── The previous definition is as follows. -//│ ║ l.45: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.39: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^ //│ ╔══[WARNING] Useless pattern binding: Identifier. -//│ ║ l.45: (Identifier as name) ~ "@" ~ (Identifier as domain) +//│ ║ l.39: (Identifier as name) ~ "@" ~ (Identifier as domain) //│ ╙── ^^^^^^^^^^ :todo // View patterns pattern GreaterThan(value) = case n and n > value then n //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.58: n and n > value then n +//│ ║ l.52: n and n > value then n //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ :todo @@ -70,11 +64,11 @@ fun foo(x) = if x is Unit then .... Arrow(...) then .... //│ ╔══[ERROR] Unrecognized pattern split (infix operator). -//│ ║ l.69: view as +//│ ║ l.63: view as //│ ║ ^^^^^^^ -//│ ║ l.70: Unit then .... +//│ ║ l.64: Unit then .... //│ ║ ^^^^^^^^^^^^^^^^^^ -//│ ║ l.71: Arrow(...) then .... +//│ ║ l.65: Arrow(...) then .... //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -92,20 +86,20 @@ 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.89: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ║ l.83: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^ //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.89: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" +//│ ║ l.83: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^^^^ :todo pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.102: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╙── ^^^ +//│ ║ l.96: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) +//│ ╙── ^^^ //│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.102: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╙── ^^^ +//│ ║ l.96: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) +//│ ╙── ^^^ :todo pattern LineSep(pattern P) = case @@ -114,11 +108,11 @@ pattern LineSep(pattern P) = case "" then nd :: Nil "\n" ~ LineSep(P, t) then nd :: t //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.112: "" then Nil +//│ ║ l.106: "" then Nil //│ ║ ^^^^^^^^^^^ -//│ ║ l.113: L(...nd) ~ +//│ ║ l.107: L(...nd) ~ //│ ║ ^^^^^^^^^^^^ -//│ ║ l.114: "" then nd :: Nil +//│ ║ l.108: "" then nd :: Nil //│ ╙── ^^^^ :todo @@ -132,25 +126,25 @@ pattern Lines(pattern L) = case :todo if input is Lines of Email then //│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.133: if input is Lines of Email then +//│ ║ l.127: if input is Lines of Email then //│ ╙── ^ //│ ╔══[ERROR] Unexpected record property pattern. -//│ ║ l.126: "" then [] +//│ ║ l.120: "" then [] //│ ║ ^^^^^^^^^^ -//│ ║ l.127: pattern Tail = case +//│ ║ l.121: pattern Tail = case //│ ║ ^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.128: "" then [] +//│ ║ l.122: "" then [] //│ ║ ^^^^^^^^^^^^^^ -//│ ║ l.129: "\n" ~ (Lines of L) as t then t +//│ ║ l.123: "\n" ~ (Lines of L) as t then t //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.130: L as h ~ Tail as t then [h, ...t] +//│ ║ l.124: L as h ~ Tail as t then [h, ...t] //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.131: +//│ ║ l.125: //│ ║ ^^^ -//│ ║ l.132: :todo +//│ ║ l.126: :todo //│ ╙── ^^^^^ //│ ╔══[ERROR] Name not found: input -//│ ║ l.133: if input is Lines of Email then +//│ ║ l.127: if input is Lines of Email then //│ ╙── ^^^^^ :todo @@ -161,25 +155,25 @@ pattern Opt(pattern P) = case :todo pattern Email(name, domain) = ... //│ ╔══[ERROR] Unexpected record property pattern. -//│ ║ l.158: P(value) then (Some(value)) +//│ ║ l.152: P(value) then (Some(value)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.159: "" then (None) +//│ ║ l.153: "" then (None) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.160: +//│ ║ l.154: //│ ║ ^^^ -//│ ║ l.161: :todo +//│ ║ l.155: :todo //│ ╙── ^^^^^ :todo if input is Opt(Email, Some((n, d))) then ... //│ ╔══[ERROR] Name not found: input -//│ ║ l.174: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.168: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^^ //│ ╔══[ERROR] Name not found: Some -//│ ║ l.174: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.168: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^ //│ ╔══[ERROR] invalid record field pattern -//│ ║ l.174: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.168: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^ //│ ═══[ERROR] Expected zero arguments, but found two arguments. @@ -188,9 +182,9 @@ pattern Opt(pattern P) = case P(...values) then (Some(values)) "" then (None) //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.188: P(...values) then (Some(values)) +//│ ║ l.182: P(...values) then (Some(values)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.189: "" then (None) +//│ ║ l.183: "" then (None) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ :todo @@ -198,16 +192,16 @@ pattern Opt(pattern P) = case P(...values) then Some(values) "" then None //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.198: P(...values) then Some(values) +//│ ║ l.192: P(...values) then Some(values) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.199: "" then None +//│ ║ l.193: "" then None //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ :todo pattern Digits = "0" to "9" ~ (Digits | "") //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.207: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.201: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^ //│ ╔══[ERROR] Found an unrecognizable pattern. -//│ ║ l.207: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.201: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls b/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls index 9814c02ed1..713234a79c 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls @@ -123,4 +123,4 @@ print of isDeepDnf of cnf6 //│ > false pattern Cnf'(pattern P) = And(Cnf'(pattern P), Cnf'(pattern P)) -pattern DnfOrCnf = Dnf'(DnfOrCnf) | Cnf'(DnfOrCnf) +pattern DnfOrCnf = Dnf'(pattern DnfOrCnf) | Cnf'(pattern DnfOrCnf) From ab8596ba58f13757045a205076cb06f2f1fab441 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:15:46 +0800 Subject: [PATCH 36/62] Do no generate objects for direct use of patterns --- .../hkmc2/semantics/ucs/Normalization.scala | 2 + .../scala/hkmc2/semantics/ups/Compiler.scala | 38 ++++++++++---- .../hkmc2/semantics/ups/NaiveCompiler.scala | 51 ++++++++++--------- .../mlscript/rp/parametric/EtaConversion.mls | 35 +++++++++++++ 4 files changed, 93 insertions(+), 33 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/rp/parametric/EtaConversion.mls 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 ed1b35a725..f403075d96 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -343,6 +343,8 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas 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) 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 b3aea68710..0d131dab78 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -13,6 +13,7 @@ import ucs.{DesugaringBase as Base, FlatPattern, safeRef} import Message.MessageContext, ucs.error import collection.mutable.{Queue, Map as MutMap}, collection.immutable.{Set, Map} +import scala.annotation.tailrec /** The compiler for pattern definitions. It compiles instantiated patterns into * a few matcher functions. Each matcher function matches a set of patterns @@ -42,7 +43,7 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Bas /** 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 sym: ClassLikeSymbol => + case sym: (ClassSymbol | ModuleSymbol) => FlatPattern.ClassLike(reference(sym).getOrElse(Term.Error), N, Nil) def showDbg: Str = head match case lit: syntax.Literal => lit.idStr @@ -396,25 +397,42 @@ object Compiler: /** Perform a reverse lookup for a term that references a symbol in the * current context. */ - def reference(symbol: ClassLikeSymbol)(using tl: TL)(using Ctx, State): Opt[Term] = - /** To make lowering happy about the terms. */ + def reference(symbol: ClassSymbol | ModuleSymbol | PatternSymbol)(using tl: TL)(using Ctx, State): Opt[Term] = + /** To make `Lowering` happy about the terms. */ def fillImplicitArgs(term: Term): Term = term match case ref: Ref => ref.withIArgs(Nil) case sel: SynthSel => fillImplicitArgs(sel.prefix) sel.withIArgs(Nil) - case other => other - def go(ctx: Ctx): Opt[Term] = - ctx.env.values.collectFirst: - case elem if elem.symbol.flatMap(_.asClsLike).contains(symbol) => - fillImplicitArgs(elem.ref(symbol.id)) - .orElse(ctx.parent.flatMap(go)) + case _: Term => term + def findSymbol(elem: Ctx.Elem): Opt[Term] = + elem.symbol.flatMap(_.asClsLike).collectFirst: + // Check the element's symbol. + case `symbol` => + val id = symbol match + case symbol: PatternSymbol => symbol.id + case symbol: (ClassSymbol | ModuleSymbol) => symbol.id + S(elem.ref(id)) + // Look up the symbol in module members. + case module: ModuleSymbol => + val moduleRef = module.defn.get.bsym.ref() + module.tree.definedSymbols.iterator.map(_.mapSecond(_.asClsLike)).collectFirst: + case (key, S(`symbol`)) => + val memberSymbol = symbol.defn.get.bsym + SynthSel(moduleRef, Ident(key))(S(memberSymbol)) + .flatten + @tailrec def go(ctx: Ctx): Opt[Term] = + ctx.env.values.iterator.map(findSymbol).firstSome match + case S(term) => S(fillImplicitArgs(term)) + case N => ctx.parent match + case N => N + case S(parent) => go(parent) go(ctx).map: term => // If the `symbol` is a virtual class, then do not select `class`. symbol match case s: ClassSymbol if !(ctx.builtins.virtualClasses contains s) => SynthSel(term, Ident("class"))(S(s)).withIArgs(Nil) - case _: (ClassSymbol | ModuleSymbol) => term + case _: (ClassSymbol | ModuleSymbol | PatternSymbol) => term import 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 index 67df3fbce4..505b03830d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala @@ -557,28 +557,33 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas decl :: defineVar :: field :: Nil /** Translate an anonymous pattern. They are usually pattern arguments. */ - def compileAnonymousPattern(patternParams: Ls[Param], params: Ls[Param], pattern: SP): Term.Rcd = trace( + def compileAnonymousPattern(patternParams: Ls[Param], params: Ls[Param], pattern: SP): Term = trace( pre = s"compileAnonymousPattern <<< $pattern", - post = (blk: Term.Rcd) => s"compileAnonymousPattern >>> $blk" + post = (blk: Term) => s"compileAnonymousPattern >>> $blk" ): - // We should apply an optimization to avoid generating unnecessary objects. - // If the pattern is a constructor pattern, we can just reference the - // `target` term. Currently, we don't do this because it is until resolution - // stage that we can know the `target` refers to a pattern or not. - 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`: ${display(topmost)}") - 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`: ${display(topmost)}") - makeUnapplyRecordStatements("unapplyStringPrefix", patternParams, inputSymbol, topmost) - Term.Rcd(false, unapply ::: unapplyStringPrefix) + // 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) + 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`: ${display(topmost)}") + 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`: ${display(topmost)}") + makeUnapplyRecordStatements("unapplyStringPrefix", patternParams, inputSymbol, topmost) + Term.Rcd(false, unapply ::: unapplyStringPrefix) diff --git a/hkmc2/shared/src/test/mlscript/rp/parametric/EtaConversion.mls b/hkmc2/shared/src/test/mlscript/rp/parametric/EtaConversion.mls new file mode 100644 index 0000000000..700551e6be --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/parametric/EtaConversion.mls @@ -0,0 +1,35 @@ +:js + +pattern Nullable(pattern T) = T | null + +// Using patterns as pattern argument directly should not create local objects. + +pattern Zero = 0 + +:ucs normalized +0 is Nullable(pattern Zero) +//│ Normalized: +//│ > if +//│ > let $scrut = 0 +//│ > let $param0 = member:Zero#666 +//│ > let $matchResult = (member:Nullable#666.)unapply($param0#0, $scrut#0) +//│ > $matchResult is $runtime.MatchResult.class then true +//│ > else false +//│ = true + +import "../../../mlscript-compile/Char.mls" + +:ucs normalized +fun foo(x) = x is Nullable(pattern Char.Letter) +//│ Normalized: +//│ > if +//│ > let $param0 = (member:Char#0.)Letter‹member:Letter› +//│ > let $matchResult = (member:Nullable#666.)unapply($param0#0, x#0) +//│ > $matchResult is $runtime.MatchResult.class then true +//│ > else false + +foo of null +//│ = true + +foo of "A" +//│ = true From 39d804648452b488d22f5a4018a758361a72e3f6 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:36:18 +0800 Subject: [PATCH 37/62] Pattern definitions do not need parameter lists --- hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala | 2 +- hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index d21e2b40de..2f6634d58f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1203,7 +1203,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, N, + val pd = PatternDef(owner, patSym, sym, tps, patternParams, extractionParams, pat, annotations) patSym.defn = S(pd) pd diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 6cd7b388ae..17bf9a0eeb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -502,7 +502,6 @@ case class PatternDef( sym: PatternSymbol, bsym: BlockMemberSymbol, tparams: Ls[TyParam], - paramsOpt: Opt[ParamList], /** The pattern parameters, for example, `T` in * `pattern Nullable(pattern T) = null | T`. */ patternParams: Ls[Param], @@ -522,7 +521,8 @@ case class PatternDef( * `unapplyStringPrefix`, which are generated in `Lowering`. Hence, there * is no need to make `body` an parameter. */ val body: ObjBody = ObjBody(Blk(Nil, Term.Lit(syntax.Tree.UnitLit(false)))) - /** Pattern definitions can only have one parameter list. */ + /** Pattern definitions do not need parameter lists. */ + val paramsOpt: Opt[ParamList] = N val auxParams: Ls[ParamList] = Nil From 41f42c173403901a9ca88d345f4562f7d03832ad Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:36:34 +0800 Subject: [PATCH 38/62] Correct the use of top and bottom --- .../shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 0d131dab78..8c9e4941de 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -486,11 +486,11 @@ object Compiler: if patterns.isEmpty then S(symbol.nme) else patterns.iterator.mapOption(_.shortName(0)).map: _.reverse.mkString(symbol.nme + FAKE_LEFT_ANGLE, FAKE_COMMA, FAKE_RIGHT_ANGLE) - case Or(Nil) => S(FAKE_TOP) + case Or(Nil) => S(FAKE_BOTTOM) case Or(patterns) => patterns.iterator.mapOption(_.shortName(1)).map: _.reverse.mkString( if prec > 0 then FAKE_LEFT_PAREN else "", FAKE_BAR, if prec > 0 then FAKE_RIGHT_PAREN else "") - case And(Nil) => S(FAKE_BOTTOM) + case And(Nil) => S(FAKE_TOP) case _ => N From 513b1ea61f0ce1c27220fdcdb39e8697e1e9e606 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:38:40 +0800 Subject: [PATCH 39/62] Correct the grammar in a comment Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 2f6634d58f..e127c336ab 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1193,7 +1193,7 @@ extends Importer: pat.variables.report // Note that the remaining variables have not been bound to any // `VarSymbol` yet. Thus, we need to pair them with the extraction - // parameters. We only report warnings for unbounded variables + // parameters. We only report warnings for unbound variables // because they are harmless. pat.variables.varMap.foreach: (name, aliases) => extractionParams.find(_.sym.name == name) match From 6c61134bcf9425b82beab48c964fa3563be94337 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sat, 9 Aug 2025 15:29:37 +0800 Subject: [PATCH 40/62] Add a space between two imported names Co-authored-by: Flandia --- hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala index df6fd79362..90679a42a1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala @@ -10,7 +10,7 @@ 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} +import FlatPattern.{Argument, MatchMode} object Desugarer: extension (op: Keyword.Infix) From 3346a6db28b1a86379627bd9ce3e3fd604229a23 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:21:21 +0800 Subject: [PATCH 41/62] Correct the grammar in a comment Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 17bf9a0eeb..6432a0cd89 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -519,7 +519,7 @@ case class PatternDef( val ext: Opt[New] = N /** Each pattern definition should contain two methods: `unapply` and * `unapplyStringPrefix`, which are generated in `Lowering`. Hence, there - * is no need to make `body` an parameter. */ + * is no need to make `body` a parameter. */ val body: ObjBody = ObjBody(Blk(Nil, Term.Lit(syntax.Tree.UnitLit(false)))) /** Pattern definitions do not need parameter lists. */ val paramsOpt: Opt[ParamList] = N From 41ca4cd94d1d707379feb5a680365d0ba05ab579 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 10 Aug 2025 14:25:00 +0800 Subject: [PATCH 42/62] Replace the Unicode escapes with actual characters --- hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala | 6 +++--- .../src/main/scala/hkmc2/semantics/ups/Compiler.scala | 6 +++--- .../src/main/scala/hkmc2/semantics/ups/Pattern.scala | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index 0a50303862..ac6267399c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -295,9 +295,9 @@ enum Pattern extends AutoLocated: val argumentsText = arguments.fold(""): args => s"(${args.map(_.showDbg).mkString(", ")})" s"$targetText$patternArgumentsText$argumentsText" - case Composition(true, left, right) => s"${left.showDbg} \u2228 ${right.showDbg}" - case Composition(false, left, right) => s"${left.showDbg} \u2227 ${right.showDbg}" - case Negation(pattern) => s"\u00ac${pattern.showDbgWithPar}" + 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}" case Wildcard() => "_" case Literal(literal) => literal.idStr case Range(lower, upper, rightInclusive) => 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 8c9e4941de..72d97c1db5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -56,9 +56,9 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Bas patterns.map: (label, pattern) => val spec = pattern.specialize(head) val simp = spec.simplify - log(s"\u2022 Label $label") - log(s" \u2023 Expanded: ${spec.showDbg}") - log(s" \u2023 Simplified: ${simp.showDbg}") + log(s"• Label $label") + log(s" ‣ Expanded: ${spec.showDbg}") + log(s" ‣ Simplified: ${simp.showDbg}") (label, simp) val labelMap: MutMap[Pat, Label] = MutMap() 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 56ad6b6256..569c3aa807 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Pattern.scala @@ -219,14 +219,14 @@ sealed abstract class Pattern[+K <: Kind.Complete] extends AutoLocated: val spreadText = spread.map(_.showDbg).mkString(", ") val trailingText = trailing.map(_.showDbg).mkString(", ") List(leadingText, spreadText, trailingText).mkString("[", ", ", "]") - case And(Nil) => "\u22A5" + case And(Nil) => "⊤" case And(pattern :: Nil) => pattern.showDbg case And(patterns) => - patterns.map(_.showDbg).mkString("(", " \u2227 ", ")") - case Or(Nil) => "\u22A4" + patterns.map(_.showDbg).mkString("(", " ∧ ", ")") + case Or(Nil) => "⊥" case Or(pattern :: Nil) => pattern.showDbg case Or(patterns) => - patterns.map(_.showDbg).mkString("(", " \u2228 ", ")") + patterns.map(_.showDbg).mkString("(", " ∨ ", ")") case Not(pattern) => s"!${pattern.showDbg}" case Rename(Or(Nil), name) => name.name case Rename(pattern, name) => s"${pattern.showDbg} as $name" From 125b12888a0e4db7f895b6f82d4460ae70d45114 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:18:20 +0800 Subject: [PATCH 43/62] Encapsulate pretty printing of splits better --- .../main/scala/hkmc2/codegen/Lowering.scala | 2 +- .../scala/hkmc2/semantics/Elaborator.scala | 6 ++--- .../main/scala/hkmc2/semantics/Split.scala | 25 ++++++++++--------- .../hkmc2/semantics/ucs/Normalization.scala | 10 ++++---- .../scala/hkmc2/semantics/ups/Compiler.scala | 2 +- .../hkmc2/semantics/ups/NaiveCompiler.scala | 10 ++++---- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 61f73603e0..237c8beb91 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -569,7 +569,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): val normalized = tl.scoped("ucs:normalize"): normalize(iftrm.desugared) tl.scoped("ucs:normalized"): - tl.log(s"Normalized:\n${Split.display(normalized)}") + tl.log(s"Normalized:\n${normalized.prettyPrint}") if k.isInstanceOf[TailOp] && isIf then go(normalized, topLevel = true) else diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index e127c336ab..a288dee79a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -480,7 +480,7 @@ extends Importer: 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${Split.display(des)}") + log(s"Desugared:\n${des.prettyPrint}") Term.IfLike(Keyword.`if`, des) case InfixApp(lhs, kw @ (Keyword.`then` | Keyword.`with`), rhs) => raise: @@ -623,7 +623,7 @@ extends Importer: case tree @ IfLike(kw, _, split) => val desugared = new ucs.Desugarer(this)(tree) scoped("ucs:desugared"): - log(s"Desugared:\n${Split.display(desugared)}") + log(s"Desugared:\n${desugared.prettyPrint}") Term.IfLike(kw, desugared) case Quoted(body) => Term.Quoted(subterm(body)) case Unquoted(body) => Term.Unquoted(subterm(body)) @@ -631,7 +631,7 @@ extends Importer: val scrut = VarSymbol(Ident("caseScrut")) val des = new ucs.Desugarer(this)(tree, scrut) scoped("ucs:desugared"): - log(s"Desugared:\n${Split.display(des)}") + log(s"Desugared:\n${des.prettyPrint}") Term.Lam(PlainParamList( Param(FldFlags.empty, scrut, N, Modulefulness.none) :: Nil ), Term.IfLike(Keyword.`if`, des)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala index d6dcddfeb2..e30ee56ae4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Split.scala @@ -17,7 +17,7 @@ enum Split extends AutoLocated with ProductWithTail: case Let(sym: BlockLocalSymbol, term: Term, tail: Split) case Else(default: Term) case End - + inline def ~:(head: Branch): Split = Split.Cons(head, this) lazy val isFull: Bool = this match @@ -25,12 +25,12 @@ enum Split extends AutoLocated with ProductWithTail: case Split.Let(_, _, tail) => tail.isFull case Split.Else(_) => true case Split.End => false - + lazy val isEmpty: Bool = this match case Split.Let(_, _, tail) => tail.isEmpty case Split.Else(_) | Split.Cons(_, _) => false case Split.End => true - + final override def children: Ls[Located] = this match case Split.Cons(head, tail) => List(head, tail) case Split.Let(name, term, tail) => List(name, term, tail) @@ -49,7 +49,7 @@ enum Split extends AutoLocated with ProductWithTail: case Split.Let(name, term, tail) => s"let ${name} = ${term.showDbg}; ${tail.showDbg}" case Split.Else(default) => s"else ${default.showDbg}" case Split.End => "" - + final override def withLoc(loco: Option[Loc]): this.type = super.withLoc: this match @@ -58,8 +58,10 @@ enum Split extends AutoLocated with ProductWithTail: case Split.End => N case _: Split.Else => N // FIXME: @Luyu pls clean up this mess case _ => loco - + var isFallback: Bool = false + + def prettyPrint: Str = Split.prettyPrint(this) end Split extension (split: Split) @@ -74,20 +76,20 @@ extension (split: Split) object Split: def default(term: Term): Split = Split.Else(term) - object display: + private 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 @@ -102,7 +104,7 @@ object Split: case lines => (0, prefix) :: lines.indent inline def apply(s: Split): Str = showSplit("if", s) - + private def showSplit(prefix: Str, s: Split): Str = /** Show a split as a list of lines. * @param isFirst whether this is the first and frontmost branch @@ -116,7 +118,6 @@ object Split: case Split.Let(nme, rhs, tail) => (0, s"let $nme = ${rhs.showDbg}") :: split(tail, false, true) case Split.Else(t) => - // (if isFirst then (0, s"then ${t.showDbg}") else (0, s"else ${t.showDbg}")) :: Nil (if isFirst && !isTopLevel then "" else "else") #: term(t) case Split.End => Nil def term(t: Statement): Lines = t match @@ -137,4 +138,4 @@ object Split: val lines = split(s, true, true) (if prefix.isEmpty then lines else prefix #: lines).toIndentedString - end display + end prettyPrint 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 f403075d96..8652a16dca 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -115,8 +115,8 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas * @return the normalized term */ private def normalize(split: Split)(using vs: VarSet): Split = trace( - pre = s"normalize <<< ${Split.display(split)}", - post = (res: Split) => "normalize >>> " + Split.display(res), + pre = s"normalize <<< ${split.prettyPrint}", + post = (res: Split) => "normalize >>> " + res.prettyPrint, ): normalizeImpl(split) @@ -444,7 +444,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas alternative: Split, )(using VarSet): Split = trace( pre = s"normalizeStringPrefixPattern <<< ${ctorTerm.showDbg}", - post = (r: Split) => s"normalizeStringPrefixPattern >>> ${Split.display(r)}" + post = (r: Split) => s"normalizeStringPrefixPattern >>> ${r.prettyPrint}" ): val patternArguments = allArgsOpt.fold(Nil)(_.collect: case Argument.Pattern(symbol, pattern) => symbol -> pattern) @@ -519,8 +519,8 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas scrutinee: Term.Ref, pattern: FlatPattern )(using VarSet): Split = trace( - pre = s"S$mode <<< ${scrutinee.showDbg} is ${pattern.showDbg} : ${Split.display(split)}", - post = (r: Split) => s"S$mode >>> ${Split.display(r)}" + pre = s"S$mode <<< ${scrutinee.showDbg} is ${pattern.showDbg} : ${split.prettyPrint}", + post = (r: Split) => s"S$mode >>> ${r.prettyPrint}" ): def rec(split: Split)(using mode: Mode, vs: VarSet): Split = split match case Split.End => log("CASE Nil"); split 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 72d97c1db5..e80333ce93 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -120,7 +120,7 @@ class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Bas // Make a split that tries all branches in order. val topmostSplit = branches.foldRight(default)(_ ~: _) val bodyTerm = IfLike(Keyword.`if`, topmostSplit) - log(s"Multi-matcher body:\n${Split.display(topmostSplit)}") + log(s"Multi-matcher body:\n${topmostSplit.prettyPrint}") (paramList(param(scrutinee)), bodyTerm) def multiMatcherBranch( diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala index 505b03830d..6b407310e6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala @@ -4,7 +4,7 @@ package ups import mlscript.utils.*, shorthands.* import Message.MessageContext -import Split.display, ucs.{DesugaringBase, FlatPattern, error, safeRef}, ucs.extractors.* +import ucs.{DesugaringBase, 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 @@ -524,7 +524,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas val inputSymbol = VarSymbol(Ident("input")) val topmost = makeMatchSplit(inputSymbol.toScrut, pd.pattern) ((output, bindings) => Split.Else(makeMatchResult(output())), failure) - log(s"Translated `unapply`: ${display(topmost)}") + log(s"Translated `unapply`: ${topmost.prettyPrint}") makeMethod(N, "unapply", pd.patternParams, inputSymbol, topmost) // TODO: Use `pd.extractionParams`. val unapplyStringPrefix = scoped("ucs:cp"): @@ -535,7 +535,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas val topmost = makeStringPrefixMatchSplit(inputSymbol.toScrut, pd.pattern) ((consumedOutput, remainingOutput, bindings) => Split.Else: makeMatchResult(tup(fld(consumedOutput()), fld(remainingOutput()))), failure) - log(s"Translated `unapplyStringPrefix`: ${display(topmost)}") + log(s"Translated `unapplyStringPrefix`: ${topmost.prettyPrint}") makeMethod(N, "unapplyStringPrefix", pd.patternParams, inputSymbol, topmost) unapply :: unapplyStringPrefix :: Nil @@ -574,7 +574,7 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas val inputSymbol = VarSymbol(Ident("input")) val topmost = makeMatchSplit(inputSymbol.toScrut, pattern) ((output, bindings) => Split.Else(makeMatchResult(output())), failure) - log(s"Translated `unapply`: ${display(topmost)}") + 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 @@ -584,6 +584,6 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends DesugaringBas val topmost = makeStringPrefixMatchSplit(inputSymbol.toScrut, pattern) ((consumedOutput, remainingOutput, bindings) => Split.Else: makeMatchResult(tup(fld(consumedOutput()), fld(remainingOutput()))), failure) - log(s"Translated `unapplyStringPrefix`: ${display(topmost)}") + log(s"Translated `unapplyStringPrefix`: ${topmost.prettyPrint}") makeUnapplyRecordStatements("unapplyStringPrefix", patternParams, inputSymbol, topmost) Term.Rcd(false, unapply ::: unapplyStringPrefix) From 52775c532915cc8a3200e653ccada808acf0ddb3 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:23:23 +0800 Subject: [PATCH 44/62] Revert an outdated change --- hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala | 2 +- hkmc2/shared/src/test/mlscript/rp/syntax/Precedence.mls | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 6432a0cd89..51506e4a42 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -304,7 +304,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: 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 Lam(params, body) => s"λ${params.paramSyms.map(_.id.name).mkString(", ")}. ${body.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 + " " })) .mkString("( ", "", ")") diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Precedence.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/Precedence.mls index 657723c360..cabdfd69d8 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/Precedence.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/Precedence.mls @@ -19,14 +19,14 @@ pattern Test = 1 => 2 | 4 => 8 //│ ╔══[ERROR] Expected a valid parameter, found integer literal //│ ║ l.18: pattern Test = 1 => 2 | 4 => 8 //│ ╙── ^ -//│ elaborated pattern body: 1 => 2 | λ. 8 +//│ elaborated pattern body: 1 => 2 | λ(). 8 :todo pattern Test = 1 => 2 | (4 => 8) //│ ╔══[ERROR] Expected a valid parameter, found integer literal //│ ║ l.25: pattern Test = 1 => 2 | (4 => 8) //│ ╙── ^ -//│ elaborated pattern body: 1 => 2 | λ. 8 +//│ elaborated pattern body: 1 => 2 | λ(). 8 pattern Test = (1 => 2) | 4 => 8 //│ elaborated pattern body: 1 => 2 ∨ 4 => 8 From 7ae33a75f20c672791c5f9f07919b2d97a32f533 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:30:40 +0800 Subject: [PATCH 45/62] Revert the error message for unrecognized patterns --- .../main/scala/hkmc2/semantics/Elaborator.scala | 4 ++-- hkmc2/shared/src/test/mlscript/rp/Future.mls | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index a288dee79a..2fb32c13aa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1457,8 +1457,8 @@ extends Importer: case S(target) => Constructor(target, N) case N => Variable(id) // Fallback to variable pattern. case sel: (SynthSel | Sel) => Constructor(term(sel), N) - case other: Tree => - raise(ErrorReport(msg"Found an unrecognizable pattern." -> t.toLoc :: Nil)) + case _: Tree => + raise(ErrorReport(msg"Unrecognized pattern (${t.describe})." -> t.toLoc :: Nil)) Pattern.Wildcard() go(t) diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/rp/Future.mls index e56e3ba654..1525182b72 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Future.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Future.mls @@ -2,7 +2,7 @@ :todo // Parameterized patterns. pattern Rep0[A] = "" | A ~ Rep0[A] -//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ╔══[ERROR] Unrecognized pattern (application). //│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] //│ ╙── ^^^^^^^ @@ -24,7 +24,7 @@ pattern Rep0(pattern A, B, C)(head) = //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.21: "" | (A as head) ~ Rep0[A] //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ╔══[ERROR] Unrecognized pattern (application). //│ ║ l.21: "" | (A as head) ~ Rep0[A] //│ ╙── ^^^^^^^ //│ ╔══[ERROR] Found an inconsistent variable in disjunction patterns. @@ -50,7 +50,7 @@ pattern Email(name, domain) = :todo // View patterns pattern GreaterThan(value) = case n and n > value then n -//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ╔══[ERROR] Unrecognized pattern (case). //│ ║ l.52: n and n > value then n //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ @@ -88,7 +88,7 @@ pattern Email(name, domains) = //│ ╔══[PARSE ERROR] Expected end of input; found literal instead //│ ║ l.83: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^ -//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ╔══[ERROR] Unrecognized pattern (juxtaposition). //│ ║ l.83: pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" //│ ╙── ^^^^^^ @@ -107,7 +107,7 @@ pattern LineSep(pattern P) = case L(...nd) ~ "" then nd :: Nil "\n" ~ LineSep(P, t) then nd :: t -//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ╔══[ERROR] Unrecognized pattern (case). //│ ║ l.106: "" then Nil //│ ║ ^^^^^^^^^^^ //│ ║ l.107: L(...nd) ~ @@ -181,7 +181,7 @@ if input is Opt(Email, Some((n, d))) then ... pattern Opt(pattern P) = case P(...values) then (Some(values)) "" then (None) -//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ╔══[ERROR] Unrecognized pattern (case). //│ ║ l.182: P(...values) then (Some(values)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.183: "" then (None) @@ -191,7 +191,7 @@ pattern Opt(pattern P) = case pattern Opt(pattern P) = case P(...values) then Some(values) "" then None -//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ╔══[ERROR] Unrecognized pattern (case). //│ ║ l.192: P(...values) then Some(values) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.193: "" then None @@ -202,6 +202,6 @@ pattern Digits = "0" to "9" ~ (Digits | "") //│ ╔══[PARSE ERROR] Expected end of input; found literal instead //│ ║ l.201: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^ -//│ ╔══[ERROR] Found an unrecognizable pattern. +//│ ╔══[ERROR] Unrecognized pattern (juxtaposition). //│ ║ l.201: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^^^^ From d7087603aa566c556337a250a9b6465c9288286b Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 10 Aug 2025 17:00:49 +0800 Subject: [PATCH 46/62] Remove an outdated test command --- hkmc2/shared/src/test/mlscript/llir/Split.mls | 2 -- 1 file changed, 2 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/llir/Split.mls b/hkmc2/shared/src/test/mlscript/llir/Split.mls index df97268d1a..2e2d5cf54b 100644 --- a/hkmc2/shared/src/test/mlscript/llir/Split.mls +++ b/hkmc2/shared/src/test/mlscript/llir/Split.mls @@ -26,8 +26,6 @@ map_sum(x => x, 200) //│ Stopped due to an error during the Llir generation :intl -:todo -// Works fine with `~hkmc2DiffTests/Test/run` but not with `hkmc2AllTests/test`. abstract class Iter[T] object Done data class Yield(n) From 5d14750d5f02aa3e60af2acdb702c849744e6350 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 10 Aug 2025 17:12:51 +0800 Subject: [PATCH 47/62] Fix the separation of test blocks --- hkmc2/shared/src/test/mlscript/rp/Future.mls | 56 +++++++++----------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/rp/Future.mls index 1525182b72..533e20af94 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Future.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Future.mls @@ -122,13 +122,7 @@ pattern Lines(pattern L) = case "" then [] "\n" ~ (Lines of L) as t then t L as h ~ Tail as t then [h, ...t] - -:todo -if input is Lines of Email then -//│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.127: if input is Lines of Email then -//│ ╙── ^ -//│ ╔══[ERROR] Unexpected record property pattern. +//│ ╔══[ERROR] Unrecognized pattern (case). //│ ║ l.120: "" then [] //│ ║ ^^^^^^^^^^ //│ ║ l.121: pattern Tail = case @@ -138,42 +132,40 @@ if input is Lines of Email then //│ ║ l.123: "\n" ~ (Lines of L) as t then t //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.124: L as h ~ Tail as t then [h, ...t] -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.125: -//│ ║ ^^^ -//│ ║ l.126: :todo -//│ ╙── ^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:todo +if input is Lines of Email then +//│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead +//│ ║ l.138: if input is Lines of Email then +//│ ╙── ^ //│ ╔══[ERROR] Name not found: input -//│ ║ l.127: if input is Lines of Email then +//│ ║ l.138: if input is Lines of Email then //│ ╙── ^^^^^ :todo pattern Opt(pattern P) = case P(value) then (Some(value)) "" then (None) - +//│ ╔══[ERROR] Unrecognized pattern (case). +//│ ║ l.148: P(value) then (Some(value)) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.149: "" then (None) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ + :todo pattern Email(name, domain) = ... -//│ ╔══[ERROR] Unexpected record property pattern. -//│ ║ l.152: P(value) then (Some(value)) -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.153: "" then (None) -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.154: -//│ ║ ^^^ -//│ ║ l.155: :todo -//│ ╙── ^^^^^ :todo if input is Opt(Email, Some((n, d))) then ... //│ ╔══[ERROR] Name not found: input -//│ ║ l.168: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.160: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^^ //│ ╔══[ERROR] Name not found: Some -//│ ║ l.168: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.160: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^^^^ //│ ╔══[ERROR] invalid record field pattern -//│ ║ l.168: if input is Opt(Email, Some((n, d))) then ... +//│ ║ l.160: if input is Opt(Email, Some((n, d))) then ... //│ ╙── ^ //│ ═══[ERROR] Expected zero arguments, but found two arguments. @@ -182,9 +174,9 @@ pattern Opt(pattern P) = case P(...values) then (Some(values)) "" then (None) //│ ╔══[ERROR] Unrecognized pattern (case). -//│ ║ l.182: P(...values) then (Some(values)) +//│ ║ l.174: P(...values) then (Some(values)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.183: "" then (None) +//│ ║ l.175: "" then (None) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ :todo @@ -192,16 +184,16 @@ pattern Opt(pattern P) = case P(...values) then Some(values) "" then None //│ ╔══[ERROR] Unrecognized pattern (case). -//│ ║ l.192: P(...values) then Some(values) +//│ ║ l.184: P(...values) then Some(values) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.193: "" then None +//│ ║ l.185: "" then None //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ :todo pattern Digits = "0" to "9" ~ (Digits | "") //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.201: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.193: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^ //│ ╔══[ERROR] Unrecognized pattern (juxtaposition). -//│ ║ l.201: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ║ l.193: pattern Digits = "0" to "9" ~ (Digits | "") //│ ╙── ^^^^^^ From 7a52f7b998695593f0913554e288bdf992923816 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:09:03 +0800 Subject: [PATCH 48/62] Do no use `showDbg` in error messages --- .../scala/hkmc2/semantics/ucs/Normalization.scala | 2 +- .../src/test/mlscript/rp/syntax/WrongArguments.mls | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 hkmc2/shared/src/test/mlscript/rp/syntax/WrongArguments.mls 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 8652a16dca..878d233ae6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -294,7 +294,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBas _.flatMap: case arg: FlatPattern.Argument.Term => S(arg) case FlatPattern.Argument.Pattern(_, pattern) => - error(msg"Pattern argument `${pattern.showDbg}` cannot be used here." -> pattern.toLoc); N + error(msg"The pattern argument cannot be used here." -> pattern.toLoc); N /** Warn about inappropriate annotations used on class or object patterns. */ private def validateMatchMode( diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArguments.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArguments.mls new file mode 100644 index 0000000000..ca7fa3122e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArguments.mls @@ -0,0 +1,12 @@ +:js + +class Foo(val bar: Int) + +:e +pattern Bar = Foo(pattern 0) +//│ ╔══[ERROR] The pattern argument cannot be used here. +//│ ║ l.6: pattern Bar = Foo(pattern 0) +//│ ╙── ^ +//│ ╔══[ERROR] Expected one argument, but found only zero arguments. +//│ ║ l.6: pattern Bar = Foo(pattern 0) +//│ ╙── ^^^ From 6f6abdaacfbaac867e3136f68370bdffbdbccc55 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 01:17:58 +0800 Subject: [PATCH 49/62] Break the rendering output into multiple lines --- .../src/test/mlscript-compile/Runtime.mjs | 2 +- .../src/test/mlscript-compile/Runtime.mls | 2 +- .../src/test/mlscript/apps/AccountingTest.mls | 8 +- .../src/test/mlscript/codegen/BadThis.mls | 8 +- .../src/test/mlscript/codegen/GlobalThis.mls | 10 +- .../src/test/mlscript/codegen/ImportMLsJS.mls | 7 +- .../src/test/mlscript/codegen/QQImport.mls | 42 +- .../src/test/mlscript/codegen/Quasiquotes.mls | 84 ++- .../shared/src/test/mlscript/codegen/Repl.mls | 18 +- .../src/test/mlscript/handlers/Debugging.mls | 8 +- .../src/test/mlscript/handlers/Generators.mls | 27 +- .../shared/src/test/mlscript/nofib/boyer2.mls | 638 +++++++++++++++++- .../src/test/mlscript/nofib/cichelli.mls | 6 +- hkmc2/shared/src/test/mlscript/nofib/cse.mls | 20 +- .../shared/src/test/mlscript/nofib/eliza.mls | 5 +- .../shared/src/test/mlscript/nofib/lambda.mls | 41 +- .../src/test/mlscript/nofib/last-piece.mls | 66 +- .../shared/src/test/mlscript/nofib/mandel.mls | 7 +- .../src/test/mlscript/rp/LocalPatterns.mls | 6 +- .../src/test/mlscript/rp/examples/DnfCnf.mls | 20 +- .../src/test/mlscript/std/RenderingTest.mls | 4 +- .../ucs/examples/BinarySearchTree.mls | 14 +- .../mlscript/ucs/examples/LeftistTree.mls | 7 +- .../src/test/mlscript/ucs/examples/ULC.mls | 8 +- .../ucs/patterns/ConjunctionPattern.mls | 14 +- .../test/scala/hkmc2/JSBackendDiffMaker.scala | 8 +- 26 files changed, 1018 insertions(+), 62 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index 6e09edc945..89f1425ef5 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -432,7 +432,7 @@ let Runtime1; } static printRaw(x2) { let tmp; - tmp = runtime.safeCall(Runtime.render(x2)); + tmp = Runtime.render(x2, globalThis.Object.freeze({ "indent": 2, "breakLength": 76 })); return runtime.safeCall(globalThis.console.log(tmp)) } static raisePrintStackEffect(showLocals) { diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls index b4e0c4bdc1..1e34824a7d 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mls @@ -93,7 +93,7 @@ module Str with // Re-export rendering functions val render = Rendering.render -fun printRaw(x) = console.log(render(x)) +fun printRaw(x) = console.log(render(x, indent: 2, breakLength: 76)) // TraceLogger diff --git a/hkmc2/shared/src/test/mlscript/apps/AccountingTest.mls b/hkmc2/shared/src/test/mlscript/apps/AccountingTest.mls index b1e9e6ed65..503c10f10e 100644 --- a/hkmc2/shared/src/test/mlscript/apps/AccountingTest.mls +++ b/hkmc2/shared/src/test/mlscript/apps/AccountingTest.mls @@ -4,7 +4,13 @@ import "../../mlscript-compile/apps/Accounting.mls" val acc = new Accounting -//│ acc = Accounting { warnings: [], Project: fun Project { class: class Project }, Line: fun Line { class: class Line }, lines: [], Report: fun Report { class: class Report } } +//│ acc = Accounting { +//│ warnings: [], +//│ Project: fun Project { class: class Project }, +//│ Line: fun Line { class: class Line }, +//│ lines: [], +//│ Report: fun Report { class: class Report } +//│ } val proj = acc.Project("P1") //│ proj = Project("P1") diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadThis.mls b/hkmc2/shared/src/test/mlscript/codegen/BadThis.mls index 1bf8e4451a..0b5125db56 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadThis.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadThis.mls @@ -15,17 +15,17 @@ if 0 is A then 1 // * TODO: prevent rebinding of `globalThis` :re val globalThis = "oops" -//│ ═══[RUNTIME ERROR] TypeError: Right-hand side of 'instanceof' is not an object -//│ ═══[RUNTIME ERROR] TypeError: Right-hand side of 'instanceof' is not an object +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') :re globalThis.clearInterval //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') -//│ ═══[RUNTIME ERROR] TypeError: Right-hand side of 'instanceof' is not an object +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') :re if 0 is A then 1 //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') -//│ ═══[RUNTIME ERROR] TypeError: Right-hand side of 'instanceof' is not an object +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') diff --git a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls index 76e5a47a7a..d185ee4909 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls @@ -8,12 +8,12 @@ let g = globalThis // FIXME prevent rebinding of this name :re let globalThis = "oops" -//│ ═══[RUNTIME ERROR] TypeError: Right-hand side of 'instanceof' is not an object -//│ ═══[RUNTIME ERROR] TypeError: Right-hand side of 'instanceof' is not an object +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') :re globalThis -//│ ═══[RUNTIME ERROR] TypeError: Right-hand side of 'instanceof' is not an object +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') // * This one uses `this.Error` so it's fine @@ -29,7 +29,7 @@ if false then 0 //│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ } //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') -//│ ═══[RUNTIME ERROR] TypeError: Right-hand side of 'instanceof' is not an object +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') // * This one uses `globalThis.Error` // * Notice the failed exception throw @@ -51,6 +51,6 @@ foo() //│ }; //│ foo() //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') -//│ ═══[RUNTIME ERROR] TypeError: Right-hand side of 'instanceof' is not an object +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls index 986f1947e2..56422f9403 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportMLsJS.mls @@ -2,7 +2,12 @@ import "../../mlscript-compile/Option.mjs" -//│ Option = module Option { Some: fun Some { class: class Some }, None: None, Both: fun Both { class: class Both }, unsafe: module unsafe } +//│ Option = module Option { +//│ Some: fun Some { class: class Some }, +//│ None: None, +//│ Both: fun Both { class: class Both }, +//│ unsafe: module unsafe +//│ } Option.isDefined(new Option.Some(1)) diff --git a/hkmc2/shared/src/test/mlscript/codegen/QQImport.mls b/hkmc2/shared/src/test/mlscript/codegen/QQImport.mls index 266193013b..93ed399759 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/QQImport.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/QQImport.mls @@ -1,7 +1,47 @@ :js :qq -//│ Term = module Term { Symbol: fun Symbol { class: class Symbol }, Pattern: class Pattern, LitPattern: fun LitPattern { class: class LitPattern }, Var: fun Var { class: class Var }, ClassLike: fun ClassLike { class: class ClassLike }, Tuple: fun Tuple { class: class Tuple }, Record: fun Record { class: class Record }, Branch: fun Branch { class: class Branch }, Split: class Split, Cons: fun Cons { class: class Cons }, Let: fun Let { class: class Let }, Else: fun Else { class: class Else }, End: class End, Keyword: module Keyword { If: If, While: While }, Statement: class Statement, LetDecl: fun LetDecl { class: class LetDecl }, DefineVar: fun DefineVar { class: class DefineVar }, Term: class Term, Lit: fun Lit { class: class Lit }, Builtin: fun Builtin { class: class Builtin }, Ref: fun Ref { class: class Ref }, CSRef: fun CSRef { class: class CSRef }, App: fun App { class: class App }, Sel: fun Sel { class: class Sel }, DynSel: fun DynSel { class: class DynSel }, Tup: fun Tup { class: class Tup }, IfLike: fun IfLike { class: class IfLike }, Lam: fun Lam { class: class Lam }, Blk: fun Blk { class: class Blk }, New: fun New { class: class New }, Region: fun Region { class: class Region }, RegRef: fun RegRef { class: class RegRef }, Assgn: fun Assgn { class: class Assgn }, Deref: fun Deref { class: class Deref }, SetRef: fun SetRef { class: class SetRef }, Ret: fun Ret { class: class Ret }, Throw: fun Throw { class: class Throw }, Try: fun Try { class: class Try }, Context: fun Context { class: class Context } } +//│ Term = module Term { +//│ Symbol: fun Symbol { class: class Symbol }, +//│ Pattern: class Pattern, +//│ LitPattern: fun LitPattern { class: class LitPattern }, +//│ Var: fun Var { class: class Var }, +//│ ClassLike: fun ClassLike { class: class ClassLike }, +//│ Tuple: fun Tuple { class: class Tuple }, +//│ Record: fun Record { class: class Record }, +//│ Branch: fun Branch { class: class Branch }, +//│ Split: class Split, +//│ Cons: fun Cons { class: class Cons }, +//│ Let: fun Let { class: class Let }, +//│ Else: fun Else { class: class Else }, +//│ End: class End, +//│ Keyword: module Keyword { If: If, While: While }, +//│ Statement: class Statement, +//│ LetDecl: fun LetDecl { class: class LetDecl }, +//│ DefineVar: fun DefineVar { class: class DefineVar }, +//│ Term: class Term, +//│ Lit: fun Lit { class: class Lit }, +//│ Builtin: fun Builtin { class: class Builtin }, +//│ Ref: fun Ref { class: class Ref }, +//│ CSRef: fun CSRef { class: class CSRef }, +//│ App: fun App { class: class App }, +//│ Sel: fun Sel { class: class Sel }, +//│ DynSel: fun DynSel { class: class DynSel }, +//│ Tup: fun Tup { class: class Tup }, +//│ IfLike: fun IfLike { class: class IfLike }, +//│ Lam: fun Lam { class: class Lam }, +//│ Blk: fun Blk { class: class Blk }, +//│ New: fun New { class: class New }, +//│ Region: fun Region { class: class Region }, +//│ RegRef: fun RegRef { class: class RegRef }, +//│ Assgn: fun Assgn { class: class Assgn }, +//│ Deref: fun Deref { class: class Deref }, +//│ SetRef: fun SetRef { class: class SetRef }, +//│ Ret: fun Ret { class: class Ret }, +//│ Throw: fun Throw { class: class Throw }, +//│ Try: fun Try { class: class Try }, +//│ Context: fun Context { class: class Context } +//│ } Term.print //│ = fun print diff --git a/hkmc2/shared/src/test/mlscript/codegen/Quasiquotes.mls b/hkmc2/shared/src/test/mlscript/codegen/Quasiquotes.mls index e9e1752f79..452f10f6b9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Quasiquotes.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Quasiquotes.mls @@ -1,7 +1,47 @@ :js :qq -//│ Term = module Term { Symbol: fun Symbol { class: class Symbol }, Pattern: class Pattern, LitPattern: fun LitPattern { class: class LitPattern }, Var: fun Var { class: class Var }, ClassLike: fun ClassLike { class: class ClassLike }, Tuple: fun Tuple { class: class Tuple }, Record: fun Record { class: class Record }, Branch: fun Branch { class: class Branch }, Split: class Split, Cons: fun Cons { class: class Cons }, Let: fun Let { class: class Let }, Else: fun Else { class: class Else }, End: class End, Keyword: module Keyword { If: If, While: While }, Statement: class Statement, LetDecl: fun LetDecl { class: class LetDecl }, DefineVar: fun DefineVar { class: class DefineVar }, Term: class Term, Lit: fun Lit { class: class Lit }, Builtin: fun Builtin { class: class Builtin }, Ref: fun Ref { class: class Ref }, CSRef: fun CSRef { class: class CSRef }, App: fun App { class: class App }, Sel: fun Sel { class: class Sel }, DynSel: fun DynSel { class: class DynSel }, Tup: fun Tup { class: class Tup }, IfLike: fun IfLike { class: class IfLike }, Lam: fun Lam { class: class Lam }, Blk: fun Blk { class: class Blk }, New: fun New { class: class New }, Region: fun Region { class: class Region }, RegRef: fun RegRef { class: class RegRef }, Assgn: fun Assgn { class: class Assgn }, Deref: fun Deref { class: class Deref }, SetRef: fun SetRef { class: class SetRef }, Ret: fun Ret { class: class Ret }, Throw: fun Throw { class: class Throw }, Try: fun Try { class: class Try }, Context: fun Context { class: class Context } } +//│ Term = module Term { +//│ Symbol: fun Symbol { class: class Symbol }, +//│ Pattern: class Pattern, +//│ LitPattern: fun LitPattern { class: class LitPattern }, +//│ Var: fun Var { class: class Var }, +//│ ClassLike: fun ClassLike { class: class ClassLike }, +//│ Tuple: fun Tuple { class: class Tuple }, +//│ Record: fun Record { class: class Record }, +//│ Branch: fun Branch { class: class Branch }, +//│ Split: class Split, +//│ Cons: fun Cons { class: class Cons }, +//│ Let: fun Let { class: class Let }, +//│ Else: fun Else { class: class Else }, +//│ End: class End, +//│ Keyword: module Keyword { If: If, While: While }, +//│ Statement: class Statement, +//│ LetDecl: fun LetDecl { class: class LetDecl }, +//│ DefineVar: fun DefineVar { class: class DefineVar }, +//│ Term: class Term, +//│ Lit: fun Lit { class: class Lit }, +//│ Builtin: fun Builtin { class: class Builtin }, +//│ Ref: fun Ref { class: class Ref }, +//│ CSRef: fun CSRef { class: class CSRef }, +//│ App: fun App { class: class App }, +//│ Sel: fun Sel { class: class Sel }, +//│ DynSel: fun DynSel { class: class DynSel }, +//│ Tup: fun Tup { class: class Tup }, +//│ IfLike: fun IfLike { class: class IfLike }, +//│ Lam: fun Lam { class: class Lam }, +//│ Blk: fun Blk { class: class Blk }, +//│ New: fun New { class: class New }, +//│ Region: fun Region { class: class Region }, +//│ RegRef: fun RegRef { class: class RegRef }, +//│ Assgn: fun Assgn { class: class Assgn }, +//│ Deref: fun Deref { class: class Deref }, +//│ SetRef: fun SetRef { class: class SetRef }, +//│ Ret: fun Ret { class: class Ret }, +//│ Throw: fun Throw { class: class Throw }, +//│ Try: fun Try { class: class Try }, +//│ Context: fun Context { class: class Context } +//│ } `42 //│ = Lit(42) @@ -51,16 +91,25 @@ let f = x `=> x `+ `1 f`(`0) -//│ = App(Lam([Symbol("x")], App(Builtin("+"), Tup([Ref(Symbol("x")), Lit(1)]))), Tup([Lit(0)])) +//│ = App( +//│ Lam([Symbol("x")], App(Builtin("+"), Tup([Ref(Symbol("x")), Lit(1)]))), +//│ Tup([Lit(0)]) +//│ ) `let x = `42 `in x -//│ = Blk([LetDecl(Symbol("x")), DefineVar(Symbol("x"), Lit(42))], Ref(Symbol("x"))) +//│ = Blk( +//│ [LetDecl(Symbol("x")), DefineVar(Symbol("x"), Lit(42))], +//│ Ref(Symbol("x")) +//│ ) `let x = `42 `in print(x); x //│ > Ref(Symbol("x")) -//│ = Blk([LetDecl(Symbol("x")), DefineVar(Symbol("x"), Lit(42))], Ref(Symbol("x"))) +//│ = Blk( +//│ [LetDecl(Symbol("x")), DefineVar(Symbol("x"), Lit(42))], +//│ Ref(Symbol("x")) +//│ ) :sjs @@ -80,9 +129,32 @@ f`(`0) //│ tmp32 = globalThis.Object.freeze(new Term.Cons(tmp37, tmp38)); //│ tmp33 = globalThis.Object.freeze(new Term.Let(tmp30, tmp31, tmp32)); //│ globalThis.Object.freeze(new Term.IfLike(Term.Keyword.If, tmp33)) -//│ = IfLike(If, Let(Symbol("scrut"), Lit(true), Cons(Branch(Ref(Symbol("scrut")), LitPattern(true), Else(Lit(true))), Else(Lit(false))))) +//│ = IfLike( +//│ If, +//│ Let( +//│ Symbol("scrut"), +//│ Lit(true), +//│ Cons( +//│ Branch(Ref(Symbol("scrut")), LitPattern(true), Else(Lit(true))), +//│ Else(Lit(false)) +//│ ) +//│ ) +//│ ) x `=> `if x `== `0.0 then `1.0 else x -//│ = Lam([Symbol("x")], IfLike(If, Let(Symbol("scrut"), App(Builtin("=="), Tup([Ref(Symbol("x")), Lit(0)])), Cons(Branch(Ref(Symbol("scrut")), LitPattern(true), Else(Lit(1))), Else(Ref(Symbol("x"))))))) +//│ = Lam( +//│ [Symbol("x")], +//│ IfLike( +//│ If, +//│ Let( +//│ Symbol("scrut"), +//│ App(Builtin("=="), Tup([Ref(Symbol("x")), Lit(0)])), +//│ Cons( +//│ Branch(Ref(Symbol("scrut")), LitPattern(true), Else(Lit(1))), +//│ Else(Ref(Symbol("x"))) +//│ ) +//│ ) +//│ ) +//│ ) diff --git a/hkmc2/shared/src/test/mlscript/codegen/Repl.mls b/hkmc2/shared/src/test/mlscript/codegen/Repl.mls index 7cfbf84e26..1b9480d15a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Repl.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Repl.mls @@ -95,6 +95,22 @@ let x = 1, print(x), x // FIXME multiline result is treated as part stdout ["***", "***", "***", "***", "***", "***", "***", "***", "***", "***", "***", "***", "***", "***", "***"] -//│ = ["***", "***", "***", "***", "***", "***", "***", "***", "***", "***", "***", "***", "***", "***", "***"] +//│ = [ +//│ "***", +//│ "***", +//│ "***", +//│ "***", +//│ "***", +//│ "***", +//│ "***", +//│ "***", +//│ "***", +//│ "***", +//│ "***", +//│ "***", +//│ "***", +//│ "***", +//│ "***" +//│ ] diff --git a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls index 8422ca696b..1ca6aba7b7 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls @@ -141,7 +141,13 @@ import "../../mlscript-compile/Runtime.mls" let res = Runtime.try(f) res.reified.contTrace.next.getLocals -//│ = [FnLocalsInfo("‹top level›", []), FnLocalsInfo("f", [LocalVarInfo("i", 0), LocalVarInfo("j", 100), LocalVarInfo("k", 2000)])] +//│ = [ +//│ FnLocalsInfo("‹top level›", []), +//│ FnLocalsInfo( +//│ "f", +//│ [LocalVarInfo("i", 0), LocalVarInfo("j", 100), LocalVarInfo("k", 2000)] +//│ ) +//│ ] Runtime.debugEff(res.reified) //│ > Debug EffectSig: diff --git a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls index 250ffdd417..6c4e5c7824 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Generators.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Generators.mls @@ -26,7 +26,32 @@ handle gen = Generator with res.push(result) resume(()) in permutations(gen, [1, 2, 3, 4]) -//│ res = [[1, 2, 3, 4], [1, 2, 4, 3], [1, 3, 2, 4], [1, 3, 4, 2], [1, 4, 2, 3], [1, 4, 3, 2], [2, 1, 3, 4], [2, 1, 4, 3], [2, 3, 1, 4], [2, 3, 4, 1], [2, 4, 1, 3], [2, 4, 3, 1], [3, 1, 2, 4], [3, 1, 4, 2], [3, 2, 1, 4], [3, 2, 4, 1], [3, 4, 1, 2], [3, 4, 2, 1], [4, 1, 2, 3], [4, 1, 3, 2], [4, 2, 1, 3], [4, 2, 3, 1], [4, 3, 1, 2], [4, 3, 2, 1]] +//│ res = [ +//│ [1, 2, 3, 4], +//│ [1, 2, 4, 3], +//│ [1, 3, 2, 4], +//│ [1, 3, 4, 2], +//│ [1, 4, 2, 3], +//│ [1, 4, 3, 2], +//│ [2, 1, 3, 4], +//│ [2, 1, 4, 3], +//│ [2, 3, 1, 4], +//│ [2, 3, 4, 1], +//│ [2, 4, 1, 3], +//│ [2, 4, 3, 1], +//│ [3, 1, 2, 4], +//│ [3, 1, 4, 2], +//│ [3, 2, 1, 4], +//│ [3, 2, 4, 1], +//│ [3, 4, 1, 2], +//│ [3, 4, 2, 1], +//│ [4, 1, 2, 3], +//│ [4, 1, 3, 2], +//│ [4, 2, 1, 3], +//│ [4, 2, 3, 1], +//│ [4, 3, 1, 2], +//│ [4, 3, 2, 1] +//│ ] fun permutations_foreach(l, f) = handle gen = Generator with diff --git a/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls b/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls index 4c7d88b06b..b6d5b4a9aa 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls @@ -197,7 +197,110 @@ fun rewrite(x, term) = if x is let statement = mkLispList(strToToken( nofibStringToList("( implies ( and ( implies x y )( and ( implies y z )( and ( implies z u )( implies u w ) ) ) )( implies x w ) )") )) -//│ statement = Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["z"]), Conss([Atom(["u"]), Nill])])]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["u"]), Conss([Atom(["w"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["x"]), Conss([Atom(["w"]), Nill])])]), Nill])])]) +//│ statement = Conss( +//│ [ +//│ Atom(["i","m","p","l","i","e","s"]), +//│ Conss( +//│ [ +//│ Conss( +//│ [ +//│ Atom(["a","n","d"]), +//│ Conss( +//│ [ +//│ Conss( +//│ [ +//│ Atom(["i","m","p","l","i","e","s"]), +//│ Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])]) +//│ ] +//│ ), +//│ Conss( +//│ [ +//│ Conss( +//│ [ +//│ Atom(["a","n","d"]), +//│ Conss( +//│ [ +//│ Conss( +//│ [ +//│ Atom(["i","m","p","l","i","e","s"]), +//│ Conss( +//│ [Atom(["y"]), Conss([Atom(["z"]), Nill])] +//│ ) +//│ ] +//│ ), +//│ Conss( +//│ [ +//│ Conss( +//│ [ +//│ Atom(["a","n","d"]), +//│ Conss( +//│ [ +//│ Conss( +//│ [ +//│ Atom( +//│ ["i","m","p","l","i","e","s"] +//│ ), +//│ Conss( +//│ [ +//│ Atom(["z"]), +//│ Conss([Atom(["u"]), Nill]) +//│ ] +//│ ) +//│ ] +//│ ), +//│ Conss( +//│ [ +//│ Conss( +//│ [ +//│ Atom( +//│ ["i","m","p","l","i","e","s"] +//│ ), +//│ Conss( +//│ [ +//│ Atom(["u"]), +//│ Conss( +//│ [Atom(["w"]), Nill] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ), +//│ Nill +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ), +//│ Nill +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ), +//│ Nill +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ), +//│ Conss( +//│ [ +//│ Conss( +//│ [ +//│ Atom(["i","m","p","l","i","e","s"]), +//│ Conss([Atom(["x"]), Conss([Atom(["w"]), Nill])]) +//│ ] +//│ ), +//│ Nill +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ) fun subterm(i) = let c = stringConcat("c", stringOfInt(i)) @@ -325,7 +428,538 @@ let rules = //│ rules = [["(","e","q","u","a","l"," ","(","c","o","m","p","i","l","e"," ","f","o","r","m",")","(","r","e","v","e","r","s","e"," ","(","c","o","d","e","g","e","n"," ","(","o","p","t","i","m","i","z","e"," ","f","o","r","m",")"," ","(","N","i","l","l",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","p"," ","x"," ","y",")","(","e","q","u","a","l"," ","(","f","i","x"," ","x",")","(","f","i","x"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","g","r","e","a","t","e","r","p"," ","x"," ","y",")","(","l","e","s","s","p"," ","y"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","e","q","p"," ","x"," ","y",")","(","n","o","t"," ","(","l","e","s","s","p"," ","y"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","g","r","e","a","t","e","r","e","q","p"," ","x"," ","y",")","(","n","o","t"," ","(","l","e","s","s","p"," ","y"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","b","o","o","l","e","a","n"," ","x",")","(","o","r"," ","(","e","q","u","a","l"," ","x"," ","(","t",")"," ",")","(","e","q","u","a","l"," ","x"," ","(","f",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","i","f","f"," ","x"," ","y",")","(","a","n","d"," ","(","i","m","p","l","i","e","s"," ","x"," ","y",")","(","i","m","p","l","i","e","s"," ","y"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","v","e","n","1"," ","x",")","(","i","f"," ","(","z","e","r","o","p"," ","x",")","(","t",")","(","o","d","d"," ","(","1","-"," ","x",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","c","o","u","n","t","p","s","-"," ","l"," ","p","r","e","d",")","(","c","o","u","n","t","p","s","-","l","o","o","p"," ","l"," ","p","r","e","d"," ","(","z","e","r","o",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","f","a","c","t","-"," ","i",")","(","f","a","c","t","-","l","o","o","p"," ","i"," ","1",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","v","e","r","s","e","-"," ","x",")","(","r","e","v","e","r","s","e","-","l","o","o","p"," ","x"," ","(","N","i","l","l",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","v","i","d","e","s"," ","x"," ","y",")","(","z","e","r","o","p"," ","(","r","e","m","a","i","n","d","e","r"," ","y"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","a","s","s","u","m","e","-","t","r","u","e"," ","v","a","r"," ","a","l","i","s","t",")","(","C","o","n","s","s"," ","(","C","o","n","s","s"," ","v","a","r"," ","(","t",")"," ",")","a","l","i","s","t",")"," ",")"],["(","e","q","u","a","l"," ","(","a","s","s","u","m","e","-","f","a","l","s","e"," ","v","a","r"," ","a","l","i","s","t",")","(","C","o","n","s","s"," ","(","C","o","n","s","s"," ","v","a","r"," ","(","f",")"," ",")","a","l","i","s","t",")"," ",")"],["(","e","q","u","a","l"," ","(","t","a","u","t","o","l","o","g","y","-","c","h","e","c","k","e","r"," ","x",")","(","t","a","u","t","o","l","o","g","y","p"," ","(","n","o","r","m","a","l","i","z","e"," ","x",")","(","N","i","l","l",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","f","a","l","s","i","f","y"," ","x",")","(","f","a","l","s","i","f","y","1"," ","(","n","o","r","m","a","l","i","z","e"," ","x",")","(","N","i","l","l",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","p","r","i","m","e"," ","x",")","(","a","n","d"," ","(","n","o","t"," ","(","z","e","r","o","p"," ","x",")",")","(","n","o","t"," ","(","e","q","u","a","l"," ","x"," ","(","a","d","d","1"," ","(","z","e","r","o",")"," ",")"," ",")"," ",")","(","p","r","i","m","e","1"," ","x"," ","(","1","-"," ","x",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","a","n","d"," ","p"," ","q",")","(","i","f"," ","p"," ","(","i","f"," ","q"," ","(","t",")"," ","(","f",")"," ",")"," ","(","f",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","o","r"," ","p"," ","q",")","(","i","f"," ","p"," ","(","t",")"," ","(","i","f"," ","q"," ","(","t",")"," ","(","f",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","n","o","t"," ","p",")","(","i","f"," ","p"," ","(","f",")"," ","(","t",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","i","m","p","l","i","e","s"," ","p"," ","q",")","(","i","f"," ","p"," ","(","i","f"," ","q"," ","(","t",")"," ","(","f",")"," ",")"," ","(","t",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","f","i","x"," ","x",")","(","i","f"," ","(","n","u","m","b","e","r","p"," ","x",")"," ","x"," ","(","z","e","r","o",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","i","f"," ","(","i","f"," ","a"," ","b"," ","c",")"," ","d"," ","e",")","(","i","f"," ","a"," ","(","i","f"," ","b"," ","d"," ","e",")"," ","(","i","f"," ","c"," ","d"," ","e",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","z","e","r","o","p"," ","x",")","(","o","r"," ","(","e","q","u","a","l"," ","x"," ","(","z","e","r","o",")"," ",")","(","n","o","t"," ","(","n","u","m","b","e","r","p"," ","x",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","p","l","u","s"," ","(","p","l","u","s"," ","x"," ","y",")"," ","z"," ",")","(","p","l","u","s"," ","x"," ","(","p","l","u","s"," ","y"," ","z",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","p","l","u","s"," ","a"," ","b",")"," ","(","z","e","r","o"," ",")"," ",")","(","a","n","d"," ","(","z","e","r","o","p"," ","a",")"," ","(","z","e","r","o","p"," ","b",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","x"," ","x",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","p","l","u","s"," ","a"," ","b",")"," ","(","p","l","u","s"," ","a"," ","c",")"," ",")","(","e","q","u","a","l"," ","(","f","i","x"," ","b",")"," ","(","f","i","x"," ","c",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","z","e","r","o",")"," ","(","d","i","f","f","e","r","e","n","c","e"," ","x"," ","y",")"," ",")","(","n","o","t"," ","(","l","e","s","s","p"," ","y"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","x"," ","(","d","i","f","f","e","r","e","n","c","e"," ","x"," ","y",")"," ",")","(","a","n","d"," ","(","n","u","m","b","e","r","p"," ","x",")","(","o","r"," ","(","e","q","u","a","l"," ","x"," ","(","z","e","r","o",")"," ",")","(","z","e","r","o","p"," ","y",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","(","a","p","p","e","n","d"," ","x"," ","y",")"," ",")"," ","a",")","(","p","l","u","s"," ","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","x",")"," ","a",")","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","y",")"," ","a",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","(","p","l","u","s","-","f","r","i","n","g","e"," ","x",")"," ",")"," ","a",")","(","f","i","x"," ","(","m","e","a","n","i","n","g"," ","x"," ","a",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","a","p","p","e","n","d"," ","(","a","p","p","e","n","d"," ","x"," ","y",")"," ","z",")","(","a","p","p","e","n","d"," ","x"," ","(","a","p","p","e","n","d"," ","y"," ","z",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","v","e","r","s","e"," ","(","a","p","p","e","n","d"," ","a"," ","b",")"," ",")","(","a","p","p","e","n","d"," ","(","r","e","v","e","r","s","e"," ","b",")"," ","(","r","e","v","e","r","s","e"," ","a",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","x"," ","(","p","l","u","s"," ","y"," ","z",")"," ",")","(","p","l","u","s"," ","(","t","i","m","e","s"," ","x"," ","y",")","(","t","i","m","e","s"," ","x"," ","z",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","(","t","i","m","e","s"," ","x"," ","y",")"," ","z",")","(","t","i","m","e","s"," ","x"," ","(","t","i","m","e","s"," ","y"," ","z",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","x"," ","y",")"," ","(","z","e","r","o",")"," ",")","(","o","r"," ","(","z","e","r","o","p"," ","x",")","(","z","e","r","o","p"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","x","e","c"," ","(","a","p","p","e","n","d"," ","x"," ","y",")","p","d","s"," ","e","n","v","r","n",")","(","e","x","e","c"," ","y"," ","(","e","x","e","c"," ","x"," ","p","d","s"," ","e","n","v","r","n",")","e","n","v","r","n",")"," ",")"],["(","e","q","u","a","l"," ","(","m","c","-","f","l","a","t","t","e","n"," ","x"," ","y",")","(","a","p","p","e","n","d"," ","(","f","l","a","t","t","e","n"," ","x",")","y",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","m","b","e","r"," ","x"," ","(","a","p","p","e","n","d"," ","a"," ","b",")"," ",")","(","o","r"," ","(","m","e","m","b","e","r"," ","x"," ","a",")","(","m","e","m","b","e","r"," ","x"," ","b",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","m","b","e","r"," ","x"," ","(","r","e","v","e","r","s","e"," ","y",")"," ",")","(","m","e","m","b","e","r"," ","x"," ","y",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","n","g","t","h"," ","(","r","e","v","e","r","s","e"," ","x",")"," ",")","(","l","e","n","g","t","h"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","m","b","e","r"," ","a"," ","(","i","n","t","e","r","s","e","c","t"," ","b"," ","c",")"," ",")","(","a","n","d"," ","(","m","e","m","b","e","r"," ","a"," ","b",")","(","m","e","m","b","e","r"," ","a"," ","c",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","n","t","h"," ","(","z","e","r","o",")","i",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","e","x","p"," ","i"," ","(","p","l","u","s"," ","j"," ","k",")"," ",")","(","t","i","m","e","s"," ","(","e","x","p"," ","i"," ","j",")","(","e","x","p"," ","i"," ","k",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","x","p"," ","i"," ","(","t","i","m","e","s"," ","j"," ","k",")"," ",")","(","e","x","p"," ","(","e","x","p"," ","i"," ","j",")","k",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","v","e","r","s","e","-","l","o","o","p"," ","x"," ","y",")","(","a","p","p","e","n","d"," ","(","r","e","v","e","r","s","e"," ","x",")","y",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","v","e","r","s","e","-","l","o","o","p"," ","x"," ","(","N","i","l","l",")"," ",")","(","r","e","v","e","r","s","e"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","c","o","u","n","t","-","l","i","s","t"," ","z"," ","(","s","o","r","t","-","l","p"," ","x"," ","y",")"," ",")","(","p","l","u","s"," ","(","c","o","u","n","t","-","l","i","s","t"," ","z"," ","x",")","(","c","o","u","n","t","-","l","i","s","t"," ","z"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","a","p","p","e","n","d"," ","a"," ","b",")","(","a","p","p","e","n","d"," ","a"," ","c",")"," ",")","(","e","q","u","a","l"," ","b"," ","c",")"," ",")"],["(","e","q","u","a","l"," ","(","p","l","u","s"," ","(","r","e","m","a","i","n","d","e","r"," ","x"," ","y",")","(","t","i","m","e","s"," ","y"," ","(","q","u","o","t","i","e","n","t"," ","x"," ","y",")"," ",")"," ",")","(","f","i","x"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","p","o","w","e","r","-","e","v","a","l"," ","(","b","i","g","-","p","l","u","s","1"," ","l"," ","i"," ","b","a","s","e",")","b","a","s","e",")","(","p","l","u","s"," ","(","p","o","w","e","r","-","e","v","a","l"," ","l"," ","b","a","s","e",")","i",")"," ",")"],["(","e","q","u","a","l"," ","(","p","o","w","e","r","-","e","v","a","l"," ","(","b","i","g","-","p","l","u","s"," ","x"," ","y"," ","i"," ","b","a","s","e",")","b","a","s","e",")","(","p","l","u","s"," ","i"," ","(","p","l","u","s"," ","(","p","o","w","e","r","-","e","v","a","l"," ","x"," ","b","a","s","e",")","(","p","o","w","e","r","-","e","v","a","l"," ","y"," ","b","a","s","e",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","m","a","i","n","d","e","r"," ","y"," ","1",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","r","e","m","a","i","n","d","e","r"," ","x"," ","y",")","y",")","(","n","o","t"," ","(","z","e","r","o","p"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","m","a","i","n","d","e","r"," ","x"," ","x",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","q","u","o","t","i","e","n","t"," ","i"," ","j",")","i",")","(","a","n","d"," ","(","n","o","t"," ","(","z","e","r","o","p"," ","i",")"," ",")","(","o","r"," ","(","z","e","r","o","p"," ","j",")","(","n","o","t"," ","(","e","q","u","a","l"," ","j"," ","1",")"," ",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","r","e","m","a","i","n","d","e","r"," ","x"," ","y",")","x",")","(","a","n","d"," ","(","n","o","t"," ","(","z","e","r","o","p"," ","y",")"," ",")","(","n","o","t"," ","(","z","e","r","o","p"," ","x",")"," ",")","(","n","o","t"," ","(","l","e","s","s","p"," ","x"," ","y",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","p","o","w","e","r","-","e","v","a","l"," ","(","p","o","w","e","r","-","r","e","p"," ","i"," ","b","a","s","e",")","b","a","s","e",")","(","f","i","x"," ","i",")"," ",")"],["(","e","q","u","a","l"," ","(","p","o","w","e","r","-","e","v","a","l"," ","(","b","i","g","-","p","l","u","s"," ","(","p","o","w","e","r","-","r","e","p"," ","i"," ","b","a","s","e",")","(","p","o","w","e","r","-","r","e","p"," ","j"," ","b","a","s","e",")","(","z","e","r","o",")","b","a","s","e",")","b","a","s","e",")","(","p","l","u","s"," ","i"," ","j",")"," ",")"],["(","e","q","u","a","l"," ","(","g","c","d"," ","x"," ","y",")","(","g","c","d"," ","y"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","n","t","h"," ","(","a","p","p","e","n","d"," ","a"," ","b",")","i",")","(","a","p","p","e","n","d"," ","(","n","t","h"," ","a"," ","i",")","(","n","t","h"," ","b"," ","(","d","i","f","f","e","r","e","n","c","e"," ","i"," ","(","l","e","n","g","t","h"," ","a",")"," ",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","p","l","u","s"," ","x"," ","y",")","x",")","(","f","i","x"," ","y",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","p","l","u","s"," ","y"," ","x",")","x",")","(","f","i","x"," ","y",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","p","l","u","s"," ","x"," ","y",")","(","p","l","u","s"," ","x"," ","z",")"," ",")","(","d","i","f","f","e","r","e","n","c","e"," ","y"," ","z",")"," ",")"],["(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","x"," ","(","d","i","f","f","e","r","e","n","c","e"," ","c"," ","w",")"," ",")","(","d","i","f","f","e","r","e","n","c","e"," ","(","t","i","m","e","s"," ","c"," ","x",")","(","t","i","m","e","s"," ","w"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","m","a","i","n","d","e","r"," ","(","t","i","m","e","s"," ","x"," ","z",")","z",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","p","l","u","s"," ","b"," ","(","p","l","u","s"," ","a"," ","c",")"," ",")","a",")","(","p","l","u","s"," ","b"," ","c",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","a","d","d","1"," ","(","p","l","u","s"," ","y"," ","z",")","z",")","(","a","d","d","1"," ","y",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","p","l","u","s"," ","x"," ","y",")","(","p","l","u","s"," ","x"," ","z"," ",")"," ",")","(","l","e","s","s","p"," ","y"," ","z",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","t","i","m","e","s"," ","x"," ","z",")","(","t","i","m","e","s"," ","y"," ","z",")"," ",")","(","a","n","d"," ","(","n","o","t"," ","(","z","e","r","o","p"," ","z",")"," ",")","(","l","e","s","s","p"," ","x"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","y"," ","(","p","l","u","s"," ","x"," ","y",")"," ",")","(","n","o","t"," ","(","z","e","r","o","p"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","g","c","d"," ","(","t","i","m","e","s"," ","x"," ","z",")","(","t","i","m","e","s"," ","y"," ","z",")"," ",")","(","t","i","m","e","s"," ","z"," ","(","g","c","d"," ","x"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","v","a","l","u","e"," ","(","n","o","r","m","a","l","i","z","e"," ","x",")","a",")","(","v","a","l","u","e"," ","x"," ","a",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","f","l","a","t","t","e","n"," ","x",")","(","C","o","n","s","s"," ","y"," ","(","N","i","l","l",")"," ",")"," ",")","(","a","n","d"," ","(","n","l","i","s","t","p"," ","x",")","(","e","q","u","a","l"," ","x"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","l","i","s","t","p"," ","(","g","o","p","h","e","r"," ","x",")"," ",")","(","l","i","s","t","p"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","s","a","m","e","f","r","i","n","g","e"," ","x"," ","y",")","(","e","q","u","a","l"," ","(","f","l","a","t","t","e","n"," ","x",")","(","f","l","a","t","t","e","n"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"," ","x"," ","y",")","(","z","e","r","o",")"," ",")","(","a","n","d"," ","(","o","r"," ","(","z","e","r","o","p"," ","y",")","(","e","q","u","a","l"," ","y"," ","1",")"," ",")","(","e","q","u","a","l"," ","x"," ","(","z","e","r","o",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"," ","x"," ","y",")","1",")","(","e","q","u","a","l"," ","x"," ","1",")"," ",")"],["(","e","q","u","a","l"," ","(","n","u","m","b","e","r","p"," ","(","g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"," ","x"," ","y",")"," ",")","(","n","o","t"," ","(","a","n","d"," ","(","o","r"," ","(","z","e","r","o","p"," ","y",")","(","e","q","u","a","l"," ","y"," ","1",")"," ",")","(","n","o","t"," ","(","n","u","m","b","e","r","p"," ","x",")"," ",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","t","i","m","e","s","-","l","i","s","t"," ","(","a","p","p","e","n","d"," ","x"," ","y",")"," ",")","(","t","i","m","e","s"," ","(","t","i","m","e","s","-","l","i","s","t"," ","x",")","(","t","i","m","e","s","-","l","i","s","t"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","p","r","i","m","e","-","l","i","s","t"," ","(","a","p","p","e","n","d"," ","x"," ","y",")"," ",")","(","a","n","d"," ","(","p","r","i","m","e","-","l","i","s","t"," ","x",")","(","p","r","i","m","e","-","l","i","s","t"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","z"," ","(","t","i","m","e","s"," ","w"," ","z",")"," ",")","(","a","n","d"," ","(","n","u","m","b","e","r","p"," ","z",")","(","o","r"," ","(","e","q","u","a","l"," ","z"," ","(","z","e","r","o",")"," ",")","(","e","q","u","a","l"," ","w"," ","1",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","g","r","e","a","t","e","r","e","q","p","r"," ","x"," ","y",")","(","n","o","t"," ","(","l","e","s","s","p"," ","x"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","x"," ","(","t","i","m","e","s"," ","x"," ","y",")"," ",")","(","o","r"," ","(","e","q","u","a","l"," ","x"," ","(","z","e","r","o",")"," ",")","(","a","n","d"," ","(","n","u","m","b","e","r","p"," ","x",")","(","e","q","u","a","l"," ","y"," ","1",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","m","a","i","n","d","e","r"," ","(","t","i","m","e","s"," ","y"," ","x",")","y",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","a"," ","b",")","1",")","(","a","n","d"," ","(","n","o","t"," ","(","e","q","u","a","l"," ","a"," ","(","z","e","r","o",")"," ",")"," ",")","(","n","o","t"," ","(","e","q","u","a","l"," ","b"," ","(","z","e","r","o",")"," ",")"," ",")","(","n","u","m","b","e","r","p"," ","a",")","(","n","u","m","b","e","r","p"," ","b",")","(","e","q","u","a","l"," ","(","1","-"," ","a",")","(","z","e","r","o",")"," ",")","(","e","q","u","a","l"," ","(","1","-"," ","b",")","(","z","e","r","o",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","l","e","n","g","t","h"," ","(","d","e","l","e","t","e"," ","x"," ","l",")"," ",")","(","l","e","n","g","t","h"," ","l",")"," ",")","(","m","e","m","b","e","r"," ","x"," ","l",")"," ",")"],["(","e","q","u","a","l"," ","(","s","o","r","t","2"," ","(","d","e","l","e","t","e"," ","x"," ","l",")"," ",")","(","d","e","l","e","t","e"," ","x"," ","(","s","o","r","t","2"," ","l",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","d","s","o","r","t"," ","x",")","(","s","o","r","t","2"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","n","g","t","h","(","C","o","n","s","s"," ","x","1","(","C","o","n","s","s"," ","x","2","(","C","o","n","s","s"," ","x","3","(","C","o","n","s","s"," ","x","4","(","C","o","n","s","s"," ","x","5","(","C","o","n","s","s"," ","x","6"," ","x","7",")"," ",")"," ",")"," ",")"," ",")"," ",")"," ",")","(","p","l","u","s"," ","6"," ","(","l","e","n","g","t","h"," ","x","7",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","a","d","d","1"," ","(","a","d","d","1"," ","x",")"," ",")","2",")","(","f","i","x"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","q","u","o","t","i","e","n","t"," ","(","p","l","u","s"," ","x"," ","(","p","l","u","s"," ","x"," ","y",")"," ",")","2",")","(","p","l","u","s"," ","x"," ","(","q","u","o","t","i","e","n","t"," ","y"," ","2",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","s","i","g","m","a"," ","(","z","e","r","o",")","i",")","(","q","u","o","t","i","e","n","t"," ","(","t","i","m","e","s"," ","i"," ","(","a","d","d","1"," ","i",")"," ",")","2",")"," ",")"],["(","e","q","u","a","l"," ","(","p","l","u","s"," ","x"," ","(","a","d","d","1"," ","y",")"," ",")","(","i","f"," ","(","n","u","m","b","e","r","p"," ","y",")","(","a","d","d","1"," ","(","p","l","u","s"," ","x"," ","y",")"," ",")","(","a","d","d","1"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","x"," ","y",")","(","d","i","f","f","e","r","e","n","c","e"," ","z"," ","y",")"," ",")","(","i","f"," ","(","l","e","s","s","p"," ","x"," ","y",")","(","n","o","t"," ","(","l","e","s","s","p"," ","y"," ","z",")"," ",")","(","i","f"," ","(","l","e","s","s","p"," ","z"," ","y",")","(","n","o","t"," ","(","l","e","s","s","p"," ","y"," ","x",")"," ",")","(","e","q","u","a","l"," ","(","f","i","x"," ","x",")","(","f","i","x"," ","z",")"," ",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","(","d","e","l","e","t","e"," ","x"," ","y",")"," ",")","a",")","(","i","f"," ","(","m","e","m","b","e","r"," ","x"," ","y",")","(","d","i","f","f","e","r","e","n","c","e"," ","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","y",")","a",")","(","m","e","a","n","i","n","g"," ","x"," ","a",")"," ",")","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","y",")","a",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","x"," ","(","a","d","d","1"," ","y",")"," ",")","(","i","f"," ","(","n","u","m","b","e","r","p"," ","y",")","(","p","l","u","s"," ","x"," ","(","t","i","m","e","s"," ","x"," ","y",")"," ",")","(","f","i","x"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","n","t","h"," ","(","N","i","l","l",")","i",")","(","i","f"," ","(","z","e","r","o","p"," ","i",")","(","N","i","l","l",")","(","z","e","r","o",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","l","a","s","t"," ","(","a","p","p","e","n","d"," ","a"," ","b",")"," ",")","(","i","f"," ","(","l","i","s","t","p"," ","b",")","(","l","a","s","t"," ","b",")","(","i","f"," ","(","l","i","s","t","p"," ","a",")","(","C","o","n","s","s"," ","(","c","a","r"," ","(","l","a","s","t"," ","a",")"," ",")","b",")","b",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","x"," ","y",")","z",")","(","i","f"," ","(","l","e","s","s","p"," ","x"," ","y",")","(","e","q","u","a","l"," ","t"," ","z",")","(","e","q","u","a","l"," ","f"," ","z",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","a","s","s","i","g","n","m","e","n","t"," ","x"," ","(","a","p","p","e","n","d"," ","a"," ","b",")"," ",")","(","i","f"," ","(","a","s","s","i","g","n","e","d","p"," ","x"," ","a",")","(","a","s","s","i","g","n","m","e","n","t"," ","x"," ","a",")","(","a","s","s","i","g","n","m","e","n","t"," ","x"," ","b",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","c","a","r"," ","(","g","o","p","h","e","r"," ","x",")"," ",")","(","i","f"," ","(","l","i","s","t","p"," ","x",")","(","c","a","r"," ","(","f","l","a","t","t","e","n"," ","x",")"," ",")","(","z","e","r","o",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","f","l","a","t","t","e","n"," ","(","c","d","r"," ","(","g","o","p","h","e","r"," ","x",")"," ",")"," ",")","(","i","f"," ","(","l","i","s","t","p"," ","x",")","(","c","d","r"," ","(","f","l","a","t","t","e","n"," ","x",")"," ",")","(","C","o","n","s","s"," ","(","z","e","r","o",")","(","N","i","l","l",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","q","u","o","t","i","e","n","t"," ","(","t","i","m","e","s"," ","y"," ","x",")","y",")","(","i","f"," ","(","z","e","r","o","p"," ","y",")","(","z","e","r","o",")","(","f","i","x"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","g","e","t"," ","j"," ","(","s","e","t"," ","i"," ","v","a","l"," ","m","e","m",")"," ",")","(","i","f"," ","(","e","q","p"," ","j"," ","i",")","v","a","l","(","g","e","t"," ","j"," ","m","e","m",")"," ",")"," ",")"]] let lemmas = addlemmalst(makelemmas(rules), Empty) -//│ lemmas = Node([Node([Node([Node([Node([Empty, [["a","n","d"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","n","d"]), Conss([Atom(["p"]), Conss([Atom(["q"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["q"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Nill])])])]], Node([Empty, [["a","p","p","e","n","d"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])])]], Node([Empty, [["a","s","s","i","g","n","m","e","n","t"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","s","s","i","g","n","m","e","n","t"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["a","s","s","i","g","n","e","d","p"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["a","s","s","i","g","n","m","e","n","t"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["a","s","s","i","g","n","m","e","n","t"]), Conss([Atom(["x"]), Conss([Atom(["b"]), Nill])])]), Nill])])])]), Nill])])])]], Empty])])]), [["a","s","s","u","m","e","-","f","a","l","s","e"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","s","s","u","m","e","-","f","a","l","s","e"]), Conss([Atom(["v","a","r"]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["v","a","r"]), Conss([Conss([Atom(["f"]), Nill]), Nill])])]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Nill])])])]], Empty]), [["a","s","s","u","m","e","-","t","r","u","e"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","s","s","u","m","e","-","t","r","u","e"]), Conss([Atom(["v","a","r"]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["v","a","r"]), Conss([Conss([Atom(["t"]), Nill]), Nill])])]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Nill])])])]], Empty]), [["b","o","o","l","e","a","n"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["b","o","o","l","e","a","n"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t"]), Nill]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["f"]), Nill]), Nill])])]), Nill])])]), Nill])])])]], Node([Empty, [["c","a","r"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","a","r"]), Conss([Conss([Atom(["g","o","p","h","e","r"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["c","a","r"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])])]], Empty])]), [["c","o","m","p","i","l","e"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","o","m","p","i","l","e"]), Conss([Atom(["f","o","r","m"]), Nill])]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Conss([Atom(["c","o","d","e","g","e","n"]), Conss([Conss([Atom(["o","p","t","i","m","i","z","e"]), Conss([Atom(["f","o","r","m"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])]), Nill])])])]], Node([Node([Node([Empty, [["c","o","u","n","t","-","l","i","s","t"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","o","u","n","t","-","l","i","s","t"]), Conss([Atom(["z"]), Conss([Conss([Atom(["s","o","r","t","-","l","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["c","o","u","n","t","-","l","i","s","t"]), Conss([Atom(["z"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["c","o","u","n","t","-","l","i","s","t"]), Conss([Atom(["z"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])])]], Empty]), [["c","o","u","n","t","p","s","-"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","o","u","n","t","p","s","-"]), Conss([Atom(["l"]), Conss([Atom(["p","r","e","d"]), Nill])])]), Conss([Conss([Atom(["c","o","u","n","t","p","s","-","l","o","o","p"]), Conss([Atom(["l"]), Conss([Atom(["p","r","e","d"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])])]], Node([Node([Empty, [["d","i","f","f","e","r","e","n","c","e"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Atom(["2"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["b"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["y"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["y"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]], Empty]), [["d","i","v","i","d","e","s"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","v","i","d","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])])]], Node([Empty, [["d","s","o","r","t"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","s","o","r","t"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["s","o","r","t","2"]), Conss([Atom(["x"]), Nill])]), Nill])])])]], Empty])])]), [["e","q","p"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])]], Node([Node([Node([Node([Empty, [["e","q","u","a","l"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["t"]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["f"]), Conss([Atom(["z"]), Nill])])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["z"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["z"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["z"]), Nill])]), Nill])])]), Nill])])])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Atom(["1"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["a"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["b"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])])])])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["z"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["w"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["z"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["z"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["w"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["1"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Atom(["1"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["y"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["c"]), Nill])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["b"]), Nill])]), Nill])])]), Nill])])])]], Empty]), [["e","v","e","n","1"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","v","e","n","1"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["o","d","d"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])])]), Nill])])])]], Node([Node([Empty, [["e","x","e","c"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","x","e","c"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["p","d","s"]), Conss([Atom(["e","n","v","r","n"]), Nill])])])]), Conss([Conss([Atom(["e","x","e","c"]), Conss([Atom(["y"]), Conss([Conss([Atom(["e","x","e","c"]), Conss([Atom(["x"]), Conss([Atom(["p","d","s"]), Conss([Atom(["e","n","v","r","n"]), Nill])])])]), Conss([Atom(["e","n","v","r","n"]), Nill])])])]), Nill])])])]], Node([Empty, [["e","x","p"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["j"]), Conss([Atom(["k"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","x","p"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Conss([Atom(["k"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["j"]), Conss([Atom(["k"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Atom(["k"]), Nill])])]), Nill])])]), Nill])])])]], Empty])]), [["f","a","c","t","-"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","a","c","t","-"]), Conss([Atom(["i"]), Nill])]), Conss([Conss([Atom(["f","a","c","t","-","l","o","o","p"]), Conss([Atom(["i"]), Conss([Atom(["1"]), Nill])])]), Nill])])])]], Node([Empty, [["f","a","l","s","i","f","y"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","a","l","s","i","f","y"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","a","l","s","i","f","y","1"]), Conss([Conss([Atom(["n","o","r","m","a","l","i","z","e"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])]], Node([Empty, [["f","i","x"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])])]], Node([Node([Empty, [["f","l","a","t","t","e","n"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Conss([Atom(["c","d","r"]), Conss([Conss([Atom(["g","o","p","h","e","r"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["c","d","r"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])]), Nill])])])]], Empty]), [["g","c","d"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","c","d"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["z"]), Conss([Conss([Atom(["g","c","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","c","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["g","c","d"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])])])]], Node([Empty, [["g","e","t"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","e","t"]), Conss([Atom(["j"]), Conss([Conss([Atom(["s","e","t"]), Conss([Atom(["i"]), Conss([Atom(["v","a","l"]), Conss([Atom(["m","e","m"]), Nill])])])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["e","q","p"]), Conss([Atom(["j"]), Conss([Atom(["i"]), Nill])])]), Conss([Atom(["v","a","l"]), Conss([Conss([Atom(["g","e","t"]), Conss([Atom(["j"]), Conss([Atom(["m","e","m"]), Nill])])]), Nill])])])]), Nill])])])]], Empty])])])])])]), [["g","r","e","a","t","e","r","e","q","p"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","r","e","q","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])])]], Node([Empty, [["g","r","e","a","t","e","r","e","q","p","r"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","r","e","q","p","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Nill])])])]], Empty])]), [["g","r","e","a","t","e","r","p"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","r","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])])])]], Node([Node([Node([Empty, [["i","f"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])])]), Conss([Atom(["d"]), Conss([Atom(["e"]), Nill])])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["a"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["b"]), Conss([Atom(["d"]), Conss([Atom(["e"]), Nill])])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["c"]), Conss([Atom(["d"]), Conss([Atom(["e"]), Nill])])])]), Nill])])])]), Nill])])])]], Empty]), [["i","f","f"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["i","f","f"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])])]), Nill])])])]], Node([Empty, [["i","m","p","l","i","e","s"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["p"]), Conss([Atom(["q"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["q"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Conss([Conss([Atom(["t"]), Nill]), Nill])])])]), Nill])])])]], Node([Node([Empty, [["l","a","s","t"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","a","s","t"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["l","a","s","t"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["c","a","r"]), Conss([Conss([Atom(["l","a","s","t"]), Conss([Atom(["a"]), Nill])]), Nill])]), Conss([Atom(["b"]), Nill])])]), Conss([Atom(["b"]), Nill])])])]), Nill])])])]), Nill])])])]], Empty]), [["l","e","n","g","t","h"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","1"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","2"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","3"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","4"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","5"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","6"]), Conss([Atom(["x","7"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]), Nill])])]), Nill])])]), Nill])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["6"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["x","7"]), Nill])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["x"]), Nill])]), Nill])])])]], Empty])])]), [["l","e","s","s","e","q","p"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","e","q","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])])]], Node([Node([Node([Node([Node([Node([Node([Empty, [["l","e","s","s","p"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Atom(["l"]), Nill])])]), Nill])]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["l"]), Nill])]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["l"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["z"]), Nill])]), Nill])]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["i"]), Nill])]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["j"]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["j"]), Conss([Atom(["1"]), Nill])])]), Nill])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])]), Nill])])])]], Node([Empty, [["l","i","s","t","p"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Conss([Atom(["g","o","p","h","e","r"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Nill])])])]], Empty])]), [["m","c","-","f","l","a","t","t","e","n"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","c","-","f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["y"]), Nill])])]), Nill])])])]], Empty]), [["m","e","a","n","i","n","g"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["y"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["y"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Conss([Atom(["p","l","u","s","-","f","r","i","n","g","e"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["y"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Nill])])]), Nill])])])]], Node([Empty, [["m","e","m","b","e","r"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["a"]), Conss([Conss([Atom(["i","n","t","e","r","s","e","c","t"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["b"]), Nill])])]), Nill])])]), Nill])])])]], Empty])]), [["n","o","t"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","o","t"]), Conss([Atom(["p"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["f"]), Nill]), Conss([Conss([Atom(["t"]), Nill]), Nill])])])]), Nill])])])]], Node([Empty, [["n","t","h"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","t","h"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["i"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","t","h"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["n","t","h"]), Conss([Atom(["a"]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["n","t","h"]), Conss([Atom(["b"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["i"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["a"]), Nill])]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","t","h"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]], Node([Empty, [["n","u","m","b","e","r","p"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Conss([Atom(["g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])]), Nill])]), Nill])])])]], Empty])])]), [["o","r"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["o","r"]), Conss([Atom(["p"]), Conss([Atom(["q"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["q"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Nill])])])]), Nill])])])]], Node([Empty, [["p","l","u","s"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["x"]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])])]], Node([Empty, [["p","o","w","e","r","-","e","v","a","l"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["b","i","g","-","p","l","u","s"]), Conss([Conss([Atom(["p","o","w","e","r","-","r","e","p"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","o","w","e","r","-","r","e","p"]), Conss([Atom(["j"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Atom(["b","a","s","e"]), Nill])])])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","r","e","p"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["i"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["b","i","g","-","p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["i"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Atom(["x"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Atom(["y"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["b","i","g","-","p","l","u","s","1"]), Conss([Atom(["l"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Atom(["l"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Atom(["i"]), Nill])])]), Nill])])])]], Empty])])]), [["p","r","i","m","e"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","r","i","m","e"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])]), Nill])])]), Nill])]), Conss([Conss([Atom(["p","r","i","m","e","1"]), Conss([Atom(["x"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["x"]), Nill])]), Nill])])]), Nill])])])]), Nill])])])]], Node([Node([Node([Empty, [["p","r","i","m","e","-","l","i","s","t"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","r","i","m","e","-","l","i","s","t"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["p","r","i","m","e","-","l","i","s","t"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["p","r","i","m","e","-","l","i","s","t"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])]], Node([Empty, [["q","u","o","t","i","e","n","t"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Atom(["2"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Atom(["y"]), Conss([Atom(["2"]), Nill])])]), Nill])])]), Nill])])])]], Empty])]), [["r","e","m","a","i","n","d","e","r"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]], Empty]), [["r","e","v","e","r","s","e"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["a"]), Nill])]), Nill])])]), Nill])])])]], Empty])]), [["r","e","v","e","r","s","e","-"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-","l","o","o","p"]), Conss([Atom(["x"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])]], Node([Node([Empty, [["r","e","v","e","r","s","e","-","l","o","o","p"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-","l","o","o","p"]), Conss([Atom(["x"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["x"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-","l","o","o","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["y"]), Nill])])]), Nill])])])]], Node([Empty, [["s","a","m","e","f","r","i","n","g","e"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["s","a","m","e","f","r","i","n","g","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])]], Node([Node([Empty, [["s","i","g","m","a"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["s","i","g","m","a"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["i"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["i"]), Nill])]), Nill])])]), Conss([Atom(["2"]), Nill])])]), Nill])])])]], Empty]), [["s","o","r","t","2"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["s","o","r","t","2"]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Atom(["l"]), Nill])])]), Nill])]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Conss([Atom(["s","o","r","t","2"]), Conss([Atom(["l"]), Nill])]), Nill])])]), Nill])])])]], Empty])])]), [["t","a","u","t","o","l","o","g","y","-","c","h","e","c","k","e","r"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","a","u","t","o","l","o","g","y","-","c","h","e","c","k","e","r"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["t","a","u","t","o","l","o","g","y","p"]), Conss([Conss([Atom(["n","o","r","m","a","l","i","z","e"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])]], Node([Node([Empty, [["t","i","m","e","s"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["c"]), Conss([Atom(["w"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["c"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["w"]), Conss([Atom(["x"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])])]], Node([Node([Empty, [["t","i","m","e","s","-","l","i","s","t"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s","-","l","i","s","t"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Conss([Atom(["t","i","m","e","s","-","l","i","s","t"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["t","i","m","e","s","-","l","i","s","t"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])]], Empty]), [["v","a","l","u","e"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["v","a","l","u","e"]), Conss([Conss([Atom(["n","o","r","m","a","l","i","z","e"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["v","a","l","u","e"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Nill])])])]], Empty])]), [["z","e","r","o","p"], [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])]), Nill])])])]], Empty])])])])])])]) +//│ lemmas = Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["a","n","d"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","n","d"]), Conss([Atom(["p"]), Conss([Atom(["q"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["q"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["a","p","p","e","n","d"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["a","s","s","i","g","n","m","e","n","t"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","s","s","i","g","n","m","e","n","t"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["a","s","s","i","g","n","e","d","p"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["a","s","s","i","g","n","m","e","n","t"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["a","s","s","i","g","n","m","e","n","t"]), Conss([Atom(["x"]), Conss([Atom(["b"]), Nill])])]), Nill])])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["a","s","s","u","m","e","-","f","a","l","s","e"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","s","s","u","m","e","-","f","a","l","s","e"]), Conss([Atom(["v","a","r"]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["v","a","r"]), Conss([Conss([Atom(["f"]), Nill]), Nill])])]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["a","s","s","u","m","e","-","t","r","u","e"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","s","s","u","m","e","-","t","r","u","e"]), Conss([Atom(["v","a","r"]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["v","a","r"]), Conss([Conss([Atom(["t"]), Nill]), Nill])])]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["b","o","o","l","e","a","n"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["b","o","o","l","e","a","n"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t"]), Nill]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["f"]), Nill]), Nill])])]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["c","a","r"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","a","r"]), Conss([Conss([Atom(["g","o","p","h","e","r"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["c","a","r"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["c","o","m","p","i","l","e"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","o","m","p","i","l","e"]), Conss([Atom(["f","o","r","m"]), Nill])]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Conss([Atom(["c","o","d","e","g","e","n"]), Conss([Conss([Atom(["o","p","t","i","m","i","z","e"]), Conss([Atom(["f","o","r","m"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["c","o","u","n","t","-","l","i","s","t"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","o","u","n","t","-","l","i","s","t"]), Conss([Atom(["z"]), Conss([Conss([Atom(["s","o","r","t","-","l","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["c","o","u","n","t","-","l","i","s","t"]), Conss([Atom(["z"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["c","o","u","n","t","-","l","i","s","t"]), Conss([Atom(["z"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["c","o","u","n","t","p","s","-"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","o","u","n","t","p","s","-"]), Conss([Atom(["l"]), Conss([Atom(["p","r","e","d"]), Nill])])]), Conss([Conss([Atom(["c","o","u","n","t","p","s","-","l","o","o","p"]), Conss([Atom(["l"]), Conss([Atom(["p","r","e","d"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["d","i","f","f","e","r","e","n","c","e"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Atom(["2"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["b"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["y"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["y"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["d","i","v","i","d","e","s"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","v","i","d","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["d","s","o","r","t"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","s","o","r","t"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["s","o","r","t","2"]), Conss([Atom(["x"]), Nill])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["e","q","p"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["e","q","u","a","l"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["t"]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["f"]), Conss([Atom(["z"]), Nill])])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["z"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["z"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["z"]), Nill])]), Nill])])]), Nill])])])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Atom(["1"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["a"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["b"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])])])])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["z"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["w"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["z"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["z"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["w"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["1"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Atom(["1"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["y"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["c"]), Nill])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["b"]), Nill])]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["e","v","e","n","1"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","v","e","n","1"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["o","d","d"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["e","x","e","c"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","x","e","c"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["p","d","s"]), Conss([Atom(["e","n","v","r","n"]), Nill])])])]), Conss([Conss([Atom(["e","x","e","c"]), Conss([Atom(["y"]), Conss([Conss([Atom(["e","x","e","c"]), Conss([Atom(["x"]), Conss([Atom(["p","d","s"]), Conss([Atom(["e","n","v","r","n"]), Nill])])])]), Conss([Atom(["e","n","v","r","n"]), Nill])])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["e","x","p"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["j"]), Conss([Atom(["k"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","x","p"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Conss([Atom(["k"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["j"]), Conss([Atom(["k"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Atom(["k"]), Nill])])]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["f","a","c","t","-"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","a","c","t","-"]), Conss([Atom(["i"]), Nill])]), Conss([Conss([Atom(["f","a","c","t","-","l","o","o","p"]), Conss([Atom(["i"]), Conss([Atom(["1"]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["f","a","l","s","i","f","y"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","a","l","s","i","f","y"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","a","l","s","i","f","y","1"]), Conss([Conss([Atom(["n","o","r","m","a","l","i","z","e"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["f","i","x"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["f","l","a","t","t","e","n"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Conss([Atom(["c","d","r"]), Conss([Conss([Atom(["g","o","p","h","e","r"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["c","d","r"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["g","c","d"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","c","d"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["z"]), Conss([Conss([Atom(["g","c","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","c","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["g","c","d"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["g","e","t"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","e","t"]), Conss([Atom(["j"]), Conss([Conss([Atom(["s","e","t"]), Conss([Atom(["i"]), Conss([Atom(["v","a","l"]), Conss([Atom(["m","e","m"]), Nill])])])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["e","q","p"]), Conss([Atom(["j"]), Conss([Atom(["i"]), Nill])])]), Conss([Atom(["v","a","l"]), Conss([Conss([Atom(["g","e","t"]), Conss([Atom(["j"]), Conss([Atom(["m","e","m"]), Nill])])]), Nill])])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["g","r","e","a","t","e","r","e","q","p"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","r","e","q","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["g","r","e","a","t","e","r","e","q","p","r"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","r","e","q","p","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["g","r","e","a","t","e","r","p"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","r","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["i","f"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])])]), Conss([Atom(["d"]), Conss([Atom(["e"]), Nill])])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["a"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["b"]), Conss([Atom(["d"]), Conss([Atom(["e"]), Nill])])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["c"]), Conss([Atom(["d"]), Conss([Atom(["e"]), Nill])])])]), Nill])])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["i","f","f"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["i","f","f"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["i","m","p","l","i","e","s"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["p"]), Conss([Atom(["q"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["q"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Conss([Conss([Atom(["t"]), Nill]), Nill])])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["l","a","s","t"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","a","s","t"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["l","a","s","t"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["c","a","r"]), Conss([Conss([Atom(["l","a","s","t"]), Conss([Atom(["a"]), Nill])]), Nill])]), Conss([Atom(["b"]), Nill])])]), Conss([Atom(["b"]), Nill])])])]), Nill])])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["l","e","n","g","t","h"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","1"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","2"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","3"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","4"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","5"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","6"]), Conss([Atom(["x","7"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]), Nill])])]), Nill])])]), Nill])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["6"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["x","7"]), Nill])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["x"]), Nill])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["l","e","s","s","e","q","p"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","e","q","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["l","e","s","s","p"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Atom(["l"]), Nill])])]), Nill])]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["l"]), Nill])]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["l"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["z"]), Nill])]), Nill])]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["i"]), Nill])]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["j"]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["j"]), Conss([Atom(["1"]), Nill])])]), Nill])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["l","i","s","t","p"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Conss([Atom(["g","o","p","h","e","r"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["m","c","-","f","l","a","t","t","e","n"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","c","-","f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["y"]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["m","e","a","n","i","n","g"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["y"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["y"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Conss([Atom(["p","l","u","s","-","f","r","i","n","g","e"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["y"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["m","e","m","b","e","r"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["a"]), Conss([Conss([Atom(["i","n","t","e","r","s","e","c","t"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["b"]), Nill])])]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["n","o","t"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","o","t"]), Conss([Atom(["p"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["f"]), Nill]), Conss([Conss([Atom(["t"]), Nill]), Nill])])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["n","t","h"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","t","h"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["i"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","t","h"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["n","t","h"]), Conss([Atom(["a"]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["n","t","h"]), Conss([Atom(["b"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["i"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["a"]), Nill])]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","t","h"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["n","u","m","b","e","r","p"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Conss([Atom(["g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])]), Nill])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["o","r"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["o","r"]), Conss([Atom(["p"]), Conss([Atom(["q"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["q"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Nill])])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["p","l","u","s"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["x"]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["p","o","w","e","r","-","e","v","a","l"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["b","i","g","-","p","l","u","s"]), Conss([Conss([Atom(["p","o","w","e","r","-","r","e","p"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","o","w","e","r","-","r","e","p"]), Conss([Atom(["j"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Atom(["b","a","s","e"]), Nill])])])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","r","e","p"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["i"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["b","i","g","-","p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["i"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Atom(["x"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Atom(["y"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["b","i","g","-","p","l","u","s","1"]), Conss([Atom(["l"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Atom(["l"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Atom(["i"]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["p","r","i","m","e"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","r","i","m","e"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])]), Nill])])]), Nill])]), Conss([Conss([Atom(["p","r","i","m","e","1"]), Conss([Atom(["x"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["x"]), Nill])]), Nill])])]), Nill])])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["p","r","i","m","e","-","l","i","s","t"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","r","i","m","e","-","l","i","s","t"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["p","r","i","m","e","-","l","i","s","t"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["p","r","i","m","e","-","l","i","s","t"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["q","u","o","t","i","e","n","t"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Atom(["2"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Atom(["y"]), Conss([Atom(["2"]), Nill])])]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["r","e","m","a","i","n","d","e","r"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["r","e","v","e","r","s","e"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["a"]), Nill])]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["r","e","v","e","r","s","e","-"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-","l","o","o","p"]), Conss([Atom(["x"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["r","e","v","e","r","s","e","-","l","o","o","p"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-","l","o","o","p"]), Conss([Atom(["x"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["x"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-","l","o","o","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["y"]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["s","a","m","e","f","r","i","n","g","e"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["s","a","m","e","f","r","i","n","g","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["s","i","g","m","a"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["s","i","g","m","a"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["i"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["i"]), Nill])]), Nill])])]), Conss([Atom(["2"]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["s","o","r","t","2"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["s","o","r","t","2"]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Atom(["l"]), Nill])])]), Nill])]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Conss([Atom(["s","o","r","t","2"]), Conss([Atom(["l"]), Nill])]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["t","a","u","t","o","l","o","g","y","-","c","h","e","c","k","e","r"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","a","u","t","o","l","o","g","y","-","c","h","e","c","k","e","r"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["t","a","u","t","o","l","o","g","y","p"]), Conss([Conss([Atom(["n","o","r","m","a","l","i","z","e"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["t","i","m","e","s"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["c"]), Conss([Atom(["w"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["c"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["w"]), Conss([Atom(["x"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])])] +//│ ], +//│ Node( +//│ [ +//│ Node( +//│ [ +//│ Empty, +//│ [ +//│ ["t","i","m","e","s","-","l","i","s","t"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s","-","l","i","s","t"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Conss([Atom(["t","i","m","e","s","-","l","i","s","t"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["t","i","m","e","s","-","l","i","s","t"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ), +//│ [ +//│ ["v","a","l","u","e"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["v","a","l","u","e"]), Conss([Conss([Atom(["n","o","r","m","a","l","i","z","e"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["v","a","l","u","e"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ), +//│ [ +//│ ["z","e","r","o","p"], +//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])]), Nill])])])] +//│ ], +//│ Empty +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ) +//│ ] +//│ ) fun tautp(term) = tautologyp([rewrite(term, lemmas), Nill, Nill]) diff --git a/hkmc2/shared/src/test/mlscript/nofib/cichelli.mls b/hkmc2/shared/src/test/mlscript/nofib/cichelli.mls index 51bb37e366..c8618b65f7 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/cichelli.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/cichelli.mls @@ -152,7 +152,7 @@ fun cichelli(n) = fun prog(n) = cichelli(n) -:expect YesIts(113994, [["w", 16],["r", 17],["o", 11],["m", 17],["l", 11],["x", 2],["n", 11],["i", 4],["f", 10],["h", 3],["g", 0],["t", 5],["d", 0],["a", 0],["s", 2],["c", 0],["e", 1]]) -prog(6) -//│ = YesIts(113994, [["w", 16],["r", 17],["o", 11],["m", 17],["l", 11],["x", 2],["n", 11],["i", 4],["f", 10],["h", 3],["g", 0],["t", 5],["d", 0],["a", 0],["s", 2],["c", 0],["e", 1]]) +:expect "YesIts(113994, [[\"w\", 16],[\"r\", 17],[\"o\", 11],[\"m\", 17],[\"l\", 11],[\"x\", 2],[\"n\", 11],[\"i\", 4],[\"f\", 10],[\"h\", 3],[\"g\", 0],[\"t\", 5],[\"d\", 0],[\"a\", 0],[\"s\", 2],[\"c\", 0],[\"e\", 1]])" +render of prog(6) +//│ = "YesIts(113994, [[\"w\", 16],[\"r\", 17],[\"o\", 11],[\"m\", 17],[\"l\", 11],[\"x\", 2],[\"n\", 11],[\"i\", 4],[\"f\", 10],[\"h\", 3],[\"g\", 0],[\"t\", 5],[\"d\", 0],[\"a\", 0],[\"s\", 2],[\"c\", 0],[\"e\", 1]])" diff --git a/hkmc2/shared/src/test/mlscript/nofib/cse.mls b/hkmc2/shared/src/test/mlscript/nofib/cse.mls index c5f10a37a0..624be71e9b 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/cse.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/cse.mls @@ -121,16 +121,28 @@ let example1 = plus_(a, a) //│ example1 = Node("+", [Node("a", []),Node("a", [])]) let example2 = plus_(mult_(a, b), mult_(a, b)) -//│ example2 = Node("+", [Node("*", [Node("a", []),Node("b", [])]),Node("*", [Node("a", []),Node("b", [])])]) +//│ example2 = Node( +//│ "+", +//│ [Node("*", [Node("a", []),Node("b", [])]),Node("*", [Node("a", []),Node("b", [])])] +//│ ) let example3 = plus_(mult_(plus_(a, b), c), plus_(a, b)) -//│ example3 = Node("+", [Node("*", [Node("+", [Node("a", []),Node("b", [])]),Node("c", [])]),Node("+", [Node("a", []),Node("b", [])])]) +//│ example3 = Node( +//│ "+", +//│ [Node("*", [Node("+", [Node("a", []),Node("b", [])]),Node("c", [])]),Node("+", [Node("a", []),Node("b", [])])] +//│ ) let example4 = prod(scanl(plus_, zerO, a :: b :: c :: d :: Nil)) -//│ example4 = Node("X", [Node("0", []),Node("+", [Node("0", []),Node("a", [])]),Node("+", [Node("+", [Node("0", []),Node("a", [])]),Node("b", [])]),Node("+", [Node("+", [Node("+", [Node("0", []),Node("a", [])]),Node("b", [])]),Node("c", [])]),Node("+", [Node("+", [Node("+", [Node("+", [Node("0", []),Node("a", [])]),Node("b", [])]),Node("c", [])]),Node("d", [])])]) +//│ example4 = Node( +//│ "X", +//│ [Node("0", []),Node("+", [Node("0", []),Node("a", [])]),Node("+", [Node("+", [Node("0", []),Node("a", [])]),Node("b", [])]),Node("+", [Node("+", [Node("+", [Node("0", []),Node("a", [])]),Node("b", [])]),Node("c", [])]),Node("+", [Node("+", [Node("+", [Node("+", [Node("0", []),Node("a", [])]),Node("b", [])]),Node("c", [])]),Node("d", [])])] +//│ ) let example5 = prod(scanr(plus_, zerO, a :: b :: c :: d :: Nil)) -//│ example5 = Node("X", [Node("+", [Node("a", []),Node("+", [Node("b", []),Node("+", [Node("c", []),Node("+", [Node("d", []),Node("0", [])])])])]),Node("+", [Node("b", []),Node("+", [Node("c", []),Node("+", [Node("d", []),Node("0", [])])])]),Node("+", [Node("c", []),Node("+", [Node("d", []),Node("0", [])])]),Node("+", [Node("d", []),Node("0", [])]),Node("0", [])]) +//│ example5 = Node( +//│ "X", +//│ [Node("+", [Node("a", []),Node("+", [Node("b", []),Node("+", [Node("c", []),Node("+", [Node("d", []),Node("0", [])])])])]),Node("+", [Node("b", []),Node("+", [Node("c", []),Node("+", [Node("d", []),Node("0", [])])])]),Node("+", [Node("c", []),Node("+", [Node("d", []),Node("0", [])])]),Node("+", [Node("d", []),Node("0", [])]),Node("0", [])] +//│ ) fun testCse_nofib(n) = map( i => map( diff --git a/hkmc2/shared/src/test/mlscript/nofib/eliza.mls b/hkmc2/shared/src/test/mlscript/nofib/eliza.mls index d7c4a18f01..44262f3f15 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/eliza.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/eliza.mls @@ -246,7 +246,10 @@ let initial = Nil then Nil [k, rs] :: t then [words(k), cycle(rs)] :: lscomp(t) [lscomp(respMsgs), cycle(repeatMsgs)] -//│ initial = [[[[["C","A","N"],["Y","O","U"]], Lazy(fun)],[[["C","A","N"],["I"]], Lazy(fun)],[[["Y","O","U"],["A","R","E"]], Lazy(fun)],[[["Y","O","U","'","R","E"]], Lazy(fun)],[[["I"],["D","O","N","'","T"]], Lazy(fun)],[[["I"],["F","E","E","L"]], Lazy(fun)],[[["W","H","Y"],["D","O","N","'","T"],["Y","O","U"]], Lazy(fun)],[[["W","H","Y"],["C","A","N","'","T"],["I"]], Lazy(fun)],[[["A","R","E"],["Y","O","U"]], Lazy(fun)],[[["I"],["C","A","N","'","T"]], Lazy(fun)],[[["I"],["A","M"]], Lazy(fun)],[[["I","'","M"]], Lazy(fun)],[[["Y","O","U"]], Lazy(fun)],[[["Y","E","S"]], Lazy(fun)],[[["N","O"]], Lazy(fun)],[[["C","O","M","P","U","T","E","R"]], Lazy(fun)],[[["C","O","M","P","U","T","E","R","S"]], Lazy(fun)],[[["I"],["W","A","N","T"]], Lazy(fun)],[[["W","H","A","T"]], Lazy(fun)],[[["H","O","W"]], Lazy(fun)],[[["W","H","O"]], Lazy(fun)],[[["W","H","E","R","E"]], Lazy(fun)],[[["W","H","E","N"]], Lazy(fun)],[[["N","A","M","E"]], Lazy(fun)],[[["W","H","Y"]], Lazy(fun)],[[["C","A","U","S","E"]], Lazy(fun)],[[["B","E","C","A","U","S","E"]], Lazy(fun)],[[["D","R","E","A","M"]], Lazy(fun)],[[["S","O","R","R","Y"]], Lazy(fun)],[[["H","I"]], Lazy(fun)],[[["D","R","E","A","M","S"]], Lazy(fun)],[[["M","A","Y","B","E"]], Lazy(fun)],[[["H","E","L","L","O"]], Lazy(fun)],[[["A","L","W","A","Y","S"]], Lazy(fun)],[[["Y","O","U","R"]], Lazy(fun)],[[["A","L","I","K","E"]], Lazy(fun)],[[["T","H","I","N","K"]], Lazy(fun)],[[["F","R","I","E","N","D","S"]], Lazy(fun)],[[["F","R","I","E","N","D"]], Lazy(fun)],[[], Lazy(fun)]], Lazy(fun)] +//│ initial = [ +//│ [[[["C","A","N"],["Y","O","U"]], Lazy(fun)],[[["C","A","N"],["I"]], Lazy(fun)],[[["Y","O","U"],["A","R","E"]], Lazy(fun)],[[["Y","O","U","'","R","E"]], Lazy(fun)],[[["I"],["D","O","N","'","T"]], Lazy(fun)],[[["I"],["F","E","E","L"]], Lazy(fun)],[[["W","H","Y"],["D","O","N","'","T"],["Y","O","U"]], Lazy(fun)],[[["W","H","Y"],["C","A","N","'","T"],["I"]], Lazy(fun)],[[["A","R","E"],["Y","O","U"]], Lazy(fun)],[[["I"],["C","A","N","'","T"]], Lazy(fun)],[[["I"],["A","M"]], Lazy(fun)],[[["I","'","M"]], Lazy(fun)],[[["Y","O","U"]], Lazy(fun)],[[["Y","E","S"]], Lazy(fun)],[[["N","O"]], Lazy(fun)],[[["C","O","M","P","U","T","E","R"]], Lazy(fun)],[[["C","O","M","P","U","T","E","R","S"]], Lazy(fun)],[[["I"],["W","A","N","T"]], Lazy(fun)],[[["W","H","A","T"]], Lazy(fun)],[[["H","O","W"]], Lazy(fun)],[[["W","H","O"]], Lazy(fun)],[[["W","H","E","R","E"]], Lazy(fun)],[[["W","H","E","N"]], Lazy(fun)],[[["N","A","M","E"]], Lazy(fun)],[[["W","H","Y"]], Lazy(fun)],[[["C","A","U","S","E"]], Lazy(fun)],[[["B","E","C","A","U","S","E"]], Lazy(fun)],[[["D","R","E","A","M"]], Lazy(fun)],[[["S","O","R","R","Y"]], Lazy(fun)],[[["H","I"]], Lazy(fun)],[[["D","R","E","A","M","S"]], Lazy(fun)],[[["M","A","Y","B","E"]], Lazy(fun)],[[["H","E","L","L","O"]], Lazy(fun)],[[["A","L","W","A","Y","S"]], Lazy(fun)],[[["Y","O","U","R"]], Lazy(fun)],[[["A","L","I","K","E"]], Lazy(fun)],[[["T","H","I","N","K"]], Lazy(fun)],[[["F","R","I","E","N","D","S"]], Lazy(fun)],[[["F","R","I","E","N","D"]], Lazy(fun)],[[], Lazy(fun)]], +//│ Lazy(fun) +//│ ] fun prefix(xxs, yys) = if xxs is Nil then true diff --git a/hkmc2/shared/src/test/mlscript/nofib/lambda.mls b/hkmc2/shared/src/test/mlscript/nofib/lambda.mls index 700d5a1c7f..1cf574f735 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/lambda.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/lambda.mls @@ -166,16 +166,51 @@ let lfxx = Lam(nofibStringToList("x"), App(Var(nofibStringToList("F")), App(Var( //│ lfxx = Lam(["x"], App(Var(["F"]), App(Var(["x"]), Var(["x"])))) let fix = Lam(nofibStringToList("F"), App(lfxx, lfxx)) -//│ fix = Lam(["F"], App(Lam(["x"], App(Var(["F"]), App(Var(["x"]), Var(["x"])))), Lam(["x"], App(Var(["F"]), App(Var(["x"]), Var(["x"])))))) +//│ fix = Lam( +//│ ["F"], +//│ App( +//│ Lam(["x"], App(Var(["F"]), App(Var(["x"]), Var(["x"])))), +//│ Lam(["x"], App(Var(["F"]), App(Var(["x"]), Var(["x"])))) +//│ ) +//│ ) let nMinus1 = Add(Var(nofibStringToList("n")), Con(-1)) //│ nMinus1 = Add(Var(["n"]), Con(-1)) let partialSum0 = Lam(nofibStringToList("sum"), Lam(nofibStringToList("n"), IfZero(Var(nofibStringToList("n")), Con(0), Add(Var(nofibStringToList("n")), App(Var(nofibStringToList("sum")), nMinus1))))) -//│ partialSum0 = Lam(["s","u","m"], Lam(["n"], IfZero(Var(["n"]), Con(0), Add(Var(["n"]), App(Var(["s","u","m"]), Add(Var(["n"]), Con(-1))))))) +//│ partialSum0 = Lam( +//│ ["s","u","m"], +//│ Lam( +//│ ["n"], +//│ IfZero( +//│ Var(["n"]), +//│ Con(0), +//│ Add(Var(["n"]), App(Var(["s","u","m"]), Add(Var(["n"]), Con(-1)))) +//│ ) +//│ ) +//│ ) let sum0 = App(fix, partialSum0) -//│ sum0 = App(Lam(["F"], App(Lam(["x"], App(Var(["F"]), App(Var(["x"]), Var(["x"])))), Lam(["x"], App(Var(["F"]), App(Var(["x"]), Var(["x"])))))), Lam(["s","u","m"], Lam(["n"], IfZero(Var(["n"]), Con(0), Add(Var(["n"]), App(Var(["s","u","m"]), Add(Var(["n"]), Con(-1)))))))) +//│ sum0 = App( +//│ Lam( +//│ ["F"], +//│ App( +//│ Lam(["x"], App(Var(["F"]), App(Var(["x"]), Var(["x"])))), +//│ Lam(["x"], App(Var(["F"]), App(Var(["x"]), Var(["x"])))) +//│ ) +//│ ), +//│ Lam( +//│ ["s","u","m"], +//│ Lam( +//│ ["n"], +//│ IfZero( +//│ Var(["n"]), +//│ Con(0), +//│ Add(Var(["n"]), App(Var(["s","u","m"]), Add(Var(["n"]), Con(-1)))) +//│ ) +//│ ) +//│ ) +//│ ) fun showTerm(t) = if t is Con(a) then nofibStringToList("Con ") +: nofibStringToList(stringOfInt(a)) diff --git a/hkmc2/shared/src/test/mlscript/nofib/last-piece.mls b/hkmc2/shared/src/test/mlscript/nofib/last-piece.mls index 6bbe23537f..0438846c93 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/last-piece.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/last-piece.mls @@ -186,7 +186,11 @@ let mPiece = P( ([0,1] :: [0,2] :: [0,3] :: [1,3] :: Nil) :: ([1,0] :: [2,0] :: [3,0] :: [3,-1] :: Nil) :: Nil ) -//│ mPiece = P("m", [[[0, 1],[1, 0],[2, 0],[3, 0]]], [[[0, 1],[0, 2],[0, 3],[1, 3]],[[1, 0],[2, 0],[3, 0],[3, -1]]]) +//│ mPiece = P( +//│ "m", +//│ [[[0, 1],[1, 0],[2, 0],[3, 0]]], +//│ [[[0, 1],[0, 2],[0, 3],[1, 3]],[[1, 0],[2, 0],[3, 0],[3, -1]]] +//│ ) let lPiece = P( "l", @@ -195,7 +199,11 @@ let lPiece = P( ([1,-1] :: [1,0] :: [1,1] :: [1,2] :: Nil) :: ([1,0] :: [2,0] :: [3,0] :: [1,1] :: Nil) :: Nil ) -//│ lPiece = P("l", [[[0, 1],[0, 2],[0, 3],[1, 2]],[[1, 0],[2, 0],[3, 0],[2, -1]]], [[[1, -1],[1, 0],[1, 1],[1, 2]],[[1, 0],[2, 0],[3, 0],[1, 1]]]) +//│ lPiece = P( +//│ "l", +//│ [[[0, 1],[0, 2],[0, 3],[1, 2]],[[1, 0],[2, 0],[3, 0],[2, -1]]], +//│ [[[1, -1],[1, 0],[1, 1],[1, 2]],[[1, 0],[2, 0],[3, 0],[1, 1]]] +//│ ) let kPiece = P( "k", @@ -211,7 +219,11 @@ let jPiece = P( ([1,-2] :: [1,-1] :: [1,0] :: [1,1] :: Nil) :: Nil, ([1,0] :: [2,0] :: [3,0] :: [2,2] :: Nil) :: Nil ) -//│ jPiece = P("j", [[[0, 1],[0, 2],[0, 3],[1, 1]],[[1, 0],[2, 0],[3, 0],[1, -1]],[[1, -2],[1, -1],[1, 0],[1, 1]]], [[[1, 0],[2, 0],[3, 0],[2, 2]]]) +//│ jPiece = P( +//│ "j", +//│ [[[0, 1],[0, 2],[0, 3],[1, 1]],[[1, 0],[2, 0],[3, 0],[1, -1]],[[1, -2],[1, -1],[1, 0],[1, 1]]], +//│ [[[1, 0],[2, 0],[3, 0],[2, 2]]] +//│ ) let iPiece = P( "i", @@ -220,7 +232,11 @@ let iPiece = P( ([1,0] :: [1,1] :: [2,1] :: [3,1] :: Nil) :: Nil, ([0,1] :: [1,0] :: [1,-1] :: [1,-2] :: Nil) :: Nil ) -//│ iPiece = P("i", [[[1, 0],[2, 0],[2, 1],[3, 1]],[[0, 1],[0, 2],[1, 0],[1, -1]],[[1, 0],[1, 1],[2, 1],[3, 1]]], [[[0, 1],[1, 0],[1, -1],[1, -2]]]) +//│ iPiece = P( +//│ "i", +//│ [[[1, 0],[2, 0],[2, 1],[3, 1]],[[0, 1],[0, 2],[1, 0],[1, -1]],[[1, 0],[1, 1],[2, 1],[3, 1]]], +//│ [[[0, 1],[1, 0],[1, -1],[1, -2]]] +//│ ) let hPiece = P( "h", @@ -229,7 +245,11 @@ let hPiece = P( ([1,0] :: [1,1] :: [2,1] :: [2,2] :: Nil) :: Nil, ([0,1] :: [1,0] :: [1,-1] :: [2,-1] :: Nil) :: Nil ) -//│ hPiece = P("h", [[[0, 1],[1, 1],[1, 2],[2, 2]],[[1, 0],[1, -1],[2, -1],[2, -2]],[[1, 0],[1, 1],[2, 1],[2, 2]]], [[[0, 1],[1, 0],[1, -1],[2, -1]]]) +//│ hPiece = P( +//│ "h", +//│ [[[0, 1],[1, 1],[1, 2],[2, 2]],[[1, 0],[1, -1],[2, -1],[2, -2]],[[1, 0],[1, 1],[2, 1],[2, 2]]], +//│ [[[0, 1],[1, 0],[1, -1],[2, -1]]] +//│ ) let gPiece = P( "g", @@ -239,7 +259,11 @@ let gPiece = P( ([0,1] :: [0,2] :: [1,2] :: [1,3] :: Nil) :: ([1,0] :: [2,0] :: [2,-1] :: [3,-1] :: Nil) :: Nil ) -//│ gPiece = P("g", [], [[[0, 1],[1, 1],[1, 2],[1, 3]],[[1, 0],[1, -1],[2, -1],[3, -1]],[[0, 1],[0, 2],[1, 2],[1, 3]],[[1, 0],[2, 0],[2, -1],[3, -1]]]) +//│ gPiece = P( +//│ "g", +//│ [], +//│ [[[0, 1],[1, 1],[1, 2],[1, 3]],[[1, 0],[1, -1],[2, -1],[3, -1]],[[0, 1],[0, 2],[1, 2],[1, 3]],[[1, 0],[2, 0],[2, -1],[3, -1]]] +//│ ) let fPiece = P( "f", @@ -248,7 +272,11 @@ let fPiece = P( ([1,0] :: [2,0] :: [3,0] :: [3,1] :: Nil) :: Nil, ([0,1] :: [0,2] :: [0,3] :: [1,0] :: Nil) :: Nil ) -//│ fPiece = P("f", [[[0, 1],[1, 1],[2, 1],[3, 1]],[[1, 0],[1, -1],[1, -2],[1, -3]],[[1, 0],[2, 0],[3, 0],[3, 1]]], [[[0, 1],[0, 2],[0, 3],[1, 0]]]) +//│ fPiece = P( +//│ "f", +//│ [[[0, 1],[1, 1],[2, 1],[3, 1]],[[1, 0],[1, -1],[1, -2],[1, -3]],[[1, 0],[2, 0],[3, 0],[3, 1]]], +//│ [[[0, 1],[0, 2],[0, 3],[1, 0]]] +//│ ) let ePiece = P( "e", @@ -257,7 +285,11 @@ let ePiece = P( ([0,1] :: [1,1] :: [1,2] :: Nil) :: ([1,0] :: [1,-1] :: [2,-1] :: Nil) :: Nil ) -//│ ePiece = P("e", [[[0, 1],[1, 1],[1, 2]],[[1, 0],[1, -1],[2, -1]]], [[[0, 1],[1, 1],[1, 2]],[[1, 0],[1, -1],[2, -1]]]) +//│ ePiece = P( +//│ "e", +//│ [[[0, 1],[1, 1],[1, 2]],[[1, 0],[1, -1],[2, -1]]], +//│ [[[0, 1],[1, 1],[1, 2]],[[1, 0],[1, -1],[2, -1]]] +//│ ) let dPiece = P( "d", @@ -265,7 +297,11 @@ let dPiece = P( ([1,0] :: [1,-1] :: [1,-2] :: Nil) :: Nil, ([1,0] :: [2,0] :: [2,1] :: Nil) :: Nil ) -//│ dPiece = P("d", [[[0, 1],[1, 1],[2, 1]],[[1, 0],[1, -1],[1, -2]]], [[[1, 0],[2, 0],[2, 1]]]) +//│ dPiece = P( +//│ "d", +//│ [[[0, 1],[1, 1],[2, 1]],[[1, 0],[1, -1],[1, -2]]], +//│ [[[1, 0],[2, 0],[2, 1]]] +//│ ) let cPiece = P( "c", @@ -275,7 +311,11 @@ let cPiece = P( ([1,-1] :: [1,0] :: [1,1] :: Nil) :: ([1,0] :: [1,1] :: [2,0] :: Nil) :: Nil ) -//│ cPiece = P("c", [], [[[0, 1],[0, 2],[1, 1]],[[1, 0],[1, -1],[2, 0]],[[1, -1],[1, 0],[1, 1]],[[1, 0],[1, 1],[2, 0]]]) +//│ cPiece = P( +//│ "c", +//│ [], +//│ [[[0, 1],[0, 2],[1, 1]],[[1, 0],[1, -1],[2, 0]],[[1, -1],[1, 0],[1, 1]],[[1, 0],[1, 1],[2, 0]]] +//│ ) let bPiece = P( "b", @@ -284,7 +324,11 @@ let bPiece = P( ([0,1] :: [1,0] :: [2,0] :: Nil) :: Nil, ([1,0] :: [1,1] :: [1,2] :: Nil) :: Nil ) -//│ bPiece = P("b", [[[0, 1],[0, 2],[1, 2]],[[1, 0],[2, 0],[2, -1]],[[0, 1],[1, 0],[2, 0]]], [[[1, 0],[1, 1],[1, 2]]]) +//│ bPiece = P( +//│ "b", +//│ [[[0, 1],[0, 2],[1, 2]],[[1, 0],[2, 0],[2, -1]],[[0, 1],[1, 0],[2, 0]]], +//│ [[[1, 0],[1, 1],[1, 2]]] +//│ ) let initialPieces = bPiece :: cPiece :: dPiece :: ePiece :: fPiece :: gPiece :: hPiece :: iPiece :: jPiece :: kPiece :: lPiece :: diff --git a/hkmc2/shared/src/test/mlscript/nofib/mandel.mls b/hkmc2/shared/src/test/mlscript/nofib/mandel.mls index 726a818866..77376272ea 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/mandel.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/mandel.mls @@ -71,4 +71,9 @@ fun testMandel_nofib(dummy) = testMandel_nofib(0) -//│ = Pixmap(25, 25, 75, [[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[5, 70, 70],[5, 70, 70],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[4, 71, 71],[14, 61, 61],[55, 20, 20],[4, 71, 71],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[6, 69, 69],[6, 69, 69],[59, 16, 16],[75, 0, 0],[7, 68, 68],[5, 70, 70],[4, 71, 71],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[4, 71, 71],[4, 71, 71],[5, 70, 70],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[39, 36, 36],[23, 52, 52],[3, 72, 72],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[2, 73, 73],[2, 73, 73],[3, 72, 72],[4, 71, 71],[6, 69, 69],[6, 69, 69],[6, 69, 69],[9, 66, 66],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[8, 67, 67],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[3, 72, 72],[3, 72, 72],[4, 71, 71],[6, 69, 69],[19, 56, 56],[75, 0, 0],[24, 51, 51],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[24, 51, 51],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[3, 72, 72],[5, 70, 70],[6, 69, 69],[18, 57, 57],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[6, 69, 69],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[3, 72, 72],[5, 70, 70],[6, 69, 69],[18, 57, 57],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[6, 69, 69],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[3, 72, 72],[3, 72, 72],[4, 71, 71],[6, 69, 69],[19, 56, 56],[75, 0, 0],[24, 51, 51],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[24, 51, 51],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[2, 73, 73],[2, 73, 73],[3, 72, 72],[4, 71, 71],[6, 69, 69],[6, 69, 69],[6, 69, 69],[9, 66, 66],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[8, 67, 67],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[4, 71, 71],[4, 71, 71],[5, 70, 70],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[39, 36, 36],[23, 52, 52],[3, 72, 72],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[6, 69, 69],[6, 69, 69],[59, 16, 16],[75, 0, 0],[7, 68, 68],[5, 70, 70],[4, 71, 71],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[4, 71, 71],[14, 61, 61],[55, 20, 20],[4, 71, 71],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[5, 70, 70],[5, 70, 70],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75]]) +//│ = Pixmap( +//│ 25, +//│ 25, +//│ 75, +//│ [[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[5, 70, 70],[5, 70, 70],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[4, 71, 71],[14, 61, 61],[55, 20, 20],[4, 71, 71],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[6, 69, 69],[6, 69, 69],[59, 16, 16],[75, 0, 0],[7, 68, 68],[5, 70, 70],[4, 71, 71],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[4, 71, 71],[4, 71, 71],[5, 70, 70],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[39, 36, 36],[23, 52, 52],[3, 72, 72],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[2, 73, 73],[2, 73, 73],[3, 72, 72],[4, 71, 71],[6, 69, 69],[6, 69, 69],[6, 69, 69],[9, 66, 66],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[8, 67, 67],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[3, 72, 72],[3, 72, 72],[4, 71, 71],[6, 69, 69],[19, 56, 56],[75, 0, 0],[24, 51, 51],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[24, 51, 51],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[3, 72, 72],[5, 70, 70],[6, 69, 69],[18, 57, 57],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[6, 69, 69],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[3, 72, 72],[5, 70, 70],[6, 69, 69],[18, 57, 57],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[6, 69, 69],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[3, 72, 72],[3, 72, 72],[4, 71, 71],[6, 69, 69],[19, 56, 56],[75, 0, 0],[24, 51, 51],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[24, 51, 51],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[2, 73, 73],[2, 73, 73],[3, 72, 72],[4, 71, 71],[6, 69, 69],[6, 69, 69],[6, 69, 69],[9, 66, 66],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[8, 67, 67],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[4, 71, 71],[4, 71, 71],[5, 70, 70],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[75, 0, 0],[39, 36, 36],[23, 52, 52],[3, 72, 72],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[6, 69, 69],[6, 69, 69],[59, 16, 16],[75, 0, 0],[7, 68, 68],[5, 70, 70],[4, 71, 71],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[4, 71, 71],[14, 61, 61],[55, 20, 20],[4, 71, 71],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[3, 72, 72],[3, 72, 72],[5, 70, 70],[5, 70, 70],[3, 72, 72],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[2, 73, 73],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[1, 74, 74],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75],[0, 75, 75]] +//│ ) diff --git a/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls b/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls index 9ec42c3e07..f903b00c84 100644 --- a/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls +++ b/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls @@ -8,7 +8,11 @@ module Playground with pattern ZeroOne = Zero ~ One Playground -//│ = module Playground { Zero: pattern Zero, DoubleZero: pattern DoubleZero, ZeroOne: pattern ZeroOne } +//│ = module Playground { +//│ Zero: pattern Zero, +//│ DoubleZero: pattern DoubleZero, +//│ ZeroOne: pattern ZeroOne +//│ } // Pattern defined in a module can be used with qualified name. // ============================================================ diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls b/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls index 713234a79c..1590e55188 100644 --- a/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls +++ b/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls @@ -66,16 +66,28 @@ let cnf2 = And(Or(Var("a"), Var("b")), Or(Pred("P", Var("c")), Var("d"))) //│ cnf2 = And(Or(Var("a"), Var("b")), Or(Pred("P", Var("c")), Var("d"))) let cnf3 = And(And(Or(Var("x"), Pred("Q", Var("y"))), Or(Var("z"), Var("w"))), Or(Pred("R", Var("u")), Var("v"))) -//│ cnf3 = And(And(Or(Var("x"), Pred("Q", Var("y"))), Or(Var("z"), Var("w"))), Or(Pred("R", Var("u")), Var("v"))) +//│ cnf3 = And( +//│ And(Or(Var("x"), Pred("Q", Var("y"))), Or(Var("z"), Var("w"))), +//│ Or(Pred("R", Var("u")), Var("v")) +//│ ) let cnf4 = And(Or(And(Var("m"), Var("n")), Var("o")), Or(Pred("S", Var("p")), And(Var("q"), Var("r")))) -//│ cnf4 = And(Or(And(Var("m"), Var("n")), Var("o")), Or(Pred("S", Var("p")), And(Var("q"), Var("r")))) +//│ cnf4 = And( +//│ Or(And(Var("m"), Var("n")), Var("o")), +//│ Or(Pred("S", Var("p")), And(Var("q"), Var("r"))) +//│ ) let cnf5 = And(Or(Var("a"), Or(Var("b"), Var("c"))), And(Or(Pred("T", Var("d")), Var("e")), Or(Var("f"), Var("g")))) -//│ cnf5 = And(Or(Var("a"), Or(Var("b"), Var("c"))), And(Or(Pred("T", Var("d")), Var("e")), Or(Var("f"), Var("g")))) +//│ cnf5 = And( +//│ Or(Var("a"), Or(Var("b"), Var("c"))), +//│ And(Or(Pred("T", Var("d")), Var("e")), Or(Var("f"), Var("g"))) +//│ ) let cnf6 = And(Or(Var("x"), And(Var("y"), Or(Var("z"), Var("w")))), Or(Pred("U", Var("t")), Var("s"))) -//│ cnf6 = And(Or(Var("x"), And(Var("y"), Or(Var("z"), Var("w")))), Or(Pred("U", Var("t")), Var("s"))) +//│ cnf6 = And( +//│ Or(Var("x"), And(Var("y"), Or(Var("z"), Var("w")))), +//│ Or(Pred("U", Var("t")), Var("s")) +//│ ) print of cnf1 is Dnf print of cnf2 is Dnf diff --git a/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls b/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls index 00f5c00475..f8aaef3b86 100644 --- a/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls @@ -243,13 +243,13 @@ tree // If the same circular object appears multiple times, it should be rendered as // the same reference. let b = mut [a, a] -//│ b = [{x: ref'1, y: 1} as ref'1, {x: ref'1, y: 1} as ref'1] +//│ b = [{x: ref'1, y: 1} as ref'1, {x: ref'2, y: 1} as ref'2] b.push(b) //│ = 3 b -//│ = [{x: ref'1, y: 1} as ref'1, {x: ref'1, y: 1} as ref'1, ref'2] as ref'2 +//│ = [{x: ref'1, y: 1} as ref'1, {x: ref'2, y: 1} as ref'2, ref'3] as ref'3 let functionWithProperties = Object.assign(x => x, "kind": "Callable") //│ functionWithProperties = fun { kind: "Callable" } diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/BinarySearchTree.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/BinarySearchTree.mls index b45911e3e0..f056d748ca 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/BinarySearchTree.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/BinarySearchTree.mls @@ -174,7 +174,19 @@ lowerBound(example1, 5) ?? "not found" //│ = 4 let example2 = fromList(1 :: 5 :: 42 :: 10 :: 23 :: 59 :: 81 :: Nil) -//│ example2 = Node(1, Empty, Node(5, Empty, Node(42, Node(10, Empty, Node(23, Empty, Empty)), Node(59, Empty, Node(81, Empty, Empty))))) +//│ example2 = Node( +//│ 1, +//│ Empty, +//│ Node( +//│ 5, +//│ Empty, +//│ Node( +//│ 42, +//│ Node(10, Empty, Node(23, Empty, Empty)), +//│ Node(59, Empty, Node(81, Empty, Empty)) +//│ ) +//│ ) +//│ ) lowerBound(example2, 0) ?? "not found" //│ = "not found" diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/LeftistTree.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/LeftistTree.mls index 68f6f475eb..1a6f26f4b9 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/LeftistTree.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/LeftistTree.mls @@ -55,7 +55,12 @@ fun fromList(t, xs) = Nil then t let tree1 = fromList(Empty, 3 :: 4 :: 1 :: 2 :: Nil) -//│ tree1 = Node(1, Node(2, Empty, Node(3, Empty, Node(4, Empty, Empty, 1), 1), 1), Empty, 1) +//│ tree1 = Node( +//│ 1, +//│ Node(2, Empty, Node(3, Empty, Node(4, Empty, Empty, 1), 1), 1), +//│ Empty, +//│ 1 +//│ ) tree1 |> show //│ = "((• 2 (• 3 (• 4 •))) 1 •)" diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/ULC.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/ULC.mls index 9011171912..2350f0ba6e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/ULC.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/ULC.mls @@ -329,7 +329,13 @@ String of stepByValue of one //│ = "Normal form: λf. λx. (f x)" let succ = Abs(Var("n"), Abs(Var("f"), Abs(Var("x"), App(Var("f"), App(App(Var("n"), Var("f")), Var("x")))))) -//│ succ = Abs(Var("n"), Abs(Var("f"), Abs(Var("x"), App(Var("f"), App(App(Var("n"), Var("f")), Var("x")))))) +//│ succ = Abs( +//│ Var("n"), +//│ Abs( +//│ Var("f"), +//│ Abs(Var("x"), App(Var("f"), App(App(Var("n"), Var("f")), Var("x")))) +//│ ) +//│ ) String of stepByValue of succ //│ = "Normal form: λn. λf. λx. (f ((n f) x))" diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls index 55caf69ed2..003be9bb5b 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/ConjunctionPattern.mls @@ -39,7 +39,19 @@ fun suffixes(l) = if l is l & (_ :: tl) then l :: suffixes(tl) suffixes(range(0, 5)) -//│ = Cons(Cons(0, Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil)))))), Cons(Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil))))), Cons(Cons(2, Cons(3, Cons(4, Cons(5, Nil)))), Cons(Cons(3, Cons(4, Cons(5, Nil))), Cons(Cons(4, Cons(5, Nil)), Cons(Cons(5, Nil), Cons(Nil, Nil))))))) +//│ = Cons( +//│ Cons(0, Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil)))))), +//│ Cons( +//│ Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil))))), +//│ Cons( +//│ Cons(2, Cons(3, Cons(4, Cons(5, Nil)))), +//│ Cons( +//│ Cons(3, Cons(4, Cons(5, Nil))), +//│ Cons(Cons(4, Cons(5, Nil)), Cons(Cons(5, Nil), Cons(Nil, Nil))) +//│ ) +//│ ) +//│ ) +//│ ) suffixes(range(0, -1)) //│ = Cons(Nil, Nil) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala index 085f7411ff..1adcd726b3 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -194,7 +194,10 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: jsb.block(le, endSemi = false) val jsStr = je.stripBreaks.mkString(100) mkQuery("", jsStr): out => - val result = out.splitSane('\n').init.mkString // should always ends with "undefined" (TODO: check) + // Omit the last line which is always "undefined" or the unit. + val result = out.lastIndexOf('\n') match + case n if n >= 0 => out.substring(0, n) + case _ => "" expect match case S(expected) if result =/= expected => raise: ErrorReport(msg"Expected: '${expected}', got: '${result}'" -> N :: Nil, @@ -204,7 +207,6 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: result match case "undefined" if anon => case "()" if anon => - case _ => - output(s"${if anon then "" else s"$nme "}= ${result.indentNewLines("| ")}") + case _ => output(s"${if anon then "" else s"$nme "}= $result") From 9f4e6d9208c0021d55b50929dce0875768148bc6 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 01:44:56 +0800 Subject: [PATCH 50/62] Better document the function `tuple` in `Elaborator.pattern` --- .../main/scala/hkmc2/semantics/Elaborator.scala | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 2fb32c13aa..eb34379cfe 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1357,23 +1357,28 @@ extends Importer: Transform(pattern, term(rhs)(using termCtx)) /** 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(`...`, _, _)) => - // Found two spreads in the same tuple pattern. Report an error. + // Found two `...p` 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. + acc // Do not modify the accumulator and skip this `Spread`. case ((leading, N, trailing), Spread(`...`, _, S(t))) => - // Elaborate the spread pattern and add it to spread element. + // Found `...p`, elaborate `p` and assign it to the spread pattern. (leading, S(go(t)), trailing) case ((leading, N, trailing), Spread(`...`, _, N)) => - // Empty spreads results in a wildcard pattern. + // 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) => - // The spread is not filled. Add new patterns to the leading. + // 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) => - // The spread is filled. Add new patterns to the trailing. + // 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) /** Elaborate record patterns like `(a: p1, b: p2, ...pn)`. */ From dc2e9deb8317d0932504ba5ce6ed4fd2415e2001 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 01:53:23 +0800 Subject: [PATCH 51/62] Amend test changes caused by previous changes --- hkmc2/shared/src/test/mlscript/handlers/Debugging.mls | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls index 1ca6aba7b7..a66c95abd8 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Debugging.mls @@ -211,9 +211,9 @@ fun f() = f() //│ > [FnLocalsInfo("‹top level›", [LocalVarInfo("i", 100)]), FnLocalsInfo("f", [LocalVarInfo("j", 200)])] //│ > Stack Trace: -//│ > at f (Debugging.mls:198:3) with locals: j=200 +//│ > at f (Debugging.mls:204:3) with locals: j=200 //│ > Stack Trace: -//│ > at f (Debugging.mls:200:3) +//│ > at f (Debugging.mls:206:3) //│ > Stack Trace: -//│ > at f (Debugging.mls:201:3) with locals: j=300 +//│ > at f (Debugging.mls:207:3) with locals: j=300 //│ > [FnLocalsInfo("‹top level›", [LocalVarInfo("i", 100)]), FnLocalsInfo("f", [LocalVarInfo("j", 300)])] From 08036b903ded8ddf8b340bb94e6ebd4dbf40df25 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:19:37 +0800 Subject: [PATCH 52/62] Improve backlog tests --- hkmc2/shared/src/test/mlscript/rp/Future.mls | 117 +++--------------- .../test/mlscript/rp/regex/EmailAddress.mls | 18 ++- .../src/test/mlscript/rp/regex/Separation.mls | 15 +++ 3 files changed, 48 insertions(+), 102 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/rp/regex/Separation.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/rp/Future.mls index 533e20af94..dbf939c63d 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Future.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Future.mls @@ -1,6 +1,6 @@ :js -:todo // Parameterized patterns. +:todo // Parameterized patterns declared by type parameters? pattern Rep0[A] = "" | A ~ Rep0[A] //│ ╔══[ERROR] Unrecognized pattern (application). //│ ║ l.4: pattern Rep0[A] = "" | A ~ Rep0[A] @@ -77,7 +77,7 @@ fun foo(x) = if x is // (case { ... }) as ... -:todo +:todo // `to` instead of ugly `..=` // in-type: "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" // out-type: "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" pattern Char = "a" to "z" | "A" to "Z" | "0" to "9" | "_" | "-" @@ -93,107 +93,22 @@ pattern Email(name, domains) = //│ ╙── ^^^^^^ :todo +pattern Digits = "0" to "9" ~ (Digits | "") +//│ ╔══[PARSE ERROR] Expected end of input; found literal instead +//│ ║ l.96: pattern Digits = "0" to "9" ~ (Digits | "") +//│ ╙── ^^^ +//│ ╔══[ERROR] Unrecognized pattern (juxtaposition). +//│ ║ l.96: 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.96: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╙── ^^^ +//│ ║ l.105: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) +//│ ╙── ^^^ //│ ╔══[ERROR] Cannot use this reference as a pattern -//│ ║ l.96: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) -//│ ╙── ^^^ - -:todo -pattern LineSep(pattern P) = case - "" then Nil - L(...nd) ~ - "" then nd :: Nil - "\n" ~ LineSep(P, t) then nd :: t -//│ ╔══[ERROR] Unrecognized pattern (case). -//│ ║ l.106: "" then Nil -//│ ║ ^^^^^^^^^^^ -//│ ║ l.107: L(...nd) ~ -//│ ║ ^^^^^^^^^^^^ -//│ ║ l.108: "" then nd :: Nil -//│ ╙── ^^^^ +//│ ║ l.105: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) +//│ ╙── ^^^ -:todo -pattern Lines(pattern L) = case - "" then [] - pattern Tail = case - "" then [] - "\n" ~ (Lines of L) as t then t - L as h ~ Tail as t then [h, ...t] -//│ ╔══[ERROR] Unrecognized pattern (case). -//│ ║ l.120: "" then [] -//│ ║ ^^^^^^^^^^ -//│ ║ l.121: pattern Tail = case -//│ ║ ^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.122: "" then [] -//│ ║ ^^^^^^^^^^^^^^ -//│ ║ l.123: "\n" ~ (Lines of L) as t then t -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.124: L as h ~ Tail as t then [h, ...t] -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:todo -if input is Lines of Email then -//│ ╔══[PARSE ERROR] Expected start of expression in this position; found end of input instead -//│ ║ l.138: if input is Lines of Email then -//│ ╙── ^ -//│ ╔══[ERROR] Name not found: input -//│ ║ l.138: if input is Lines of Email then -//│ ╙── ^^^^^ - -:todo -pattern Opt(pattern P) = case - P(value) then (Some(value)) - "" then (None) -//│ ╔══[ERROR] Unrecognized pattern (case). -//│ ║ l.148: P(value) then (Some(value)) -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.149: "" then (None) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^ - -:todo +:todo // Should report errors pattern Email(name, domain) = ... - -:todo -if input is Opt(Email, Some((n, d))) then ... -//│ ╔══[ERROR] Name not found: input -//│ ║ l.160: if input is Opt(Email, Some((n, d))) then ... -//│ ╙── ^^^^^ -//│ ╔══[ERROR] Name not found: Some -//│ ║ l.160: if input is Opt(Email, Some((n, d))) then ... -//│ ╙── ^^^^ -//│ ╔══[ERROR] invalid record field pattern -//│ ║ l.160: if input is Opt(Email, Some((n, d))) then ... -//│ ╙── ^ -//│ ═══[ERROR] Expected zero arguments, but found two arguments. - -:todo -pattern Opt(pattern P) = case - P(...values) then (Some(values)) - "" then (None) -//│ ╔══[ERROR] Unrecognized pattern (case). -//│ ║ l.174: P(...values) then (Some(values)) -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.175: "" then (None) -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:todo -pattern Opt(pattern P) = case - P(...values) then Some(values) - "" then None -//│ ╔══[ERROR] Unrecognized pattern (case). -//│ ║ l.184: P(...values) then Some(values) -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.185: "" then None -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^ - -:todo -pattern Digits = "0" to "9" ~ (Digits | "") -//│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.193: pattern Digits = "0" to "9" ~ (Digits | "") -//│ ╙── ^^^ -//│ ╔══[ERROR] Unrecognized pattern (juxtaposition). -//│ ║ l.193: pattern Digits = "0" to "9" ~ (Digits | "") -//│ ╙── ^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/EmailAddress.mls b/hkmc2/shared/src/test/mlscript/rp/regex/EmailAddress.mls index 4449e7c90b..4de7f9a3ac 100644 --- a/hkmc2/shared/src/test/mlscript/rp/regex/EmailAddress.mls +++ b/hkmc2/shared/src/test/mlscript/rp/regex/EmailAddress.mls @@ -65,7 +65,8 @@ pattern Domain = DomainName ~ DomainSuffix pattern Email = UserName ~ "@" ~ Domain -[ +:silent +let emails = [ "example@example.com" "john.doe@guardian.co.uk" "alice_bob123@sub-domain.example.org" @@ -74,6 +75,21 @@ pattern Email = UserName ~ "@" ~ Domain "foo-bar@company-name.io" "simple123@abc.xyz" ] + +emails Iter.mapping of _ is Email Iter.folded of true, _ && _ //│ = true + +open Stack + +pattern CommaSep(pattern S) = + ("" => Nil) | + ((S as head) ~ "," ~ (CommaSep(pattern S) as tail)) => head :: tail + +fun parseEmails(input) = + if input is CommaSep(pattern Email) as emails then emails else Nil + +:todo +parseEmails of emails.join(",") +//│ = Nil diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/Separation.mls b/hkmc2/shared/src/test/mlscript/rp/regex/Separation.mls new file mode 100644 index 0000000000..2f38c80f84 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/rp/regex/Separation.mls @@ -0,0 +1,15 @@ +:js + + +pattern TailLines(pattern L) = + ("" => []) | + ("\n" ~ (Lines(pattern L) as t)) => t +pattern Lines(pattern L) = + ("" => []) | + (((L as h) ~ (TailLines(pattern L) as t)) => [h, ...t]) + +fun foo(input) = input is Lines("hello") + +:fixme +foo of "hello" +//│ ═══[RUNTIME ERROR] Error: Function 'unapply' expected 2 arguments but got 1 From 883f40b94eb641a9cb7556da29efb4713dadefd6 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:47:08 +0800 Subject: [PATCH 53/62] Do not make terms with implicit args before resolution --- .../scala/hkmc2/semantics/ucs/Desugarer.scala | 25 +++++++++++++++---- .../hkmc2/semantics/ucs/Normalization.scala | 2 +- ...garingBase.scala => TermSynthesizer.scala} | 7 ++++-- .../scala/hkmc2/semantics/ups/Compiler.scala | 4 +-- .../hkmc2/semantics/ups/NaiveCompiler.scala | 4 +-- 5 files changed, 30 insertions(+), 12 deletions(-) rename hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/{DesugaringBase.scala => TermSynthesizer.scala} (95%) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala index 90679a42a1..6262bbff7c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala @@ -27,9 +27,19 @@ object Desugarer: val tupleLast: HashMap[Int, BlockLocalSymbol] = HashMap.empty end Desugarer -class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) extends DesugaringBase: +class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, 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) + private def tupleSlice = Term.SynthSel(selectTuple, Ident("slice"))(N) + private def tupleLazySlice = Term.SynthSel(selectTuple, Ident("lazySlice"))(N) + private def tupleGet = Term.SynthSel(selectTuple, Ident("get"))(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) @@ -515,15 +525,20 @@ class Desugarer(elaborator: Elaborator)(using Ctx, Raise, State, UnderCtx) exten Split.Let(sym, callTupleGet(ref, -1 - lastIndex, sym), wrapInner(split)) (wrap, Argument(sym, pat) :: matches) val lastMatches = reversedLastMatches.reverse - val sliceFn = kw match - case Keyword.`..` => tupleLazySlice - case Keyword.`...` => tupleSlice rest match case N => (wrapLast, lastMatches) case S(pat) => val sym = TempSymbol(N, "rest") val wrap = (split: Split) => - Split.Let(sym, app(sliceFn, tup(fld(ref), fld(int(lead.length)), fld(int(last.length))), sym), wrapLast(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)): 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 878d233ae6..41b2f9b1e4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -11,7 +11,7 @@ import FlatPattern.Argument import ups.Instantiator import hkmc2.semantics.ups.NaiveCompiler -class Normalization(using tl: TL)(using Raise, Ctx, State) extends DesugaringBase: +class Normalization(using tl: TL)(using Raise, Ctx, State) extends TermSynthesizer: import Normalization.*, Mode.*, FlatPattern.MatchMode import tl.* diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala similarity index 95% rename from hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala rename to hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala index b08e79314c..1740d40a70 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/DesugaringBase.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala @@ -5,8 +5,11 @@ package ucs import mlscript.utils.*, shorthands.* import syntax.Tree.*, Elaborator.{Ctx, State, ctx} -/** Contains some helpers that makes UCS desugaring easier. */ -trait DesugaringBase(using Ctx, State): +/** 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): protected final def sel(p: Term, k: Ident): Term.SynthSel = (Term.SynthSel(p, k)(N): Term.SynthSel).withIArgs(Nil) protected final def sel(p: Term, k: Ident, s: FieldSymbol): Term.SynthSel = 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 e80333ce93..a47df583cd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/Compiler.scala @@ -9,7 +9,7 @@ import syntax.{Keyword, LetBind, Tree}, Tree.{DecLit, Ident, IntLit, StrLit, Uni import Term.{Blk, IfLike, Rcd, Ref, SynthSel} import Pattern.{Instantiation, Head} import Elaborator.{Ctx, State, ctx}, utils.TL -import ucs.{DesugaringBase as Base, FlatPattern, safeRef} +import ucs.{TermSynthesizer, FlatPattern, safeRef} import Message.MessageContext, ucs.error import collection.mutable.{Queue, Map as MutMap}, collection.immutable.{Set, Map} @@ -19,7 +19,7 @@ import scala.annotation.tailrec * a few matcher functions. Each matcher function matches a set of patterns * and returns a record that contains the results of each pattern. */ -class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends Base: +class Compiler(using Context)(using tl: TL)(using Ctx, State, Raise) extends TermSynthesizer: import Compiler.*, tl.* extension (label: Label) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala index 6b407310e6..2e2955835d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala @@ -4,7 +4,7 @@ package ups import mlscript.utils.*, shorthands.* import Message.MessageContext -import ucs.{DesugaringBase, FlatPattern, error, safeRef}, ucs.extractors.* +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 @@ -77,7 +77,7 @@ 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 DesugaringBase: +class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends TermSynthesizer: import tl.*, FlatPattern.MatchMode, SP.* private lazy val lteq = State.builtinOpsMap("<=") From d7c9b8965642e40f0faafc15336d23bc09bff2f9 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:52:51 +0800 Subject: [PATCH 54/62] Revert newlines --- hkmc2/shared/src/test/mlscript/HkScratch.mls | 1 + 1 file changed, 1 insertion(+) diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index 48d7326643..ef38e8b363 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -9,3 +9,4 @@ // :todo + From bc0f600b7ed3d7b78d54d8c1a8f23278e2ebe229 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 03:04:45 +0800 Subject: [PATCH 55/62] Improve the check for `isMlsFun` --- .../src/main/scala/hkmc2/codegen/Lowering.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 237c8beb91..a04d02ffce 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -342,10 +342,14 @@ class Lowering()(using Config, TL, Raise, State, Ctx): val isMlsFun = f.resolvedSymbol.fold(f.isInstanceOf[st.Lam]): case _: sem.BuiltinSymbol => true case sym: sem.BlockMemberSymbol => - sym.trmImplTree.fold(sym.clsTree.isDefined)(_.k is syntax.Fun) - // Do not perform safety check on `MatchResult` and `MatchFailure`. - case sym => (sym is State.matchResultClsSymbol) || - (sym is State.matchFailureClsSymbol) + // Do no check if the symbol refers to a function definition or + sym.trmImplTree.fold(sym.clsTree.isDefined)(_.k is syntax.Fun) || ( + // it refers to classes `MatchResult` or `MatchFailure`. + sym.asCls match + case S(cls) => (sym is State.matchResultClsSymbol) || + (sym is State.matchFailureClsSymbol) + case N => false) + case _ => false def conclude(fr: Path) = arg match case Tup(fs) => From df53e8e56a918bee3745091713c51c24c733a340 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:04:40 +0800 Subject: [PATCH 56/62] Hide a long-winded test output Co-authored-by: Lionel Parreaux --- .../shared/src/test/mlscript/nofib/boyer2.mls | 533 +----------------- 1 file changed, 1 insertion(+), 532 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls b/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls index b6d5b4a9aa..e4a9ece8f6 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/boyer2.mls @@ -427,539 +427,8 @@ let rules = nofibStringToList("(equal (get j (set i val mem) )(if (eqp j i)val(get j mem) ) )") :: Nil //│ rules = [["(","e","q","u","a","l"," ","(","c","o","m","p","i","l","e"," ","f","o","r","m",")","(","r","e","v","e","r","s","e"," ","(","c","o","d","e","g","e","n"," ","(","o","p","t","i","m","i","z","e"," ","f","o","r","m",")"," ","(","N","i","l","l",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","p"," ","x"," ","y",")","(","e","q","u","a","l"," ","(","f","i","x"," ","x",")","(","f","i","x"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","g","r","e","a","t","e","r","p"," ","x"," ","y",")","(","l","e","s","s","p"," ","y"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","e","q","p"," ","x"," ","y",")","(","n","o","t"," ","(","l","e","s","s","p"," ","y"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","g","r","e","a","t","e","r","e","q","p"," ","x"," ","y",")","(","n","o","t"," ","(","l","e","s","s","p"," ","y"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","b","o","o","l","e","a","n"," ","x",")","(","o","r"," ","(","e","q","u","a","l"," ","x"," ","(","t",")"," ",")","(","e","q","u","a","l"," ","x"," ","(","f",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","i","f","f"," ","x"," ","y",")","(","a","n","d"," ","(","i","m","p","l","i","e","s"," ","x"," ","y",")","(","i","m","p","l","i","e","s"," ","y"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","v","e","n","1"," ","x",")","(","i","f"," ","(","z","e","r","o","p"," ","x",")","(","t",")","(","o","d","d"," ","(","1","-"," ","x",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","c","o","u","n","t","p","s","-"," ","l"," ","p","r","e","d",")","(","c","o","u","n","t","p","s","-","l","o","o","p"," ","l"," ","p","r","e","d"," ","(","z","e","r","o",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","f","a","c","t","-"," ","i",")","(","f","a","c","t","-","l","o","o","p"," ","i"," ","1",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","v","e","r","s","e","-"," ","x",")","(","r","e","v","e","r","s","e","-","l","o","o","p"," ","x"," ","(","N","i","l","l",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","v","i","d","e","s"," ","x"," ","y",")","(","z","e","r","o","p"," ","(","r","e","m","a","i","n","d","e","r"," ","y"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","a","s","s","u","m","e","-","t","r","u","e"," ","v","a","r"," ","a","l","i","s","t",")","(","C","o","n","s","s"," ","(","C","o","n","s","s"," ","v","a","r"," ","(","t",")"," ",")","a","l","i","s","t",")"," ",")"],["(","e","q","u","a","l"," ","(","a","s","s","u","m","e","-","f","a","l","s","e"," ","v","a","r"," ","a","l","i","s","t",")","(","C","o","n","s","s"," ","(","C","o","n","s","s"," ","v","a","r"," ","(","f",")"," ",")","a","l","i","s","t",")"," ",")"],["(","e","q","u","a","l"," ","(","t","a","u","t","o","l","o","g","y","-","c","h","e","c","k","e","r"," ","x",")","(","t","a","u","t","o","l","o","g","y","p"," ","(","n","o","r","m","a","l","i","z","e"," ","x",")","(","N","i","l","l",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","f","a","l","s","i","f","y"," ","x",")","(","f","a","l","s","i","f","y","1"," ","(","n","o","r","m","a","l","i","z","e"," ","x",")","(","N","i","l","l",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","p","r","i","m","e"," ","x",")","(","a","n","d"," ","(","n","o","t"," ","(","z","e","r","o","p"," ","x",")",")","(","n","o","t"," ","(","e","q","u","a","l"," ","x"," ","(","a","d","d","1"," ","(","z","e","r","o",")"," ",")"," ",")"," ",")","(","p","r","i","m","e","1"," ","x"," ","(","1","-"," ","x",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","a","n","d"," ","p"," ","q",")","(","i","f"," ","p"," ","(","i","f"," ","q"," ","(","t",")"," ","(","f",")"," ",")"," ","(","f",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","o","r"," ","p"," ","q",")","(","i","f"," ","p"," ","(","t",")"," ","(","i","f"," ","q"," ","(","t",")"," ","(","f",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","n","o","t"," ","p",")","(","i","f"," ","p"," ","(","f",")"," ","(","t",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","i","m","p","l","i","e","s"," ","p"," ","q",")","(","i","f"," ","p"," ","(","i","f"," ","q"," ","(","t",")"," ","(","f",")"," ",")"," ","(","t",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","f","i","x"," ","x",")","(","i","f"," ","(","n","u","m","b","e","r","p"," ","x",")"," ","x"," ","(","z","e","r","o",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","i","f"," ","(","i","f"," ","a"," ","b"," ","c",")"," ","d"," ","e",")","(","i","f"," ","a"," ","(","i","f"," ","b"," ","d"," ","e",")"," ","(","i","f"," ","c"," ","d"," ","e",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","z","e","r","o","p"," ","x",")","(","o","r"," ","(","e","q","u","a","l"," ","x"," ","(","z","e","r","o",")"," ",")","(","n","o","t"," ","(","n","u","m","b","e","r","p"," ","x",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","p","l","u","s"," ","(","p","l","u","s"," ","x"," ","y",")"," ","z"," ",")","(","p","l","u","s"," ","x"," ","(","p","l","u","s"," ","y"," ","z",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","p","l","u","s"," ","a"," ","b",")"," ","(","z","e","r","o"," ",")"," ",")","(","a","n","d"," ","(","z","e","r","o","p"," ","a",")"," ","(","z","e","r","o","p"," ","b",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","x"," ","x",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","p","l","u","s"," ","a"," ","b",")"," ","(","p","l","u","s"," ","a"," ","c",")"," ",")","(","e","q","u","a","l"," ","(","f","i","x"," ","b",")"," ","(","f","i","x"," ","c",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","z","e","r","o",")"," ","(","d","i","f","f","e","r","e","n","c","e"," ","x"," ","y",")"," ",")","(","n","o","t"," ","(","l","e","s","s","p"," ","y"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","x"," ","(","d","i","f","f","e","r","e","n","c","e"," ","x"," ","y",")"," ",")","(","a","n","d"," ","(","n","u","m","b","e","r","p"," ","x",")","(","o","r"," ","(","e","q","u","a","l"," ","x"," ","(","z","e","r","o",")"," ",")","(","z","e","r","o","p"," ","y",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","(","a","p","p","e","n","d"," ","x"," ","y",")"," ",")"," ","a",")","(","p","l","u","s"," ","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","x",")"," ","a",")","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","y",")"," ","a",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","(","p","l","u","s","-","f","r","i","n","g","e"," ","x",")"," ",")"," ","a",")","(","f","i","x"," ","(","m","e","a","n","i","n","g"," ","x"," ","a",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","a","p","p","e","n","d"," ","(","a","p","p","e","n","d"," ","x"," ","y",")"," ","z",")","(","a","p","p","e","n","d"," ","x"," ","(","a","p","p","e","n","d"," ","y"," ","z",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","v","e","r","s","e"," ","(","a","p","p","e","n","d"," ","a"," ","b",")"," ",")","(","a","p","p","e","n","d"," ","(","r","e","v","e","r","s","e"," ","b",")"," ","(","r","e","v","e","r","s","e"," ","a",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","x"," ","(","p","l","u","s"," ","y"," ","z",")"," ",")","(","p","l","u","s"," ","(","t","i","m","e","s"," ","x"," ","y",")","(","t","i","m","e","s"," ","x"," ","z",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","(","t","i","m","e","s"," ","x"," ","y",")"," ","z",")","(","t","i","m","e","s"," ","x"," ","(","t","i","m","e","s"," ","y"," ","z",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","x"," ","y",")"," ","(","z","e","r","o",")"," ",")","(","o","r"," ","(","z","e","r","o","p"," ","x",")","(","z","e","r","o","p"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","x","e","c"," ","(","a","p","p","e","n","d"," ","x"," ","y",")","p","d","s"," ","e","n","v","r","n",")","(","e","x","e","c"," ","y"," ","(","e","x","e","c"," ","x"," ","p","d","s"," ","e","n","v","r","n",")","e","n","v","r","n",")"," ",")"],["(","e","q","u","a","l"," ","(","m","c","-","f","l","a","t","t","e","n"," ","x"," ","y",")","(","a","p","p","e","n","d"," ","(","f","l","a","t","t","e","n"," ","x",")","y",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","m","b","e","r"," ","x"," ","(","a","p","p","e","n","d"," ","a"," ","b",")"," ",")","(","o","r"," ","(","m","e","m","b","e","r"," ","x"," ","a",")","(","m","e","m","b","e","r"," ","x"," ","b",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","m","b","e","r"," ","x"," ","(","r","e","v","e","r","s","e"," ","y",")"," ",")","(","m","e","m","b","e","r"," ","x"," ","y",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","n","g","t","h"," ","(","r","e","v","e","r","s","e"," ","x",")"," ",")","(","l","e","n","g","t","h"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","m","b","e","r"," ","a"," ","(","i","n","t","e","r","s","e","c","t"," ","b"," ","c",")"," ",")","(","a","n","d"," ","(","m","e","m","b","e","r"," ","a"," ","b",")","(","m","e","m","b","e","r"," ","a"," ","c",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","n","t","h"," ","(","z","e","r","o",")","i",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","e","x","p"," ","i"," ","(","p","l","u","s"," ","j"," ","k",")"," ",")","(","t","i","m","e","s"," ","(","e","x","p"," ","i"," ","j",")","(","e","x","p"," ","i"," ","k",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","x","p"," ","i"," ","(","t","i","m","e","s"," ","j"," ","k",")"," ",")","(","e","x","p"," ","(","e","x","p"," ","i"," ","j",")","k",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","v","e","r","s","e","-","l","o","o","p"," ","x"," ","y",")","(","a","p","p","e","n","d"," ","(","r","e","v","e","r","s","e"," ","x",")","y",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","v","e","r","s","e","-","l","o","o","p"," ","x"," ","(","N","i","l","l",")"," ",")","(","r","e","v","e","r","s","e"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","c","o","u","n","t","-","l","i","s","t"," ","z"," ","(","s","o","r","t","-","l","p"," ","x"," ","y",")"," ",")","(","p","l","u","s"," ","(","c","o","u","n","t","-","l","i","s","t"," ","z"," ","x",")","(","c","o","u","n","t","-","l","i","s","t"," ","z"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","a","p","p","e","n","d"," ","a"," ","b",")","(","a","p","p","e","n","d"," ","a"," ","c",")"," ",")","(","e","q","u","a","l"," ","b"," ","c",")"," ",")"],["(","e","q","u","a","l"," ","(","p","l","u","s"," ","(","r","e","m","a","i","n","d","e","r"," ","x"," ","y",")","(","t","i","m","e","s"," ","y"," ","(","q","u","o","t","i","e","n","t"," ","x"," ","y",")"," ",")"," ",")","(","f","i","x"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","p","o","w","e","r","-","e","v","a","l"," ","(","b","i","g","-","p","l","u","s","1"," ","l"," ","i"," ","b","a","s","e",")","b","a","s","e",")","(","p","l","u","s"," ","(","p","o","w","e","r","-","e","v","a","l"," ","l"," ","b","a","s","e",")","i",")"," ",")"],["(","e","q","u","a","l"," ","(","p","o","w","e","r","-","e","v","a","l"," ","(","b","i","g","-","p","l","u","s"," ","x"," ","y"," ","i"," ","b","a","s","e",")","b","a","s","e",")","(","p","l","u","s"," ","i"," ","(","p","l","u","s"," ","(","p","o","w","e","r","-","e","v","a","l"," ","x"," ","b","a","s","e",")","(","p","o","w","e","r","-","e","v","a","l"," ","y"," ","b","a","s","e",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","m","a","i","n","d","e","r"," ","y"," ","1",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","r","e","m","a","i","n","d","e","r"," ","x"," ","y",")","y",")","(","n","o","t"," ","(","z","e","r","o","p"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","m","a","i","n","d","e","r"," ","x"," ","x",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","q","u","o","t","i","e","n","t"," ","i"," ","j",")","i",")","(","a","n","d"," ","(","n","o","t"," ","(","z","e","r","o","p"," ","i",")"," ",")","(","o","r"," ","(","z","e","r","o","p"," ","j",")","(","n","o","t"," ","(","e","q","u","a","l"," ","j"," ","1",")"," ",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","r","e","m","a","i","n","d","e","r"," ","x"," ","y",")","x",")","(","a","n","d"," ","(","n","o","t"," ","(","z","e","r","o","p"," ","y",")"," ",")","(","n","o","t"," ","(","z","e","r","o","p"," ","x",")"," ",")","(","n","o","t"," ","(","l","e","s","s","p"," ","x"," ","y",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","p","o","w","e","r","-","e","v","a","l"," ","(","p","o","w","e","r","-","r","e","p"," ","i"," ","b","a","s","e",")","b","a","s","e",")","(","f","i","x"," ","i",")"," ",")"],["(","e","q","u","a","l"," ","(","p","o","w","e","r","-","e","v","a","l"," ","(","b","i","g","-","p","l","u","s"," ","(","p","o","w","e","r","-","r","e","p"," ","i"," ","b","a","s","e",")","(","p","o","w","e","r","-","r","e","p"," ","j"," ","b","a","s","e",")","(","z","e","r","o",")","b","a","s","e",")","b","a","s","e",")","(","p","l","u","s"," ","i"," ","j",")"," ",")"],["(","e","q","u","a","l"," ","(","g","c","d"," ","x"," ","y",")","(","g","c","d"," ","y"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","n","t","h"," ","(","a","p","p","e","n","d"," ","a"," ","b",")","i",")","(","a","p","p","e","n","d"," ","(","n","t","h"," ","a"," ","i",")","(","n","t","h"," ","b"," ","(","d","i","f","f","e","r","e","n","c","e"," ","i"," ","(","l","e","n","g","t","h"," ","a",")"," ",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","p","l","u","s"," ","x"," ","y",")","x",")","(","f","i","x"," ","y",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","p","l","u","s"," ","y"," ","x",")","x",")","(","f","i","x"," ","y",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","p","l","u","s"," ","x"," ","y",")","(","p","l","u","s"," ","x"," ","z",")"," ",")","(","d","i","f","f","e","r","e","n","c","e"," ","y"," ","z",")"," ",")"],["(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","x"," ","(","d","i","f","f","e","r","e","n","c","e"," ","c"," ","w",")"," ",")","(","d","i","f","f","e","r","e","n","c","e"," ","(","t","i","m","e","s"," ","c"," ","x",")","(","t","i","m","e","s"," ","w"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","m","a","i","n","d","e","r"," ","(","t","i","m","e","s"," ","x"," ","z",")","z",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","p","l","u","s"," ","b"," ","(","p","l","u","s"," ","a"," ","c",")"," ",")","a",")","(","p","l","u","s"," ","b"," ","c",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","a","d","d","1"," ","(","p","l","u","s"," ","y"," ","z",")","z",")","(","a","d","d","1"," ","y",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","p","l","u","s"," ","x"," ","y",")","(","p","l","u","s"," ","x"," ","z"," ",")"," ",")","(","l","e","s","s","p"," ","y"," ","z",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","t","i","m","e","s"," ","x"," ","z",")","(","t","i","m","e","s"," ","y"," ","z",")"," ",")","(","a","n","d"," ","(","n","o","t"," ","(","z","e","r","o","p"," ","z",")"," ",")","(","l","e","s","s","p"," ","x"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","y"," ","(","p","l","u","s"," ","x"," ","y",")"," ",")","(","n","o","t"," ","(","z","e","r","o","p"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","g","c","d"," ","(","t","i","m","e","s"," ","x"," ","z",")","(","t","i","m","e","s"," ","y"," ","z",")"," ",")","(","t","i","m","e","s"," ","z"," ","(","g","c","d"," ","x"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","v","a","l","u","e"," ","(","n","o","r","m","a","l","i","z","e"," ","x",")","a",")","(","v","a","l","u","e"," ","x"," ","a",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","f","l","a","t","t","e","n"," ","x",")","(","C","o","n","s","s"," ","y"," ","(","N","i","l","l",")"," ",")"," ",")","(","a","n","d"," ","(","n","l","i","s","t","p"," ","x",")","(","e","q","u","a","l"," ","x"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","l","i","s","t","p"," ","(","g","o","p","h","e","r"," ","x",")"," ",")","(","l","i","s","t","p"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","s","a","m","e","f","r","i","n","g","e"," ","x"," ","y",")","(","e","q","u","a","l"," ","(","f","l","a","t","t","e","n"," ","x",")","(","f","l","a","t","t","e","n"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"," ","x"," ","y",")","(","z","e","r","o",")"," ",")","(","a","n","d"," ","(","o","r"," ","(","z","e","r","o","p"," ","y",")","(","e","q","u","a","l"," ","y"," ","1",")"," ",")","(","e","q","u","a","l"," ","x"," ","(","z","e","r","o",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"," ","x"," ","y",")","1",")","(","e","q","u","a","l"," ","x"," ","1",")"," ",")"],["(","e","q","u","a","l"," ","(","n","u","m","b","e","r","p"," ","(","g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"," ","x"," ","y",")"," ",")","(","n","o","t"," ","(","a","n","d"," ","(","o","r"," ","(","z","e","r","o","p"," ","y",")","(","e","q","u","a","l"," ","y"," ","1",")"," ",")","(","n","o","t"," ","(","n","u","m","b","e","r","p"," ","x",")"," ",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","t","i","m","e","s","-","l","i","s","t"," ","(","a","p","p","e","n","d"," ","x"," ","y",")"," ",")","(","t","i","m","e","s"," ","(","t","i","m","e","s","-","l","i","s","t"," ","x",")","(","t","i","m","e","s","-","l","i","s","t"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","p","r","i","m","e","-","l","i","s","t"," ","(","a","p","p","e","n","d"," ","x"," ","y",")"," ",")","(","a","n","d"," ","(","p","r","i","m","e","-","l","i","s","t"," ","x",")","(","p","r","i","m","e","-","l","i","s","t"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","z"," ","(","t","i","m","e","s"," ","w"," ","z",")"," ",")","(","a","n","d"," ","(","n","u","m","b","e","r","p"," ","z",")","(","o","r"," ","(","e","q","u","a","l"," ","z"," ","(","z","e","r","o",")"," ",")","(","e","q","u","a","l"," ","w"," ","1",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","g","r","e","a","t","e","r","e","q","p","r"," ","x"," ","y",")","(","n","o","t"," ","(","l","e","s","s","p"," ","x"," ","y",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","x"," ","(","t","i","m","e","s"," ","x"," ","y",")"," ",")","(","o","r"," ","(","e","q","u","a","l"," ","x"," ","(","z","e","r","o",")"," ",")","(","a","n","d"," ","(","n","u","m","b","e","r","p"," ","x",")","(","e","q","u","a","l"," ","y"," ","1",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","r","e","m","a","i","n","d","e","r"," ","(","t","i","m","e","s"," ","y"," ","x",")","y",")","(","z","e","r","o",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","a"," ","b",")","1",")","(","a","n","d"," ","(","n","o","t"," ","(","e","q","u","a","l"," ","a"," ","(","z","e","r","o",")"," ",")"," ",")","(","n","o","t"," ","(","e","q","u","a","l"," ","b"," ","(","z","e","r","o",")"," ",")"," ",")","(","n","u","m","b","e","r","p"," ","a",")","(","n","u","m","b","e","r","p"," ","b",")","(","e","q","u","a","l"," ","(","1","-"," ","a",")","(","z","e","r","o",")"," ",")","(","e","q","u","a","l"," ","(","1","-"," ","b",")","(","z","e","r","o",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","(","l","e","n","g","t","h"," ","(","d","e","l","e","t","e"," ","x"," ","l",")"," ",")","(","l","e","n","g","t","h"," ","l",")"," ",")","(","m","e","m","b","e","r"," ","x"," ","l",")"," ",")"],["(","e","q","u","a","l"," ","(","s","o","r","t","2"," ","(","d","e","l","e","t","e"," ","x"," ","l",")"," ",")","(","d","e","l","e","t","e"," ","x"," ","(","s","o","r","t","2"," ","l",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","d","s","o","r","t"," ","x",")","(","s","o","r","t","2"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","l","e","n","g","t","h","(","C","o","n","s","s"," ","x","1","(","C","o","n","s","s"," ","x","2","(","C","o","n","s","s"," ","x","3","(","C","o","n","s","s"," ","x","4","(","C","o","n","s","s"," ","x","5","(","C","o","n","s","s"," ","x","6"," ","x","7",")"," ",")"," ",")"," ",")"," ",")"," ",")"," ",")","(","p","l","u","s"," ","6"," ","(","l","e","n","g","t","h"," ","x","7",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","(","a","d","d","1"," ","(","a","d","d","1"," ","x",")"," ",")","2",")","(","f","i","x"," ","x",")"," ",")"],["(","e","q","u","a","l"," ","(","q","u","o","t","i","e","n","t"," ","(","p","l","u","s"," ","x"," ","(","p","l","u","s"," ","x"," ","y",")"," ",")","2",")","(","p","l","u","s"," ","x"," ","(","q","u","o","t","i","e","n","t"," ","y"," ","2",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","s","i","g","m","a"," ","(","z","e","r","o",")","i",")","(","q","u","o","t","i","e","n","t"," ","(","t","i","m","e","s"," ","i"," ","(","a","d","d","1"," ","i",")"," ",")","2",")"," ",")"],["(","e","q","u","a","l"," ","(","p","l","u","s"," ","x"," ","(","a","d","d","1"," ","y",")"," ",")","(","i","f"," ","(","n","u","m","b","e","r","p"," ","y",")","(","a","d","d","1"," ","(","p","l","u","s"," ","x"," ","y",")"," ",")","(","a","d","d","1"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","d","i","f","f","e","r","e","n","c","e"," ","x"," ","y",")","(","d","i","f","f","e","r","e","n","c","e"," ","z"," ","y",")"," ",")","(","i","f"," ","(","l","e","s","s","p"," ","x"," ","y",")","(","n","o","t"," ","(","l","e","s","s","p"," ","y"," ","z",")"," ",")","(","i","f"," ","(","l","e","s","s","p"," ","z"," ","y",")","(","n","o","t"," ","(","l","e","s","s","p"," ","y"," ","x",")"," ",")","(","e","q","u","a","l"," ","(","f","i","x"," ","x",")","(","f","i","x"," ","z",")"," ",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","(","d","e","l","e","t","e"," ","x"," ","y",")"," ",")","a",")","(","i","f"," ","(","m","e","m","b","e","r"," ","x"," ","y",")","(","d","i","f","f","e","r","e","n","c","e"," ","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","y",")","a",")","(","m","e","a","n","i","n","g"," ","x"," ","a",")"," ",")","(","m","e","a","n","i","n","g"," ","(","p","l","u","s","-","t","r","e","e"," ","y",")","a",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","t","i","m","e","s"," ","x"," ","(","a","d","d","1"," ","y",")"," ",")","(","i","f"," ","(","n","u","m","b","e","r","p"," ","y",")","(","p","l","u","s"," ","x"," ","(","t","i","m","e","s"," ","x"," ","y",")"," ",")","(","f","i","x"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","n","t","h"," ","(","N","i","l","l",")","i",")","(","i","f"," ","(","z","e","r","o","p"," ","i",")","(","N","i","l","l",")","(","z","e","r","o",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","l","a","s","t"," ","(","a","p","p","e","n","d"," ","a"," ","b",")"," ",")","(","i","f"," ","(","l","i","s","t","p"," ","b",")","(","l","a","s","t"," ","b",")","(","i","f"," ","(","l","i","s","t","p"," ","a",")","(","C","o","n","s","s"," ","(","c","a","r"," ","(","l","a","s","t"," ","a",")"," ",")","b",")","b",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","e","q","u","a","l"," ","(","l","e","s","s","p"," ","x"," ","y",")","z",")","(","i","f"," ","(","l","e","s","s","p"," ","x"," ","y",")","(","e","q","u","a","l"," ","t"," ","z",")","(","e","q","u","a","l"," ","f"," ","z",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","a","s","s","i","g","n","m","e","n","t"," ","x"," ","(","a","p","p","e","n","d"," ","a"," ","b",")"," ",")","(","i","f"," ","(","a","s","s","i","g","n","e","d","p"," ","x"," ","a",")","(","a","s","s","i","g","n","m","e","n","t"," ","x"," ","a",")","(","a","s","s","i","g","n","m","e","n","t"," ","x"," ","b",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","c","a","r"," ","(","g","o","p","h","e","r"," ","x",")"," ",")","(","i","f"," ","(","l","i","s","t","p"," ","x",")","(","c","a","r"," ","(","f","l","a","t","t","e","n"," ","x",")"," ",")","(","z","e","r","o",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","f","l","a","t","t","e","n"," ","(","c","d","r"," ","(","g","o","p","h","e","r"," ","x",")"," ",")"," ",")","(","i","f"," ","(","l","i","s","t","p"," ","x",")","(","c","d","r"," ","(","f","l","a","t","t","e","n"," ","x",")"," ",")","(","C","o","n","s","s"," ","(","z","e","r","o",")","(","N","i","l","l",")"," ",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","q","u","o","t","i","e","n","t"," ","(","t","i","m","e","s"," ","y"," ","x",")","y",")","(","i","f"," ","(","z","e","r","o","p"," ","y",")","(","z","e","r","o",")","(","f","i","x"," ","x",")"," ",")"," ",")"],["(","e","q","u","a","l"," ","(","g","e","t"," ","j"," ","(","s","e","t"," ","i"," ","v","a","l"," ","m","e","m",")"," ",")","(","i","f"," ","(","e","q","p"," ","j"," ","i",")","v","a","l","(","g","e","t"," ","j"," ","m","e","m",")"," ",")"," ",")"]] +:silent let lemmas = addlemmalst(makelemmas(rules), Empty) -//│ lemmas = Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["a","n","d"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","n","d"]), Conss([Atom(["p"]), Conss([Atom(["q"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["q"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["a","p","p","e","n","d"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["a","s","s","i","g","n","m","e","n","t"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","s","s","i","g","n","m","e","n","t"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["a","s","s","i","g","n","e","d","p"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["a","s","s","i","g","n","m","e","n","t"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["a","s","s","i","g","n","m","e","n","t"]), Conss([Atom(["x"]), Conss([Atom(["b"]), Nill])])]), Nill])])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["a","s","s","u","m","e","-","f","a","l","s","e"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","s","s","u","m","e","-","f","a","l","s","e"]), Conss([Atom(["v","a","r"]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["v","a","r"]), Conss([Conss([Atom(["f"]), Nill]), Nill])])]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["a","s","s","u","m","e","-","t","r","u","e"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","s","s","u","m","e","-","t","r","u","e"]), Conss([Atom(["v","a","r"]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["v","a","r"]), Conss([Conss([Atom(["t"]), Nill]), Nill])])]), Conss([Atom(["a","l","i","s","t"]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["b","o","o","l","e","a","n"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["b","o","o","l","e","a","n"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t"]), Nill]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["f"]), Nill]), Nill])])]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["c","a","r"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","a","r"]), Conss([Conss([Atom(["g","o","p","h","e","r"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["c","a","r"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["c","o","m","p","i","l","e"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","o","m","p","i","l","e"]), Conss([Atom(["f","o","r","m"]), Nill])]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Conss([Atom(["c","o","d","e","g","e","n"]), Conss([Conss([Atom(["o","p","t","i","m","i","z","e"]), Conss([Atom(["f","o","r","m"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["c","o","u","n","t","-","l","i","s","t"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","o","u","n","t","-","l","i","s","t"]), Conss([Atom(["z"]), Conss([Conss([Atom(["s","o","r","t","-","l","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["c","o","u","n","t","-","l","i","s","t"]), Conss([Atom(["z"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["c","o","u","n","t","-","l","i","s","t"]), Conss([Atom(["z"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["c","o","u","n","t","p","s","-"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["c","o","u","n","t","p","s","-"]), Conss([Atom(["l"]), Conss([Atom(["p","r","e","d"]), Nill])])]), Conss([Conss([Atom(["c","o","u","n","t","p","s","-","l","o","o","p"]), Conss([Atom(["l"]), Conss([Atom(["p","r","e","d"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["d","i","f","f","e","r","e","n","c","e"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Atom(["2"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["b"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["y"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["y"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["d","i","v","i","d","e","s"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","v","i","d","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["d","s","o","r","t"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","s","o","r","t"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["s","o","r","t","2"]), Conss([Atom(["x"]), Nill])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["e","q","p"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["e","q","u","a","l"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["t"]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["f"]), Conss([Atom(["z"]), Nill])])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["z"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["z"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["z"]), Nill])]), Nill])])]), Nill])])])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Atom(["1"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["a"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["b"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])])])])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["z"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["w"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["z"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["z"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["w"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["1"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Atom(["1"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["y"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["c"]), Nill])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["b"]), Nill])]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["e","v","e","n","1"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","v","e","n","1"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["o","d","d"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["e","x","e","c"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","x","e","c"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["p","d","s"]), Conss([Atom(["e","n","v","r","n"]), Nill])])])]), Conss([Conss([Atom(["e","x","e","c"]), Conss([Atom(["y"]), Conss([Conss([Atom(["e","x","e","c"]), Conss([Atom(["x"]), Conss([Atom(["p","d","s"]), Conss([Atom(["e","n","v","r","n"]), Nill])])])]), Conss([Atom(["e","n","v","r","n"]), Nill])])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["e","x","p"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["j"]), Conss([Atom(["k"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["e","x","p"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Conss([Atom(["k"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["j"]), Conss([Atom(["k"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Conss([Conss([Atom(["e","x","p"]), Conss([Atom(["i"]), Conss([Atom(["k"]), Nill])])]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["f","a","c","t","-"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","a","c","t","-"]), Conss([Atom(["i"]), Nill])]), Conss([Conss([Atom(["f","a","c","t","-","l","o","o","p"]), Conss([Atom(["i"]), Conss([Atom(["1"]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["f","a","l","s","i","f","y"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","a","l","s","i","f","y"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","a","l","s","i","f","y","1"]), Conss([Conss([Atom(["n","o","r","m","a","l","i","z","e"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["f","i","x"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["f","l","a","t","t","e","n"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Conss([Atom(["c","d","r"]), Conss([Conss([Atom(["g","o","p","h","e","r"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["c","d","r"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["g","c","d"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","c","d"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["z"]), Conss([Conss([Atom(["g","c","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","c","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["g","c","d"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["g","e","t"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","e","t"]), Conss([Atom(["j"]), Conss([Conss([Atom(["s","e","t"]), Conss([Atom(["i"]), Conss([Atom(["v","a","l"]), Conss([Atom(["m","e","m"]), Nill])])])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["e","q","p"]), Conss([Atom(["j"]), Conss([Atom(["i"]), Nill])])]), Conss([Atom(["v","a","l"]), Conss([Conss([Atom(["g","e","t"]), Conss([Atom(["j"]), Conss([Atom(["m","e","m"]), Nill])])]), Nill])])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["g","r","e","a","t","e","r","e","q","p"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","r","e","q","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["g","r","e","a","t","e","r","e","q","p","r"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","r","e","q","p","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["g","r","e","a","t","e","r","p"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["g","r","e","a","t","e","r","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["i","f"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])])]), Conss([Atom(["d"]), Conss([Atom(["e"]), Nill])])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["a"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["b"]), Conss([Atom(["d"]), Conss([Atom(["e"]), Nill])])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["c"]), Conss([Atom(["d"]), Conss([Atom(["e"]), Nill])])])]), Nill])])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["i","f","f"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["i","f","f"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["i","m","p","l","i","e","s"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["i","m","p","l","i","e","s"]), Conss([Atom(["p"]), Conss([Atom(["q"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["q"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Conss([Conss([Atom(["t"]), Nill]), Nill])])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["l","a","s","t"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","a","s","t"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["l","a","s","t"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["a"]), Nill])]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Conss([Atom(["c","a","r"]), Conss([Conss([Atom(["l","a","s","t"]), Conss([Atom(["a"]), Nill])]), Nill])]), Conss([Atom(["b"]), Nill])])]), Conss([Atom(["b"]), Nill])])])]), Nill])])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["l","e","n","g","t","h"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","1"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","2"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","3"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","4"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","5"]), Conss([Conss([Atom(["C","o","n","s","s"]), Conss([Atom(["x","6"]), Conss([Atom(["x","7"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]), Nill])])]), Nill])])]), Nill])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["6"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["x","7"]), Nill])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["x"]), Nill])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["l","e","s","s","e","q","p"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","e","q","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Nill])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["l","e","s","s","p"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Atom(["l"]), Nill])])]), Nill])]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["l"]), Nill])]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["l"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["z"]), Nill])]), Nill])]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["i"]), Nill])]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["j"]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["j"]), Conss([Atom(["1"]), Nill])])]), Nill])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","e","s","s","p"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Nill])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["l","i","s","t","p"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Conss([Atom(["g","o","p","h","e","r"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["l","i","s","t","p"]), Conss([Atom(["x"]), Nill])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["m","c","-","f","l","a","t","t","e","n"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","c","-","f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["y"]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["m","e","a","n","i","n","g"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["y"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["y"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Conss([Atom(["p","l","u","s","-","f","r","i","n","g","e"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["m","e","a","n","i","n","g"]), Conss([Conss([Atom(["p","l","u","s","-","t","r","e","e"]), Conss([Atom(["y"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["m","e","m","b","e","r"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["a"]), Conss([Conss([Atom(["i","n","t","e","r","s","e","c","t"]), Conss([Atom(["b"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["a"]), Conss([Atom(["c"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["m","e","m","b","e","r"]), Conss([Atom(["x"]), Conss([Atom(["b"]), Nill])])]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["n","o","t"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","o","t"]), Conss([Atom(["p"]), Nill])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["f"]), Nill]), Conss([Conss([Atom(["t"]), Nill]), Nill])])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["n","t","h"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","t","h"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["i"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","t","h"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["n","t","h"]), Conss([Atom(["a"]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["n","t","h"]), Conss([Atom(["b"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["i"]), Conss([Conss([Atom(["l","e","n","g","t","h"]), Conss([Atom(["a"]), Nill])]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","t","h"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["n","u","m","b","e","r","p"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Conss([Atom(["g","r","e","a","t","e","s","t","-","f","a","c","t","o","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])]), Nill])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["o","r"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["o","r"]), Conss([Atom(["p"]), Conss([Atom(["q"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["p"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["i","f"]), Conss([Atom(["q"]), Conss([Conss([Atom(["t"]), Nill]), Conss([Conss([Atom(["f"]), Nill]), Nill])])])]), Nill])])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["p","l","u","s"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["x"]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["p","o","w","e","r","-","e","v","a","l"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["b","i","g","-","p","l","u","s"]), Conss([Conss([Atom(["p","o","w","e","r","-","r","e","p"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","o","w","e","r","-","r","e","p"]), Conss([Atom(["j"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Atom(["b","a","s","e"]), Nill])])])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["i"]), Conss([Atom(["j"]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","r","e","p"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["i"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["b","i","g","-","p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["i"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Atom(["x"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Atom(["y"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Conss([Atom(["b","i","g","-","p","l","u","s","1"]), Conss([Atom(["l"]), Conss([Atom(["i"]), Conss([Atom(["b","a","s","e"]), Nill])])])]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["p","o","w","e","r","-","e","v","a","l"]), Conss([Atom(["l"]), Conss([Atom(["b","a","s","e"]), Nill])])]), Conss([Atom(["i"]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["p","r","i","m","e"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","r","i","m","e"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])]), Nill])])]), Nill])]), Conss([Conss([Atom(["p","r","i","m","e","1"]), Conss([Atom(["x"]), Conss([Conss([Atom(["1","-"]), Conss([Atom(["x"]), Nill])]), Nill])])]), Nill])])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["p","r","i","m","e","-","l","i","s","t"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["p","r","i","m","e","-","l","i","s","t"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["a","n","d"]), Conss([Conss([Atom(["p","r","i","m","e","-","l","i","s","t"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["p","r","i","m","e","-","l","i","s","t"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["q","u","o","t","i","e","n","t"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Atom(["2"]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Atom(["y"]), Conss([Atom(["2"]), Nill])])]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["r","e","m","a","i","n","d","e","r"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["x"]), Nill])])]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["x"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","m","a","i","n","d","e","r"]), Conss([Atom(["y"]), Conss([Atom(["1"]), Nill])])]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["r","e","v","e","r","s","e"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["a"]), Conss([Atom(["b"]), Nill])])]), Nill])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["b"]), Nill])]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["a"]), Nill])]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["r","e","v","e","r","s","e","-"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-","l","o","o","p"]), Conss([Atom(["x"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["r","e","v","e","r","s","e","-","l","o","o","p"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-","l","o","o","p"]), Conss([Atom(["x"]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["x"]), Nill])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["r","e","v","e","r","s","e","-","l","o","o","p"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Conss([Atom(["r","e","v","e","r","s","e"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["y"]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["s","a","m","e","f","r","i","n","g","e"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["s","a","m","e","f","r","i","n","g","e"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["f","l","a","t","t","e","n"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["s","i","g","m","a"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["s","i","g","m","a"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Conss([Atom(["i"]), Nill])])]), Conss([Conss([Atom(["q","u","o","t","i","e","n","t"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["i"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["i"]), Nill])]), Nill])])]), Conss([Atom(["2"]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["s","o","r","t","2"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["s","o","r","t","2"]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Atom(["l"]), Nill])])]), Nill])]), Conss([Conss([Atom(["d","e","l","e","t","e"]), Conss([Atom(["x"]), Conss([Conss([Atom(["s","o","r","t","2"]), Conss([Atom(["l"]), Nill])]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["t","a","u","t","o","l","o","g","y","-","c","h","e","c","k","e","r"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","a","u","t","o","l","o","g","y","-","c","h","e","c","k","e","r"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["t","a","u","t","o","l","o","g","y","p"]), Conss([Conss([Atom(["n","o","r","m","a","l","i","z","e"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["N","i","l","l"]), Nill]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["t","i","m","e","s"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["a","d","d","1"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Conss([Conss([Atom(["i","f"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["y"]), Nill])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["f","i","x"]), Conss([Atom(["x"]), Nill])]), Nill])])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Atom(["c"]), Conss([Atom(["w"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["d","i","f","f","e","r","e","n","c","e"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["c"]), Conss([Atom(["x"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["w"]), Conss([Atom(["x"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Atom(["z"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])]),Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Atom(["y"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Conss([Conss([Atom(["p","l","u","s"]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Atom(["x"]), Conss([Atom(["z"]), Nill])])]), Nill])])]), Nill])])])] -//│ ], -//│ Node( -//│ [ -//│ Node( -//│ [ -//│ Empty, -//│ [ -//│ ["t","i","m","e","s","-","l","i","s","t"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["t","i","m","e","s","-","l","i","s","t"]), Conss([Conss([Atom(["a","p","p","e","n","d"]), Conss([Atom(["x"]), Conss([Atom(["y"]), Nill])])]), Nill])]), Conss([Conss([Atom(["t","i","m","e","s"]), Conss([Conss([Atom(["t","i","m","e","s","-","l","i","s","t"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["t","i","m","e","s","-","l","i","s","t"]), Conss([Atom(["y"]), Nill])]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ), -//│ [ -//│ ["v","a","l","u","e"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["v","a","l","u","e"]), Conss([Conss([Atom(["n","o","r","m","a","l","i","z","e"]), Conss([Atom(["x"]), Nill])]), Conss([Atom(["a"]), Nill])])]), Conss([Conss([Atom(["v","a","l","u","e"]), Conss([Atom(["x"]), Conss([Atom(["a"]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ), -//│ [ -//│ ["z","e","r","o","p"], -//│ [Conss([Atom(["e","q","u","a","l"]), Conss([Conss([Atom(["z","e","r","o","p"]), Conss([Atom(["x"]), Nill])]), Conss([Conss([Atom(["o","r"]), Conss([Conss([Atom(["e","q","u","a","l"]), Conss([Atom(["x"]), Conss([Conss([Atom(["z","e","r","o"]), Nill]), Nill])])]), Conss([Conss([Atom(["n","o","t"]), Conss([Conss([Atom(["n","u","m","b","e","r","p"]), Conss([Atom(["x"]), Nill])]), Nill])]), Nill])])]), Nill])])])] -//│ ], -//│ Empty -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ) -//│ ] -//│ ) fun tautp(term) = tautologyp([rewrite(term, lemmas), Nill, Nill]) From 62ca88236862e30cc359659eb68fdbddef347a6d Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:09:40 +0800 Subject: [PATCH 57/62] Improve an error message --- .../src/main/scala/hkmc2/semantics/Pattern.scala | 15 +++++++++++++++ .../scala/hkmc2/semantics/ucs/Normalization.scala | 2 +- .../test/mlscript/rp/syntax/WrongArguments.mls | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala index ac6267399c..032e8014a6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Pattern.scala @@ -279,6 +279,21 @@ enum Pattern extends AutoLocated: case Alias(pattern, _) => pattern.subTerms case Transform(pattern, transform) => pattern.subTerms :+ transform + def describe: Str = this match + case Constructor(_, _, _) => "constructor" + case Composition(true, _, _) => "disjunction" + case Composition(false, _, _) => "conjunction" + case Negation(_) => "negation" + case Wildcard() => "wildcard" + case Literal(_) => "literal" + case Range(_, _, _) => "range" + case Concatenation(_, _) => "concatenation" + case Tuple(_, _, _) => "tuple" + case Record(_) => "record" + case Chain(_, _) => "chain" + case Alias(_, _) => "alias" + case Transform(_, _) => "transform" + private def showDbgWithPar = val addPar = this match case _: (Constructor | Wildcard | Literal | Tuple | Record | Negation) => false 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 41b2f9b1e4..94199dbc66 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -294,7 +294,7 @@ class Normalization(using tl: TL)(using Raise, Ctx, State) extends TermSynthesiz _.flatMap: case arg: FlatPattern.Argument.Term => S(arg) case FlatPattern.Argument.Pattern(_, pattern) => - error(msg"The pattern argument cannot be used here." -> pattern.toLoc); N + 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( diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArguments.mls b/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArguments.mls index ca7fa3122e..98649d3c2e 100644 --- a/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArguments.mls +++ b/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArguments.mls @@ -4,7 +4,7 @@ class Foo(val bar: Int) :e pattern Bar = Foo(pattern 0) -//│ ╔══[ERROR] The pattern argument cannot be used here. +//│ ╔══[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. From 2b722c98b4cc3eccc47968b2058fd01294a98fad Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:19:13 +0800 Subject: [PATCH 58/62] Document a test which should be reported --- hkmc2/shared/src/test/mlscript/rp/Future.mls | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/rp/Future.mls index dbf939c63d..a68ca1b139 100644 --- a/hkmc2/shared/src/test/mlscript/rp/Future.mls +++ b/hkmc2/shared/src/test/mlscript/rp/Future.mls @@ -110,5 +110,8 @@ pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ║ l.105: pattern Test(foo, bar) = ("foo" as foo) ~ ("bar" as bar) //│ ╙── ^^^ -:todo // Should report errors +:todo +// This is tokenized into: +// ┊pattern┊ ┊Email┊(⟨┊name┊,┊ ┊domain┊⟩)┊ ┊=┊ ┊→⟨┊undefined┊⟩←┊ +// We should report this error. pattern Email(name, domain) = ... From 4143501c344ac330f8fff277df5399f390729126 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:23:34 +0800 Subject: [PATCH 59/62] Remove an outdated comment --- hkmc2/shared/src/test/mlscript/std/RenderingTest.mls | 2 -- 1 file changed, 2 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls b/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls index f8aaef3b86..207b904e23 100644 --- a/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls @@ -240,8 +240,6 @@ tree // Test: nested circular reference. -// If the same circular object appears multiple times, it should be rendered as -// the same reference. let b = mut [a, a] //│ b = [{x: ref'1, y: 1} as ref'1, {x: ref'2, y: 1} as ref'2] From cc7cb3684666896b2a88937ae6288bf0fc2ef4d7 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:27:31 +0800 Subject: [PATCH 60/62] Add a test to show the difference of two rendering modes --- hkmc2/shared/src/test/mlscript/std/RenderingTest.mls | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls b/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls index 207b904e23..ea5fdfde51 100644 --- a/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/RenderingTest.mls @@ -243,6 +243,11 @@ tree let b = mut [a, a] //│ b = [{x: ref'1, y: 1} as ref'1, {x: ref'2, y: 1} as ref'2] +// If the same circular object appears multiple times, it should be rendered as +// the same reference in single-line mode. +render(b) +//│ = "[{x: ref'1, y: 1} as ref'1, {x: ref'1, y: 1} as ref'1]" + b.push(b) //│ = 3 From e0e6d1f89a114f2c2a1cd102564bf14b820def1a Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:35:25 +0800 Subject: [PATCH 61/62] Rename the test folder rp to ups --- hkmc2/shared/src/test/mlscript/{rp => ups}/Future.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/JoinPatterns.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/LocalPatterns.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/MatchResult.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/RangePatterns.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/SimpleConjunction.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/SimpleTransform.mls | 0 .../shared/src/test/mlscript/{rp => ups}/examples/Computation.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/examples/DnfCnf.mls | 0 .../shared/src/test/mlscript/{rp => ups}/examples/DoubleOrSum.mls | 0 .../src/test/mlscript/{rp => ups}/examples/DoubleTripleList.mls | 0 .../shared/src/test/mlscript/{rp => ups}/examples/Extraction.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/examples/Flatten.mls | 0 .../src/test/mlscript/{rp => ups}/examples/ListPredicates.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/examples/Negation.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/examples/Record.mls | 0 .../shared/src/test/mlscript/{rp => ups}/examples/TupleSpread.mls | 0 .../test/mlscript/{rp => ups}/nondeterminism/BitArithmetic.mls | 0 .../src/test/mlscript/{rp => ups}/nondeterminism/EvenOddTree.mls | 0 .../src/test/mlscript/{rp => ups}/nondeterminism/LaRbTree.mls | 0 .../src/test/mlscript/{rp => ups}/parametric/EtaConversion.mls | 0 .../test/mlscript/{rp => ups}/parametric/HigherOrderPattern.mls | 0 .../shared/src/test/mlscript/{rp => ups}/parametric/ListLike.mls | 0 .../shared/src/test/mlscript/{rp => ups}/parametric/Nullable.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/recursion/BitSeq.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/recursion/BitTree.mls | 0 .../src/test/mlscript/{rp => ups}/recursion/LeafEvenOddTree.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/recursion/NatBox.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/recursion/NullTree.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/recursion/SignBox.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/regex/EmailAddress.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/regex/EmptyString.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/regex/Identifier.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/regex/Number.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/regex/Separation.mls | 0 .../shared/src/test/mlscript/{rp => ups}/regex/Simplification.mls | 0 .../shared/src/test/mlscript/{rp => ups}/regex/TailRepetition.mls | 0 .../src/test/mlscript/{rp => ups}/specialization/SimpleList.mls | 0 .../test/mlscript/{rp => ups}/specialization/SimpleLiterals.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/syntax/Declaration.mls | 0 .../src/test/mlscript/{rp => ups}/syntax/InterestingPatterns.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/syntax/PatternBody.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/syntax/Precedence.mls | 0 .../src/test/mlscript/{rp => ups}/syntax/WrongArguments.mls | 0 hkmc2/shared/src/test/mlscript/{rp => ups}/syntax/WrongArity.mls | 0 45 files changed, 0 insertions(+), 0 deletions(-) rename hkmc2/shared/src/test/mlscript/{rp => ups}/Future.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/JoinPatterns.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/LocalPatterns.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/MatchResult.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/RangePatterns.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/SimpleConjunction.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/SimpleTransform.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/examples/Computation.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/examples/DnfCnf.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/examples/DoubleOrSum.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/examples/DoubleTripleList.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/examples/Extraction.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/examples/Flatten.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/examples/ListPredicates.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/examples/Negation.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/examples/Record.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/examples/TupleSpread.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/nondeterminism/BitArithmetic.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/nondeterminism/EvenOddTree.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/nondeterminism/LaRbTree.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/parametric/EtaConversion.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/parametric/HigherOrderPattern.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/parametric/ListLike.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/parametric/Nullable.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/recursion/BitSeq.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/recursion/BitTree.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/recursion/LeafEvenOddTree.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/recursion/NatBox.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/recursion/NullTree.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/recursion/SignBox.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/regex/EmailAddress.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/regex/EmptyString.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/regex/Identifier.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/regex/Number.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/regex/Separation.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/regex/Simplification.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/regex/TailRepetition.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/specialization/SimpleList.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/specialization/SimpleLiterals.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/syntax/Declaration.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/syntax/InterestingPatterns.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/syntax/PatternBody.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/syntax/Precedence.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/syntax/WrongArguments.mls (100%) rename hkmc2/shared/src/test/mlscript/{rp => ups}/syntax/WrongArity.mls (100%) diff --git a/hkmc2/shared/src/test/mlscript/rp/Future.mls b/hkmc2/shared/src/test/mlscript/ups/Future.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/Future.mls rename to hkmc2/shared/src/test/mlscript/ups/Future.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/JoinPatterns.mls b/hkmc2/shared/src/test/mlscript/ups/JoinPatterns.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/JoinPatterns.mls rename to hkmc2/shared/src/test/mlscript/ups/JoinPatterns.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls b/hkmc2/shared/src/test/mlscript/ups/LocalPatterns.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/LocalPatterns.mls rename to hkmc2/shared/src/test/mlscript/ups/LocalPatterns.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/MatchResult.mls b/hkmc2/shared/src/test/mlscript/ups/MatchResult.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/MatchResult.mls rename to hkmc2/shared/src/test/mlscript/ups/MatchResult.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls b/hkmc2/shared/src/test/mlscript/ups/RangePatterns.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/RangePatterns.mls rename to hkmc2/shared/src/test/mlscript/ups/RangePatterns.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/SimpleConjunction.mls b/hkmc2/shared/src/test/mlscript/ups/SimpleConjunction.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/SimpleConjunction.mls rename to hkmc2/shared/src/test/mlscript/ups/SimpleConjunction.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls b/hkmc2/shared/src/test/mlscript/ups/SimpleTransform.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/SimpleTransform.mls rename to hkmc2/shared/src/test/mlscript/ups/SimpleTransform.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Computation.mls b/hkmc2/shared/src/test/mlscript/ups/examples/Computation.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/Computation.mls rename to hkmc2/shared/src/test/mlscript/ups/examples/Computation.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls b/hkmc2/shared/src/test/mlscript/ups/examples/DnfCnf.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/DnfCnf.mls rename to hkmc2/shared/src/test/mlscript/ups/examples/DnfCnf.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/DoubleOrSum.mls b/hkmc2/shared/src/test/mlscript/ups/examples/DoubleOrSum.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/DoubleOrSum.mls rename to hkmc2/shared/src/test/mlscript/ups/examples/DoubleOrSum.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/DoubleTripleList.mls b/hkmc2/shared/src/test/mlscript/ups/examples/DoubleTripleList.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/DoubleTripleList.mls rename to hkmc2/shared/src/test/mlscript/ups/examples/DoubleTripleList.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Extraction.mls b/hkmc2/shared/src/test/mlscript/ups/examples/Extraction.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/Extraction.mls rename to hkmc2/shared/src/test/mlscript/ups/examples/Extraction.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Flatten.mls b/hkmc2/shared/src/test/mlscript/ups/examples/Flatten.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/Flatten.mls rename to hkmc2/shared/src/test/mlscript/ups/examples/Flatten.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/ListPredicates.mls b/hkmc2/shared/src/test/mlscript/ups/examples/ListPredicates.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/ListPredicates.mls rename to hkmc2/shared/src/test/mlscript/ups/examples/ListPredicates.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Negation.mls b/hkmc2/shared/src/test/mlscript/ups/examples/Negation.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/Negation.mls rename to hkmc2/shared/src/test/mlscript/ups/examples/Negation.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/Record.mls b/hkmc2/shared/src/test/mlscript/ups/examples/Record.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/Record.mls rename to hkmc2/shared/src/test/mlscript/ups/examples/Record.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/examples/TupleSpread.mls b/hkmc2/shared/src/test/mlscript/ups/examples/TupleSpread.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/examples/TupleSpread.mls rename to hkmc2/shared/src/test/mlscript/ups/examples/TupleSpread.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/nondeterminism/BitArithmetic.mls b/hkmc2/shared/src/test/mlscript/ups/nondeterminism/BitArithmetic.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/nondeterminism/BitArithmetic.mls rename to hkmc2/shared/src/test/mlscript/ups/nondeterminism/BitArithmetic.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/nondeterminism/EvenOddTree.mls b/hkmc2/shared/src/test/mlscript/ups/nondeterminism/EvenOddTree.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/nondeterminism/EvenOddTree.mls rename to hkmc2/shared/src/test/mlscript/ups/nondeterminism/EvenOddTree.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/nondeterminism/LaRbTree.mls b/hkmc2/shared/src/test/mlscript/ups/nondeterminism/LaRbTree.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/nondeterminism/LaRbTree.mls rename to hkmc2/shared/src/test/mlscript/ups/nondeterminism/LaRbTree.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/parametric/EtaConversion.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/parametric/EtaConversion.mls rename to hkmc2/shared/src/test/mlscript/ups/parametric/EtaConversion.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/parametric/HigherOrderPattern.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/parametric/HigherOrderPattern.mls rename to hkmc2/shared/src/test/mlscript/ups/parametric/HigherOrderPattern.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/parametric/ListLike.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/ListLike.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/parametric/ListLike.mls rename to hkmc2/shared/src/test/mlscript/ups/parametric/ListLike.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/parametric/Nullable.mls b/hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/parametric/Nullable.mls rename to hkmc2/shared/src/test/mlscript/ups/parametric/Nullable.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/recursion/BitSeq.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/BitSeq.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/recursion/BitSeq.mls rename to hkmc2/shared/src/test/mlscript/ups/recursion/BitSeq.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/recursion/BitTree.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/BitTree.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/recursion/BitTree.mls rename to hkmc2/shared/src/test/mlscript/ups/recursion/BitTree.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/recursion/LeafEvenOddTree.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/LeafEvenOddTree.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/recursion/LeafEvenOddTree.mls rename to hkmc2/shared/src/test/mlscript/ups/recursion/LeafEvenOddTree.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/recursion/NatBox.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/NatBox.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/recursion/NatBox.mls rename to hkmc2/shared/src/test/mlscript/ups/recursion/NatBox.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/recursion/NullTree.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/NullTree.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/recursion/NullTree.mls rename to hkmc2/shared/src/test/mlscript/ups/recursion/NullTree.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/recursion/SignBox.mls b/hkmc2/shared/src/test/mlscript/ups/recursion/SignBox.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/recursion/SignBox.mls rename to hkmc2/shared/src/test/mlscript/ups/recursion/SignBox.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/EmailAddress.mls b/hkmc2/shared/src/test/mlscript/ups/regex/EmailAddress.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/regex/EmailAddress.mls rename to hkmc2/shared/src/test/mlscript/ups/regex/EmailAddress.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/EmptyString.mls b/hkmc2/shared/src/test/mlscript/ups/regex/EmptyString.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/regex/EmptyString.mls rename to hkmc2/shared/src/test/mlscript/ups/regex/EmptyString.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/Identifier.mls b/hkmc2/shared/src/test/mlscript/ups/regex/Identifier.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/regex/Identifier.mls rename to hkmc2/shared/src/test/mlscript/ups/regex/Identifier.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/Number.mls b/hkmc2/shared/src/test/mlscript/ups/regex/Number.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/regex/Number.mls rename to hkmc2/shared/src/test/mlscript/ups/regex/Number.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/Separation.mls b/hkmc2/shared/src/test/mlscript/ups/regex/Separation.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/regex/Separation.mls rename to hkmc2/shared/src/test/mlscript/ups/regex/Separation.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/Simplification.mls b/hkmc2/shared/src/test/mlscript/ups/regex/Simplification.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/regex/Simplification.mls rename to hkmc2/shared/src/test/mlscript/ups/regex/Simplification.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/regex/TailRepetition.mls b/hkmc2/shared/src/test/mlscript/ups/regex/TailRepetition.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/regex/TailRepetition.mls rename to hkmc2/shared/src/test/mlscript/ups/regex/TailRepetition.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls b/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleList.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/specialization/SimpleList.mls rename to hkmc2/shared/src/test/mlscript/ups/specialization/SimpleList.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/specialization/SimpleLiterals.mls b/hkmc2/shared/src/test/mlscript/ups/specialization/SimpleLiterals.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/specialization/SimpleLiterals.mls rename to hkmc2/shared/src/test/mlscript/ups/specialization/SimpleLiterals.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/Declaration.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/syntax/Declaration.mls rename to hkmc2/shared/src/test/mlscript/ups/syntax/Declaration.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/InterestingPatterns.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/syntax/InterestingPatterns.mls rename to hkmc2/shared/src/test/mlscript/ups/syntax/InterestingPatterns.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/syntax/PatternBody.mls rename to hkmc2/shared/src/test/mlscript/ups/syntax/PatternBody.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/Precedence.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/Precedence.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/syntax/Precedence.mls rename to hkmc2/shared/src/test/mlscript/ups/syntax/Precedence.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArguments.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/syntax/WrongArguments.mls rename to hkmc2/shared/src/test/mlscript/ups/syntax/WrongArguments.mls diff --git a/hkmc2/shared/src/test/mlscript/rp/syntax/WrongArity.mls b/hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/rp/syntax/WrongArity.mls rename to hkmc2/shared/src/test/mlscript/ups/syntax/WrongArity.mls From 739040232d79b116dd7c8ac9dc6b658dc76d1b4e Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 12 Aug 2025 12:28:21 +0800 Subject: [PATCH 62/62] Revert needless change --- .../src/main/scala/hkmc2/codegen/Lowering.scala | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index a04d02ffce..237c8beb91 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -342,14 +342,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx): val isMlsFun = f.resolvedSymbol.fold(f.isInstanceOf[st.Lam]): case _: sem.BuiltinSymbol => true case sym: sem.BlockMemberSymbol => - // Do no check if the symbol refers to a function definition or - sym.trmImplTree.fold(sym.clsTree.isDefined)(_.k is syntax.Fun) || ( - // it refers to classes `MatchResult` or `MatchFailure`. - sym.asCls match - case S(cls) => (sym is State.matchResultClsSymbol) || - (sym is State.matchFailureClsSymbol) - case N => false) - case _ => false + sym.trmImplTree.fold(sym.clsTree.isDefined)(_.k is syntax.Fun) + // Do not perform safety check on `MatchResult` and `MatchFailure`. + case sym => (sym is State.matchResultClsSymbol) || + (sym is State.matchFailureClsSymbol) def conclude(fr: Path) = arg match case Tup(fs) =>