diff --git a/src/main/wacc/lexer.scala b/src/main/wacc/lexer.scala index 094ac12..4a810c0 100644 --- a/src/main/wacc/lexer.scala +++ b/src/main/wacc/lexer.scala @@ -4,7 +4,36 @@ import parsley.Parsley import parsley.character import parsley.token.{Basic, Lexer} import parsley.token.descriptions.* +import parsley.token.errors._ +val errConfig = new ErrorConfig { + override def labelSymbol = Map( + "!=" -> Label("binary operator"), + "%" -> Label("binary operator"), + "&&" -> Label("binary operator"), + "*" -> Label("binary operator"), + "/" -> Label("binary operator"), + "<" -> Label("binary operator"), + "<=" -> Label("binary operator"), + "==" -> Label("binary operator"), + ">" -> Label("binary operator"), + ">=" -> Label("binary operator"), + "||" -> Label("binary operator"), + "!" -> Label("unary operator"), + "len" -> Label("unary operator"), + "ord" -> Label("unary operator"), + "chr" -> Label("unary operator"), + "bool" -> Label("valid type"), + "char" -> Label("valid type"), + "int" -> Label("valid type"), + "pair" -> Label("valid type"), + "string" -> Label("valid type"), + "fst" -> Label("pair extraction"), + "snd" -> Label("pair extraction"), + "false" -> Label("boolean literal"), + "true" -> Label("boolean literal") + ) +} object lexer { private val desc = LexicalDesc.plain.copy( nameDesc = NameDesc.plain.copy( @@ -43,7 +72,7 @@ object lexer { ) ) - private val lexer = Lexer(desc) + private val lexer = Lexer(desc, errConfig) val ident = lexer.lexeme.names.identifier val integer = lexer.lexeme.integer.decimal32[Int] val negateCheck = lexer.nonlexeme.symbol("-") ~> character.digit @@ -51,5 +80,15 @@ object lexer { val stringLit = lexer.lexeme.string.ascii val implicits = lexer.lexeme.symbol.implicits + val errTokens = Seq( + lexer.nonlexeme.names.identifier.map(v => s"identifier $v"), + lexer.nonlexeme.integer.decimal32[Int].map(n => s"integer $n"), + lexer.nonlexeme.character.ascii.map(c => s"character literal $c"), + lexer.nonlexeme.string.ascii.map(s => s"string literal $s"), + character.whitespace.map(_ => "") + ) ++ desc.symbolDesc.hardKeywords.map { k => + lexer.nonlexeme.symbol(k).as(s"keyword $k") + } + def fully[A](p: Parsley[A]): Parsley[A] = lexer.fully(p) } diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index c4e2a7f..5751732 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -6,14 +6,51 @@ 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 parsley.syntax.zipped._ +import parsley.cats.combinator.{some} import cats.data.NonEmptyList +import parsley.errors.DefaultErrorBuilder +import parsley.errors.ErrorBuilder +import parsley.errors.tokenextractors.LexToken object parser { import lexer.implicits.implicitSymbol - import lexer.{ident, integer, charLit, stringLit, negateCheck} + import lexer.{ident, integer, charLit, stringLit, negateCheck, errTokens} import ast._ + // error extensions + extension [A](p: Parsley[A]) { + // combines label and explain together into one function call + def labelAndExplain(label: String, explanation: String): Parsley[A] = { + p.label(label).explain(explanation) + } + def labelAndExplain(t: LabelType): Parsley[A] = { + t match { + case LabelType.Expr => + labelWithType(t).explain( + "a valid expression can start with: null, literals, identifiers, unary operators, or parentheses. " + + "Expressions can also contain array indexing and binary operators. " + + "Pair extraction is not allowed in expressions, only in assignments." + ) + case _ => labelWithType(t) + } + } + + def labelWithType(t: LabelType): Parsley[A] = { + t match { + case LabelType.Expr => p.label("valid expression") + case LabelType.Pair => p.label("valid pair") + } + } + } + + enum LabelType: + case Expr + case Pair + + implicit val builder: ErrorBuilder[String] = new DefaultErrorBuilder with LexToken { + def tokens = errTokens + } def parse(input: String): Result[String, Program] = parser.parse(input) private val parser = lexer.fully(``) @@ -28,11 +65,14 @@ object parser { Greater from ">", GreaterEq from ">=" ) +: - SOps(InfixL)(Add from "+", Sub from "-") +: + SOps(InfixL)( + (Add from "+").label("binary operator"), + (Sub from "-").label("binary operator") + ) +: SOps(InfixL)(Mul from "*", Div from "/", Mod from "%") +: SOps(Prefix)( Not from "!", - Negate from (notFollowedBy(negateCheck) ~> "-"), + (Negate from (notFollowedBy(negateCheck) ~> "-")).hide, Len from "len", Ord from "ord", Chr from "chr" @@ -42,10 +82,10 @@ object parser { // Atoms private lazy val ``: Atoms[Expr6] = Atoms( - IntLiter(integer), - BoolLiter(("true" as true) | ("false" as false)), - CharLiter(charLit), - StrLiter(stringLit), + IntLiter(integer).label("integer literal"), + BoolLiter(("true" as true) | ("false" as false)).label("boolean literal"), + CharLiter(charLit).label("character literal"), + StrLiter(stringLit).label("string literal"), PairLiter from "null", ``, Parens("(" ~> `` <~ ")") @@ -75,8 +115,10 @@ object parser { // Statements private lazy val `` = Program( - "begin" ~> many(atomic(`` <~> `` <~ "(") <**> ``), - `` <~ "end" + "begin" ~> many( + atomic(``.label("function declaration") <~> `` <~ "(") <**> `` + ).label("function declaration"), + ``.label("main program body") <~ "end" ) private lazy val `` = FuncDecl( @@ -87,23 +129,28 @@ object parser { ) private lazy val `` = Param(``, ``) private lazy val ``: Parsley[NonEmptyList[Stmt]] = - sepBy1(``, ";") + ( + ``.label("main program body"), + (many(";" ~> ``.label("statement after ';'"))) Nil + ).zipped(NonEmptyList.apply) + private lazy val `` = (Skip from "skip") | Read("read" ~> ``) - | Free("free" ~> ``) - | Return("return" ~> ``) - | Exit("exit" ~> ``) - | Print("print" ~> ``, pure(false)) - | Print("println" ~> ``, pure(true)) + | Free("free" ~> ``.labelAndExplain(LabelType.Expr)) + | Return("return" ~> ``.labelAndExplain(LabelType.Expr)) + | Exit("exit" ~> ``.labelAndExplain(LabelType.Expr)) + | Print("print" ~> ``.labelAndExplain(LabelType.Expr), pure(false)) + | Print("println" ~> ``.labelAndExplain(LabelType.Expr), pure(true)) | If( - "if" ~> `` <~ "then", + "if" ~> ``.labelWithType(LabelType.Expr) <~ "then", `` <~ "else", `` <~ "fi" ) - | While("while" ~> `` <~ "do", `` <~ "done") + | While("while" ~> ``.labelWithType(LabelType.Expr) <~ "do", `` <~ "done") | Block("begin" ~> `` <~ "end") - | VarDecl(``, `` <~ "=", ``) + | VarDecl(``, `` <~ "=", ``.label("valid initial value for variable")) + // TODO: Can we inline the name of the variable in the message | Assign(`` <~ "=", ``) private lazy val ``: Parsley[LValue] = `` | `` @@ -117,9 +164,10 @@ object parser { Call( "call" ~> `` <~ "(", sepBy(``, ",") <~ ")" - ) | `` + ) | ``.labelWithType(LabelType.Expr) private lazy val `` = - Fst("fst" ~> ``) | Snd("snd" ~> ``) + Fst("fst" ~> ``.label("valid pair")) + | Snd("snd" ~> ``.label("valid pair")) private lazy val `` = ArrayLiter( "[" ~> sepBy(``, ",") <~ "]" )