From fbb75b2f43f672db61c906e2f0f5add11db1a594 Mon Sep 17 00:00:00 2001 From: vbergeron Date: Thu, 23 Nov 2023 14:49:41 +0100 Subject: [PATCH] Added switch conditional implementation --- src/main/scala/sail/main.scala | 16 ++++++++++++++++ src/main/scala/sail/model.scala | 2 ++ src/main/scala/sail/parser.scala | 27 ++++++++++++++++++++++----- src/test/scala/parser.test.scala | 24 ++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/main/scala/sail/main.scala b/src/main/scala/sail/main.scala index 80a6e01..40460a4 100644 --- a/src/main/scala/sail/main.scala +++ b/src/main/scala/sail/main.scala @@ -53,6 +53,7 @@ def show(expr: Expr): String = s"" case Expr.Module(name, source) => s" "" def render(expr: Expr): String = expr match @@ -195,6 +196,21 @@ def reduce(cmd: Args, env: Env, expr: Expr): (Env, Expr) = (sym, expr) => sym.copy(module = Some(mod.name.value)) -> expr (env, Expr.Unit) + case switch: Expr.Switch => + import BooleanExpr.* + val branch = switch.clauses.find: (cond, _) => + val reduced = reduce(cmd, env, cond)._2 match + case x: (True.type | False.type) => + x + case e => + throw Exception( + s"Expression did not reduce to boolean: $cond (reduced: $e)" + ) + reduced == True + (branch.map(_._2) orElse switch.default) + .fold(throw Exception(s"Non exhaustive switch: $switch")): expr => + reduce(cmd, env, expr) + def reduceFile(cmd: Args, exprs: Seq[Expr]): Env = exprs.foldLeft(Map.empty[Expr.Sym, Expr])(reduce(cmd, _, _)._1) diff --git a/src/main/scala/sail/model.scala b/src/main/scala/sail/model.scala index 672dbe2..319a8a8 100644 --- a/src/main/scala/sail/model.scala +++ b/src/main/scala/sail/model.scala @@ -13,6 +13,8 @@ object Expr: case class FuncCall(name: Sym, args: Seq[Expr]) extends Expr case class Container(from: Str, build: Instr) extends Expr case class Scope(bindings: Seq[FuncDef], body: Expr) extends Expr + case class Switch(clauses: Seq[(Expr, Expr)], default: Option[Expr]) + extends Expr case class Module(name: Expr.Sym, sourcePath: Expr.Str) extends Expr diff --git a/src/main/scala/sail/parser.scala b/src/main/scala/sail/parser.scala index 96fef8f..eed3693 100644 --- a/src/main/scala/sail/parser.scala +++ b/src/main/scala/sail/parser.scala @@ -18,7 +18,7 @@ def part[$: P]: P[Part] = capture | content def expr[$: P]: P[Expr] = - scope | container | module | funcDef | instr | template | str | num | funcCall | sym | boolean.apply + cond.apply | scope | container | module | funcDef | instr | template | str | num | funcCall | sym | boolean.apply object boolean: def t[$: P]: P[BooleanExpr.True.type] = @@ -28,22 +28,25 @@ object boolean: P("false").map(_ => BooleanExpr.False) def and[$: P]: P[BooleanExpr.And] = - P(expr ~ ws ~ "and" ~/ ws ~ expr) + val inner = eq | expr + P(inner ~ ws ~ "and" ~ ws ~ inner) .map(BooleanExpr.And.apply) def or[$: P]: P[BooleanExpr.Or] = - P(expr ~ ws ~ "or" ~/ ws ~ expr) + val inner = expr + P(inner ~ ws ~ "or" ~ ws ~ inner) .map(BooleanExpr.Or.apply) def not[$: P]: P[BooleanExpr.Not] = P("not" ~ ws ~ expr).map(BooleanExpr.Not.apply) def eq[$: P]: P[BooleanExpr.Eq] = - P(expr ~ ws ~ "==" ~/ ws ~ expr) + val inner = expr + P(inner ~ ws ~ "==" ~ ws ~ inner) .map(BooleanExpr.Eq.apply) def apply[$: P]: P[BooleanExpr] = - eq | and | or | not | t | f + and | or | eq | not | t | f def instr[$: P]: P[Instr] = def run[$: P]: P[Instr.Run] = @@ -69,6 +72,20 @@ def instr[$: P]: P[Instr] = rec +object cond: + def clause[$: P]: P[(Expr, Expr)] = + P("when" ~ ws ~ expr ~ ws ~ "then" ~ ws ~ expr) + + def default[$: P]: P[Expr] = + P("else" ~ ws ~ expr) + + def apply[$: P]: P[Expr.Switch] = + P(clause.rep ~ ws ~ default.?) + .filter: (clauses, _) => + clauses.size > 0 + .map: (clauses, default) => + Expr.Switch(clauses, default) + def scope[$: P]: P[Expr.Scope] = P("let" ~ ws ~ (funcDef ~ ws).rep ~ "in" ~ ws ~ expr).map(Expr.Scope.apply) diff --git a/src/test/scala/parser.test.scala b/src/test/scala/parser.test.scala index 207b9a3..35e6f11 100644 --- a/src/test/scala/parser.test.scala +++ b/src/test/scala/parser.test.scala @@ -107,5 +107,29 @@ class ParserTest extends FunSuite: passing("Boolean", "false", boolean(_)) passing("Boolean", "bar or bar", boolean(_)) passing("Boolean", "f(x) and g(y)", boolean(_)) + // passingT("Boolean", "foo == bar and foo == baz", boolean.and(_)): expr => + // import BooleanExpr.* + // import Expr.* + // assertEquals( + // expr, + // And( + // Eq(Sym(None, "foo"), Sym(None, "bar")), + // Eq(Sym(None, "foo"), Sym(None, "bar")) + // ) + // ) + passing("Boolean", "f(x) == g(y)", boolean(_)) passing("Boolean", "not f(x)", boolean(_)) + + passing("Switch", "when foo then bar", cond.apply) + passing("Switch", "when foo then bar when foobar then barbar", cond.apply) + + passing("Switch", "when foo then bar else baz", cond.apply) + passing( + "Switch", + """when foo then bar + |when foo == bar then barbar + |else baz + |""".stripMargin, + cond.apply + )