fix: convert parser to use FParsley

This commit is contained in:
2025-03-13 13:26:35 +00:00
parent 3fff9d3825
commit 5141a2369f
2 changed files with 59 additions and 38 deletions

View File

@@ -18,6 +18,7 @@ import org.typelevel.log4cats.Logger
import assemblyIR as asm import assemblyIR as asm
import cats.data.ValidatedNel import cats.data.ValidatedNel
import java.io.File
/* /*
TODO: TODO:
@@ -68,11 +69,12 @@ val outputOpt: Opts[Option[Path]] =
.orNone .orNone
def frontend( def frontend(
contents: String contents: String, file: File
): Either[NonEmptyList[Error], microWacc.Program] = ): Either[NonEmptyList[Error], microWacc.Program] =
parser.parse(contents) match { parser.parse(contents) match {
case Failure(msg) => Left(NonEmptyList.one(Error.SyntaxError(msg))) case Failure(msg) => Left(NonEmptyList.one(Error.SyntaxError(msg)))
case Success(ast.PartialProgram(_, prog)) => case Success(fn) =>
val ast.PartialProgram(_, prog) = fn(file)
given errors: mutable.Builder[Error, List[Error]] = List.newBuilder given errors: mutable.Builder[Error, List[Error]] = List.newBuilder
val (names, funcs) = renamer.rename(prog) val (names, funcs) = renamer.rename(prog)
@@ -105,8 +107,8 @@ def compile(
writer.writeTo(backend(typedProg), outputPath) *> writer.writeTo(backend(typedProg), outputPath) *>
logger.info(s"Success: ${outputPath.toAbsolutePath}") logger.info(s"Success: ${outputPath.toAbsolutePath}")
def processProgram(contents: String, outDir: Path): IO[Int] = def processProgram(contents: String, file: File, outDir: Path): IO[Int] =
frontend(contents) match { frontend(contents, file) match {
case Left(errors) => case Left(errors) =>
val code = errors.map(err => err.exitCode).toList.min val code = errors.map(err => err.exitCode).toList.min
given errorContent: String = contents given errorContent: String = contents
@@ -127,7 +129,7 @@ def compile(
for { for {
contents <- readSourceFile contents <- readSourceFile
_ <- logAction(s"Compiling file: ${filePath.toAbsolutePath}") _ <- logAction(s"Compiling file: ${filePath.toAbsolutePath}")
exitCode <- processProgram(contents, outputDir.getOrElse(filePath.getParent)) exitCode <- processProgram(contents, filePath.toFile, outputDir.getOrElse(filePath.getParent))
} yield exitCode } yield exitCode
} }

View File

@@ -1,10 +1,11 @@
package wacc package wacc
import java.io.File
import parsley.Result import parsley.Result
import parsley.Parsley import parsley.Parsley
import parsley.Parsley.{atomic, many, notFollowedBy, pure, unit} import parsley.Parsley.{atomic, many, notFollowedBy, pure, unit}
import parsley.combinator.{countSome, sepBy, option} import parsley.combinator.{countSome, sepBy, option}
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.errors.patterns.VerifiedErrors import parsley.errors.patterns.VerifiedErrors
import parsley.syntax.zipped._ import parsley.syntax.zipped._
@@ -52,13 +53,30 @@ object parser {
implicit val builder: ErrorBuilder[String] = new DefaultErrorBuilder with LexToken { implicit val builder: ErrorBuilder[String] = new DefaultErrorBuilder with LexToken {
def tokens = errTokens def tokens = errTokens
} }
def parse(input: String): Result[String, PartialProgram] = parser.parse(input) def parse(input: String): Result[String, File => PartialProgram] = parser.parse(input)
private val parser = lexer.fully(`<partial-program>`) private val parser = lexer.fully(`<partial-program>`)
private type FParsley[A] = Parsley[File => A]
private def fParsley[A](p: Parsley[A]): FParsley[A] =
p map { a => file => a }
private def fList[A](p: Parsley[List[File => A]]): FParsley[List[A]] =
p map { l => file => l.map(_(file)) }
private def fNonEmptyList[A](p: Parsley[NonEmptyList[File => A]]): FParsley[NonEmptyList[A]] =
p map { l => file => l.map(_(file)) }
private def fPair[A, B](p: Parsley[(File => A, File => B)]): FParsley[(A, B)] =
p map { case (a, b) => file => (a(file), b(file)) }
private def fOption[A](p: Parsley[Option[File => A]]): FParsley[Option[A]] =
p map { l => file => l.map(_(file)) }
// Expressions // Expressions
private lazy val `<expr>`: Parsley[Expr] = precedence { private lazy val `<expr>`: FParsley[Expr] = precedence {
SOps(InfixR)(Or from "||") +: // SOps(InfixR)(Or from "||") +:
SOps(InfixR)(And from "&&") +: // SOps(InfixR)(And from "&&") +:
SOps(InfixN)(Eq from "==", Neq from "!=") +: SOps(InfixN)(Eq from "==", Neq from "!=") +:
SOps(InfixN)( SOps(InfixN)(
Less from "<", Less from "<",
@@ -83,7 +101,7 @@ object parser {
} }
// Atoms // Atoms
private lazy val `<atom>`: Atoms[Expr6] = Atoms( private lazy val `<atom>`: Atoms[File => Expr6] = Atoms(
IntLiter(integer).label("integer literal"), IntLiter(integer).label("integer literal"),
BoolLiter(("true" as true) | ("false" as false)).label("boolean literal"), BoolLiter(("true" as true) | ("false" as false)).label("boolean literal"),
CharLiter(charLit).label("character literal"), CharLiter(charLit).label("character literal"),
@@ -92,24 +110,24 @@ object parser {
`<ident-or-array-elem>`, `<ident-or-array-elem>`,
Parens("(" ~> `<expr>` <~ ")") Parens("(" ~> `<expr>` <~ ")")
) )
private val `<str-liter>` = StrLiter(stringLit) private lazy val `<str-liter>` = StrLiter(stringLit)
private val `<ident>` = private lazy val `<ident>` =
Ident(ident) | some("*" | "&").verifiedExplain("pointer operators are not allowed") Ident(ident) | some("*" | "&").verifiedExplain("pointer operators are not allowed")
private lazy val `<ident-or-array-elem>` = private lazy val `<ident-or-array-elem>` =
(`<ident>` <~ ("(".verifiedExplain( (`<ident>` <~ ("(".verifiedExplain(
"functions can only be called using 'call' keyword" "functions can only be called using 'call' keyword"
) | unit)) <**> (`<array-indices>` </> identity) ) | unit)) <**> (`<array-indices>` </> identity)
private val `<array-indices>` = ArrayElem(some("[" ~> `<expr>` <~ "]")) private lazy val `<array-indices>` = ArrayElem(fNonEmptyList(some("[" ~> `<expr>` <~ "]")))
// Types // Types
private lazy val `<type>`: Parsley[Type] = private lazy val `<type>`: FParsley[Type] =
(`<base-type>` | (`<pair-type>` ~> `<pair-elems-type>`)) <**> (`<array-type>` </> identity) (`<base-type>` | (`<pair-type>` ~> `<pair-elems-type>`)) <**> (`<array-type>` </> identity)
private val `<base-type>` = private val `<base-type>` =
(IntType from "int") | (BoolType from "bool") | (CharType from "char") | (StringType from "string") (IntType from "int") | (BoolType from "bool") | (CharType from "char") | (StringType from "string")
private lazy val `<array-type>` = private lazy val `<array-type>` =
ArrayType(countSome("[" ~> "]")) ArrayType(fParsley(countSome("[" ~> "]")))
private val `<pair-type>` = "pair" private val `<pair-type>` = "pair"
private val `<pair-elems-type>`: Parsley[PairType] = PairType( private val `<pair-elems-type>`: FParsley[PairType] = PairType(
"(" ~> `<pair-elem-type>` <~ ",", "(" ~> `<pair-elem-type>` <~ ",",
`<pair-elem-type>` <~ ")" `<pair-elem-type>` <~ ")"
) )
@@ -117,7 +135,7 @@ object parser {
(`<base-type>` <**> (`<array-type>` </> identity)) | (`<base-type>` <**> (`<array-type>` </> identity)) |
((UntypedPairType from `<pair-type>`) <**> ((UntypedPairType from `<pair-type>`) <**>
((`<pair-elems-type>` <**> `<array-type>`) ((`<pair-elems-type>` <**> `<array-type>`)
.map(arr => (_: UntypedPairType) => arr) </> identity)) .map(arr => (_: File => UntypedPairType) => arr) </> identity))
/* Statements /* Statements
Atomic is used in two places here: Atomic is used in two places here:
@@ -129,25 +147,25 @@ object parser {
concern. concern.
*/ */
private lazy val `<partial-program>` = PartialProgram( private lazy val `<partial-program>` = PartialProgram(
many(`<import>`), fList(many(`<import>`)),
`<program>` `<program>`
) )
private lazy val `<import>` = Import( private lazy val `<import>` = Import(
"import" ~> `<import-filename>`, "import" ~> `<import-filename>`,
"(" ~> sepBy1(`<imported-func>`, ",") <~ ")" "(" ~> fNonEmptyList(sepBy1(`<imported-func>`, ",")) <~ ")"
) )
private lazy val `<import-filename>` = `<str-liter>`.label("import file name") private lazy val `<import-filename>` = `<str-liter>`.label("import file name")
private lazy val `<imported-func>` = ImportedFunc( private lazy val `<imported-func>` = ImportedFunc(
`<ident>`.label("imported function name"), `<ident>`.label("imported function name"),
option("as" ~> `<ident>`).label("imported function alias") fOption(option("as" ~> `<ident>`)).label("imported function alias")
) )
private lazy val `<program>` = Program( private lazy val `<program>` = Program(
"begin" ~> ( "begin" ~> (
many( fList(many(
atomic( fPair(atomic(
`<type>`.label("function declaration") <~> `<ident>` <~ "(" `<type>`.label("function declaration") <~> `<ident>` <~ "("
) <**> `<partial-func-decl>` )) <**> `<partial-func-decl>`
).label("function declaration") | ).label("function declaration")) |
atomic(`<ident>` <~ "(").verifiedExplain("function declaration is missing return type") atomic(`<ident>` <~ "(").verifiedExplain("function declaration is missing return type")
), ),
`<stmt>`.label( `<stmt>`.label(
@@ -156,17 +174,18 @@ object parser {
) )
private lazy val `<partial-func-decl>` = private lazy val `<partial-func-decl>` =
FuncDecl( FuncDecl(
sepBy(`<param>`, ",") <~ ")" <~ "is", fPair((fList(sepBy(`<param>`, ",")) <~ ")" <~ "is") <~>
`<stmt>`.guardAgainst { (`<stmt>`.guardAgainst {
case stmts if !stmts.isReturning => Seq("all functions must end in a returning statement") // TODO: passing in an arbitrary file works but is ugly
} <~ "end" case stmts if !(stmts(File("."))).isReturning => Seq("all functions must end in a returning statement")
} <~ "end"))
) )
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>`: FParsley[NonEmptyList[Stmt]] =
( fNonEmptyList((
`<basic-stmt>`.label("main program body"), `<basic-stmt>`.label("main program body"),
(many(";" ~> `<basic-stmt>`.label("statement after ';'"))) </> Nil (many(";" ~> `<basic-stmt>`.label("statement after ';'"))) </> Nil
).zipped(NonEmptyList.apply) ).zipped(NonEmptyList.apply))
private lazy val `<basic-stmt>` = private lazy val `<basic-stmt>` =
(Skip from "skip") (Skip from "skip")
@@ -174,8 +193,8 @@ object parser {
| Free("free" ~> `<expr>`.labelAndExplain(LabelType.Expr)) | Free("free" ~> `<expr>`.labelAndExplain(LabelType.Expr))
| Return("return" ~> `<expr>`.labelAndExplain(LabelType.Expr)) | Return("return" ~> `<expr>`.labelAndExplain(LabelType.Expr))
| Exit("exit" ~> `<expr>`.labelAndExplain(LabelType.Expr)) | Exit("exit" ~> `<expr>`.labelAndExplain(LabelType.Expr))
| Print("print" ~> `<expr>`.labelAndExplain(LabelType.Expr), pure(false)) | Print("print" ~> `<expr>`.labelAndExplain(LabelType.Expr), fParsley(pure(false)))
| Print("println" ~> `<expr>`.labelAndExplain(LabelType.Expr), pure(true)) | Print("println" ~> `<expr>`.labelAndExplain(LabelType.Expr), fParsley(pure(true)))
| If( | If(
"if" ~> `<expr>`.labelWithType(LabelType.Expr) <~ "then", "if" ~> `<expr>`.labelWithType(LabelType.Expr) <~ "then",
`<stmt>` <~ "else", `<stmt>` <~ "else",
@@ -199,9 +218,9 @@ object parser {
("call" ~> `<ident>`).verifiedExplain( ("call" ~> `<ident>`).verifiedExplain(
"function calls' results must be assigned to a variable" "function calls' results must be assigned to a variable"
) )
private lazy val `<lvalue>`: Parsley[LValue] = private lazy val `<lvalue>`: FParsley[LValue] =
`<pair-elem>` | `<ident-or-array-elem>` `<pair-elem>` | `<ident-or-array-elem>`
private lazy val `<rvalue>`: Parsley[RValue] = private lazy val `<rvalue>`: FParsley[RValue] =
`<array-liter>` | `<array-liter>` |
NewPair( NewPair(
"newpair" ~> "(" ~> `<expr>` <~ ",", "newpair" ~> "(" ~> `<expr>` <~ ",",
@@ -210,13 +229,13 @@ object parser {
`<pair-elem>` | `<pair-elem>` |
Call( Call(
"call" ~> `<ident>` <~ "(", "call" ~> `<ident>` <~ "(",
sepBy(`<expr>`, ",") <~ ")" fList(sepBy(`<expr>`, ",")) <~ ")"
) | `<expr>`.labelWithType(LabelType.Expr) ) | `<expr>`.labelWithType(LabelType.Expr)
private lazy val `<pair-elem>` = private lazy val `<pair-elem>` =
Fst("fst" ~> `<lvalue>`.label("valid pair")) Fst("fst" ~> `<lvalue>`.label("valid pair"))
| Snd("snd" ~> `<lvalue>`.label("valid pair")) | Snd("snd" ~> `<lvalue>`.label("valid pair"))
private lazy val `<array-liter>` = ArrayLiter( private lazy val `<array-liter>` = ArrayLiter(
"[" ~> sepBy(`<expr>`, ",") <~ "]" "[" ~> fList(sepBy(`<expr>`, ",")) <~ "]"
) )
extension (stmts: NonEmptyList[Stmt]) { extension (stmts: NonEmptyList[Stmt]) {