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.errors.patterns.VerifiedErrors 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 import parsley.character.char 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 val _parensCheck = char('(').verifiedExplain("functions can only be called using 'call' keyword") 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") | _parensCheck), ((Sub from "-").label("binary operator") | _parensCheck) ) +: SOps(InfixL)(Mul from "*", Div from "/", Mod from "%") +: SOps(Prefix)( Not from "!", // notFollowedBy(negateCheck) ensures that negative numbers are parsed as a single int literal (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) | some("*" | "&").verifiedExplain("pointer operators are not allowed") 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 Atomic is used in two places here: 1. Atomic for function return type - code may be a variable declaration instead, If we were to factor out the type, the resulting code would be rather messy. It can only fail once in the entire program so it creates minimal overhead. 2. Atomic for function missing return type check - there is no easy way around an explicit invalid syntax check, this only happens at most once per program so this is not a major concern. */ private lazy val `` = Program( "begin" ~> ( many( atomic( ``.label("function declaration") <~> `` <~ "(" ) <**> `` ).label("function declaration") | atomic(`` <~ "(").verifiedExplain("function declaration 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( ``, `` <~ ("=" | "(".verifiedExplain( "all function declarations must be above the main program body" )), ``.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]) { /** Determines whether a function body is guaranteed to return in all cases This is required as * all functions must end via a "return" or "exit" statement * * @return * true if the statement list ends in a return statement, false otherwise */ 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 } } }