refactor: implemented labelAndExplain(), combining label and explain, and...

Merge request lab2425_spring/WACC_37!9

Co-authored-by: Barf-Vader <47476490+Barf-Vader@users.noreply.github.com>
Co-authored-by: Guy C <gc1523@ic.ac.uk>
This commit is contained in:
Gleb Koval 2025-02-06 20:30:20 +00:00
commit 4dc07c249a
2 changed files with 109 additions and 22 deletions

View File

@ -4,7 +4,36 @@ import parsley.Parsley
import parsley.character import parsley.character
import parsley.token.{Basic, Lexer} import parsley.token.{Basic, Lexer}
import parsley.token.descriptions.* 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 { object lexer {
private val desc = LexicalDesc.plain.copy( private val desc = LexicalDesc.plain.copy(
nameDesc = NameDesc.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 ident = lexer.lexeme.names.identifier
val integer = lexer.lexeme.integer.decimal32[Int] val integer = lexer.lexeme.integer.decimal32[Int]
val negateCheck = lexer.nonlexeme.symbol("-") ~> character.digit val negateCheck = lexer.nonlexeme.symbol("-") ~> character.digit
@ -51,5 +80,15 @@ object lexer {
val stringLit = lexer.lexeme.string.ascii val stringLit = lexer.lexeme.string.ascii
val implicits = lexer.lexeme.symbol.implicits 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) def fully[A](p: Parsley[A]): Parsley[A] = lexer.fully(p)
} }

View File

@ -6,14 +6,51 @@ import parsley.Parsley.{atomic, many, notFollowedBy, pure}
import parsley.combinator.{countSome, sepBy} import parsley.combinator.{countSome, sepBy}
import parsley.expr.{precedence, SOps, InfixL, InfixN, InfixR, Prefix, Atoms} import parsley.expr.{precedence, SOps, InfixL, InfixN, InfixR, Prefix, Atoms}
import parsley.errors.combinator._ import parsley.errors.combinator._
import parsley.cats.combinator.{sepBy1, some} import parsley.syntax.zipped._
import parsley.cats.combinator.{some}
import cats.data.NonEmptyList import cats.data.NonEmptyList
import parsley.errors.DefaultErrorBuilder
import parsley.errors.ErrorBuilder
import parsley.errors.tokenextractors.LexToken
object parser { object parser {
import lexer.implicits.implicitSymbol import lexer.implicits.implicitSymbol
import lexer.{ident, integer, charLit, stringLit, negateCheck} import lexer.{ident, integer, charLit, stringLit, negateCheck, errTokens}
import ast._ 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) def parse(input: String): Result[String, Program] = parser.parse(input)
private val parser = lexer.fully(`<program>`) private val parser = lexer.fully(`<program>`)
@ -28,11 +65,14 @@ object parser {
Greater from ">", Greater from ">",
GreaterEq 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(InfixL)(Mul from "*", Div from "/", Mod from "%") +:
SOps(Prefix)( SOps(Prefix)(
Not from "!", Not from "!",
Negate from (notFollowedBy(negateCheck) ~> "-"), (Negate from (notFollowedBy(negateCheck) ~> "-")).hide,
Len from "len", Len from "len",
Ord from "ord", Ord from "ord",
Chr from "chr" Chr from "chr"
@ -42,10 +82,10 @@ object parser {
// Atoms // Atoms
private lazy val `<atom>`: Atoms[Expr6] = Atoms( private lazy val `<atom>`: Atoms[Expr6] = Atoms(
IntLiter(integer), IntLiter(integer).label("integer literal"),
BoolLiter(("true" as true) | ("false" as false)), BoolLiter(("true" as true) | ("false" as false)).label("boolean literal"),
CharLiter(charLit), CharLiter(charLit).label("character literal"),
StrLiter(stringLit), StrLiter(stringLit).label("string literal"),
PairLiter from "null", PairLiter from "null",
`<ident-or-array-elem>`, `<ident-or-array-elem>`,
Parens("(" ~> `<expr>` <~ ")") Parens("(" ~> `<expr>` <~ ")")
@ -75,8 +115,10 @@ object parser {
// Statements // Statements
private lazy val `<program>` = Program( private lazy val `<program>` = Program(
"begin" ~> many(atomic(`<type>` <~> `<ident>` <~ "(") <**> `<partial-func-decl>`), "begin" ~> many(
`<stmt>` <~ "end" atomic(`<type>`.label("function declaration") <~> `<ident>` <~ "(") <**> `<partial-func-decl>`
).label("function declaration"),
`<stmt>`.label("main program body") <~ "end"
) )
private lazy val `<partial-func-decl>` = private lazy val `<partial-func-decl>` =
FuncDecl( FuncDecl(
@ -87,23 +129,28 @@ object parser {
) )
private lazy val `<param>` = Param(`<type>`, `<ident>`) private lazy val `<param>` = Param(`<type>`, `<ident>`)
private lazy val `<stmt>`: Parsley[NonEmptyList[Stmt]] = private lazy val `<stmt>`: Parsley[NonEmptyList[Stmt]] =
sepBy1(`<basic-stmt>`, ";") (
`<basic-stmt>`.label("main program body"),
(many(";" ~> `<basic-stmt>`.label("statement after ';'"))) </> Nil
).zipped(NonEmptyList.apply)
private lazy val `<basic-stmt>` = private lazy val `<basic-stmt>` =
(Skip from "skip") (Skip from "skip")
| Read("read" ~> `<lvalue>`) | Read("read" ~> `<lvalue>`)
| Free("free" ~> `<expr>`) | Free("free" ~> `<expr>`.labelAndExplain(LabelType.Expr))
| Return("return" ~> `<expr>`) | Return("return" ~> `<expr>`.labelAndExplain(LabelType.Expr))
| Exit("exit" ~> `<expr>`) | Exit("exit" ~> `<expr>`.labelAndExplain(LabelType.Expr))
| Print("print" ~> `<expr>`, pure(false)) | Print("print" ~> `<expr>`.labelAndExplain(LabelType.Expr), pure(false))
| Print("println" ~> `<expr>`, pure(true)) | Print("println" ~> `<expr>`.labelAndExplain(LabelType.Expr), pure(true))
| If( | If(
"if" ~> `<expr>` <~ "then", "if" ~> `<expr>`.labelWithType(LabelType.Expr) <~ "then",
`<stmt>` <~ "else", `<stmt>` <~ "else",
`<stmt>` <~ "fi" `<stmt>` <~ "fi"
) )
| While("while" ~> `<expr>` <~ "do", `<stmt>` <~ "done") | While("while" ~> `<expr>`.labelWithType(LabelType.Expr) <~ "do", `<stmt>` <~ "done")
| Block("begin" ~> `<stmt>` <~ "end") | Block("begin" ~> `<stmt>` <~ "end")
| VarDecl(`<type>`, `<ident>` <~ "=", `<rvalue>`) | VarDecl(`<type>`, `<ident>` <~ "=", `<rvalue>`.label("valid initial value for variable"))
// TODO: Can we inline the name of the variable in the message
| Assign(`<lvalue>` <~ "=", `<rvalue>`) | Assign(`<lvalue>` <~ "=", `<rvalue>`)
private lazy val `<lvalue>`: Parsley[LValue] = private lazy val `<lvalue>`: Parsley[LValue] =
`<pair-elem>` | `<ident-or-array-elem>` `<pair-elem>` | `<ident-or-array-elem>`
@ -117,9 +164,10 @@ object parser {
Call( Call(
"call" ~> `<ident>` <~ "(", "call" ~> `<ident>` <~ "(",
sepBy(`<expr>`, ",") <~ ")" sepBy(`<expr>`, ",") <~ ")"
) | `<expr>` ) | `<expr>`.labelWithType(LabelType.Expr)
private lazy val `<pair-elem>` = private lazy val `<pair-elem>` =
Fst("fst" ~> `<lvalue>`) | Snd("snd" ~> `<lvalue>`) Fst("fst" ~> `<lvalue>`.label("valid pair"))
| Snd("snd" ~> `<lvalue>`.label("valid pair"))
private lazy val `<array-liter>` = ArrayLiter( private lazy val `<array-liter>` = ArrayLiter(
"[" ~> sepBy(`<expr>`, ",") <~ "]" "[" ~> sepBy(`<expr>`, ",") <~ "]"
) )