From 9a623d597aab8a9636ca63567eea36e39edb503d Mon Sep 17 00:00:00 2001 From: Oscar Boykin Date: Sat, 7 Dec 2024 11:58:28 -1000 Subject: [PATCH 1/4] Remove SearchList from Matchless --- .../scala/org/bykn/bosatsu/Matchless.scala | 177 +++++++++++++++--- .../org/bykn/bosatsu/MatchlessToValue.scala | 85 +++------ .../bykn/bosatsu/codegen/clang/ClangGen.scala | 66 ++++--- .../bosatsu/codegen/python/PythonGen.scala | 49 +++-- 4 files changed, 232 insertions(+), 145 deletions(-) diff --git a/core/src/main/scala/org/bykn/bosatsu/Matchless.scala b/core/src/main/scala/org/bykn/bosatsu/Matchless.scala index d83ad5419..44ed27019 100644 --- a/core/src/main/scala/org/bykn/bosatsu/Matchless.scala +++ b/core/src/main/scala/org/bykn/bosatsu/Matchless.scala @@ -106,17 +106,6 @@ object Matchless { size: Int, famArities: List[Int] ) extends BoolExpr - // handle list matching, this is a while loop, that is evaluting - // lst is initialized to init, leftAcc is initialized to empty - // tail until it is true while mutating lst => lst.tail - // this has the side-effect of mutating lst and leftAcc as well as any side effects that check has - // which could have nested searches of its own - case class SearchList( - lst: LocalAnonMut, - init: CheapExpr, - check: BoolExpr, - leftAcc: Option[LocalAnonMut] - ) extends BoolExpr // set the mutable variable to the given expr and return true // string matching is complex done at a lower level case class MatchString( @@ -128,6 +117,17 @@ object Matchless { // set the mutable variable to the given expr and return true case class SetMut(target: LocalAnonMut, expr: Expr) extends BoolExpr case object TrueConst extends BoolExpr + case class LetBool( + arg: Either[LocalAnon, Bindable], + expr: Expr, + in: BoolExpr + ) extends BoolExpr + + case class LetMutBool(name: LocalAnonMut, span: BoolExpr) extends BoolExpr + object LetMutBool { + def apply(lst: List[LocalAnonMut], span: BoolExpr): BoolExpr = + lst.foldRight(span)(LetMutBool(_, _)) + } def hasSideEffect(bx: BoolExpr): Boolean = bx match { @@ -137,8 +137,31 @@ object Matchless { false case MatchString(_, _, b, _) => b.nonEmpty case And(b1, b2) => hasSideEffect(b1) || hasSideEffect(b2) - case SearchList(_, _, b, l) => - l.nonEmpty || hasSideEffect(b) + case LetBool(_, x, b) => + hasSideEffect(b) || hasSideEffect(x) + case LetMutBool(_, b) => hasSideEffect(b) + } + + def hasSideEffect(bx: Expr): Boolean = + bx match { + case _: CheapExpr => false + case Always(b, x) => hasSideEffect(b) || hasSideEffect(x) + case App(f, as) => + (f :: as).exists(hasSideEffect(_)) + case If(c, t, f) => + hasSideEffect(c) || hasSideEffect(t) || hasSideEffect(f) + case Let(_, x, b) => + hasSideEffect(b) || hasSideEffect(x) + case LetMut(_, in) => hasSideEffect(in) + case PrevNat(n) => hasSideEffect(n) + case MakeEnum(_, _, _) | MakeStruct(_) | SuccNat | ZeroNat | Lambda(_, _, _, _) => + // making a lambda or const is a pure function + false + case WhileExpr(_, _, _) => + // not all while loops have side effects technically, but we assume yes + // for now. We could have a list of all the known control mutables here + // but it seems hard + true } case class If(cond: BoolExpr, thenExpr: Expr, elseExpr: Expr) extends Expr { @@ -191,13 +214,30 @@ object Matchless { extends ConsExpr private val boolFamArities = 0 :: 0 :: Nil + private val listFamArities = 0 :: 2 :: Nil val FalseExpr: Expr = MakeEnum(0, 0, boolFamArities) val TrueExpr: Expr = MakeEnum(1, 0, boolFamArities) val UnitExpr: Expr = MakeStruct(0) def isTrueExpr(e: CheapExpr): BoolExpr = CheckVariant(e, 1, 0, boolFamArities) - + + object ListExpr { + val Nil: Expr = MakeEnum(0, 0, listFamArities) + private val consFn = MakeEnum(1, 2, listFamArities) + + def cons(h: Expr, t: Expr): Expr = + App(consFn, NonEmptyList(h, t :: List.empty)) + + def notNil(e: CheapExpr): BoolExpr = + CheckVariant(e, 1, 2, listFamArities) + + def head(arg: CheapExpr): CheapExpr = + GetEnumElement(arg, 1, 0, 2) + + def tail(arg: CheapExpr): CheapExpr = + GetEnumElement(arg, 1, 1, 2) + } case class MakeStruct(arity: Int) extends ConsExpr case object ZeroNat extends ConsExpr { def arity = 0 @@ -325,11 +365,14 @@ object Matchless { CheckVariant(translateLocalsCheap(m, expr), expect, sz, fam) case ms: MatchString => ms.copy(arg = translateLocalsCheap(m, ms.arg)) - case sl: SearchList => - sl.copy( - init = translateLocalsCheap(m, sl.init), - check = translateLocalsBool(m, sl.check) - ) + case LetBool(b, a, in) => + val m1 = b match { + case Right(b) => m - b + case _ => m + } + LetBool(b, translateLocals(m, a), translateLocalsBool(m1, in)) + case LetMutBool(b, in) => + LetMutBool(b, translateLocalsBool(m, in)) } def translateLocals(m: Map[Bindable, LocalAnonMut], e: Expr): Expr = @@ -380,10 +423,6 @@ object Matchless { args: NonEmptyList[Bindable], body: Expr): F[Expr] = { - def setAll(ls: List[(LocalAnonMut, Expr)], ret: Expr): Expr = - ls.foldRight(ret) { case ((l, e), r) => - Always(SetMut(l, e), r) - } // assign any results to result and set the condition to false // and replace any tail calls to nm(args) with assigning args to those values case class ArgRecord(name: Bindable, tmp: LocalAnon, loopVar: LocalAnonMut) @@ -631,6 +670,82 @@ object Matchless { } } + // handle list matching, this is a while loop, that is evaluting + // lst is initialized to init, leftAcc is initialized to empty + // tail until it is true while mutating lst => lst.tail + // this has the side-effect of mutating lst and leftAcc as well as any side effects that check has + // which could have nested searches of its own + def searchList( + lst: LocalAnonMut, + init: CheapExpr, + check: BoolExpr, + leftAcc: Option[LocalAnonMut] + ): F[BoolExpr] = { + ( + makeAnon.map(LocalAnonMut(_)), + makeAnon.map(LocalAnon(_)), + makeAnon.map(LocalAnonMut(_)) + ) + .mapN { (resMut, letBind, currentList) => + val initSets = + (resMut, FalseExpr) :: + (currentList, init) :: + (leftAcc.toList.map { left => + (left, ListExpr.Nil) + }) + + val whileCheck = ListExpr.notNil(currentList) + val effect: Expr = { + setAll((lst, currentList) :: Nil, + If(check, { + setAll( + (currentList, ListExpr.Nil) :: + (resMut, TrueExpr) :: + Nil, + UnitExpr + ) + }, { + setAll( + (currentList, ListExpr.tail(currentList)) :: + leftAcc.toList.map { left => + (left, ListExpr.cons(ListExpr.head(currentList), left)) + }, + UnitExpr + ) + })) + } + val searchLoop = setAll(initSets, WhileExpr(whileCheck, effect, resMut)) + + LetMutBool(resMut :: currentList :: Nil, + LetBool(Left(letBind), searchLoop, isTrueExpr(resMut))) + } + /* + Dynamic { (scope: Scope) => + var res = false + var currentList = initF(scope) + var leftList = VList.VNil + scope.updateMut(left, leftList) + while (currentList ne null) { + currentList match { + case nonempty @ VList.Cons(head, tail) => + scope.updateMut(mutV, nonempty) + res = checkF(scope) + if (res) { currentList = null } + else { + currentList = tail + leftList = VList.Cons(head, leftList) + scope.updateMut(left, leftList) + } + case _ => + currentList = null + // we don't match empty lists + } + } + res + } + */ + } + // return the check expression for the check we need to do, and the list of bindings // if must match is true, we know that the pattern must match, so we can potentially remove some checks def doesMatch( @@ -708,8 +823,8 @@ object Matchless { val anonList = LocalAnonMut(tmpList) doesMatch(anonList, Pattern.ListPat(right.toList), false) - .map { cases => - cases.map { + .flatMap { cases => + cases.traverse { case (_, TrueConst, _) => // $COVERAGE-OFF$ @@ -737,11 +852,8 @@ object Matchless { (letTail, None, binds) } - ( - resLet, - SearchList(anonList, arg, expr, leftOpt), - resBind - ) + searchList(anonList, arg, expr, leftOpt) + .map { s => (resLet, s, resBind) } } } } @@ -969,6 +1081,11 @@ object Matchless { LetMut(anon, rest) } + def setAll(ls: List[(LocalAnonMut, Expr)], ret: Expr): Expr = + ls.foldRight(ret) { case ((l, e), r) => + Always(SetMut(l, e), r) + } + def matchExpr( arg: Expr, tmp: F[Long], diff --git a/core/src/main/scala/org/bykn/bosatsu/MatchlessToValue.scala b/core/src/main/scala/org/bykn/bosatsu/MatchlessToValue.scala index 4d8a56c83..95823152d 100644 --- a/core/src/main/scala/org/bykn/bosatsu/MatchlessToValue.scala +++ b/core/src/main/scala/org/bykn/bosatsu/MatchlessToValue.scala @@ -107,6 +107,8 @@ object MatchlessToValue { val mut1 = muts ++ idxs.map(l => (l, new Cell)) copy(muts = mut1) } + def letMut(idx: Long): Scope = + copy(muts = muts.updated(idx, new Cell)) def letAll(bs: NonEmptyList[Bindable], vs: NonEmptyList[Value]): Scope = { val b = bs.iterator @@ -239,6 +241,28 @@ object MatchlessToValue { scope.updateMut(mut, exprF(scope)) true } + case LetBool(localOrBind, value, in) => + val valueF = loop(value) + val inF = boolExpr(in) + + localOrBind match { + case Right(b) => + inF.withScope { (scope: Scope) => + val vv = Eval.now(valueF(scope)) + scope.let(b, vv) + } + case Left(LocalAnon(l)) => + inF.withScope { (scope: Scope) => + val vv = valueF(scope) + scope.copy(anon = scope.anon.updated(l, vv)) + } + } + case LetMutBool(LocalAnonMut(ident), in) => + val inF = boolExpr(in) + Dynamic { (scope: Scope) => + val scope1 = scope.letMut(ident) + inF(scope1) + } case MatchString(str, pat, binds, _) => // do this before we evaluate the string binds match { @@ -271,67 +295,6 @@ object MatchlessToValue { } else false } } - - case SearchList(LocalAnonMut(mutV), init, check, None) => - val initF = loop(init) - val checkF = boolExpr(check) - - // TODO we could optimize - // cases where checkF is Static(false) or Static(true) - // but that is probably so rare I don't know if it will - // help - // e.g. [*_, _] should have been normalized - // into [_, *_] which wouldn't trigger - // this branch - Dynamic { (scope: Scope) => - var currentList = initF(scope) - var res = false - while (currentList ne null) { - currentList match { - case nonempty @ VList.Cons(_, tail) => - scope.updateMut(mutV, nonempty) - res = checkF(scope) - if (res) { currentList = null } - else { currentList = tail } - case _ => - currentList = null - // we don't match empty lists - } - } - res - } - case SearchList( - LocalAnonMut(mutV), - init, - check, - Some(LocalAnonMut(left)) - ) => - val initF = loop(init) - val checkF = boolExpr(check) - - // this is always dynamic - Dynamic { (scope: Scope) => - var res = false - var currentList = initF(scope) - var leftList = VList.VNil - while (currentList ne null) { - currentList match { - case nonempty @ VList.Cons(head, tail) => - scope.updateMut(mutV, nonempty) - scope.updateMut(left, leftList) - res = checkF(scope) - if (res) { currentList = null } - else { - currentList = tail - leftList = VList.Cons(head, leftList) - } - case _ => - currentList = null - // we don't match empty lists - } - } - res - } } // the locals can be recusive, so we box into Eval for laziness diff --git a/core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala b/core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala index 9b1bd9e8f..04a32b05a 100644 --- a/core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala +++ b/core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala @@ -295,6 +295,32 @@ object ClangGen { ) } + def handleLet(name: Either[LocalAnon, Bindable], argV: Expr, in: T[Code.ValueLike]): T[Code.ValueLike] = + name match { + case Right(arg) => + // arg isn't in scope for argV + innerToValue(argV).flatMap { v => + bind(arg) { + for { + name <- getBinding(arg) + result <- in + stmt <- Code.ValueLike.declareVar(Code.TypeIdent.BValue, name, v)(newLocalName) + } yield stmt +: result + } + } + case Left(LocalAnon(idx)) => + // LocalAnon(idx) isn't in scope for argV + innerToValue(argV) + .flatMap { v => + bindAnon(idx) { + for { + name <- getAnon(idx) + result <- in + stmt <- Code.ValueLike.declareVar(Code.TypeIdent.BValue, name, v)(newLocalName) + } yield stmt +: result + } + } + } // The type of this value must be a C _Bool def boolToValue(boolExpr: BoolExpr): T[Code.ValueLike] = boolExpr match { @@ -337,11 +363,6 @@ object ClangGen { // this is just get_variant(expr) == expect vl.onExpr { expr => pv(Code.Ident("get_variant")(expr) =:= Code.IntLiteral(expect)) }(newLocalName) } - case SearchList(lst, init, check, leftAcc) => - (boolToValue(check), innerToValue(init)) - .flatMapN { (condV, initV) => - searchList(lst, initV, condV, leftAcc) - } case MatchString(arg, parts, binds, mustMatch) => ( innerToValue(arg), @@ -355,6 +376,16 @@ object ClangGen { vl <- innerToValue(expr) } yield (name := vl) +: Code.TrueLit case TrueConst => pv(Code.TrueLit) + case LetBool(name, argV, in) => + handleLet(name, argV, boolToValue(in)) + case LetMutBool(LocalAnonMut(m), span) => + bindAnon(m) { + for { + ident <- getAnon(m) + decl = Code.DeclareVar(Nil, Code.TypeIdent.BValue, ident, None) + res <- boolToValue(span) + } yield decl +: res + } } object StringApi { @@ -941,29 +972,8 @@ object ClangGen { def innerToValue(expr: Expr): T[Code.ValueLike] = expr match { case fn: FnExpr => innerFn(fn) - case Let(Right(arg), argV, in) => - // arg isn't in scope for argV - innerToValue(argV).flatMap { v => - bind(arg) { - for { - name <- getBinding(arg) - result <- innerToValue(in) - stmt <- Code.ValueLike.declareVar(Code.TypeIdent.BValue, name, v)(newLocalName) - } yield stmt +: result - } - } - case Let(Left(LocalAnon(idx)), argV, in) => - // LocalAnon(idx) isn't in scope for argV - innerToValue(argV) - .flatMap { v => - bindAnon(idx) { - for { - name <- getAnon(idx) - result <- innerToValue(in) - stmt <- Code.ValueLike.declareVar(Code.TypeIdent.BValue, name, v)(newLocalName) - } yield stmt +: result - } - } + case Let(name, argV, in) => + handleLet(name, argV, innerToValue(in)) case app @ App(_, _) => innerApp(app) case Global(pack, name) => directFn(pack, name) diff --git a/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala b/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala index 126899c34..1d0e2999d 100644 --- a/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala +++ b/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala @@ -1221,13 +1221,11 @@ object PythonGen { ).flatMapN { (strVL, binds) => Env.onLastM(strVL)(matchString(_, pat, binds, mustMatch)) } - case SearchList(locMut, init, check, optLeft) => - // check to see if we can find a non-empty - // list that matches check - (loop(init, slotName), boolExpr(check, slotName)).flatMapN { - (initVL, checkVL) => - searchList(locMut, initVL, checkVL, optLeft) - } + case LetBool(n, v, in) => + doLet(n, v, boolExpr(in, slotName), slotName) + case LetMutBool(_, in) => + // in python we just ignore this + boolExpr(in, slotName) } def matchString( @@ -1690,6 +1688,23 @@ object PythonGen { } yield (Some(slots := tup), resVal) } + def doLet(name: Either[LocalAnon, Bindable], value: Expr, inF: Env[ValueLike], slotName: Option[Code.Ident]): Env[ValueLike] = + name match { + case Right(b) => + // value b is in scope after ve + for { + ve <- loop(value, slotName) + bi <- Env.bind(b) + ine <- inF + _ <- Env.unbind(b) + } yield ((bi := ve).withValue(ine)) + case Left(LocalAnon(l)) => + // anonymous names never shadow + (Env.nameForAnon(l), loop(value, slotName)) + .flatMapN { (bi, vE) => + inF.map((bi := vE).withValue(_)) + } + } def loop(expr: Expr, slotName: Option[Code.Ident]): Env[ValueLike] = expr match { case Lambda(captures, recName, args, res) => @@ -1801,25 +1816,7 @@ object PythonGen { } case Let(localOrBind, notFn, in) => // we know that notFn is not FnExpr here - val inF = loop(in, slotName) - - localOrBind match { - case Right(b) => - // value b is in scope after ve - for { - ve <- loop(notFn, slotName) - bi <- Env.bind(b) - ine <- inF - _ <- Env.unbind(b) - } yield ((bi := ve).withValue(ine)) - case Left(LocalAnon(l)) => - // anonymous names never shadow - (Env.nameForAnon(l), loop(notFn, slotName)) - .flatMapN { (bi, vE) => - inF.map((bi := vE).withValue(_)) - } - } - + doLet(localOrBind, notFn, loop(in, slotName), slotName) case LetMut(LocalAnonMut(_), in) => // we could delete this name, but // there is no need to From 3c80cb2b025cfc2cda0ff850aa42cc93dd44671f Mon Sep 17 00:00:00 2001 From: Oscar Boykin Date: Sat, 7 Dec 2024 12:00:13 -1000 Subject: [PATCH 2/4] remove dead code --- .../bykn/bosatsu/codegen/clang/ClangGen.scala | 94 ------------------- .../bosatsu/codegen/python/PythonGen.scala | 66 ------------- 2 files changed, 160 deletions(-) diff --git a/core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala b/core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala index 04a32b05a..51d2a794b 100644 --- a/core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala +++ b/core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala @@ -715,100 +715,6 @@ object ClangGen { } yield Code.declareInt(offsetIdent, Some(0)) +: res } - def searchList( - locMut: LocalAnonMut, - initVL: Code.ValueLike, - checkVL: Code.ValueLike, - optLeft: Option[LocalAnonMut] - ): T[Code.ValueLike] = { - import Code.Expression - - val emptyList: Expression = - Code.Ident("alloc_enum0")(Code.IntLiteral(0)) - - def isNonEmptyList(expr: Expression): Expression = - Code.Ident("get_variant")(expr) =:= Code.IntLiteral(1) - - def headList(expr: Expression): Expression = - Code.Ident("get_enum_index")(expr, Code.IntLiteral(0)) - - def tailList(expr: Expression): Expression = - Code.Ident("get_enum_index")(expr, Code.IntLiteral(1)) - - def consList(head: Expression, tail: Expression): Expression = - Code.Ident("alloc_enum2")(Code.IntLiteral(1), head, tail) - /* - * here is the implementation from MatchlessToValue - * - Dynamic { (scope: Scope) => - var res = false - var currentList = initF(scope) - var leftList = VList.VNil - while (currentList ne null) { - currentList match { - case nonempty@VList.Cons(head, tail) => - scope.updateMut(mutV, nonempty) - scope.updateMut(left, leftList) - res = checkF(scope) - if (res) { currentList = null } - else { - currentList = tail - leftList = VList.Cons(head, leftList) - } - case _ => - currentList = null - // we don't match empty lists - } - } - res - } - */ - for { - currentList <- getAnon(locMut.ident) - optLeft <- optLeft.traverse(lm => getAnon(lm.ident)) - res <- newLocalName("result") - tmpList <- newLocalName("tmp_list") - declTmpList <- Code.ValueLike.declareVar(Code.TypeIdent.BValue, tmpList, initVL)(newLocalName) - /* - top <- currentTop - _ = println(s"""in $top: searchList( - $locMut: LocalAnonMut, - $initVL: Code.ValueLike, - $checkVL: Code.ValueLike, - $optLeft: Option[LocalAnonMut] - )""") - */ - } yield - (Code - .Statements( - Code.DeclareVar(Nil, Code.TypeIdent.Bool, res, Some(Code.FalseLit)), - declTmpList - ) - .maybeCombine( - optLeft.map(_ := emptyList), - ) + - // we don't match empty lists, so if currentList reaches Empty we are done - Code.While( - isNonEmptyList(tmpList), - Code.block( - currentList := tmpList, - res := checkVL, - Code.ifThenElse(res, - { tmpList := emptyList }, - { - (tmpList := tailList(tmpList)) - .maybeCombine( - optLeft.map { left => - left := consList(headList(currentList), left) - } - ) - } - ) - ) - ) - ) :+ res - } - def boxFn(ident: Code.Ident, arity: Int): Code.Expression = Code.Ident(s"alloc_boxed_pure_fn$arity")(ident) diff --git a/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala b/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala index 1d0e2999d..f76a56e30 100644 --- a/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala +++ b/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala @@ -1585,72 +1585,6 @@ object PythonGen { } yield (offsetIdent := 0).withValue(res) } - def searchList( - locMut: LocalAnonMut, - initVL: ValueLike, - checkVL: ValueLike, - optLeft: Option[LocalAnonMut] - ): Env[ValueLike] = - /* - * here is the implementation from MatchlessToValue - * - Dynamic { (scope: Scope) => - var res = false - var currentList = initF(scope) - var leftList = VList.VNil - while (currentList ne null) { - currentList match { - case nonempty@VList.Cons(head, tail) => - scope.updateMut(mutV, nonempty) - scope.updateMut(left, leftList) - res = checkF(scope) - if (res) { currentList = null } - else { - currentList = tail - leftList = VList.Cons(head, leftList) - } - case _ => - currentList = null - // we don't match empty lists - } - } - res - } - */ - ( - Env.nameForAnon(locMut.ident), - optLeft.traverse(lm => Env.nameForAnon(lm.ident)), - Env.newAssignableVar, - Env.newAssignableVar - ) - .mapN { (currentList, optLeft, res, tmpList) => - Code - .block( - res := Code.Const.False, - tmpList := initVL, - optLeft.fold(Code.pass)(_ := emptyList), - // we don't match empty lists, so if currentList reaches Empty we are done - Code.While( - isNonEmpty(tmpList), - Code.block( - currentList := tmpList, - res := checkVL, - Code.ifElseS( - res, - tmpList := emptyList, - Code.block( - tmpList := tailList(tmpList), - optLeft.fold(Code.pass) { left => - left := consList(headList(currentList), left) - } - ) - ) - ) - ) - ) - .withValue(res) - } - // if expr is a Lambda handle it def topFn( name: Code.Ident, From 45c7c0f1988dabc11d6853a00f4d7add34db584b Mon Sep 17 00:00:00 2001 From: Oscar Boykin Date: Sat, 7 Dec 2024 21:05:17 -1000 Subject: [PATCH 3/4] remove more dead code --- .../bosatsu/codegen/python/PythonGen.scala | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala b/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala index f76a56e30..6bf895857 100644 --- a/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala +++ b/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala @@ -374,55 +374,7 @@ object PythonGen { ): Code.Def = Code.Def(defName, arg.toList, toReturn(v)) - def replaceTailCallWithAssign(name: Ident, argSize: Int, body: ValueLike)( - onArgs: List[Expression] => Statement - ): Env[ValueLike] = { - val initBody = body - def loop(body: ValueLike): Env[ValueLike] = - body match { - case a @ Apply(fn0, args0) => - if (fn0 == name) { - if (args0.length == argSize) { - val all = onArgs(args0) - // set all the values and return the empty tuple - Env.pure(all.withValue(Code.Const.Unit)) - } else { - // $COVERAGE-OFF$ - throw new IllegalStateException( - s"expected a tailcall for $name in $initBody, but found: $a" - ) - // $COVERAGE-ON$ - } - } else { - Env.pure(a) - } - case Parens(p) => loop(p).flatMap(onLast(_)(Parens(_))) - case IfElse(ifCases, elseCase) => - // only the result types are in tail position, we don't need to recurse on conds - val ifs = ifCases.traverse { case (cond, res) => - loop(res).map((cond, _)) - } - (ifs, loop(elseCase)) - .mapN(ifElse(_, _)) - .flatten - case Ternary(ifTrue, cond, ifFalse) => - // both results are in the tail position - (loop(ifTrue), loop(ifFalse)).mapN { (t, f) => - ifElse1(cond, t, f) - }.flatten - case WithValue(stmt, v) => - loop(v).map(stmt.withValue(_)) - // the rest cannot have a call in the tail position - case DotSelect(_, _) | Op(_, _, _) | Lambda(_, _) | MakeTuple(_) | - MakeList(_) | SelectItem(_, _) | SelectRange(_, _, _) | Ident(_) | - PyBool(_) | PyString(_) | PyInt(_) | Not(_) => - Env.pure(body) - } - - loop(initBody) - } } - // we escape by prefixing by three underscores, ___ and n (for name) // we use other ___x escapes for different name spaces, e.g. tmps, and anons // then we escape _ by __ and any character outside the allowed From 83a911b6469089fe919adf78e48e0b82962a20cc Mon Sep 17 00:00:00 2001 From: Oscar Boykin Date: Mon, 9 Dec 2024 08:47:38 -1000 Subject: [PATCH 4/4] rename and generalize translateLocals --- .../scala/org/bykn/bosatsu/Matchless.scala | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/core/src/main/scala/org/bykn/bosatsu/Matchless.scala b/core/src/main/scala/org/bykn/bosatsu/Matchless.scala index 44ed27019..9306f1b19 100644 --- a/core/src/main/scala/org/bykn/bosatsu/Matchless.scala +++ b/core/src/main/scala/org/bykn/bosatsu/Matchless.scala @@ -351,70 +351,69 @@ object Matchless { def inLet(b: Bindable): LambdaState = copy(name = Some(b)) } - def translateLocalsBool(m: Map[Bindable, LocalAnonMut], e: BoolExpr): BoolExpr = + def substituteLocalsBool(m: Map[Bindable, CheapExpr], e: BoolExpr): BoolExpr = e match { - case SetMut(mut, e) => SetMut(mut, translateLocals(m, e)) + case SetMut(mut, e) => SetMut(mut, substituteLocals(m, e)) case And(b1, b2) => - And(translateLocalsBool(m, b1), translateLocalsBool(m, b2)) + And(substituteLocalsBool(m, b1), substituteLocalsBool(m, b2)) case EqualsLit(x, l) => - EqualsLit(translateLocalsCheap(m, x), l) + EqualsLit(substituteLocalsCheap(m, x), l) case EqualsNat(x, n) => - EqualsNat(translateLocalsCheap(m, x), n) + EqualsNat(substituteLocalsCheap(m, x), n) case TrueConst => TrueConst case CheckVariant(expr, expect, sz, fam) => - CheckVariant(translateLocalsCheap(m, expr), expect, sz, fam) + CheckVariant(substituteLocalsCheap(m, expr), expect, sz, fam) case ms: MatchString => - ms.copy(arg = translateLocalsCheap(m, ms.arg)) + ms.copy(arg = substituteLocalsCheap(m, ms.arg)) case LetBool(b, a, in) => val m1 = b match { case Right(b) => m - b case _ => m } - LetBool(b, translateLocals(m, a), translateLocalsBool(m1, in)) + LetBool(b, substituteLocals(m, a), substituteLocalsBool(m1, in)) case LetMutBool(b, in) => - LetMutBool(b, translateLocalsBool(m, in)) + LetMutBool(b, substituteLocalsBool(m, in)) } - def translateLocals(m: Map[Bindable, LocalAnonMut], e: Expr): Expr = + def substituteLocals(m: Map[Bindable, CheapExpr], e: Expr): Expr = e match { case App(fn, appArgs) => - App(translateLocals(m, fn), appArgs.map(translateLocals(m, _))) + App(substituteLocals(m, fn), appArgs.map(substituteLocals(m, _))) case If(c, tcase, fcase) => - If(translateLocalsBool(m, c), translateLocals(m, tcase), translateLocals(m, fcase)) + If(substituteLocalsBool(m, c), substituteLocals(m, tcase), substituteLocals(m, fcase)) case Always(c, e) => - Always(translateLocalsBool(m, c), translateLocals(m, e)) + Always(substituteLocalsBool(m, c), substituteLocals(m, e)) case LetMut(mut, e) => - LetMut(mut, translateLocals(m, e)) + LetMut(mut, substituteLocals(m, e)) case Let(n, v, in) => val m1 = n match { case Right(b) => m - b case _ => m } - Let(n, translateLocals(m, v), translateLocals(m1, in)) - // the rest cannot have a call in tail position + Let(n, substituteLocals(m, v), substituteLocals(m1, in)) case Local(n) => m.get(n) match { case Some(mut) => mut case None => e } - case PrevNat(n) => PrevNat(translateLocals(m, n)) + case PrevNat(n) => PrevNat(substituteLocals(m, n)) case ge: GetEnumElement => - ge.copy(arg = translateLocalsCheap(m, ge.arg)) + ge.copy(arg = substituteLocalsCheap(m, ge.arg)) case gs: GetStructElement => - gs.copy(arg = translateLocalsCheap(m, gs.arg)) + gs.copy(arg = substituteLocalsCheap(m, gs.arg)) case Lambda(c, r, as, b) => val m1 = m -- as.toList - val b1 = translateLocals(m1, b) + val b1 = substituteLocals(m1, b) Lambda(c, r, as, b1) case WhileExpr(c, ef, r) => - WhileExpr(translateLocalsBool(m, c), translateLocals(m, ef), r) + WhileExpr(substituteLocalsBool(m, c), substituteLocals(m, ef), r) case ClosureSlot(_) | Global(_, _) | LocalAnon(_) | LocalAnonMut(_) | MakeEnum(_, _, _) | MakeStruct(_) | SuccNat | Literal(_) | ZeroNat => e } - def translateLocalsCheap(m: Map[Bindable, LocalAnonMut], e: CheapExpr): CheapExpr = - translateLocals(m, e) match { + def substituteLocalsCheap(m: Map[Bindable, CheapExpr], e: CheapExpr): CheapExpr = + substituteLocals(m, e) match { case ch: CheapExpr => ch - case notCheap => sys.error(s"invariant violation: translation didn't maintain cheap: $e => $notCheap") + case notCheap => sys.error(s"invariant violation: substitution didn't maintain cheap: $e => $notCheap") } def loopFn( @@ -484,7 +483,7 @@ object Matchless { MakeEnum(_, _, _) | MakeStruct(_) | PrevNat(_) | SuccNat | WhileExpr(_, _, _) | ZeroNat => None } - val bodyTrans = translateLocals( + val bodyTrans = substituteLocals( args.toList.map(a => (a.name, a.loopVar)).toMap, body)