From bdb335d827f03715db73fa395a260e1c9d5fdec9 Mon Sep 17 00:00:00 2001 From: navidaminnn Date: Mon, 9 Dec 2024 02:23:00 -0500 Subject: [PATCH 01/11] initial calculator - uses pure scala rather than dcal node --- calc/CalcReader.scala | 79 ++++++++++++++++++++++++++++++++++++++ calc/CalcReader.test.scala | 40 +++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 calc/CalcReader.scala create mode 100644 calc/CalcReader.test.scala diff --git a/calc/CalcReader.scala b/calc/CalcReader.scala new file mode 100644 index 0000000..2434f21 --- /dev/null +++ b/calc/CalcReader.scala @@ -0,0 +1,79 @@ +package distcompiler.calc + +import cats.syntax.all.given + +import distcompiler.* +import distcompiler.Builtin.{Error, SourceMarker} +import Reader.* + +object CalcReader: + private val digit: Set[Char] = ('0' to '9').toSet + private val operations: Set[Char] = Set('+', '-', '*', '/') + private val whitespace: Set[Char] = Set(' ', '\n', '\t') + + sealed trait ParsedToken + case class Num(value: Int) extends ParsedToken + case class Operation(value: Char) extends ParsedToken + + def tokenize(input: String): List[ParsedToken] = { + val (tokens, leftoverBuffer) = input.foldLeft(List.empty[ParsedToken] -> new StringBuilder) { + case ((tokenList, numberBuffer), char) if digit.contains(char) => + (tokenList, numberBuffer.append(char)) + + case ((tokenList, numberBuffer), char) if operations.contains(char) => + val tokensWithNumbers = if (numberBuffer.nonEmpty) + tokenList :+ Num(numberBuffer.toString().toInt) + else tokenList + (tokensWithNumbers :+ Operation(char), new StringBuilder) + + case((tokenList, numberBuffer), char) if whitespace.contains(char) => + val tokensWithNumbers = if (numberBuffer.nonEmpty) + tokenList :+ Num(numberBuffer.toString().toInt) + else tokenList + (tokensWithNumbers, new StringBuilder) + + case _ => throw IllegalArgumentException("Invalid character") + } + + tokens ++ (if (leftoverBuffer.nonEmpty) List(Num(leftoverBuffer.toString().toInt)) else Nil) + } + + def evaluate(tokens: List[ParsedToken]): Int = { + def precedence(operator: Char): Int = operator match { + case '*' | '/' => 1 + case '+' | '-' => 0 + } + + def applyOperation(a: Int, b: Int, op: Char): Int = op match { + case '+' => a + b + case '-' => a - b + case '*' => a * b + case '/' => if (b != 0) a / b else throw ArithmeticException("Division by zero") + } + + val (values, ops) = ( + new scala.collection.mutable.Stack[Int](), + new scala.collection.mutable.Stack[Char]() + ) + + tokens.foreach { + case Num(value) => + values.push(value) + + case Operation(op) => + while (ops.nonEmpty && precedence(ops.top) >= precedence(op)) { + val b = values.pop() + val a = values.pop() + values.push(applyOperation(a, b, ops.pop())) + } + ops.push(op) + } + + while (ops.nonEmpty) { + val b = values.pop() + val a = values.pop() + values.push(applyOperation(a, b, ops.pop())) + } + + values.pop() + } \ No newline at end of file diff --git a/calc/CalcReader.test.scala b/calc/CalcReader.test.scala new file mode 100644 index 0000000..83ba407 --- /dev/null +++ b/calc/CalcReader.test.scala @@ -0,0 +1,40 @@ +package distcompiler.calc + +import distcompiler.* +import Builtin.{Error, SourceMarker} + +class CalcReaderTests extends munit.FunSuite: + extension (str: String) + def evaluate: Int = CalcReader.evaluate(CalcReader.tokenize(str)) + + // TODO: + // support negative numbers + // support brackets for precedence + + test("single number"): + assertEquals("42".evaluate, 42) + + test("simple addition"): + assertEquals("10 + 5".evaluate, 15) + + test("multi-digit subtraction"): + assertEquals("5 - 3 - 2".evaluate, 0) + + test("multi-digit multiplication"): + assertEquals("5 * 2 * 11".evaluate, 110) + + test("simple division"): + assertEquals("10 / 2".evaluate, 5) + + test("multiplication + division"): + assertEquals("10 * 5 / 2".evaluate, 25) + + test("invalid character error"): + intercept[IllegalArgumentException] ( + "5k * 10".evaluate + ) + + test("divide by zero error"): + intercept[ArithmeticException] ( + "5 / 0".evaluate + ) From 3eac302c04bfbfd210d46f1ad61c3412b779aac2 Mon Sep 17 00:00:00 2001 From: navidaminnn Date: Fri, 3 Jan 2025 22:20:58 -0500 Subject: [PATCH 02/11] working calculator without proper pass rules --- calc/CalcReader.scala | 200 ++++++++++++++++++++++++------------- calc/CalcReader.test.scala | 65 ++++++++---- calc/package.scala | 22 ++++ 3 files changed, 201 insertions(+), 86 deletions(-) create mode 100644 calc/package.scala diff --git a/calc/CalcReader.scala b/calc/CalcReader.scala index 2434f21..2fbb30f 100644 --- a/calc/CalcReader.scala +++ b/calc/CalcReader.scala @@ -6,74 +6,140 @@ import distcompiler.* import distcompiler.Builtin.{Error, SourceMarker} import Reader.* -object CalcReader: +object CalcReader extends Reader: + import distcompiler.dsl.* + import distcompiler.Builtin.{Error, SourceMarker} + import Reader.* + + def wellformed: Wellformed = distcompiler.calc.wellformed + private val digit: Set[Char] = ('0' to '9').toSet - private val operations: Set[Char] = Set('+', '-', '*', '/') + private val operation: Set[Char] = Set('*', '/', '+', '-') private val whitespace: Set[Char] = Set(' ', '\n', '\t') - sealed trait ParsedToken - case class Num(value: Int) extends ParsedToken - case class Operation(value: Char) extends ParsedToken - - def tokenize(input: String): List[ParsedToken] = { - val (tokens, leftoverBuffer) = input.foldLeft(List.empty[ParsedToken] -> new StringBuilder) { - case ((tokenList, numberBuffer), char) if digit.contains(char) => - (tokenList, numberBuffer.append(char)) - - case ((tokenList, numberBuffer), char) if operations.contains(char) => - val tokensWithNumbers = if (numberBuffer.nonEmpty) - tokenList :+ Num(numberBuffer.toString().toInt) - else tokenList - (tokensWithNumbers :+ Operation(char), new StringBuilder) - - case((tokenList, numberBuffer), char) if whitespace.contains(char) => - val tokensWithNumbers = if (numberBuffer.nonEmpty) - tokenList :+ Num(numberBuffer.toString().toInt) - else tokenList - (tokensWithNumbers, new StringBuilder) - - case _ => throw IllegalArgumentException("Invalid character") - } - - tokens ++ (if (leftoverBuffer.nonEmpty) List(Num(leftoverBuffer.toString().toInt)) else Nil) - } - - def evaluate(tokens: List[ParsedToken]): Int = { - def precedence(operator: Char): Int = operator match { - case '*' | '/' => 1 - case '+' | '-' => 0 - } - - def applyOperation(a: Int, b: Int, op: Char): Int = op match { - case '+' => a + b - case '-' => a - b - case '*' => a * b - case '/' => if (b != 0) a / b else throw ArithmeticException("Division by zero") - } - - val (values, ops) = ( - new scala.collection.mutable.Stack[Int](), - new scala.collection.mutable.Stack[Char]() - ) - - tokens.foreach { - case Num(value) => - values.push(value) + private lazy val unexpectedEOF: Manip[SourceRange] = + consumeMatch: m => + addChild(Error("unexpected EOF", SourceMarker(m))) + *> Manip.pure(m) + + protected lazy val rules: Manip[SourceRange] = + commit: + bytes + .selecting[SourceRange] + .onOneOf(whitespace): + extendThisNodeWithMatch(rules) + .onOneOf(digit): + numberMode + .onOneOf(operation): + operationMode + .fallback: + bytes.selectCount(1): + consumeMatch: m => + addChild(Error("invalid byte", SourceMarker(m))) + *> rules + | consumeMatch: m => + on(theTop).check + *> Manip.pure(m) + | unexpectedEOF + + private lazy val numberMode: Manip[SourceRange] = + commit: + bytes + .selecting[SourceRange] + .onOneOf(digit)(numberMode) + .fallback: + consumeMatch: m => + m.decodeString().toIntOption match + case Some(value) => + addChild(tokens.Atom(m)) + *> rules + case None => + addChild(Error("invalid number format", SourceMarker(m))) + *> rules + + private lazy val operationMode: Manip[SourceRange] = + commit: + consumeMatch: m => + addChild(tokens.Atom(m)) + *> rules + + def evaluate(top: Node.Top): Int = + val tokens = top.children.iterator.map(n => + n.asNode.sourceRange.decodeString().trim + ).toList + + def validateTokens(tokens: List[String]): Unit = + if tokens.isEmpty then + throw IllegalArgumentException("Empty expression") + + if !tokens.head.forall(_.isDigit) then + throw IllegalArgumentException("Expression must start with a number") - case Operation(op) => - while (ops.nonEmpty && precedence(ops.top) >= precedence(op)) { - val b = values.pop() - val a = values.pop() - values.push(applyOperation(a, b, ops.pop())) - } - ops.push(op) - } - - while (ops.nonEmpty) { - val b = values.pop() - val a = values.pop() - values.push(applyOperation(a, b, ops.pop())) - } - - values.pop() - } \ No newline at end of file + if !tokens.last.forall(_.isDigit) then + throw IllegalArgumentException("Expression must end with a number") + + val isValid = tokens.zipWithIndex.forall { case (token, i) => + if i % 2 == 0 then + token.forall(_.isDigit) + else + Set("+", "-", "*", "/").contains(token) + } + + if !isValid then + throw IllegalArgumentException("Invalid expression: must alternate between numbers and operators") + + def evalOp(left: Int, op: String, right: Int): Int = op match + case "*" => left * right + case "/" => + if right == 0 then throw new ArithmeticException("Division by zero") + left / right + case "+" => left + right + case "-" => left - right + case _ => throw new IllegalArgumentException(s"Invalid operator: $op") + + def evaluateWithPrecedence(tokens: List[String]): Int = + def evalMulDiv(tokens: List[String]): List[String] = + def isNumber(s: String) = s.forall(_.isDigit) + + tokens match + case num1 :: "*" :: num2 :: rest if isNumber(num1) && isNumber(num2) => + val result = evalOp(num1.toInt, "*", num2.toInt) + evalMulDiv(result.toString :: rest) + + case num1 :: "/" :: num2 :: rest if isNumber(num1) && isNumber(num2) => + val result = evalOp(num1.toInt, "/", num2.toInt) + evalMulDiv(result.toString :: rest) + + // if not multiplication or division, keep first token and process rest + case firstToken :: remainingTokens => + firstToken :: evalMulDiv(remainingTokens) + + case Nil => Nil + + val afterMulDiv = evalMulDiv(tokens) + afterMulDiv.sliding(3, 2).foldLeft(afterMulDiv.head.toInt) { + case (acc, List(_, "+", right)) => acc + right.toInt + case (acc, List(_, "-", right)) => acc - right.toInt + case (acc, _) => acc + } + + validateTokens(tokens) + evaluateWithPrecedence(tokens) + + // def evaluate(top: Node.Top): Int = + // def evalNode(nodes: Iterator[Node.Child]): Int = + // if !nodes.hasNext then 0 + // else + // var result = nodes.next().asNode.sourceRange.decodeString().trim.toInt + // while nodes.hasNext do + // val op = nodes.next().asNode.sourceRange.decodeString().trim.head + // if !nodes.hasNext then + // throw new IllegalArgumentException("Expression ends with an operator") + // val next = nodes.next().asNode.sourceRange.decodeString().trim.toInt + // op match + // case '+' => result = result + next + // case '-' => result = result - next + // case _ => throw new IllegalArgumentException("Invalid equation provided") + // result + + // evalNode(top.children.iterator) \ No newline at end of file diff --git a/calc/CalcReader.test.scala b/calc/CalcReader.test.scala index 83ba407..ff4cd35 100644 --- a/calc/CalcReader.test.scala +++ b/calc/CalcReader.test.scala @@ -5,36 +5,63 @@ import Builtin.{Error, SourceMarker} class CalcReaderTests extends munit.FunSuite: extension (str: String) - def evaluate: Int = CalcReader.evaluate(CalcReader.tokenize(str)) + def parse: Node.Top = + calc.parse.fromSourceRange(SourceRange.entire(Source.fromString(str))) - // TODO: - // support negative numbers - // support brackets for precedence + def evaluate: Int = + val top = calc.parse.fromSourceRange(SourceRange.entire(Source.fromString(str))) + CalcReader.evaluate(top) + + test("empty string"): + assertEquals("".parse, Node.Top()) + + test("only whitespace"): + assertEquals(" \n\t".parse, Node.Top()) + + test("error: invalid character"): + assertEquals("k".parse, Node.Top(Error("invalid byte", SourceMarker("k")))) test("single number"): - assertEquals("42".evaluate, 42) + assertEquals( + "5".parse, + Node.Top( + tokens.Atom("5") + ) + ) test("simple addition"): - assertEquals("10 + 5".evaluate, 15) + assertEquals( + "5 + 11".parse, + Node.Top( + tokens.Atom("5"), + tokens.Atom("+"), + tokens.Atom("11") + ) + ) + + test("addition calculation"): + assertEquals("5 + 11".evaluate, 16) - test("multi-digit subtraction"): - assertEquals("5 - 3 - 2".evaluate, 0) + test("calculation"): + assertEquals("10 - 7 + 4".evaluate, 7) - test("multi-digit multiplication"): - assertEquals("5 * 2 * 11".evaluate, 110) + test("error: incorrect format"): + intercept[IllegalArgumentException] ( + "10 - 10 7".evaluate + ) - test("simple division"): - assertEquals("10 / 2".evaluate, 5) + test("multiplication calculation"): + assertEquals("12 * 5 / 2".evaluate, 30) - test("multiplication + division"): - assertEquals("10 * 5 / 2".evaluate, 25) + test("error: divide by zero"): + intercept[ArithmeticException] ( + "16 * 2 / 0".evaluate + ) - test("invalid character error"): + test("error: invalid character error"): intercept[IllegalArgumentException] ( "5k * 10".evaluate ) - test("divide by zero error"): - intercept[ArithmeticException] ( - "5 / 0".evaluate - ) + test("full calculation"): + assertEquals("10 + 7 * 5 - 9 / 3".evaluate, 42) \ No newline at end of file diff --git a/calc/package.scala b/calc/package.scala new file mode 100644 index 0000000..b2f3578 --- /dev/null +++ b/calc/package.scala @@ -0,0 +1,22 @@ +package distcompiler.calc + +import cats.syntax.all.given + +import scala.collection.mutable + +import distcompiler.* +import dsl.* + +object tokens: + object Atom extends Token: + override def showSource: Boolean = true + +val wellformed: Wellformed = + Wellformed: + val listContents = repeated(choice(tokens.Atom)) + Node.Top ::= listContents + tokens.Atom ::= Atom + +object parse: + def fromSourceRange(sourceRange: SourceRange): Node.Top = + CalcReader(sourceRange) \ No newline at end of file From 329fa5f797e803bf0047bd6d63cd347687d687a7 Mon Sep 17 00:00:00 2001 From: navidaminnn Date: Mon, 13 Jan 2025 16:22:15 -0500 Subject: [PATCH 03/11] leveraging passes with rewrite rules --- calc/CalcParser.scala | 64 +++++++++++++++++++++++ calc/CalcReader.scala | 104 ++++++------------------------------- calc/CalcReader.test.scala | 53 +++++++++---------- calc/package.scala | 24 +++++++-- 4 files changed, 126 insertions(+), 119 deletions(-) create mode 100644 calc/CalcParser.scala diff --git a/calc/CalcParser.scala b/calc/CalcParser.scala new file mode 100644 index 0000000..1a04f5d --- /dev/null +++ b/calc/CalcParser.scala @@ -0,0 +1,64 @@ +package distcompiler.calc + +import cats.syntax.all.given + +import distcompiler.* +import dsl.* +import distcompiler.Builtin.{Error, SourceMarker} + +object CalcParser extends PassSeq: + import distcompiler.dsl.* + import distcompiler.Builtin.{Error, SourceMarker} + import Reader.* + + def inputWellformed: Wellformed = CalcReader.wellformed + + private lazy val mulDivPass = passDef: + wellformed := prevWellformed.makeDerived: + Node.Top ::=! repeated(choice(tokens.Operation, tokens.Number)) + + pass(once = false, strategy = pass.topDown) + .rules: + on( + tok(tokens.Operation).filter(node => + Set("*", "/").contains(node.sourceRange.decodeString().trim) + ).withChildren( + field(tokens.Number) + ~ field(tokens.Operator) + ~ field(tokens.Number) + ~ trailing + ) + ).rewrite: (left, op, right) => + val l = left.sourceRange.decodeString().toInt + val r = right.sourceRange.decodeString().toInt + val result = op.sourceRange.decodeString().trim match + case "*" => l * r + case "/" => + if r == 0 then throw ArithmeticException("Division by zero") + l / r + + splice(tokens.Number(result.toString)) + + private lazy val addSubPass = passDef: + wellformed := prevWellformed.makeDerived: + Node.Top ::=! fields(tokens.Number) + + pass(once = false, strategy = pass.topDown) + .rules: + on( + tok(tokens.Operation).filter(node => + Set("+", "-").contains(node.sourceRange.decodeString().trim) + ).withChildren( + field(tokens.Number) + ~ field(tokens.Operator) + ~ field(tokens.Number) + ~ trailing + ) + ).rewrite: (left, op, right) => + val l = left.sourceRange.decodeString().toInt + val r = right.sourceRange.decodeString().toInt + val result = op.sourceRange.decodeString().trim match + case "+" => l + r + case "-" => l - r + + splice(tokens.Number(result.toString)) \ No newline at end of file diff --git a/calc/CalcReader.scala b/calc/CalcReader.scala index 2fbb30f..ac0875c 100644 --- a/calc/CalcReader.scala +++ b/calc/CalcReader.scala @@ -3,15 +3,22 @@ package distcompiler.calc import cats.syntax.all.given import distcompiler.* +import dsl.* import distcompiler.Builtin.{Error, SourceMarker} import Reader.* object CalcReader extends Reader: - import distcompiler.dsl.* - import distcompiler.Builtin.{Error, SourceMarker} - import Reader.* - - def wellformed: Wellformed = distcompiler.calc.wellformed + override lazy val wellformed = Wellformed: + Node.Top ::= repeated(choice(tokens.Number, tokens.Operator)) + + tokens.Number ::= Atom + tokens.Operator ::= Atom + + tokens.Operation ::= fields( + tokens.Number, + tokens.Operator, + tokens.Number + ) private val digit: Set[Char] = ('0' to '9').toSet private val operation: Set[Char] = Set('*', '/', '+', '-') @@ -51,7 +58,7 @@ object CalcReader extends Reader: consumeMatch: m => m.decodeString().toIntOption match case Some(value) => - addChild(tokens.Atom(m)) + addChild(tokens.Number(m)) *> rules case None => addChild(Error("invalid number format", SourceMarker(m))) @@ -60,86 +67,5 @@ object CalcReader extends Reader: private lazy val operationMode: Manip[SourceRange] = commit: consumeMatch: m => - addChild(tokens.Atom(m)) - *> rules - - def evaluate(top: Node.Top): Int = - val tokens = top.children.iterator.map(n => - n.asNode.sourceRange.decodeString().trim - ).toList - - def validateTokens(tokens: List[String]): Unit = - if tokens.isEmpty then - throw IllegalArgumentException("Empty expression") - - if !tokens.head.forall(_.isDigit) then - throw IllegalArgumentException("Expression must start with a number") - - if !tokens.last.forall(_.isDigit) then - throw IllegalArgumentException("Expression must end with a number") - - val isValid = tokens.zipWithIndex.forall { case (token, i) => - if i % 2 == 0 then - token.forall(_.isDigit) - else - Set("+", "-", "*", "/").contains(token) - } - - if !isValid then - throw IllegalArgumentException("Invalid expression: must alternate between numbers and operators") - - def evalOp(left: Int, op: String, right: Int): Int = op match - case "*" => left * right - case "/" => - if right == 0 then throw new ArithmeticException("Division by zero") - left / right - case "+" => left + right - case "-" => left - right - case _ => throw new IllegalArgumentException(s"Invalid operator: $op") - - def evaluateWithPrecedence(tokens: List[String]): Int = - def evalMulDiv(tokens: List[String]): List[String] = - def isNumber(s: String) = s.forall(_.isDigit) - - tokens match - case num1 :: "*" :: num2 :: rest if isNumber(num1) && isNumber(num2) => - val result = evalOp(num1.toInt, "*", num2.toInt) - evalMulDiv(result.toString :: rest) - - case num1 :: "/" :: num2 :: rest if isNumber(num1) && isNumber(num2) => - val result = evalOp(num1.toInt, "/", num2.toInt) - evalMulDiv(result.toString :: rest) - - // if not multiplication or division, keep first token and process rest - case firstToken :: remainingTokens => - firstToken :: evalMulDiv(remainingTokens) - - case Nil => Nil - - val afterMulDiv = evalMulDiv(tokens) - afterMulDiv.sliding(3, 2).foldLeft(afterMulDiv.head.toInt) { - case (acc, List(_, "+", right)) => acc + right.toInt - case (acc, List(_, "-", right)) => acc - right.toInt - case (acc, _) => acc - } - - validateTokens(tokens) - evaluateWithPrecedence(tokens) - - // def evaluate(top: Node.Top): Int = - // def evalNode(nodes: Iterator[Node.Child]): Int = - // if !nodes.hasNext then 0 - // else - // var result = nodes.next().asNode.sourceRange.decodeString().trim.toInt - // while nodes.hasNext do - // val op = nodes.next().asNode.sourceRange.decodeString().trim.head - // if !nodes.hasNext then - // throw new IllegalArgumentException("Expression ends with an operator") - // val next = nodes.next().asNode.sourceRange.decodeString().trim.toInt - // op match - // case '+' => result = result + next - // case '-' => result = result - next - // case _ => throw new IllegalArgumentException("Invalid equation provided") - // result - - // evalNode(top.children.iterator) \ No newline at end of file + addChild(tokens.Operator(m)) + *> rules \ No newline at end of file diff --git a/calc/CalcReader.test.scala b/calc/CalcReader.test.scala index ff4cd35..d91b503 100644 --- a/calc/CalcReader.test.scala +++ b/calc/CalcReader.test.scala @@ -8,9 +8,10 @@ class CalcReaderTests extends munit.FunSuite: def parse: Node.Top = calc.parse.fromSourceRange(SourceRange.entire(Source.fromString(str))) - def evaluate: Int = - val top = calc.parse.fromSourceRange(SourceRange.entire(Source.fromString(str))) - CalcReader.evaluate(top) + def evaluate: Node.Top = + val top = parse + CalcParser(top) + top test("empty string"): assertEquals("".parse, Node.Top()) @@ -25,7 +26,7 @@ class CalcReaderTests extends munit.FunSuite: assertEquals( "5".parse, Node.Top( - tokens.Atom("5") + tokens.Number("5") ) ) @@ -33,35 +34,35 @@ class CalcReaderTests extends munit.FunSuite: assertEquals( "5 + 11".parse, Node.Top( - tokens.Atom("5"), - tokens.Atom("+"), - tokens.Atom("11") + tokens.Number("5"), + tokens.Operator("+"), + tokens.Number("11") ) ) test("addition calculation"): - assertEquals("5 + 11".evaluate, 16) + assertEquals("5 + 11".evaluate, Node.Top(tokens.Number("16"))) - test("calculation"): - assertEquals("10 - 7 + 4".evaluate, 7) + // test("calculation"): + // assertEquals("10 - 7 + 4".evaluate, 7) - test("error: incorrect format"): - intercept[IllegalArgumentException] ( - "10 - 10 7".evaluate - ) + // test("error: incorrect format"): + // intercept[IllegalArgumentException] ( + // "10 - 10 7".evaluate + // ) - test("multiplication calculation"): - assertEquals("12 * 5 / 2".evaluate, 30) + // test("multiplication calculation"): + // assertEquals("12 * 5 / 2".evaluate, 30) - test("error: divide by zero"): - intercept[ArithmeticException] ( - "16 * 2 / 0".evaluate - ) + // test("error: divide by zero"): + // intercept[ArithmeticException] ( + // "16 * 2 / 0".evaluate + // ) - test("error: invalid character error"): - intercept[IllegalArgumentException] ( - "5k * 10".evaluate - ) + // test("error: invalid character error"): + // intercept[IllegalArgumentException] ( + // "5k * 10".evaluate + // ) - test("full calculation"): - assertEquals("10 + 7 * 5 - 9 / 3".evaluate, 42) \ No newline at end of file + // test("full calculation"): + // assertEquals("10 + 7 * 5 - 9 / 3".evaluate, 42) \ No newline at end of file diff --git a/calc/package.scala b/calc/package.scala index b2f3578..535b99f 100644 --- a/calc/package.scala +++ b/calc/package.scala @@ -8,14 +8,30 @@ import distcompiler.* import dsl.* object tokens: - object Atom extends Token: + object Operation extends Token + + object Number extends Token: + override def showSource: Boolean = true + + object Operator extends Token: override def showSource: Boolean = true + object Result extends Token: + override val symbolTableFor: Set[Token] = + Set(Number) + val wellformed: Wellformed = Wellformed: - val listContents = repeated(choice(tokens.Atom)) - Node.Top ::= listContents - tokens.Atom ::= Atom + Node.Top ::= repeated(choice(tokens.Number, tokens.Operator)) + + tokens.Number ::= Atom + tokens.Operator ::= Atom + + tokens.Operation ::= fields( + tokens.Number, + tokens.Operator, + tokens.Number + ) object parse: def fromSourceRange(sourceRange: SourceRange): Node.Top = From 16c87aa7f70a9c2058c93119b60c65ead26f57e3 Mon Sep 17 00:00:00 2001 From: navidaminnn Date: Wed, 19 Feb 2025 13:48:54 -0500 Subject: [PATCH 04/11] changed logic for calculations --- calc/CalcParser.scala | 101 +++++++++++++++++++++++-------------- calc/CalcReader.scala | 41 +++++++++------ calc/CalcReader.test.scala | 77 +++++++++++++++++++++------- calc/package.scala | 28 +++++----- 4 files changed, 165 insertions(+), 82 deletions(-) diff --git a/calc/CalcParser.scala b/calc/CalcParser.scala index 1a04f5d..31d1fbe 100644 --- a/calc/CalcParser.scala +++ b/calc/CalcParser.scala @@ -5,60 +5,87 @@ import cats.syntax.all.given import distcompiler.* import dsl.* import distcompiler.Builtin.{Error, SourceMarker} +import distcompiler.calc.tokens.* object CalcParser extends PassSeq: import distcompiler.dsl.* - import distcompiler.Builtin.{Error, SourceMarker} import Reader.* + import CalcReader.* def inputWellformed: Wellformed = CalcReader.wellformed - private lazy val mulDivPass = passDef: + private val mulDivPass = passDef: + wellformed := inputWellformed.makeDerived: + Node.Top ::=! repeated(choice(Number, Expression, LowPrecOp)) + + pass(once = true, strategy = pass.topDown) + .rules: + on( + field(tok(Number, Expression)) + ~ field(tok(HighPrecOp)) + ~ field(tok(Number, Expression)) + ~ trailing + ).rewrite: (left, operator, right) => + splice( + Expression( + left.unparent(), + operator.unparent(), + right.unparent() + ) + ) + + private val addSubPass = passDef: wellformed := prevWellformed.makeDerived: - Node.Top ::=! repeated(choice(tokens.Operation, tokens.Number)) + Node.Top ::=! repeated(choice(Number, Expression)) - pass(once = false, strategy = pass.topDown) + pass(once = true, strategy = pass.topDown) .rules: on( - tok(tokens.Operation).filter(node => - Set("*", "/").contains(node.sourceRange.decodeString().trim) - ).withChildren( - field(tokens.Number) - ~ field(tokens.Operator) - ~ field(tokens.Number) - ~ trailing + field(tok(Number, Expression)) + ~ field(tok(LowPrecOp)) + ~ field(tok(Number, Expression)) + ~ trailing + ).rewrite: (left, operator, right) => + splice( + Expression( + left.unparent(), + operator.unparent(), + right.unparent() + ) ) - ).rewrite: (left, op, right) => - val l = left.sourceRange.decodeString().toInt - val r = right.sourceRange.decodeString().toInt - val result = op.sourceRange.decodeString().trim match - case "*" => l * r - case "/" => - if r == 0 then throw ArithmeticException("Division by zero") - l / r - - splice(tokens.Number(result.toString)) - private lazy val addSubPass = passDef: + private val simplifyPass = passDef: wellformed := prevWellformed.makeDerived: - Node.Top ::=! fields(tokens.Number) + Node.Top ::=! fields(Number) + + def evaluateOperation(left: Node, op: Node, right: Node): Int = + val leftNum = left.sourceRange.decodeString().toInt + val rightNum = right.sourceRange.decodeString().toInt + val operation = op.sourceRange.decodeString() + + operation match + case "+" => leftNum + rightNum + case "-" => leftNum - rightNum + case "*" => leftNum * rightNum + case "/" => leftNum / rightNum + case _ => throw IllegalArgumentException("Unknown operator") pass(once = false, strategy = pass.topDown) .rules: on( - tok(tokens.Operation).filter(node => - Set("+", "-").contains(node.sourceRange.decodeString().trim) - ).withChildren( - field(tokens.Number) - ~ field(tokens.Operator) - ~ field(tokens.Number) - ~ trailing + tok(Expression) *> children( + field(tok(Number)) + ~ field(tok(LowPrecOp, HighPrecOp)) + ~ field(tok(Number)) + ~ trailing ) ).rewrite: (left, op, right) => - val l = left.sourceRange.decodeString().toInt - val r = right.sourceRange.decodeString().toInt - val result = op.sourceRange.decodeString().trim match - case "+" => l + r - case "-" => l - r - - splice(tokens.Number(result.toString)) \ No newline at end of file + splice( + Number( + evaluateOperation( + left.unparent(), + op.unparent(), + right.unparent() + ).toString + ) + ) \ No newline at end of file diff --git a/calc/CalcReader.scala b/calc/CalcReader.scala index ac0875c..123f9c7 100644 --- a/calc/CalcReader.scala +++ b/calc/CalcReader.scala @@ -6,22 +6,25 @@ import distcompiler.* import dsl.* import distcompiler.Builtin.{Error, SourceMarker} import Reader.* +import distcompiler.calc.tokens.* object CalcReader extends Reader: override lazy val wellformed = Wellformed: - Node.Top ::= repeated(choice(tokens.Number, tokens.Operator)) + Node.Top ::= repeated(choice(tokens.Number, tokens.Expression, tokens.LowPrecOp, tokens.HighPrecOp)) - tokens.Number ::= Atom - tokens.Operator ::= Atom - - tokens.Operation ::= fields( - tokens.Number, - tokens.Operator, - tokens.Number + Number ::= Atom + LowPrecOp ::= Atom + HighPrecOp ::= Atom + + Expression ::= fields( + choice(tokens.Number, tokens.Expression), + choice(tokens.LowPrecOp, tokens.HighPrecOp), + choice(tokens.Number, tokens.Expression), ) private val digit: Set[Char] = ('0' to '9').toSet - private val operation: Set[Char] = Set('*', '/', '+', '-') + private val lowPrecOperation: Set[Char] = Set('+', '-') + private val highPrecOperation: Set[Char] = Set('*', '/') private val whitespace: Set[Char] = Set(' ', '\n', '\t') private lazy val unexpectedEOF: Manip[SourceRange] = @@ -37,10 +40,12 @@ object CalcReader extends Reader: extendThisNodeWithMatch(rules) .onOneOf(digit): numberMode - .onOneOf(operation): - operationMode + .onOneOf(lowPrecOperation): + lowPrecOpMode + .onOneOf(highPrecOperation): + highPrecOpMode .fallback: - bytes.selectCount(1): + bytes.selectOne: consumeMatch: m => addChild(Error("invalid byte", SourceMarker(m))) *> rules @@ -58,14 +63,20 @@ object CalcReader extends Reader: consumeMatch: m => m.decodeString().toIntOption match case Some(value) => - addChild(tokens.Number(m)) + addChild(Number(m)) *> rules case None => addChild(Error("invalid number format", SourceMarker(m))) *> rules - private lazy val operationMode: Manip[SourceRange] = + private lazy val lowPrecOpMode: Manip[SourceRange] = + commit: + consumeMatch: m => + addChild(LowPrecOp(m)) + *> rules + + private lazy val highPrecOpMode: Manip[SourceRange] = commit: consumeMatch: m => - addChild(tokens.Operator(m)) + addChild(HighPrecOp(m)) *> rules \ No newline at end of file diff --git a/calc/CalcReader.test.scala b/calc/CalcReader.test.scala index d91b503..4b61efa 100644 --- a/calc/CalcReader.test.scala +++ b/calc/CalcReader.test.scala @@ -10,7 +10,15 @@ class CalcReaderTests extends munit.FunSuite: def evaluate: Node.Top = val top = parse - CalcParser(top) + + CalcParser(top, tracer = Manip.RewriteDebugTracer(os.pwd / "dbg_calc_passes")) + + os.write.over( + os.pwd / "dbg_calc_parser" / "test_output.dbg", + top.toPrettyWritable(CalcReader.wellformed), + createFolders = true + ) + top test("empty string"): @@ -35,34 +43,69 @@ class CalcReaderTests extends munit.FunSuite: "5 + 11".parse, Node.Top( tokens.Number("5"), - tokens.Operator("+"), + tokens.LowPrecOp("+"), + tokens.Number("11") + ) + ) + + test("simple multiplication"): + assertEquals( + "5 * 11".parse, + Node.Top( + tokens.Number("5"), + tokens.HighPrecOp("*"), tokens.Number("11") ) ) test("addition calculation"): - assertEquals("5 + 11".evaluate, Node.Top(tokens.Number("16"))) + assertEquals( + "5 + 11".evaluate, + Node.Top( + tokens.Number("16") + )) - // test("calculation"): - // assertEquals("10 - 7 + 4".evaluate, 7) + test("multiplication calculation"): + assertEquals( + "5 * 11".evaluate, + Node.Top( + tokens.Number("55") + )) + + test("full calculation"): + assertEquals( + "5 + 11 * 4".evaluate, + Node.Top( + tokens.Number("49") + )) + + test("full calculation 2"): + assertEquals( + "5 * 4 + 4 / 2".evaluate, + Node.Top( + tokens.Number("22") + )) + + test("full calculation 3"): + assertEquals( + "5 * 4 + 4 / 2 - 6".evaluate, + Node.Top( + tokens.Number("16") + )) + + test("full calculation 4"): + assertEquals( + "5 * 4 + 4 / 2 - 6 * 2".evaluate, + Node.Top( + tokens.Number("10") + )) // test("error: incorrect format"): // intercept[IllegalArgumentException] ( // "10 - 10 7".evaluate // ) - // test("multiplication calculation"): - // assertEquals("12 * 5 / 2".evaluate, 30) - // test("error: divide by zero"): // intercept[ArithmeticException] ( // "16 * 2 / 0".evaluate - // ) - - // test("error: invalid character error"): - // intercept[IllegalArgumentException] ( - // "5k * 10".evaluate - // ) - - // test("full calculation"): - // assertEquals("10 + 7 * 5 - 9 / 3".evaluate, 42) \ No newline at end of file + // ) \ No newline at end of file diff --git a/calc/package.scala b/calc/package.scala index 535b99f..a1bcca4 100644 --- a/calc/package.scala +++ b/calc/package.scala @@ -6,31 +6,33 @@ import scala.collection.mutable import distcompiler.* import dsl.* +import distcompiler.calc.tokens.* object tokens: - object Operation extends Token + object LowPrecOp extends Token: + override def showSource: Boolean = true - object Number extends Token: + object HighPrecOp extends Token: override def showSource: Boolean = true - object Operator extends Token: + object Expression extends Token: override def showSource: Boolean = true - object Result extends Token: - override val symbolTableFor: Set[Token] = - Set(Number) + object Number extends Token: + override def showSource: Boolean = true val wellformed: Wellformed = Wellformed: - Node.Top ::= repeated(choice(tokens.Number, tokens.Operator)) + Node.Top ::= repeated(choice(tokens.Number, tokens.Expression, tokens.LowPrecOp, tokens.HighPrecOp)) tokens.Number ::= Atom - tokens.Operator ::= Atom - - tokens.Operation ::= fields( - tokens.Number, - tokens.Operator, - tokens.Number + tokens.LowPrecOp ::= Atom + tokens.HighPrecOp ::= Atom + + tokens.Expression ::= fields( + choice(tokens.Number, tokens.Expression), + choice(tokens.LowPrecOp, tokens.HighPrecOp), + choice(tokens.Number, tokens.Expression), ) object parse: From 5a43bf61a6473f45dc33553695db2b89c3249645 Mon Sep 17 00:00:00 2001 From: navidaminnn Date: Wed, 26 Feb 2025 13:54:49 -0500 Subject: [PATCH 05/11] added more Token types for stricter pattern matching --- calc/CalcParser.scala | 84 +++++++++++------------ calc/CalcReader.scala | 77 +++++++++++++++------- calc/CalcReader.test.scala | 132 ++++++++++++++++++++++++++++++++----- calc/package.scala | 59 ++++++++++++++--- 4 files changed, 261 insertions(+), 91 deletions(-) diff --git a/calc/CalcParser.scala b/calc/CalcParser.scala index 31d1fbe..457c4ca 100644 --- a/calc/CalcParser.scala +++ b/calc/CalcParser.scala @@ -16,76 +16,70 @@ object CalcParser extends PassSeq: private val mulDivPass = passDef: wellformed := inputWellformed.makeDerived: - Node.Top ::=! repeated(choice(Number, Expression, LowPrecOp)) + Node.Top ::=! repeated(choice(Number, Expression, AddOp, SubOp)) - pass(once = true, strategy = pass.topDown) + pass(once = false, strategy = pass.topDown) .rules: on( field(tok(Number, Expression)) - ~ field(tok(HighPrecOp)) + ~ skip(tok(MulOp)) ~ field(tok(Number, Expression)) ~ trailing - ).rewrite: (left, operator, right) => + ).rewrite: (left, right) => splice( Expression( - left.unparent(), - operator.unparent(), - right.unparent() + Mul( + left.unparent(), + right.unparent() + ) ) ) - - private val addSubPass = passDef: - wellformed := prevWellformed.makeDerived: - Node.Top ::=! repeated(choice(Number, Expression)) - - pass(once = true, strategy = pass.topDown) - .rules: - on( + | on( field(tok(Number, Expression)) - ~ field(tok(LowPrecOp)) + ~ skip(tok(DivOp)) ~ field(tok(Number, Expression)) ~ trailing - ).rewrite: (left, operator, right) => + ).rewrite: (left, right) => splice( Expression( - left.unparent(), - operator.unparent(), - right.unparent() + Div( + left.unparent(), + right.unparent() + ) ) ) - private val simplifyPass = passDef: + private val addSubPass = passDef: wellformed := prevWellformed.makeDerived: - Node.Top ::=! fields(Number) - - def evaluateOperation(left: Node, op: Node, right: Node): Int = - val leftNum = left.sourceRange.decodeString().toInt - val rightNum = right.sourceRange.decodeString().toInt - val operation = op.sourceRange.decodeString() - - operation match - case "+" => leftNum + rightNum - case "-" => leftNum - rightNum - case "*" => leftNum * rightNum - case "/" => leftNum / rightNum - case _ => throw IllegalArgumentException("Unknown operator") + Node.Top ::=! repeated(Expression) pass(once = false, strategy = pass.topDown) .rules: on( - tok(Expression) *> children( - field(tok(Number)) - ~ field(tok(LowPrecOp, HighPrecOp)) - ~ field(tok(Number)) - ~ trailing + field(tok(Number, Expression)) + ~ skip(tok(AddOp)) + ~ field(tok(Number, Expression)) + ~ trailing + ).rewrite: (left, right) => + splice( + Expression( + Add( + left.unparent(), + right.unparent() + ) + ) ) - ).rewrite: (left, op, right) => + | on( + field(tok(Number, Expression)) + ~ skip(tok(SubOp)) + ~ field(tok(Number, Expression)) + ~ trailing + ).rewrite: (left, right) => splice( - Number( - evaluateOperation( + Expression( + Sub( left.unparent(), - op.unparent(), right.unparent() - ).toString + ) ) - ) \ No newline at end of file + ) diff --git a/calc/CalcReader.scala b/calc/CalcReader.scala index 123f9c7..c6f0a3d 100644 --- a/calc/CalcReader.scala +++ b/calc/CalcReader.scala @@ -10,21 +10,43 @@ import distcompiler.calc.tokens.* object CalcReader extends Reader: override lazy val wellformed = Wellformed: - Node.Top ::= repeated(choice(tokens.Number, tokens.Expression, tokens.LowPrecOp, tokens.HighPrecOp)) + Node.Top ::= repeated(choice(Number, AddOp, SubOp, MulOp, DivOp)) Number ::= Atom - LowPrecOp ::= Atom - HighPrecOp ::= Atom + AddOp ::= Atom + SubOp ::= Atom + MulOp ::= Atom + DivOp ::= Atom - Expression ::= fields( - choice(tokens.Number, tokens.Expression), - choice(tokens.LowPrecOp, tokens.HighPrecOp), - choice(tokens.Number, tokens.Expression), + Add ::= fields( + choice(Number, Expression), + choice(Number, Expression) + ) + + Sub ::= fields( + choice(Number, Expression), + choice(Number, Expression) + ) + + Mul ::= fields( + choice(Number, Expression), + choice(Number, Expression) + ) + + Div ::= fields( + choice(Number, Expression), + choice(Number, Expression) + ) + + Expression ::= choice( + Add, + Sub, + Mul, + Div ) private val digit: Set[Char] = ('0' to '9').toSet - private val lowPrecOperation: Set[Char] = Set('+', '-') - private val highPrecOperation: Set[Char] = Set('*', '/') + private val operator: Set[Char] = Set('+', '-', '*', '/') private val whitespace: Set[Char] = Set(' ', '\n', '\t') private lazy val unexpectedEOF: Manip[SourceRange] = @@ -40,10 +62,8 @@ object CalcReader extends Reader: extendThisNodeWithMatch(rules) .onOneOf(digit): numberMode - .onOneOf(lowPrecOperation): - lowPrecOpMode - .onOneOf(highPrecOperation): - highPrecOpMode + .onOneOf(operator): + operatorMode .fallback: bytes.selectOne: consumeMatch: m => @@ -69,14 +89,27 @@ object CalcReader extends Reader: addChild(Error("invalid number format", SourceMarker(m))) *> rules - private lazy val lowPrecOpMode: Manip[SourceRange] = - commit: - consumeMatch: m => - addChild(LowPrecOp(m)) - *> rules - - private lazy val highPrecOpMode: Manip[SourceRange] = + private lazy val operatorMode: Manip[SourceRange] = commit: consumeMatch: m => - addChild(HighPrecOp(m)) - *> rules \ No newline at end of file + m.decodeString() match + case "+" => + addChild( + AddOp() + ) + *> rules + case "-" => + addChild( + SubOp() + ) + *> rules + case "*" => + addChild( + MulOp() + ) + *> rules + case "/" => + addChild( + DivOp() + ) + *> rules diff --git a/calc/CalcReader.test.scala b/calc/CalcReader.test.scala index 4b61efa..fcf3da5 100644 --- a/calc/CalcReader.test.scala +++ b/calc/CalcReader.test.scala @@ -5,13 +5,13 @@ import Builtin.{Error, SourceMarker} class CalcReaderTests extends munit.FunSuite: extension (str: String) - def parse: Node.Top = - calc.parse.fromSourceRange(SourceRange.entire(Source.fromString(str))) + def read: Node.Top = + calc.read.fromSourceRange(SourceRange.entire(Source.fromString(str))) - def evaluate: Node.Top = - val top = parse + def parse: Node.Top = + val top = read - CalcParser(top, tracer = Manip.RewriteDebugTracer(os.pwd / "dbg_calc_passes")) + CalcParser(top, tracer = Manip.RewriteDebugTracer(os.pwd / "dbg_calc_parser_passes")) os.write.over( os.pwd / "dbg_calc_parser" / "test_output.dbg", @@ -21,43 +21,145 @@ class CalcReaderTests extends munit.FunSuite: top + def evaluate: Node.Top = + val top = parse + + CalcEvaluator(top, tracer = Manip.RewriteDebugTracer(os.pwd / "dbg_calc_evaluator_passes")) + + os.write.over( + os.pwd / "dbg_calc_evaluator" / "test_output.dbg", + top.toPrettyWritable(CalcReader.wellformed), + createFolders = true + ) + + top + test("empty string"): - assertEquals("".parse, Node.Top()) + assertEquals("".read, Node.Top()) test("only whitespace"): - assertEquals(" \n\t".parse, Node.Top()) + assertEquals(" \n\t".read, Node.Top()) test("error: invalid character"): - assertEquals("k".parse, Node.Top(Error("invalid byte", SourceMarker("k")))) + assertEquals("k".read, Node.Top(Error("invalid byte", SourceMarker("k")))) test("single number"): assertEquals( - "5".parse, + "5".read, Node.Top( tokens.Number("5") ) ) - test("simple addition"): + test("read basic addition"): assertEquals( - "5 + 11".parse, + "5 + 11".read, Node.Top( tokens.Number("5"), - tokens.LowPrecOp("+"), + tokens.AddOp(), tokens.Number("11") ) ) - test("simple multiplication"): + test("read basic multiplication"): assertEquals( - "5 * 11".parse, + "5 * 11".read, Node.Top( tokens.Number("5"), - tokens.HighPrecOp("*"), + tokens.MulOp(), tokens.Number("11") ) ) + test("read full calculation"): + assertEquals( + "5 + 11 * 4".read, + Node.Top( + tokens.Number("5"), + tokens.AddOp(), + tokens.Number("11"), + tokens.MulOp(), + tokens.Number("4") + ) + ) + + test("simple addition parse"): + assertEquals( + "5 + 11".parse, + Node.Top( + tokens.Expression( + tokens.Add( + tokens.Number("5"), + tokens.Number("11") + ).at("5 + 11") + ).at("5 + 11") + ) + ) + + test("simple multiplication parse"): + assertEquals( + "5 * 11".parse, + Node.Top( + tokens.Expression( + tokens.Mul( + tokens.Number("5"), + tokens.Number("11") + ).at("5 * 11") + ).at("5 * 11") + ) + ) + + test("full calculation parse"): + assertEquals( + "5 + 11 * 4".parse, + Node.Top( + tokens.Expression( + tokens.Add( + tokens.Number("5"), + tokens.Expression( + tokens.Mul( + tokens.Number("11"), + tokens.Number("4") + ).at("11 * 4") + ).at("11 * 4") + ).at("5 + 11 * 4") + ).at("5 + 11 * 4") + ) + ) + + test("full calculation 4 parse"): + assertEquals( + "5 * 4 + 4 / 2 - 6 * 2".parse, + Node.Top( + tokens.Expression( + tokens.Sub( + tokens.Expression( + tokens.Add( + tokens.Expression( + tokens.Mul( + tokens.Number("5"), + tokens.Number("4") + ).at("5 * 4") + ).at("5 * 4"), + tokens.Expression( + tokens.Div( + tokens.Number("4"), + tokens.Number("2") + ).at("4 / 2") + ).at("4 / 2") + ).at("5 * 4 + 4 / 2") + ).at("5 * 4 + 4 / 2"), + tokens.Expression( + tokens.Mul( + tokens.Number("6"), + tokens.Number("2") + ).at("6 * 2") + ).at("6 * 2") + ).at("5 * 4 + 4 / 2 - 6 * 2") + ).at("5 * 4 + 4 / 2 - 6 * 2") + ) + ) + test("addition calculation"): assertEquals( "5 + 11".evaluate, diff --git a/calc/package.scala b/calc/package.scala index a1bcca4..dbe2a35 100644 --- a/calc/package.scala +++ b/calc/package.scala @@ -9,13 +9,31 @@ import dsl.* import distcompiler.calc.tokens.* object tokens: - object LowPrecOp extends Token: + object Expression extends Token: override def showSource: Boolean = true - object HighPrecOp extends Token: + object AddOp extends Token: override def showSource: Boolean = true - object Expression extends Token: + object DivOp extends Token: + override def showSource: Boolean = true + + object MulOp extends Token: + override def showSource: Boolean = true + + object SubOp extends Token: + override def showSource: Boolean = true + + object Add extends Token: + override def showSource: Boolean = true + + object Sub extends Token: + override def showSource: Boolean = true + + object Mul extends Token: + override def showSource: Boolean = true + + object Div extends Token: override def showSource: Boolean = true object Number extends Token: @@ -23,18 +41,41 @@ object tokens: val wellformed: Wellformed = Wellformed: - Node.Top ::= repeated(choice(tokens.Number, tokens.Expression, tokens.LowPrecOp, tokens.HighPrecOp)) + Node.Top ::= repeated(choice(tokens.Number, tokens.AddOp, tokens.SubOp, tokens.MulOp, tokens.DivOp)) tokens.Number ::= Atom - tokens.LowPrecOp ::= Atom - tokens.HighPrecOp ::= Atom + tokens.AddOp ::= Atom + tokens.SubOp ::= Atom + tokens.MulOp ::= Atom + tokens.DivOp ::= Atom + + tokens.Add ::= fields( + choice(tokens.Number, tokens.Expression), + choice(tokens.Number, tokens.Expression) + ) + + tokens.Sub ::= fields( + choice(tokens.Number, tokens.Expression), + choice(tokens.Number, tokens.Expression) + ) - tokens.Expression ::= fields( + tokens.Mul ::= fields( choice(tokens.Number, tokens.Expression), - choice(tokens.LowPrecOp, tokens.HighPrecOp), + choice(tokens.Number, tokens.Expression) + ) + + tokens.Div ::= fields( choice(tokens.Number, tokens.Expression), + choice(tokens.Number, tokens.Expression) + ) + + tokens.Expression ::= choice( + tokens.Add, + tokens.Sub, + tokens.Mul, + tokens.Div ) -object parse: +object read: def fromSourceRange(sourceRange: SourceRange): Node.Top = CalcReader(sourceRange) \ No newline at end of file From faa721c7d59027a9fd73db5dac139e8bbf3e1a50 Mon Sep 17 00:00:00 2001 From: navidaminnn Date: Wed, 19 Mar 2025 13:59:08 -0400 Subject: [PATCH 06/11] made appropriate changes to wellformed and added CalcEvaluator --- calc/CalcEvaluator.scala | 98 ++++++++++++++++++++++++++++++++++++++ calc/CalcParser.scala | 2 +- calc/CalcReader.scala | 67 +++++--------------------- calc/CalcReader.test.scala | 10 ---- 4 files changed, 111 insertions(+), 66 deletions(-) create mode 100644 calc/CalcEvaluator.scala diff --git a/calc/CalcEvaluator.scala b/calc/CalcEvaluator.scala new file mode 100644 index 0000000..6d1062f --- /dev/null +++ b/calc/CalcEvaluator.scala @@ -0,0 +1,98 @@ +package distcompiler.calc + +import cats.syntax.all.given + +import distcompiler.* +import dsl.* +import distcompiler.Builtin.{Error, SourceMarker} +import distcompiler.calc.tokens.* + +object CalcEvaluator extends PassSeq: + import distcompiler.dsl.* + import Reader.* + import CalcReader.* + + def inputWellformed: Wellformed = CalcParser.outputWellformed + + private val simplifyPass = passDef: + wellformed := inputWellformed.makeDerived: + Node.Top ::=! fields(Number) + + pass(once = false, strategy = pass.topDown) + .rules: + on( + field(tok(Expression)) *> children( + tok(Add).product(children( + field(tok(Number)) + ~ field(tok(Number)) + ~ eof + )) + ) + ).rewrite: (expression, numbers) => + val (left, right) = numbers + expression.unparent() + val leftNum = left.unparent().sourceRange.decodeString().toInt + val rightNum = right.unparent().sourceRange.decodeString().toInt + + splice( + Number( + (leftNum + rightNum).toString() + ) + ) + | on( + field(tok(Expression)) *> children( + tok(Sub).product(children( + field(tok(Number)) + ~ field(tok(Number)) + ~ eof + )) + ) + ).rewrite: (expression, numbers) => + val (left, right) = numbers + expression.unparent() + val leftNum = left.unparent().sourceRange.decodeString().toInt + val rightNum = right.unparent().sourceRange.decodeString().toInt + + splice( + Number( + (leftNum - rightNum).toString() + ) + ) + | on( + field(tok(Expression)) *> children( + tok(Mul).product(children( + field(tok(Number)) + ~ field(tok(Number)) + ~ eof + )) + ) + ).rewrite: (expression, numbers) => + val (left, right) = numbers + expression.unparent() + val leftNum = left.unparent().sourceRange.decodeString().toInt + val rightNum = right.unparent().sourceRange.decodeString().toInt + + splice( + Number( + (leftNum * rightNum).toString() + ) + ) + | on( + field(tok(Expression)) *> children( + tok(Div).product(children( + field(tok(Number)) + ~ field(tok(Number)) + ~ eof + )) + ) + ).rewrite: (expression, numbers) => + val (left, right) = numbers + expression.unparent() + val leftNum = left.unparent().sourceRange.decodeString().toInt + val rightNum = right.unparent().sourceRange.decodeString().toInt + + splice( + Number( + (leftNum / rightNum).toString() + ) + ) diff --git a/calc/CalcParser.scala b/calc/CalcParser.scala index 457c4ca..0aef4e3 100644 --- a/calc/CalcParser.scala +++ b/calc/CalcParser.scala @@ -12,7 +12,7 @@ object CalcParser extends PassSeq: import Reader.* import CalcReader.* - def inputWellformed: Wellformed = CalcReader.wellformed + def inputWellformed: Wellformed = distcompiler.calc.wellformed private val mulDivPass = passDef: wellformed := inputWellformed.makeDerived: diff --git a/calc/CalcReader.scala b/calc/CalcReader.scala index c6f0a3d..a85436f 100644 --- a/calc/CalcReader.scala +++ b/calc/CalcReader.scala @@ -18,35 +18,7 @@ object CalcReader extends Reader: MulOp ::= Atom DivOp ::= Atom - Add ::= fields( - choice(Number, Expression), - choice(Number, Expression) - ) - - Sub ::= fields( - choice(Number, Expression), - choice(Number, Expression) - ) - - Mul ::= fields( - choice(Number, Expression), - choice(Number, Expression) - ) - - Div ::= fields( - choice(Number, Expression), - choice(Number, Expression) - ) - - Expression ::= choice( - Add, - Sub, - Mul, - Div - ) - private val digit: Set[Char] = ('0' to '9').toSet - private val operator: Set[Char] = Set('+', '-', '*', '/') private val whitespace: Set[Char] = Set(' ', '\n', '\t') private lazy val unexpectedEOF: Manip[SourceRange] = @@ -62,8 +34,18 @@ object CalcReader extends Reader: extendThisNodeWithMatch(rules) .onOneOf(digit): numberMode - .onOneOf(operator): - operatorMode + .on('+'): + addChild(AddOp()) + *> rules + .on('-'): + addChild(SubOp()) + *> rules + .on('*'): + addChild(MulOp()) + *> rules + .on('/'): + addChild(DivOp()) + *> rules .fallback: bytes.selectOne: consumeMatch: m => @@ -88,28 +70,3 @@ object CalcReader extends Reader: case None => addChild(Error("invalid number format", SourceMarker(m))) *> rules - - private lazy val operatorMode: Manip[SourceRange] = - commit: - consumeMatch: m => - m.decodeString() match - case "+" => - addChild( - AddOp() - ) - *> rules - case "-" => - addChild( - SubOp() - ) - *> rules - case "*" => - addChild( - MulOp() - ) - *> rules - case "/" => - addChild( - DivOp() - ) - *> rules diff --git a/calc/CalcReader.test.scala b/calc/CalcReader.test.scala index fcf3da5..4db18e4 100644 --- a/calc/CalcReader.test.scala +++ b/calc/CalcReader.test.scala @@ -201,13 +201,3 @@ class CalcReaderTests extends munit.FunSuite: Node.Top( tokens.Number("10") )) - - // test("error: incorrect format"): - // intercept[IllegalArgumentException] ( - // "10 - 10 7".evaluate - // ) - - // test("error: divide by zero"): - // intercept[ArithmeticException] ( - // "16 * 2 / 0".evaluate - // ) \ No newline at end of file From 949c74d4408bb18fe5428991ea50fc0025f3ee67 Mon Sep 17 00:00:00 2001 From: navidaminnn Date: Fri, 21 Mar 2025 18:03:31 -0400 Subject: [PATCH 07/11] implemented nested Number & cleaned up main structure --- calc/CalcEvaluator.scala | 120 +++++++++++------- calc/CalcParser.scala | 80 +++++++++--- calc/CalcReader.scala | 28 ++++- calc/CalcReader.test.scala | 244 ++++++++++++++++++++++++++----------- calc/package.scala | 45 ++++--- 5 files changed, 361 insertions(+), 156 deletions(-) diff --git a/calc/CalcEvaluator.scala b/calc/CalcEvaluator.scala index 6d1062f..04c0361 100644 --- a/calc/CalcEvaluator.scala +++ b/calc/CalcEvaluator.scala @@ -1,3 +1,17 @@ +// Copyright 2024-2025 DCal Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package distcompiler.calc import cats.syntax.all.given @@ -12,87 +26,99 @@ object CalcEvaluator extends PassSeq: import Reader.* import CalcReader.* - def inputWellformed: Wellformed = CalcParser.outputWellformed + def inputWellformed: Wellformed = distcompiler.calc.wellformed private val simplifyPass = passDef: - wellformed := inputWellformed.makeDerived: - Node.Top ::=! fields(Number) + wellformed := inputWellformed - pass(once = false, strategy = pass.topDown) + pass(once = false, strategy = pass.bottomUp) .rules: on( - field(tok(Expression)) *> children( - tok(Add).product(children( - field(tok(Number)) - ~ field(tok(Number)) - ~ eof - )) + field(tok(Expression)) *> onlyChild( + tok(Add).withChildren: + field(tok(Expression) *> onlyChild(tok(Number))) + ~ field(tok(Expression) *> onlyChild(tok(Number))) + ~ eof ) - ).rewrite: (expression, numbers) => - val (left, right) = numbers - expression.unparent() + ).rewrite: (left, right) => val leftNum = left.unparent().sourceRange.decodeString().toInt val rightNum = right.unparent().sourceRange.decodeString().toInt splice( - Number( - (leftNum + rightNum).toString() + Expression( + Number( + (leftNum + rightNum).toString() + ) ) ) | on( - field(tok(Expression)) *> children( - tok(Sub).product(children( - field(tok(Number)) - ~ field(tok(Number)) - ~ eof - )) + field(tok(Expression)) *> onlyChild( + tok(Sub).withChildren: + field(tok(Expression) *> onlyChild(tok(Number))) + ~ field(tok(Expression) *> onlyChild(tok(Number))) + ~ eof ) - ).rewrite: (expression, numbers) => - val (left, right) = numbers - expression.unparent() + ).rewrite: (left, right) => val leftNum = left.unparent().sourceRange.decodeString().toInt val rightNum = right.unparent().sourceRange.decodeString().toInt splice( - Number( - (leftNum - rightNum).toString() + Expression( + Number( + (leftNum - rightNum).toString() + ) ) ) | on( - field(tok(Expression)) *> children( - tok(Mul).product(children( - field(tok(Number)) - ~ field(tok(Number)) - ~ eof - )) + field(tok(Expression)) *> onlyChild( + tok(Mul).withChildren: + field(tok(Expression) *> onlyChild(tok(Number))) + ~ field(tok(Expression) *> onlyChild(tok(Number))) + ~ eof ) - ).rewrite: (expression, numbers) => - val (left, right) = numbers - expression.unparent() + ).rewrite: (left, right) => val leftNum = left.unparent().sourceRange.decodeString().toInt val rightNum = right.unparent().sourceRange.decodeString().toInt splice( - Number( - (leftNum * rightNum).toString() + Expression( + Number( + (leftNum * rightNum).toString() + ) ) ) | on( - field(tok(Expression)) *> children( - tok(Div).product(children( - field(tok(Number)) - ~ field(tok(Number)) - ~ eof - )) + field(tok(Expression)) *> onlyChild( + tok(Div).withChildren: + field(tok(Expression) *> onlyChild(tok(Number))) + ~ field(tok(Expression) *> onlyChild(tok(Number))) + ~ eof ) - ).rewrite: (expression, numbers) => - val (left, right) = numbers - expression.unparent() + ).rewrite: (left, right) => val leftNum = left.unparent().sourceRange.decodeString().toInt val rightNum = right.unparent().sourceRange.decodeString().toInt + splice( + Expression( + Number( + (leftNum / rightNum).toString() + ) + ) + ) + + private val removeLayerPass = passDef: + wellformed := inputWellformed.makeDerived: + Node.Top ::=! fields(Number) + + pass(once = true, strategy = pass.topDown) + .rules: + on( + tok(Expression).withChildren: + field(tok(Number)) + ~ eof + ).rewrite: (number) => splice( Number( - (leftNum / rightNum).toString() + number.unparent().sourceRange.decodeString() ) ) diff --git a/calc/CalcParser.scala b/calc/CalcParser.scala index 0aef4e3..fde8b10 100644 --- a/calc/CalcParser.scala +++ b/calc/CalcParser.scala @@ -1,3 +1,17 @@ +// Copyright 2024-2025 DCal Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package distcompiler.calc import cats.syntax.all.given @@ -12,19 +26,47 @@ object CalcParser extends PassSeq: import Reader.* import CalcReader.* - def inputWellformed: Wellformed = distcompiler.calc.wellformed + def inputWellformed: Wellformed = + CalcReader.wellformed.makeDerived: + Add ::= fields( + Expression, + Expression + ) + + Sub ::= fields( + Expression, + Expression + ) + + Mul ::= fields( + Expression, + Expression + ) + + Div ::= fields( + Expression, + Expression + ) + + Expression ::=! choice( + Number, + Add, + Sub, + Mul, + Div + ) private val mulDivPass = passDef: wellformed := inputWellformed.makeDerived: - Node.Top ::=! repeated(choice(Number, Expression, AddOp, SubOp)) - + Node.Top ::=! repeated(choice(Expression, AddOp, SubOp)) + pass(once = false, strategy = pass.topDown) .rules: on( - field(tok(Number, Expression)) - ~ skip(tok(MulOp)) - ~ field(tok(Number, Expression)) - ~ trailing + field(tok(Expression)) + ~ skip(tok(MulOp)) + ~ field(tok(Expression)) + ~ trailing ).rewrite: (left, right) => splice( Expression( @@ -35,10 +77,10 @@ object CalcParser extends PassSeq: ) ) | on( - field(tok(Number, Expression)) - ~ skip(tok(DivOp)) - ~ field(tok(Number, Expression)) - ~ trailing + field(tok(Expression)) + ~ skip(tok(DivOp)) + ~ field(tok(Expression)) + ~ trailing ).rewrite: (left, right) => splice( Expression( @@ -56,10 +98,10 @@ object CalcParser extends PassSeq: pass(once = false, strategy = pass.topDown) .rules: on( - field(tok(Number, Expression)) - ~ skip(tok(AddOp)) - ~ field(tok(Number, Expression)) - ~ trailing + field(tok(Expression)) + ~ skip(tok(AddOp)) + ~ field(tok(Expression)) + ~ trailing ).rewrite: (left, right) => splice( Expression( @@ -70,10 +112,10 @@ object CalcParser extends PassSeq: ) ) | on( - field(tok(Number, Expression)) - ~ skip(tok(SubOp)) - ~ field(tok(Number, Expression)) - ~ trailing + field(tok(Expression)) + ~ skip(tok(SubOp)) + ~ field(tok(Expression)) + ~ trailing ).rewrite: (left, right) => splice( Expression( diff --git a/calc/CalcReader.scala b/calc/CalcReader.scala index a85436f..1697eac 100644 --- a/calc/CalcReader.scala +++ b/calc/CalcReader.scala @@ -1,3 +1,17 @@ +// Copyright 2024-2025 DCal Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package distcompiler.calc import cats.syntax.all.given @@ -10,14 +24,18 @@ import distcompiler.calc.tokens.* object CalcReader extends Reader: override lazy val wellformed = Wellformed: - Node.Top ::= repeated(choice(Number, AddOp, SubOp, MulOp, DivOp)) - + Node.Top ::= repeated(choice(Expression, AddOp, SubOp, MulOp, DivOp)) + Number ::= Atom AddOp ::= Atom SubOp ::= Atom MulOp ::= Atom DivOp ::= Atom + Expression ::= fields( + Number + ) + private val digit: Set[Char] = ('0' to '9').toSet private val whitespace: Set[Char] = Set(' ', '\n', '\t') @@ -65,7 +83,11 @@ object CalcReader extends Reader: consumeMatch: m => m.decodeString().toIntOption match case Some(value) => - addChild(Number(m)) + addChild( + Expression( + Number(m) + ) + ) *> rules case None => addChild(Error("invalid number format", SourceMarker(m))) diff --git a/calc/CalcReader.test.scala b/calc/CalcReader.test.scala index 4db18e4..3911f74 100644 --- a/calc/CalcReader.test.scala +++ b/calc/CalcReader.test.scala @@ -1,3 +1,17 @@ +// Copyright 2024-2025 DCal Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package distcompiler.calc import distcompiler.* @@ -8,10 +22,13 @@ class CalcReaderTests extends munit.FunSuite: def read: Node.Top = calc.read.fromSourceRange(SourceRange.entire(Source.fromString(str))) - def parse: Node.Top = + def parse: Node.Top = val top = read - CalcParser(top, tracer = Manip.RewriteDebugTracer(os.pwd / "dbg_calc_parser_passes")) + CalcParser( + top, + tracer = Manip.RewriteDebugTracer(os.pwd / "dbg_calc_parser_passes") + ) os.write.over( os.pwd / "dbg_calc_parser" / "test_output.dbg", @@ -21,10 +38,13 @@ class CalcReaderTests extends munit.FunSuite: top - def evaluate: Node.Top = + def evaluate: Node.Top = val top = parse - CalcEvaluator(top, tracer = Manip.RewriteDebugTracer(os.pwd / "dbg_calc_evaluator_passes")) + CalcEvaluator( + top, + tracer = Manip.RewriteDebugTracer(os.pwd / "dbg_calc_evaluator_passes") + ) os.write.over( os.pwd / "dbg_calc_evaluator" / "test_output.dbg", @@ -45,9 +65,11 @@ class CalcReaderTests extends munit.FunSuite: test("single number"): assertEquals( - "5".read, + "5".read, Node.Top( - tokens.Number("5") + tokens.Expression( + tokens.Number("5") + ) ) ) @@ -55,9 +77,13 @@ class CalcReaderTests extends munit.FunSuite: assertEquals( "5 + 11".read, Node.Top( - tokens.Number("5"), + tokens.Expression( + tokens.Number("5") + ), tokens.AddOp(), - tokens.Number("11") + tokens.Expression( + tokens.Number("11") + ) ) ) @@ -65,9 +91,13 @@ class CalcReaderTests extends munit.FunSuite: assertEquals( "5 * 11".read, Node.Top( - tokens.Number("5"), + tokens.Expression( + tokens.Number("5") + ), tokens.MulOp(), - tokens.Number("11") + tokens.Expression( + tokens.Number("11") + ) ) ) @@ -75,37 +105,59 @@ class CalcReaderTests extends munit.FunSuite: assertEquals( "5 + 11 * 4".read, Node.Top( - tokens.Number("5"), + tokens.Expression( + tokens.Number("5") + ), tokens.AddOp(), - tokens.Number("11"), + tokens.Expression( + tokens.Number("11") + ), tokens.MulOp(), - tokens.Number("4") + tokens.Expression( + tokens.Number("4") + ) ) ) test("simple addition parse"): assertEquals( - "5 + 11".parse, + "5 + 11".parse, Node.Top( - tokens.Expression( - tokens.Add( - tokens.Number("5"), - tokens.Number("11") - ).at("5 + 11") - ).at("5 + 11") + tokens + .Expression( + tokens + .Add( + tokens.Expression( + tokens.Number("5") + ), + tokens.Expression( + tokens.Number("11") + ) + ) + .at("5 + 11") + ) + .at("5 + 11") ) ) test("simple multiplication parse"): assertEquals( - "5 * 11".parse, + "5 * 11".parse, Node.Top( - tokens.Expression( - tokens.Mul( - tokens.Number("5"), - tokens.Number("11") - ).at("5 * 11") - ).at("5 * 11") + tokens + .Expression( + tokens + .Mul( + tokens.Expression( + tokens.Number("5") + ), + tokens.Expression( + tokens.Number("11") + ) + ) + .at("5 * 11") + ) + .at("5 * 11") ) ) @@ -113,17 +165,31 @@ class CalcReaderTests extends munit.FunSuite: assertEquals( "5 + 11 * 4".parse, Node.Top( - tokens.Expression( - tokens.Add( - tokens.Number("5"), - tokens.Expression( - tokens.Mul( - tokens.Number("11"), - tokens.Number("4") - ).at("11 * 4") - ).at("11 * 4") - ).at("5 + 11 * 4") - ).at("5 + 11 * 4") + tokens + .Expression( + tokens + .Add( + tokens.Expression( + tokens.Number("5") + ), + tokens + .Expression( + tokens + .Mul( + tokens.Expression( + tokens.Number("11") + ), + tokens.Expression( + tokens.Number("4") + ) + ) + .at("11 * 4") + ) + .at("11 * 4") + ) + .at("5 + 11 * 4") + ) + .at("5 + 11 * 4") ) ) @@ -131,32 +197,64 @@ class CalcReaderTests extends munit.FunSuite: assertEquals( "5 * 4 + 4 / 2 - 6 * 2".parse, Node.Top( - tokens.Expression( - tokens.Sub( - tokens.Expression( - tokens.Add( - tokens.Expression( - tokens.Mul( - tokens.Number("5"), - tokens.Number("4") - ).at("5 * 4") - ).at("5 * 4"), - tokens.Expression( - tokens.Div( - tokens.Number("4"), - tokens.Number("2") - ).at("4 / 2") - ).at("4 / 2") - ).at("5 * 4 + 4 / 2") - ).at("5 * 4 + 4 / 2"), - tokens.Expression( - tokens.Mul( - tokens.Number("6"), - tokens.Number("2") - ).at("6 * 2") - ).at("6 * 2") - ).at("5 * 4 + 4 / 2 - 6 * 2") - ).at("5 * 4 + 4 / 2 - 6 * 2") + tokens + .Expression( + tokens + .Sub( + tokens + .Expression( + tokens + .Add( + tokens + .Expression( + tokens + .Mul( + tokens.Expression( + tokens.Number("5") + ), + tokens.Expression( + tokens.Number("4") + ) + ) + .at("5 * 4") + ) + .at("5 * 4"), + tokens + .Expression( + tokens + .Div( + tokens.Expression( + tokens.Number("4") + ), + tokens.Expression( + tokens.Number("2") + ) + ) + .at("4 / 2") + ) + .at("4 / 2") + ) + .at("5 * 4 + 4 / 2") + ) + .at("5 * 4 + 4 / 2"), + tokens + .Expression( + tokens + .Mul( + tokens.Expression( + tokens.Number("6") + ), + tokens.Expression( + tokens.Number("2") + ) + ) + .at("6 * 2") + ) + .at("6 * 2") + ) + .at("5 * 4 + 4 / 2 - 6 * 2") + ) + .at("5 * 4 + 4 / 2 - 6 * 2") ) ) @@ -165,39 +263,45 @@ class CalcReaderTests extends munit.FunSuite: "5 + 11".evaluate, Node.Top( tokens.Number("16") - )) + ) + ) test("multiplication calculation"): assertEquals( "5 * 11".evaluate, Node.Top( tokens.Number("55") - )) + ) + ) test("full calculation"): assertEquals( "5 + 11 * 4".evaluate, Node.Top( tokens.Number("49") - )) + ) + ) test("full calculation 2"): assertEquals( "5 * 4 + 4 / 2".evaluate, Node.Top( tokens.Number("22") - )) + ) + ) test("full calculation 3"): assertEquals( "5 * 4 + 4 / 2 - 6".evaluate, Node.Top( tokens.Number("16") - )) + ) + ) test("full calculation 4"): assertEquals( "5 * 4 + 4 / 2 - 6 * 2".evaluate, Node.Top( tokens.Number("10") - )) + ) + ) diff --git a/calc/package.scala b/calc/package.scala index dbe2a35..0c13d6d 100644 --- a/calc/package.scala +++ b/calc/package.scala @@ -1,3 +1,17 @@ +// Copyright 2024-2025 DCal Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package distcompiler.calc import cats.syntax.all.given @@ -10,7 +24,7 @@ import distcompiler.calc.tokens.* object tokens: object Expression extends Token: - override def showSource: Boolean = true + override def showSource: Boolean = false object AddOp extends Token: override def showSource: Boolean = true @@ -41,35 +55,32 @@ object tokens: val wellformed: Wellformed = Wellformed: - Node.Top ::= repeated(choice(tokens.Number, tokens.AddOp, tokens.SubOp, tokens.MulOp, tokens.DivOp)) - + Node.Top ::= repeated(tokens.Expression) + tokens.Number ::= Atom - tokens.AddOp ::= Atom - tokens.SubOp ::= Atom - tokens.MulOp ::= Atom - tokens.DivOp ::= Atom tokens.Add ::= fields( - choice(tokens.Number, tokens.Expression), - choice(tokens.Number, tokens.Expression) + tokens.Expression, + tokens.Expression ) tokens.Sub ::= fields( - choice(tokens.Number, tokens.Expression), - choice(tokens.Number, tokens.Expression) + tokens.Expression, + tokens.Expression ) tokens.Mul ::= fields( - choice(tokens.Number, tokens.Expression), - choice(tokens.Number, tokens.Expression) + tokens.Expression, + tokens.Expression ) - + tokens.Div ::= fields( - choice(tokens.Number, tokens.Expression), - choice(tokens.Number, tokens.Expression) + tokens.Expression, + tokens.Expression ) tokens.Expression ::= choice( + tokens.Number, tokens.Add, tokens.Sub, tokens.Mul, @@ -78,4 +89,4 @@ val wellformed: Wellformed = object read: def fromSourceRange(sourceRange: SourceRange): Node.Top = - CalcReader(sourceRange) \ No newline at end of file + CalcReader(sourceRange) From 5f34b74242ab29f2a85f2d8de3b01252fecdf0c1 Mon Sep 17 00:00:00 2001 From: navidaminnn Date: Sat, 22 Mar 2025 00:02:11 -0400 Subject: [PATCH 08/11] cleaning up final pass --- calc/CalcEvaluator.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/calc/CalcEvaluator.scala b/calc/CalcEvaluator.scala index 04c0361..3404262 100644 --- a/calc/CalcEvaluator.scala +++ b/calc/CalcEvaluator.scala @@ -118,7 +118,5 @@ object CalcEvaluator extends PassSeq: ~ eof ).rewrite: (number) => splice( - Number( - number.unparent().sourceRange.decodeString() - ) + number.unparent() ) From b14b9eb1b7d0acd057076e6b70b14e51a3401fd3 Mon Sep 17 00:00:00 2001 From: navidaminnn Date: Wed, 9 Apr 2025 08:46:37 -0400 Subject: [PATCH 09/11] nitpick fix w/ building off prevWellformed --- calc/CalcEvaluator.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/calc/CalcEvaluator.scala b/calc/CalcEvaluator.scala index 3404262..5b1fca9 100644 --- a/calc/CalcEvaluator.scala +++ b/calc/CalcEvaluator.scala @@ -29,7 +29,8 @@ object CalcEvaluator extends PassSeq: def inputWellformed: Wellformed = distcompiler.calc.wellformed private val simplifyPass = passDef: - wellformed := inputWellformed + wellformed := inputWellformed.makeDerived: + Node.Top ::=! Expression pass(once = false, strategy = pass.bottomUp) .rules: @@ -107,8 +108,8 @@ object CalcEvaluator extends PassSeq: ) private val removeLayerPass = passDef: - wellformed := inputWellformed.makeDerived: - Node.Top ::=! fields(Number) + wellformed := prevWellformed.makeDerived: + Node.Top ::=! Number pass(once = true, strategy = pass.topDown) .rules: From 64d558c34ebf802fa593c2b3ef4215ea74df9901 Mon Sep 17 00:00:00 2001 From: navidaminnn Date: Wed, 9 Apr 2025 08:47:02 -0400 Subject: [PATCH 10/11] added a readme --- calc/README.md | 79 ++++++++++++++++++++++++++++++++++++++++++ calc/img/example1.jpg | Bin 0 -> 58121 bytes calc/img/example2.jpg | Bin 0 -> 90964 bytes 3 files changed, 79 insertions(+) create mode 100644 calc/README.md create mode 100644 calc/img/example1.jpg create mode 100644 calc/img/example2.jpg diff --git a/calc/README.md b/calc/README.md new file mode 100644 index 0000000..5a86316 --- /dev/null +++ b/calc/README.md @@ -0,0 +1,79 @@ +# ```calc/``` + +## Overview +A parser and evaluator for arithmetic expressions given in string inputs; supporting addition, subtraction, multiplication, and division operations. + + +## Motivation +The calculator exists as a practical example demonstrating how DCal can be leveraged to parse languages and manipulate ASTs, with many of DCal's core features such as ```Wellformed```, ```PassSeq```, and ```SeqPattern``` being heavily used. + + +## Components + +### 1. ```package.scala``` +Defines all of the ```Token``` types that are used and provides a wellformed definition representing how the AST should be structured once fully built. + + +### 2. ```CalcReader.scala``` +```CalcReader``` is the lexer that converts input strings into tokens. + +It contains a wellformed definition of the initial token types with ```Number``` tokens that're wrapped around ```Expression``` and ```Op``` tokens for operations. + +The ```rules``` method uses byte-level pattern matching to create tokens for numbers and operators and skip anything else. + + +### 3. ```CalcParser.scala``` +```CalcParser``` is the parser, transforming the flat list of tokens into a structured AST. + +The wellformed definition adds new ```Operation``` token types that have 2 ```Expression``` children. Also, ```Expression``` tokens have a new definition, being able to wrap both ```Number``` tokens as well as ```Operation``` tokens. + +```mulDivPass``` and ```addSubPass``` both create nested expressions and splicing the old ```Op``` tokens that were previously defined. Both methods do this by pattern matching on the sequence of ```(Expression, Op, Expression)``` and replacing this sequence with + +``` +Expression( + Operation( + Expression, + Expression + ) +) +``` + +```mulDivPass``` is executed before ```addSubPass``` to create precedence, allowing multiplication and division operations to be nested deeper than addition and subtraction operations in the AST. + + +### 4. ```CalcEvaluator.scala``` +```CalcEvaluator``` simplifies the AST and computes the value of the arithmetic expression. + +The wellformed definition is imported from ```package.scala```, picking up with the AST structure of where ```CalcParser``` left off. + +```simplifyPass``` splices all expressions repeatedly until there's only a single expression node at the top of the AST structure. The pass uses a bottom-up strategy to begin with simplifying the base-case expressions with no nesting as it goes up the AST. + +The pass sequence pattern matches on + +``` +Expression( + Operation( + Expression( + Number + ), + Expression( + Number + ) + ) +) +``` + +and replaces the sequence with ```Expression(Number)```. The remaining ```Expression``` token at the end of the pass contains the value of arithmetic expression. + +```removeLayerPass``` splices the ```Expression``` token at the top of the AST and replaces it with the ```Number``` token that was wrapped inside. Pattern matching is done on the sequence of ```Expression(Number)``` and replaces it with just ```Number```. + + +## Usage +To learn how to use the calculator, ```CalcReader.test.scala``` contains methods (```parse```, ```read```, ```evaluate```) that execute the different components of the calculator. + + +## Example +The following images demonstrates the state of the AST after each pass with the input "5 + 3 * 4". + +![example1](img/example1.jpg) +![exampel2](img/example2.jpg) diff --git a/calc/img/example1.jpg b/calc/img/example1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6ba2279af6b1268115d18935a02a91f97514d5dd GIT binary patch literal 58121 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 S*B)ODatPmMxL5ts^8WxU;TRAA literal 0 HcmV?d00001 diff --git a/calc/img/example2.jpg b/calc/img/example2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ac2ef1b675a6b25cbaa305699dbb255828dffa70 GIT binary patch literal 90964 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 Date: Wed, 9 Apr 2025 08:59:39 -0400 Subject: [PATCH 11/11] mentioned tracer --- calc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calc/README.md b/calc/README.md index 5a86316..c87b7d1 100644 --- a/calc/README.md +++ b/calc/README.md @@ -73,7 +73,7 @@ To learn how to use the calculator, ```CalcReader.test.scala``` contains methods ## Example -The following images demonstrates the state of the AST after each pass with the input "5 + 3 * 4". +The following images demonstrates the state of the AST after each pass with the input "5 + 3 * 4". Viewing the state of the AST after each pass can also be done by running calculator test cases with the tracer enabled. ![example1](img/example1.jpg) ![exampel2](img/example2.jpg)