From 302fecc91b22a1ccaea58514bba411b2aa64df31 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sat, 1 Feb 2025 00:58:29 +0000 Subject: [PATCH 01/14] refactor: remove boilerplate parser --- src/main/wacc/parser.scala | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index 370f8aa..871ade0 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -1,20 +1,8 @@ package wacc -import parsley.{Parsley, Result} -import parsley.expr.chain - -import lexer.implicits.implicitSymbol -import lexer.{integer, fully} +import parsley.Result object parser { def parse(input: String): Result[String, BigInt] = parser.parse(input) - private val parser = fully(expr) - - private val add = (x: BigInt, y: BigInt) => x + y - private val sub = (x: BigInt, y: BigInt) => x - y - - private lazy val expr: Parsley[BigInt] = - chain.left1(integer | "(" ~> expr <~ ")")( - ("+" as add) | ("-" as sub) - ) + private val parser = lexer.fully(???) } From 47548580940ec43cbc266102aced4c28ae878482 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sat, 1 Feb 2025 02:21:31 +0000 Subject: [PATCH 02/14] refactor: allow binPack Strings --- .scalafmt.conf | 2 ++ src/test/wacc/examples.scala | 3 +++ 2 files changed, 5 insertions(+) diff --git a/.scalafmt.conf b/.scalafmt.conf index 2f0c08c..22c361b 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,2 +1,4 @@ version = 3.8.6 runner.dialect = scala3 + +binPack.literalsExclude = [] diff --git a/src/test/wacc/examples.scala b/src/test/wacc/examples.scala index ef48195..cb4db0e 100644 --- a/src/test/wacc/examples.scala +++ b/src/test/wacc/examples.scala @@ -42,6 +42,8 @@ class ParallelExamplesSpec def fileIsDissallowed(filename: String): Boolean = Seq( + // format: off + // disable formatting to avoid binPack "wacc-examples/valid/advanced", "wacc-examples/valid/array", "wacc-examples/valid/basic/exit", @@ -88,5 +90,6 @@ class ParallelExamplesSpec "wacc-examples/invalid/semanticErr/while", // invalid (whack) "wacc-examples/invalid/whack" + // format: on ).find(filename.contains).isDefined } From e384265189733249fcd81febf1e7d4837c7b498e Mon Sep 17 00:00:00 2001 From: Jonny Date: Sat, 1 Feb 2025 02:21:39 +0000 Subject: [PATCH 03/14] feat: initial lexer implementation --- src/main/wacc/lexer.scala | 51 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/main/wacc/lexer.scala b/src/main/wacc/lexer.scala index f633bf6..907053b 100644 --- a/src/main/wacc/lexer.scala +++ b/src/main/wacc/lexer.scala @@ -1,16 +1,61 @@ package wacc import parsley.Parsley -import parsley.token.Lexer +import parsley.token.{Basic, Lexer} import parsley.token.descriptions.* object lexer { private val desc = LexicalDesc.plain.copy( - // your configuration goes here + nameDesc = NameDesc.plain.copy( + identifierStart = Basic(c => c.isLetter || c == '_'), + identifierLetter = Basic(c => c.isLetterOrDigit || c == '_') + ), + symbolDesc = SymbolDesc.plain.copy( + hardKeywords = Set( + "begin", "end", "is", "skip", "if", "then", "else", "fi", "while", "do", + "done", "read", "free", "return", "exit", "print", "println", "true", + "false", "int", "bool", "char", "string", "pair", "newpair", "fst", + "snd", "call", "chr", "ord", "len", "null" + ), + hardOperators = Set( + "+", "-", "*", "/", "%", ">", "<", ">=", "<=", "==", "!=", "&&", "||", + "!" + ) + ), + spaceDesc = SpaceDesc.plain.copy( + lineCommentStart = "#" + ), + // TODO - See BNF 1.1 and Table 5 2.3.6 + textDesc = TextDesc.plain.copy( + graphicCharacter = + Basic(c => c >= ' ' && c != '\\' && c != '\'' && c != '"'), + escapeSequences = EscapeDesc.plain.copy( + literals = Set('\\', '"', '\''), + mapping = Map( + "0" -> '\u0000', + "b" -> '\b', + "t" -> '\t', + "n" -> '\n', + "f" -> '\f', + "r" -> '\r' + ) + ) + ) ) + private val lexer = Lexer(desc) - val integer = lexer.lexeme.integer.decimal + // Enforce 32-bit signed integer range - see 1.5 + // TODO By default leadingZerosAllowed = true in NumericDesc - Wacc doesnt specify (I think) but should reach consensus + val integer = lexer.lexeme.integer.decimal32[Int] + + // TODO Check if textDesc can handle this + val charLit = lexer.lexeme.character.ascii + + // TODO Check if textDesc can handle this + val stringLit = lexer.lexeme.string.ascii + val implicits = lexer.lexeme.symbol.implicits + def fully[A](p: Parsley[A]): Parsley[A] = lexer.fully(p) } From 70aa58b87925d4cfdcbfbd934387ad5c4eb12c0d Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sat, 1 Feb 2025 02:13:55 +0000 Subject: [PATCH 04/14] fix: disallow exponents, export identifier from lexer --- src/main/wacc/lexer.scala | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/wacc/lexer.scala b/src/main/wacc/lexer.scala index 907053b..abe5cb1 100644 --- a/src/main/wacc/lexer.scala +++ b/src/main/wacc/lexer.scala @@ -25,7 +25,6 @@ object lexer { spaceDesc = SpaceDesc.plain.copy( lineCommentStart = "#" ), - // TODO - See BNF 1.1 and Table 5 2.3.6 textDesc = TextDesc.plain.copy( graphicCharacter = Basic(c => c >= ' ' && c != '\\' && c != '\'' && c != '"'), @@ -40,21 +39,17 @@ object lexer { "r" -> '\r' ) ) + ), + numericDesc = NumericDesc.plain.copy( + decimalExponentDesc = ExponentDesc.NoExponents ) ) private val lexer = Lexer(desc) - - // Enforce 32-bit signed integer range - see 1.5 - // TODO By default leadingZerosAllowed = true in NumericDesc - Wacc doesnt specify (I think) but should reach consensus + val ident = lexer.lexeme.names.identifier val integer = lexer.lexeme.integer.decimal32[Int] - - // TODO Check if textDesc can handle this val charLit = lexer.lexeme.character.ascii - - // TODO Check if textDesc can handle this val stringLit = lexer.lexeme.string.ascii - val implicits = lexer.lexeme.symbol.implicits def fully[A](p: Parsley[A]): Parsley[A] = lexer.fully(p) From cfad2f08f484c4d08c7eca4aa90a8103e7277508 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sat, 1 Feb 2025 17:15:14 +0000 Subject: [PATCH 05/14] refactor: non-recursive statements and array types --- src/main/wacc/ast.scala | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/wacc/ast.scala b/src/main/wacc/ast.scala index 59fb9fa..4af5b1d 100644 --- a/src/main/wacc/ast.scala +++ b/src/main/wacc/ast.scala @@ -78,8 +78,10 @@ object ast { case object BoolType extends BaseType with ParserBridge0[BoolType.type] case object CharType extends BaseType with ParserBridge0[CharType.type] case object StringType extends BaseType with ParserBridge0[StringType.type] - case class ArrayType(elemType: Type) extends Type with PairElemType - object ArrayType extends ParserBridge1[Type, ArrayType] + case class ArrayType(elemType: Type, dimensions: Int) + extends Type + with PairElemType + object ArrayType extends ParserBridge2[Type, Int, ArrayType] case class PairType(fst: PairElemType, snd: PairElemType) extends Type object PairType extends ParserBridge2[PairElemType, PairElemType, PairType] @@ -89,18 +91,18 @@ object ast { with ParserBridge0[UntypedPairType.type] // waccadoodledo - case class Program(funcs: List[FuncDecl], main: Stmt) - object Program extends ParserBridge2[List[FuncDecl], Stmt, Program] + case class Program(funcs: List[FuncDecl], main: List[Stmt]) + object Program extends ParserBridge2[List[FuncDecl], List[Stmt], Program] // Function Definitions case class FuncDecl( returnType: Type, name: Ident, params: List[Param], - body: Stmt + body: List[Stmt] ) object FuncDecl - extends ParserBridge4[Type, Ident, List[Param], Stmt, FuncDecl] + extends ParserBridge4[Type, Ident, List[Param], List[Stmt], FuncDecl] case class Param(paramType: Type, name: Ident) object Param extends ParserBridge2[Type, Ident, Param] @@ -122,14 +124,13 @@ object ast { object Exit extends ParserBridge1[Expr, Exit] case class Print(expr: Expr, newline: Boolean) extends Stmt object Print extends ParserBridge2[Expr, Boolean, Print] - case class If(cond: Expr, thenStmt: Stmt, elseStmt: Stmt) extends Stmt - object If extends ParserBridge3[Expr, Stmt, Stmt, If] - case class While(cond: Expr, body: Stmt) extends Stmt - object While extends ParserBridge2[Expr, Stmt, While] - case class Block(stmt: Stmt) extends Stmt - object Block extends ParserBridge1[Stmt, Block] - case class SeqStmt(stmt1: Stmt, stmt2: Stmt) extends Stmt - object SeqStmt extends ParserBridge2[Stmt, Stmt, SeqStmt] + case class If(cond: Expr, thenStmt: List[Stmt], elseStmt: List[Stmt]) + extends Stmt + object If extends ParserBridge3[Expr, List[Stmt], List[Stmt], If] + case class While(cond: Expr, body: List[Stmt]) extends Stmt + object While extends ParserBridge2[Expr, List[Stmt], While] + case class Block(stmt: List[Stmt]) extends Stmt + object Block extends ParserBridge1[List[Stmt], Block] sealed trait LValue From c5b02a00aae08d2850e29de46db933a709a3c665 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sat, 1 Feb 2025 17:15:22 +0000 Subject: [PATCH 06/14] feat: initial parser implementation --- .scalafmt.conf | 1 + src/main/wacc/Main.scala | 8 +++ src/main/wacc/parser.scala | 119 ++++++++++++++++++++++++++++++++++- src/test/wacc/examples.scala | 28 ++++----- 4 files changed, 138 insertions(+), 18 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 22c361b..a4c5951 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -2,3 +2,4 @@ version = 3.8.6 runner.dialect = scala3 binPack.literalsExclude = [] +indent.infix.excludeRegex = "^$" diff --git a/src/main/wacc/Main.scala b/src/main/wacc/Main.scala index 1aa3eb3..9c59215 100644 --- a/src/main/wacc/Main.scala +++ b/src/main/wacc/Main.scala @@ -1,5 +1,6 @@ package wacc +import parsley.{Failure, Success} import scopt.OParser import java.io.File @@ -28,6 +29,13 @@ val cliParser = { ) } +def compile(contents: String): Int = { + parser.parse(contents) match { + case Success(x) => 0 + case Failure(msg) => 100 + } +} + def main(args: Array[String]): Unit = OParser.parse(cliParser, args, CliConfig()) match { case Some(config) => diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index 871ade0..c9422d7 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -1,8 +1,123 @@ package wacc import parsley.Result +import parsley.Parsley +import parsley.Parsley.{atomic, many, pure} +import parsley.combinator.{countSome, sepBy, sepBy1} +import parsley.expr.{precedence, SOps, InfixL, InfixN, InfixR, Prefix, Atoms} object parser { - def parse(input: String): Result[String, BigInt] = parser.parse(input) - private val parser = lexer.fully(???) + import lexer.implicits.implicitSymbol + import lexer.{ident, integer, charLit, stringLit} + import ast._ + + def parse(input: String): Result[String, Program] = parser.parse(input) + private val parser = lexer.fully(``) + + // Expressions + private lazy val ``: Parsley[Expr] = precedence { + SOps(InfixR)(Or from "||") +: + SOps(InfixR)(And from "&&") +: + SOps(InfixN)(Eq from "==", Neq from "!=") +: + SOps(InfixN)( + Less from "<", + LessEq from "<=", + Greater from ">", + GreaterEq from ">=" + ) +: + SOps(InfixL)(Add from "+", Sub from "-") +: + SOps(InfixL)(Mul from "*", Div from "/", Mod from "%") +: + SOps(Prefix)( + Not from "!", + Negate from "-", + Len from "len", + Ord from "ord", + Chr from "chr" + ) +: + `` + } + + // Atoms + private lazy val ``: Atoms[Expr6] = Atoms( + IntLiter(integer), + BoolLiter(("true" as true) | ("false" as false)), + CharLiter(charLit), + StrLiter(stringLit), + PairLiter from "null", + ``, + ``, + Parens("(" ~> `` <~ ")") + ) + private val `` = Ident(ident) + private lazy val `` = + ArrayElem(`` <~ "[", sepBy1(``, "]" ~> "[") <~ "]") + + // Types + private lazy val ``: Parsley[Type] = + (`` | (`` ~> ``)) <**> (`` identity) + private val `` = + (IntType from "int") | (BoolType from "bool") | (CharType from "char") | (StringType from "string") + private lazy val `` = + countSome("[" ~> "]") map { cnt => ArrayType((_: Type), cnt) } + private val `` = "pair" + private val ``: Parsley[PairType] = PairType( + "(" ~> `` <~ ",", + `` <~ ")" + ) + private lazy val `` = + (`` <**> (`` identity)) | + `` ~> ((`` <**> ``) UntypedPairType) + + // Statements + private lazy val `` = Program( + "begin" ~> many(atomic(``)), + `` <~ "end" + ) + private lazy val `` = FuncDecl( + ``, + `` <~ "(", + sepBy(``, ",") <~ ")" <~ "is", + `` <~ "end" + ) + private lazy val `` = Param(``, ``) + private lazy val ``: Parsley[List[Stmt]] = sepBy1(``, ";") + private lazy val `` = + (Skip from atomic("skip")) + | Read(atomic("read") ~> ``) + | Free(atomic("free") ~> ``) + | Return(atomic("return") ~> ``) + | Exit(atomic("exit") ~> ``) + | Print(atomic("print") ~> ``, pure(false)) + | Print(atomic("println") ~> ``, pure(true)) + | If( + atomic("if") ~> `` <~ "then", + `` <~ "else", + `` <~ "fi" + ) + | While(atomic("while") ~> `` <~ "do", `` <~ "done") + | Block(atomic("begin") ~> `` <~ "end") + | VarDecl(atomic(``), `` <~ "=", ``) + | Assign(`` <~ "=", ``) + private lazy val ``: Parsley[LValue] = + atomic(``) | atomic(``) | `` + private lazy val ``: Parsley[RValue] = + atomic(``) | + atomic( + NewPair( + "newpair" ~> "(" ~> `` <~ ",", + `` <~ ")" + ) + ) | + atomic(``) | + atomic( + Call( + "call" ~> `` <~ "(", + sepBy(``, ",") <~ ")" + ) + ) | `` + private lazy val `` = + Fst("fst" ~> ``) | Snd("snd" ~> ``) + private lazy val `` = ArrayLiter( + "[" ~> sepBy(``, ",") <~ "]" + ) } diff --git a/src/test/wacc/examples.scala b/src/test/wacc/examples.scala index cb4db0e..3afc377 100644 --- a/src/test/wacc/examples.scala +++ b/src/test/wacc/examples.scala @@ -3,7 +3,6 @@ package wacc import org.scalatest.{ParallelTestExecution, BeforeAndAfterAll} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.Inspectors.forEvery -import parsley.{Success, Failure} class ParallelExamplesSpec extends AnyFlatSpec @@ -29,10 +28,7 @@ class ParallelExamplesSpec }) { (filename, expectedResult) => s"$filename" should "be parsed with correct result" in { val contents = os.read(os.Path(filename)) - parser.parse(contents) match { - case Success(x) => assert(expectedResult.contains(x)) - case Failure(msg) => fail(msg) - } + assert(expectedResult.contains(compile(contents))) } } @@ -63,17 +59,17 @@ class ParallelExamplesSpec "wacc-examples/valid/variables", "wacc-examples/valid/while", // invalid (syntax) - "wacc-examples/invalid/syntaxErr/array", - "wacc-examples/invalid/syntaxErr/basic", - "wacc-examples/invalid/syntaxErr/expressions", - "wacc-examples/invalid/syntaxErr/function", - "wacc-examples/invalid/syntaxErr/if", - "wacc-examples/invalid/syntaxErr/literals", - "wacc-examples/invalid/syntaxErr/pairs", - "wacc-examples/invalid/syntaxErr/print", - "wacc-examples/invalid/syntaxErr/sequence", - "wacc-examples/invalid/syntaxErr/variables", - "wacc-examples/invalid/syntaxErr/while", + // "wacc-examples/invalid/syntaxErr/array", + // "wacc-examples/invalid/syntaxErr/basic", + // "wacc-examples/invalid/syntaxErr/expressions", + // "wacc-examples/invalid/syntaxErr/function", + // "wacc-examples/invalid/syntaxErr/if", + // "wacc-examples/invalid/syntaxErr/literals", + // "wacc-examples/invalid/syntaxErr/pairs", + // "wacc-examples/invalid/syntaxErr/print", + // "wacc-examples/invalid/syntaxErr/sequence", + // "wacc-examples/invalid/syntaxErr/variables", + // "wacc-examples/invalid/syntaxErr/while", // invalid (semantic) "wacc-examples/invalid/semanticErr/array", "wacc-examples/invalid/semanticErr/exit", From f5f6628c8955ac5a4504fe6c40687ca2c1a2fe30 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sat, 1 Feb 2025 20:29:04 +0000 Subject: [PATCH 07/14] fix: extract from --- src/main/wacc/parser.scala | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index c9422d7..c33cbc8 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -2,7 +2,7 @@ package wacc import parsley.Result import parsley.Parsley -import parsley.Parsley.{atomic, many, pure} +import parsley.Parsley.{atomic, many, pure, some} import parsley.combinator.{countSome, sepBy, sepBy1} import parsley.expr.{precedence, SOps, InfixL, InfixN, InfixR, Prefix, Atoms} @@ -44,13 +44,16 @@ object parser { CharLiter(charLit), StrLiter(stringLit), PairLiter from "null", - ``, - ``, + ``, Parens("(" ~> `` <~ ")") ) private val `` = Ident(ident) - private lazy val `` = - ArrayElem(`` <~ "[", sepBy1(``, "]" ~> "[") <~ "]") + private lazy val `` = + `` <**> (`` identity) + private val `` = + some("[" ~> `` <~ "]") map { indices => + ArrayElem((_: Ident), indices) + } // Types private lazy val ``: Parsley[Type] = @@ -80,7 +83,8 @@ object parser { `` <~ "end" ) private lazy val `` = Param(``, ``) - private lazy val ``: Parsley[List[Stmt]] = sepBy1(``, ";") + private lazy val ``: Parsley[List[Stmt]] = + sepBy1(``, ";") private lazy val `` = (Skip from atomic("skip")) | Read(atomic("read") ~> ``) @@ -99,7 +103,7 @@ object parser { | VarDecl(atomic(``), `` <~ "=", ``) | Assign(`` <~ "=", ``) private lazy val ``: Parsley[LValue] = - atomic(``) | atomic(``) | `` + atomic(``) | atomic(``) private lazy val ``: Parsley[RValue] = atomic(``) | atomic( From 0db7a30af0dc088ea50eb093b11ef466e5fbc7ca Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sat, 1 Feb 2025 21:05:42 +0000 Subject: [PATCH 08/14] refactor: remove excessive atomics --- src/main/wacc/parser.scala | 44 +++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index 0d4b03b..237b6b5 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -88,38 +88,34 @@ object parser { private lazy val ``: Parsley[NonEmptyList[Stmt]] = sepBy1(``, ";") private lazy val `` = - (Skip from atomic("skip")) - | Read(atomic("read") ~> ``) - | Free(atomic("free") ~> ``) - | Return(atomic("return") ~> ``) - | Exit(atomic("exit") ~> ``) - | Print(atomic("print") ~> ``, pure(false)) - | Print(atomic("println") ~> ``, pure(true)) + (Skip from "skip") + | Read("read" ~> ``) + | Free("free" ~> ``) + | Return("return" ~> ``) + | Exit("exit" ~> ``) + | Print("print" ~> ``, pure(false)) + | Print("println" ~> ``, pure(true)) | If( - atomic("if") ~> `` <~ "then", + "if" ~> `` <~ "then", `` <~ "else", `` <~ "fi" ) - | While(atomic("while") ~> `` <~ "do", `` <~ "done") - | Block(atomic("begin") ~> `` <~ "end") - | VarDecl(atomic(``), `` <~ "=", ``) + | While("while" ~> `` <~ "do", `` <~ "done") + | Block("begin" ~> `` <~ "end") + | VarDecl(``, `` <~ "=", ``) | Assign(`` <~ "=", ``) private lazy val ``: Parsley[LValue] = - atomic(``) | atomic(``) + `` | `` private lazy val ``: Parsley[RValue] = - atomic(``) | - atomic( - NewPair( - "newpair" ~> "(" ~> `` <~ ",", - `` <~ ")" - ) + `` | + NewPair( + "newpair" ~> "(" ~> `` <~ ",", + `` <~ ")" ) | - atomic(``) | - atomic( - Call( - "call" ~> `` <~ "(", - sepBy(``, ",") <~ ")" - ) + `` | + Call( + "call" ~> `` <~ "(", + sepBy(``, ",") <~ ")" ) | `` private lazy val `` = Fst("fst" ~> ``) | Snd("snd" ~> ``) From a71045867a9de128b2458cd332d97a09c49c2b4a Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sat, 1 Feb 2025 21:22:48 +0000 Subject: [PATCH 09/14] fix: add function must return on all paths check --- src/main/wacc/ast.scala | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/wacc/ast.scala b/src/main/wacc/ast.scala index c6e743e..1fe26b7 100644 --- a/src/main/wacc/ast.scala +++ b/src/main/wacc/ast.scala @@ -1,7 +1,9 @@ package wacc +import parsley.errors.combinator._ import parsley.generic._ import cats.data.NonEmptyList +import parsley.Parsley object ast { // Expressions @@ -96,7 +98,18 @@ object ast { params: List[Param], body: NonEmptyList[Stmt] ) - object FuncDecl extends ParserBridge4[Type, Ident, List[Param], NonEmptyList[Stmt], FuncDecl] + object FuncDecl extends ParserBridge4[Type, Ident, List[Param], NonEmptyList[Stmt], FuncDecl] { + override def apply( + x1: Parsley[Type], + x2: => Parsley[Ident], + x3: => Parsley[List[Param]], + x4: => Parsley[NonEmptyList[Stmt]] + ): Parsley[FuncDecl] = + super.apply(x1, x2, x3, x4).guardAgainst { + case FuncDecl(_, _, _, body) if !body.isReturning => + Seq("Function must return on all paths") + } + } case class Param(paramType: Type, name: Ident) object Param extends ParserBridge2[Type, Ident, Param] @@ -140,4 +153,14 @@ object ast { object Fst extends ParserBridge1[LValue, Fst] case class Snd(elem: LValue) extends PairElem object Snd extends ParserBridge1[LValue, Snd] + + extension (stmts: NonEmptyList[Stmt]) { + def isReturning: Boolean = stmts.last match { + case Return(_) | Exit(_) => true + case If(_, thenStmt, elseStmt) => thenStmt.isReturning && elseStmt.isReturning + case While(_, body) => body.isReturning + case Block(body) => body.isReturning + case _ => false + } + } } From cb9796fa87a130e50f61a8f0c1399ef0069a9720 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sun, 2 Feb 2025 00:00:09 +0000 Subject: [PATCH 10/14] feat: show exact statement which must be returning at the end of a function --- src/main/wacc/ast.scala | 14 ++++++++++---- src/main/wacc/parser.scala | 10 +++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/wacc/ast.scala b/src/main/wacc/ast.scala index 1fe26b7..b28bee2 100644 --- a/src/main/wacc/ast.scala +++ b/src/main/wacc/ast.scala @@ -105,10 +105,16 @@ object ast { x3: => Parsley[List[Param]], x4: => Parsley[NonEmptyList[Stmt]] ): Parsley[FuncDecl] = - super.apply(x1, x2, x3, x4).guardAgainst { - case FuncDecl(_, _, _, body) if !body.isReturning => - Seq("Function must return on all paths") - } + super.apply( + x1, + x2, + x3, + x4.guardAgainst { + case body if !body.isReturning => + println(body) + Seq("All functions must end in a returning statement") + } + ) } case class Param(paramType: Type, name: Ident) diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index 237b6b5..04e70c1 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -75,15 +75,15 @@ object parser { // Statements private lazy val `` = Program( - "begin" ~> many(atomic(``)), + "begin" ~> many(``), `` <~ "end" ) private lazy val `` = FuncDecl( - ``, - `` <~ "(", + atomic(``), + atomic(``) <~ "(", sepBy(``, ",") <~ ")" <~ "is", - `` <~ "end" - ) + `` + ) <~ "end" private lazy val `` = Param(``, ``) private lazy val ``: Parsley[NonEmptyList[Stmt]] = sepBy1(``, ";") From b5a1f2565fa0afd71ac85b9b814177cbdb5bc35f Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sun, 2 Feb 2025 00:27:04 +0000 Subject: [PATCH 11/14] Revert "fix: add function must return on all paths check" This reverts commit a71045867a9de128b2458cd332d97a09c49c2b4a. --- src/main/wacc/ast.scala | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/src/main/wacc/ast.scala b/src/main/wacc/ast.scala index b28bee2..c6e743e 100644 --- a/src/main/wacc/ast.scala +++ b/src/main/wacc/ast.scala @@ -1,9 +1,7 @@ package wacc -import parsley.errors.combinator._ import parsley.generic._ import cats.data.NonEmptyList -import parsley.Parsley object ast { // Expressions @@ -98,24 +96,7 @@ object ast { params: List[Param], body: NonEmptyList[Stmt] ) - object FuncDecl extends ParserBridge4[Type, Ident, List[Param], NonEmptyList[Stmt], FuncDecl] { - override def apply( - x1: Parsley[Type], - x2: => Parsley[Ident], - x3: => Parsley[List[Param]], - x4: => Parsley[NonEmptyList[Stmt]] - ): Parsley[FuncDecl] = - super.apply( - x1, - x2, - x3, - x4.guardAgainst { - case body if !body.isReturning => - println(body) - Seq("All functions must end in a returning statement") - } - ) - } + object FuncDecl extends ParserBridge4[Type, Ident, List[Param], NonEmptyList[Stmt], FuncDecl] case class Param(paramType: Type, name: Ident) object Param extends ParserBridge2[Type, Ident, Param] @@ -159,14 +140,4 @@ object ast { object Fst extends ParserBridge1[LValue, Fst] case class Snd(elem: LValue) extends PairElem object Snd extends ParserBridge1[LValue, Snd] - - extension (stmts: NonEmptyList[Stmt]) { - def isReturning: Boolean = stmts.last match { - case Return(_) | Exit(_) => true - case If(_, thenStmt, elseStmt) => thenStmt.isReturning && elseStmt.isReturning - case While(_, body) => body.isReturning - case Block(body) => body.isReturning - case _ => false - } - } } From 03fdbe01d9111808ab34ebf37c669f639ad7b8ed Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sun, 2 Feb 2025 00:30:18 +0000 Subject: [PATCH 12/14] fix: put functions "(" within atomic --- src/main/wacc/parser.scala | 25 ++++++++++++++++++------- src/test/wacc/examples.scala | 36 ++++++++++++++++++------------------ 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index 04e70c1..d606d59 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -5,6 +5,7 @@ import parsley.Parsley import parsley.Parsley.{atomic, many, pure} import parsley.combinator.{countSome, sepBy} import parsley.expr.{precedence, SOps, InfixL, InfixN, InfixR, Prefix, Atoms} +import parsley.errors.combinator._ import parsley.cats.combinator.{sepBy1, some} import cats.data.NonEmptyList @@ -75,15 +76,15 @@ object parser { // Statements private lazy val `` = Program( - "begin" ~> many(``), + "begin" ~> many(atomic(`` <~> `` <~ "(") <**> ``), `` <~ "end" ) - private lazy val `` = FuncDecl( - atomic(``), - atomic(``) <~ "(", - sepBy(``, ",") <~ ")" <~ "is", - `` - ) <~ "end" + private lazy val `` = + (sepBy(``, ",") <~ ")" <~ "is" <~> ``.guardAgainst { + case stmts if !stmts.isReturning => Seq("All functions must end in a returning statement") + } <~ "end") map { (params, stmt) => + (FuncDecl((_: Type), (_: Ident), params, stmt)).tupled + } private lazy val `` = Param(``, ``) private lazy val ``: Parsley[NonEmptyList[Stmt]] = sepBy1(``, ";") @@ -122,4 +123,14 @@ object parser { private lazy val `` = ArrayLiter( "[" ~> sepBy(``, ",") <~ "]" ) + + extension (stmts: NonEmptyList[Stmt]) { + def isReturning: Boolean = stmts.last match { + case Return(_) | Exit(_) => true + case If(_, thenStmt, elseStmt) => thenStmt.isReturning && elseStmt.isReturning + case While(_, body) => body.isReturning + case Block(body) => body.isReturning + case _ => false + } + } } diff --git a/src/test/wacc/examples.scala b/src/test/wacc/examples.scala index 56f6b36..fd8c211 100644 --- a/src/test/wacc/examples.scala +++ b/src/test/wacc/examples.scala @@ -37,24 +37,24 @@ class ParallelExamplesSpec extends AnyFlatSpec with BeforeAndAfterAll with Paral Seq( // format: off // disable formatting to avoid binPack - "wacc-examples/valid/advanced", - "wacc-examples/valid/array", - "wacc-examples/valid/basic/exit", - "wacc-examples/valid/basic/skip", - "wacc-examples/valid/expressions", - "wacc-examples/valid/function/nested_functions", - "wacc-examples/valid/function/simple_functions", - "wacc-examples/valid/if", - "wacc-examples/valid/IO/print", - "wacc-examples/valid/IO/read", - "wacc-examples/valid/IO/IOLoop.wacc", - "wacc-examples/valid/IO/IOSequence.wacc", - "wacc-examples/valid/pairs", - "wacc-examples/valid/runtimeErr", - "wacc-examples/valid/scope", - "wacc-examples/valid/sequence", - "wacc-examples/valid/variables", - "wacc-examples/valid/while", + // "wacc-examples/valid/advanced", + // "wacc-examples/valid/array", + // "wacc-examples/valid/basic/exit", + // "wacc-examples/valid/basic/skip", + // "wacc-examples/valid/expressions", + // "wacc-examples/valid/function/nested_functions", + // "wacc-examples/valid/function/simple_functions", + // "wacc-examples/valid/if", + // "wacc-examples/valid/IO/print", + // "wacc-examples/valid/IO/read", + // "wacc-examples/valid/IO/IOLoop.wacc", + // "wacc-examples/valid/IO/IOSequence.wacc", + // "wacc-examples/valid/pairs", + // "wacc-examples/valid/runtimeErr", + // "wacc-examples/valid/scope", + // "wacc-examples/valid/sequence", + // "wacc-examples/valid/variables", + // "wacc-examples/valid/while", // invalid (syntax) // "wacc-examples/invalid/syntaxErr/array", // "wacc-examples/invalid/syntaxErr/basic", From 2588c8287de2acda83a128bda7a2a0861c7665a7 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sun, 2 Feb 2025 00:33:16 +0000 Subject: [PATCH 13/14] fix: change lhs of Assign to lvalue --- src/main/wacc/parser.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index d606d59..98e9484 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -104,7 +104,7 @@ object parser { | While("while" ~> `` <~ "do", `` <~ "done") | Block("begin" ~> `` <~ "end") | VarDecl(``, `` <~ "=", ``) - | Assign(`` <~ "=", ``) + | Assign(`` <~ "=", ``) private lazy val ``: Parsley[LValue] = `` | `` private lazy val ``: Parsley[RValue] = From da4b398bf0bc15b5f39a9dca798812cd6e4ee618 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sun, 2 Feb 2025 13:30:33 +0000 Subject: [PATCH 14/14] fix: do not allow negation of a positive int literal --- src/main/wacc/lexer.scala | 2 ++ src/main/wacc/parser.scala | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/wacc/lexer.scala b/src/main/wacc/lexer.scala index 4351294..094ac12 100644 --- a/src/main/wacc/lexer.scala +++ b/src/main/wacc/lexer.scala @@ -1,6 +1,7 @@ package wacc import parsley.Parsley +import parsley.character import parsley.token.{Basic, Lexer} import parsley.token.descriptions.* @@ -45,6 +46,7 @@ object lexer { private val lexer = Lexer(desc) val ident = lexer.lexeme.names.identifier val integer = lexer.lexeme.integer.decimal32[Int] + val negateCheck = lexer.nonlexeme.symbol("-") ~> character.digit val charLit = lexer.lexeme.character.ascii val stringLit = lexer.lexeme.string.ascii val implicits = lexer.lexeme.symbol.implicits diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index 98e9484..84ee093 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -2,7 +2,7 @@ package wacc import parsley.Result import parsley.Parsley -import parsley.Parsley.{atomic, many, pure} +import parsley.Parsley.{atomic, many, notFollowedBy, pure} import parsley.combinator.{countSome, sepBy} import parsley.expr.{precedence, SOps, InfixL, InfixN, InfixR, Prefix, Atoms} import parsley.errors.combinator._ @@ -11,7 +11,7 @@ import cats.data.NonEmptyList object parser { import lexer.implicits.implicitSymbol - import lexer.{ident, integer, charLit, stringLit} + import lexer.{ident, integer, charLit, stringLit, negateCheck} import ast._ def parse(input: String): Result[String, Program] = parser.parse(input) @@ -32,7 +32,7 @@ object parser { SOps(InfixL)(Mul from "*", Div from "/", Mod from "%") +: SOps(Prefix)( Not from "!", - Negate from "-", + Negate from (notFollowedBy(negateCheck) ~> "-"), Len from "len", Ord from "ord", Chr from "chr"