diff --git a/.scalafmt.conf b/.scalafmt.conf index c8ba10c..3df68b5 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -2,4 +2,5 @@ version = 3.8.6 runner.dialect = scala3 binPack.literalsExclude = [] +indent.infix.excludeRegex = "^$" maxColumn = 100 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/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 871ade0..84ee093 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -1,8 +1,136 @@ package wacc import parsley.Result +import parsley.Parsley +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._ +import parsley.cats.combinator.{sepBy1, some} +import cats.data.NonEmptyList 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, negateCheck} + 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 (notFollowedBy(negateCheck) ~> "-"), + 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 `` = + `` <**> (`` identity) + private val `` = + some("[" ~> `` <~ "]") map { indices => + ArrayElem((_: Ident), indices) + } + + // 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 `` = + (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(``, ";") + private lazy val `` = + (Skip from "skip") + | Read("read" ~> ``) + | Free("free" ~> ``) + | Return("return" ~> ``) + | Exit("exit" ~> ``) + | Print("print" ~> ``, pure(false)) + | Print("println" ~> ``, pure(true)) + | If( + "if" ~> `` <~ "then", + `` <~ "else", + `` <~ "fi" + ) + | While("while" ~> `` <~ "do", `` <~ "done") + | Block("begin" ~> `` <~ "end") + | VarDecl(``, `` <~ "=", ``) + | Assign(`` <~ "=", ``) + private lazy val ``: Parsley[LValue] = + `` | `` + private lazy val ``: Parsley[RValue] = + `` | + NewPair( + "newpair" ~> "(" ~> `` <~ ",", + `` <~ ")" + ) | + `` | + Call( + "call" ~> `` <~ "(", + sepBy(``, ",") <~ ")" + ) | `` + private lazy val `` = + Fst("fst" ~> ``) | Snd("snd" ~> ``) + 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 65aa54b..8d2b55e 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 with BeforeAndAfterAll with ParallelTestExecution { val files = @@ -26,10 +25,7 @@ class ParallelExamplesSpec extends AnyFlatSpec with BeforeAndAfterAll with Paral }) { (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))) } } @@ -41,36 +37,36 @@ 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", - "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", @@ -88,5 +84,6 @@ class ParallelExamplesSpec extends AnyFlatSpec with BeforeAndAfterAll with Paral // invalid (whack) "wacc-examples/invalid/whack" // format: on + // format: on ).find(filename.contains).isDefined }