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.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, 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(``) // 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 "+").label("binary operator"), (Sub from "-").label("binary operator") ) +: SOps(InfixL)(Mul from "*", Div from "/", Mod from "%") +: SOps(Prefix)( Not from "!", (Negate from (notFollowedBy(negateCheck) ~> "-")).hide, Len from "len", Ord from "ord", Chr from "chr" ) +: `` } // Atoms private lazy val ``: Atoms[Expr6] = Atoms( 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("(" ~> `` <~ ")") ) private val `` = Ident(ident) private lazy val `` = `` <**> (`` identity) private val `` = ArrayElem(some("[" ~> `` <~ "]")) // Types private lazy val ``: Parsley[Type] = (`` | (`` ~> ``)) <**> (`` identity) private val `` = (IntType from "int") | (BoolType from "bool") | (CharType from "char") | (StringType from "string") private lazy val `` = ArrayType(countSome("[" ~> "]")) private val `` = "pair" private val ``: Parsley[PairType] = PairType( "(" ~> `` <~ ",", `` <~ ")" ) private lazy val `` = (`` <**> (`` identity)) | ((UntypedPairType from ``) <**> ((`` <**> ``) .map(arr => (_: UntypedPairType) => arr) identity)) // Statements private lazy val `` = Program( "begin" ~> many( atomic(``.label("function declaration") <~> `` <~ "(") <**> `` ).label("function declaration"), (atomic(`` <~ "(") ~> fail("function is missing return type") | ``.label( "main program body" )) <~ "end" ) private lazy val `` = FuncDecl( sepBy(``, ",") <~ ")" <~ "is", ``.guardAgainst { case stmts if !stmts.isReturning => Seq("All functions must end in a returning statement") } <~ "end" ) private lazy val `` = Param(``, ``) private lazy val ``: Parsley[NonEmptyList[Stmt]] = ( ``.label("main program body"), (many(";" ~> ``.label("statement after ';'"))) Nil ).zipped(NonEmptyList.apply) private lazy val `` = (Skip from "skip") | Read("read" ~> ``) | 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" ~> ``.labelWithType(LabelType.Expr) <~ "then", `` <~ "else", `` <~ "fi" ) | While("while" ~> ``.labelWithType(LabelType.Expr) <~ "do", `` <~ "done") | Block("begin" ~> `` <~ "end") | 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] = `` | `` private lazy val ``: Parsley[RValue] = `` | NewPair( "newpair" ~> "(" ~> `` <~ ",", `` <~ ")" ) | `` | Call( "call" ~> `` <~ "(", sepBy(``, ",") <~ ")" ) | ``.labelWithType(LabelType.Expr) private lazy val `` = Fst("fst" ~> ``.label("valid pair")) | Snd("snd" ~> ``.label("valid pair")) 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 } } }