Scaladocs

Merge request lab2425_spring/WACC_37!18

Co-authored-by: Guy C <gc1523@ic.ac.uk>
Co-authored-by: Jonny <j.sinteix@gmail.com>
This commit is contained in:
Connolly, Guy 2025-02-07 16:50:12 +00:00
commit 2ff7fff7eb
4 changed files with 69 additions and 10 deletions

View File

@ -3,6 +3,8 @@ package wacc
import wacc.ast.Position import wacc.ast.Position
import wacc.types._ import wacc.types._
/** Error types for semantic errors
*/
enum Error { enum Error {
case DuplicateDeclaration(ident: ast.Ident) case DuplicateDeclaration(ident: ast.Ident)
case UndeclaredVariable(ident: ast.Ident) case UndeclaredVariable(ident: ast.Ident)
@ -14,6 +16,13 @@ enum Error {
case InternalError(pos: Position, msg: String) 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 = { def printError(error: Error)(using errorContent: String): Unit = {
println("Semantic error:") println("Semantic error:")
error match { 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 = { def highlight(pos: Position, size: Int)(using errorContent: String): Unit = {
val lines = errorContent.split("\n") 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 = { def printPosition(pos: Position): Unit = {
println(s"(line ${pos.line}, column ${pos.column}):") println(s"(line ${pos.line}, column ${pos.column}):")
} }

View File

@ -8,7 +8,8 @@ import parsley.syntax.zipped._
import cats.data.NonEmptyList import cats.data.NonEmptyList
object ast { object ast {
// Expressions /* ============================ EXPRESSIONS ============================ */
sealed trait Expr extends RValue { sealed trait Expr extends RValue {
val pos: Position val pos: Position
} }
@ -19,7 +20,8 @@ object ast {
sealed trait Expr5 extends Expr4 sealed trait Expr5 extends Expr4
sealed trait Expr6 extends Expr5 sealed trait Expr6 extends Expr5
// Atoms /* ============================ ATOMIC EXPRESSIONS ============================ */
case class IntLiter(v: Int)(val pos: Position) extends Expr6 case class IntLiter(v: Int)(val pos: Position) extends Expr6
object IntLiter extends ParserBridgePos1[Int, IntLiter] object IntLiter extends ParserBridgePos1[Int, IntLiter]
case class BoolLiter(v: Boolean)(val pos: Position) extends Expr6 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 case class Parens(expr: Expr)(val pos: Position) extends Expr6
object Parens extends ParserBridgePos1[Expr, Parens] object Parens extends ParserBridgePos1[Expr, Parens]
// Unary operators /* ============================ UNARY OPERATORS ============================ */
sealed trait UnaryOp extends Expr { sealed trait UnaryOp extends Expr {
val x: Expr val x: Expr
} }
@ -59,7 +62,8 @@ object ast {
case class Chr(x: Expr6)(val pos: Position) extends Expr6 with UnaryOp case class Chr(x: Expr6)(val pos: Position) extends Expr6 with UnaryOp
object Chr extends ParserBridgePos1[Expr6, Chr] object Chr extends ParserBridgePos1[Expr6, Chr]
// Binary operators /* ============================ BINARY OPERATORS ============================ */
sealed trait BinaryOp(val name: String) extends Expr { sealed trait BinaryOp(val name: String) extends Expr {
val x: Expr val x: Expr
val y: 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") case class Or(x: Expr1, y: Expr)(val pos: Position) extends Expr with BinaryOp("logical or")
object Or extends ParserBridgePos2[Expr1, Expr, Or] object Or extends ParserBridgePos2[Expr1, Expr, Or]
// Types /* ============================ TYPES ============================ */
sealed trait Type sealed trait Type
sealed trait BaseType extends Type with PairElemType sealed trait BaseType extends Type with PairElemType
case class IntType()(val pos: Position) extends BaseType case class IntType()(val pos: Position) extends BaseType
@ -125,11 +130,13 @@ object ast {
case class UntypedPairType()(val pos: Position) extends PairElemType case class UntypedPairType()(val pos: Position) extends PairElemType
object UntypedPairType extends ParserBridgePos0[UntypedPairType] object UntypedPairType extends ParserBridgePos0[UntypedPairType]
// waccadoodledo /* ============================ PROGRAM STRUCTURE ============================ */
case class Program(funcs: List[FuncDecl], main: NonEmptyList[Stmt])(val pos: Position) case class Program(funcs: List[FuncDecl], main: NonEmptyList[Stmt])(val pos: Position)
object Program extends ParserBridgePos2[List[FuncDecl], NonEmptyList[Stmt], Program] object Program extends ParserBridgePos2[List[FuncDecl], NonEmptyList[Stmt], Program]
// Function Definitions /* ============================ FUNCTION STRUCTURE ============================ */
case class FuncDecl( case class FuncDecl(
returnType: Type, returnType: Type,
name: Ident, name: Ident,
@ -151,7 +158,8 @@ object ast {
case class Param(paramType: Type, name: Ident)(val pos: Position) case class Param(paramType: Type, name: Ident)(val pos: Position)
object Param extends ParserBridgePos2[Type, Ident, Param] object Param extends ParserBridgePos2[Type, Ident, Param]
// Statements /* ============================ STATEMENTS ============================ */
sealed trait Stmt sealed trait Stmt
case class Skip()(val pos: Position) extends Stmt case class Skip()(val pos: Position) extends Stmt
object Skip extends ParserBridgePos0[Skip] object Skip extends ParserBridgePos0[Skip]
@ -178,6 +186,8 @@ object ast {
case class Block(stmt: NonEmptyList[Stmt])(val pos: Position) extends Stmt case class Block(stmt: NonEmptyList[Stmt])(val pos: Position) extends Stmt
object Block extends ParserBridgePos1[NonEmptyList[Stmt], Block] object Block extends ParserBridgePos1[NonEmptyList[Stmt], Block]
/* ============================ LVALUES & RVALUES ============================ */
sealed trait LValue { sealed trait LValue {
val pos: Position val pos: Position
} }
@ -196,7 +206,8 @@ object ast {
case class Snd(elem: LValue)(val pos: Position) extends PairElem case class Snd(elem: LValue)(val pos: Position) extends PairElem
object Snd extends ParserBridgePos1[LValue, Snd] object Snd extends ParserBridgePos1[LValue, Snd]
// Parser bridges /* ============================ PARSER BRIDGES ============================ */
case class Position(line: Int, column: Int) case class Position(line: Int, column: Int)
trait ParserSingletonBridgePos[+A] extends ErrorBridge { trait ParserSingletonBridgePos[+A] extends ErrorBridge {

View File

@ -6,6 +6,8 @@ import parsley.token.{Basic, Lexer}
import parsley.token.descriptions.* import parsley.token.descriptions.*
import parsley.token.errors._ import parsley.token.errors._
/** ErrorConfig for producing more informative error messages
*/
val errConfig = new ErrorConfig { val errConfig = new ErrorConfig {
override def labelSymbol = Map( override def labelSymbol = Map(
"!=" -> Label("binary operator"), "!=" -> Label("binary operator"),
@ -37,6 +39,9 @@ val errConfig = new ErrorConfig {
) )
} }
object lexer { object lexer {
/** Language description for the WACC lexer
*/
private val desc = LexicalDesc.plain.copy( private val desc = LexicalDesc.plain.copy(
nameDesc = NameDesc.plain.copy( nameDesc = NameDesc.plain.copy(
identifierStart = Basic(c => c.isLetter || c == '_'), identifierStart = Basic(c => c.isLetter || c == '_'),
@ -74,6 +79,8 @@ object lexer {
) )
) )
/** Token definitions for the WACC lexer
*/
private val lexer = Lexer(desc, errConfig) 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]
@ -82,6 +89,8 @@ 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
/** Tokens for producing lexer-backed error messages
*/
val errTokens = Seq( val errTokens = Seq(
lexer.nonlexeme.names.identifier.map(v => s"identifier $v"), lexer.nonlexeme.names.identifier.map(v => s"identifier $v"),
lexer.nonlexeme.integer.decimal32[Int].map(n => s"integer $n"), lexer.nonlexeme.integer.decimal32[Int].map(n => s"integer $n"),

View File

@ -77,6 +77,7 @@ object parser {
SOps(InfixL)(Mul from "*", Div from "/", Mod from "%") +: SOps(InfixL)(Mul from "*", Div from "/", Mod from "%") +:
SOps(Prefix)( SOps(Prefix)(
Not from "!", Not from "!",
// notFollowedBy(negateCheck) ensures that negative numbers are parsed as a single int literal
(Negate from (notFollowedBy(negateCheck) ~> "-")).hide, (Negate from (notFollowedBy(negateCheck) ~> "-")).hide,
Len from "len", Len from "len",
Ord from "ord", Ord from "ord",
@ -119,7 +120,15 @@ object parser {
((`<pair-elems-type>` <**> `<array-type>`) ((`<pair-elems-type>` <**> `<array-type>`)
.map(arr => (_: UntypedPairType) => arr) </> identity)) .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>` = Program( private lazy val `<program>` = Program(
"begin" ~> ( "begin" ~> (
many( many(
@ -192,6 +201,13 @@ object parser {
) )
extension (stmts: NonEmptyList[Stmt]) { 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 { def isReturning: Boolean = stmts.last match {
case Return(_) | Exit(_) => true case Return(_) | Exit(_) => true
case If(_, thenStmt, elseStmt) => thenStmt.isReturning && elseStmt.isReturning case If(_, thenStmt, elseStmt) => thenStmt.isReturning && elseStmt.isReturning