diff --git a/src/main/wacc/Error.scala b/src/main/wacc/Error.scala index ba810f5..fec2662 100644 --- a/src/main/wacc/Error.scala +++ b/src/main/wacc/Error.scala @@ -3,6 +3,8 @@ package wacc import wacc.ast.Position import wacc.types._ +/** Error types for semantic errors + */ enum Error { case DuplicateDeclaration(ident: ast.Ident) case UndeclaredVariable(ident: ast.Ident) @@ -14,6 +16,13 @@ enum Error { case InternalError(pos: Position, msg: String) } +/** Function to handle printing the details of a given semantic error + * + * @param error + * Error object + * @param errorContent + * Contents of the file to generate code snippets + */ def printError(error: Error)(using errorContent: String): Unit = { println("Semantic error:") error match { @@ -49,6 +58,15 @@ def printError(error: Error)(using errorContent: String): Unit = { } +/** Function to highlight a section of code for an error message + * + * @param pos + * Position of the error + * @param size + * Size(in chars) of section to highlight + * @param errorContent + * Contents of the file to generate code snippets + */ def highlight(pos: Position, size: Int)(using errorContent: String): Unit = { val lines = errorContent.split("\n") @@ -62,6 +80,11 @@ def highlight(pos: Position, size: Int)(using errorContent: String): Unit = { ) } +/** Function to print the position of an error + * + * @param pos + * Position of the error + */ def printPosition(pos: Position): Unit = { println(s"(line ${pos.line}, column ${pos.column}):") } diff --git a/src/main/wacc/ast.scala b/src/main/wacc/ast.scala index 62b134c..ac0e585 100644 --- a/src/main/wacc/ast.scala +++ b/src/main/wacc/ast.scala @@ -8,7 +8,8 @@ import parsley.syntax.zipped._ import cats.data.NonEmptyList object ast { - // Expressions + /* ============================ EXPRESSIONS ============================ */ + sealed trait Expr extends RValue { val pos: Position } @@ -19,7 +20,8 @@ object ast { sealed trait Expr5 extends Expr4 sealed trait Expr6 extends Expr5 - // Atoms + /* ============================ ATOMIC EXPRESSIONS ============================ */ + case class IntLiter(v: Int)(val pos: Position) extends Expr6 object IntLiter extends ParserBridgePos1[Int, IntLiter] case class BoolLiter(v: Boolean)(val pos: Position) extends Expr6 @@ -44,7 +46,8 @@ object ast { case class Parens(expr: Expr)(val pos: Position) extends Expr6 object Parens extends ParserBridgePos1[Expr, Parens] - // Unary operators + /* ============================ UNARY OPERATORS ============================ */ + sealed trait UnaryOp extends Expr { val x: Expr } @@ -59,7 +62,8 @@ object ast { case class Chr(x: Expr6)(val pos: Position) extends Expr6 with UnaryOp object Chr extends ParserBridgePos1[Expr6, Chr] - // Binary operators + /* ============================ BINARY OPERATORS ============================ */ + sealed trait BinaryOp(val name: String) extends Expr { val x: Expr val y: Expr @@ -101,7 +105,8 @@ object ast { case class Or(x: Expr1, y: Expr)(val pos: Position) extends Expr with BinaryOp("logical or") object Or extends ParserBridgePos2[Expr1, Expr, Or] - // Types + /* ============================ TYPES ============================ */ + sealed trait Type sealed trait BaseType extends Type with PairElemType case class IntType()(val pos: Position) extends BaseType @@ -125,11 +130,13 @@ object ast { case class UntypedPairType()(val pos: Position) extends PairElemType object UntypedPairType extends ParserBridgePos0[UntypedPairType] - // waccadoodledo + /* ============================ PROGRAM STRUCTURE ============================ */ + case class Program(funcs: List[FuncDecl], main: NonEmptyList[Stmt])(val pos: Position) object Program extends ParserBridgePos2[List[FuncDecl], NonEmptyList[Stmt], Program] - // Function Definitions + /* ============================ FUNCTION STRUCTURE ============================ */ + case class FuncDecl( returnType: Type, name: Ident, @@ -151,7 +158,8 @@ object ast { case class Param(paramType: Type, name: Ident)(val pos: Position) object Param extends ParserBridgePos2[Type, Ident, Param] - // Statements + /* ============================ STATEMENTS ============================ */ + sealed trait Stmt case class Skip()(val pos: Position) extends Stmt object Skip extends ParserBridgePos0[Skip] @@ -178,6 +186,8 @@ object ast { case class Block(stmt: NonEmptyList[Stmt])(val pos: Position) extends Stmt object Block extends ParserBridgePos1[NonEmptyList[Stmt], Block] + /* ============================ LVALUES & RVALUES ============================ */ + sealed trait LValue { val pos: Position } @@ -196,7 +206,8 @@ object ast { case class Snd(elem: LValue)(val pos: Position) extends PairElem object Snd extends ParserBridgePos1[LValue, Snd] - // Parser bridges + /* ============================ PARSER BRIDGES ============================ */ + case class Position(line: Int, column: Int) trait ParserSingletonBridgePos[+A] extends ErrorBridge { diff --git a/src/main/wacc/lexer.scala b/src/main/wacc/lexer.scala index c6e2781..2efe517 100644 --- a/src/main/wacc/lexer.scala +++ b/src/main/wacc/lexer.scala @@ -6,6 +6,8 @@ import parsley.token.{Basic, Lexer} import parsley.token.descriptions.* import parsley.token.errors._ +/** ErrorConfig for producing more informative error messages + */ val errConfig = new ErrorConfig { override def labelSymbol = Map( "!=" -> Label("binary operator"), @@ -37,6 +39,9 @@ val errConfig = new ErrorConfig { ) } object lexer { + + /** Language description for the WACC lexer + */ private val desc = LexicalDesc.plain.copy( nameDesc = NameDesc.plain.copy( identifierStart = Basic(c => c.isLetter || c == '_'), @@ -74,6 +79,8 @@ object lexer { ) ) + /** Token definitions for the WACC lexer + */ private val lexer = Lexer(desc, errConfig) val ident = lexer.lexeme.names.identifier val integer = lexer.lexeme.integer.decimal32[Int] @@ -82,6 +89,8 @@ object lexer { val stringLit = lexer.lexeme.string.ascii val implicits = lexer.lexeme.symbol.implicits + /** Tokens for producing lexer-backed error messages + */ val errTokens = Seq( lexer.nonlexeme.names.identifier.map(v => s"identifier $v"), lexer.nonlexeme.integer.decimal32[Int].map(n => s"integer $n"), diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index 6a3407d..c167ca1 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -77,6 +77,7 @@ object parser { 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", @@ -119,7 +120,15 @@ object parser { ((`` <**> ``) .map(arr => (_: UntypedPairType) => arr) identity)) - // Statements + /* 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( @@ -192,6 +201,13 @@ object parser { ) 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