diff --git a/src/main/wacc/Error.scala b/src/main/wacc/Error.scala index fec2662..0f3c01d 100644 --- a/src/main/wacc/Error.scala +++ b/src/main/wacc/Error.scala @@ -10,7 +10,7 @@ enum Error { case UndeclaredVariable(ident: ast.Ident) case UndefinedFunction(ident: ast.Ident) - case FunctionParamsMismatch(pos: Position, expected: Int, got: Int) + case FunctionParamsMismatch(ident: ast.Ident, expected: Int, got: Int, funcType: FuncType) case SemanticError(pos: Position, msg: String) case TypeMismatch(pos: Position, expected: SemType, got: SemType, msg: String) case InternalError(pos: Position, msg: String) @@ -38,10 +38,13 @@ def printError(error: Error)(using errorContent: String): Unit = { printPosition(ident.pos) println(s"Undefined function ${ident.v}") highlight(ident.pos, ident.v.length) - case Error.FunctionParamsMismatch(pos, expected, got) => - printPosition(pos) + case Error.FunctionParamsMismatch(id, expected, got, funcType) => + printPosition(id.pos) println(s"Function expects $expected parameters, got $got") - highlight(pos, 1) + println( + s"(function ${id.v} has type (${funcType.params.mkString(", ")}) -> ${funcType.returnType})" + ) + highlight(id.pos, 1) case Error.TypeMismatch(pos, expected, got, msg) => printPosition(pos) println(s"Type mismatch: $msg\nExpected: $expected\nGot: $got") diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index 7d76969..ddb5987 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -2,7 +2,7 @@ package wacc import parsley.Result import parsley.Parsley -import parsley.Parsley.{atomic, many, notFollowedBy, pure} +import parsley.Parsley.{atomic, many, notFollowedBy, pure, unit} import parsley.combinator.{countSome, sepBy} import parsley.expr.{precedence, SOps, InfixL, InfixN, InfixR, Prefix, Atoms} import parsley.errors.combinator._ @@ -95,7 +95,9 @@ object parser { private val `` = Ident(ident) | some("*" | "&").verifiedExplain("pointer operators are not allowed") private lazy val `` = - `` <**> (`` identity) + (`` <~ ("(".verifiedExplain( + "functions can only be called using 'call' keyword" + ) | unit)) <**> (`` identity) private val `` = ArrayElem(some("[" ~> `` <~ "]")) // Types @@ -175,7 +177,15 @@ object parser { ``.label("valid initial value for variable") ) // TODO: Can we inline the name of the variable in the message - | Assign(`` <~ "=", ``) + | Assign( + `` <~ ("=" | "(".verifiedExplain( + "function calls must use the 'call' keyword and the result must be assigned to a variable" + )), + `` + ) | + ("call" ~> ``).verifiedExplain( + "function calls' results must be assigned to a variable" + ) private lazy val ``: Parsley[LValue] = `` | `` private lazy val ``: Parsley[RValue] = diff --git a/src/main/wacc/typeChecker.scala b/src/main/wacc/typeChecker.scala index 8c01379..c766905 100644 --- a/src/main/wacc/typeChecker.scala +++ b/src/main/wacc/typeChecker.scala @@ -95,28 +95,27 @@ object typeChecker { ) case Assign(lhs, rhs) => val lhsTy = checkValue(lhs, Constraint.Unconstrained) - checkValue(rhs, Constraint.Is(lhsTy, s"assignment must have type $lhsTy")) match { - case ? => + (lhsTy, checkValue(rhs, Constraint.Is(lhsTy, s"assignment must have type $lhsTy"))) match { + case (?, ?) => ctx.error( Error.SemanticError(lhs.pos, "assignment with both sides of unknown type is illegal") ) case _ => () } - case Read(lhs) => - val lhsTy = checkValue(lhs, Constraint.Unconstrained) - lhsTy match { + case Read(dest) => + checkValue(dest, Constraint.Unconstrained) match { case ? => ctx.error( - Error.SemanticError(lhs.pos, "cannot read into a destination with an unknown type") + Error.SemanticError(dest.pos, "cannot read into a destination with an unknown type") ) - case _ => - lhsTy.satisfies( + case destTy => + destTy.satisfies( Constraint.IsEither( KnownType.Int, KnownType.Char, "read must be applied to an int or char" ), - lhs.pos + dest.pos ) } case Free(lhs) => @@ -125,7 +124,7 @@ object typeChecker { Constraint.IsEither( KnownType.Array(?), KnownType.Pair(?, ?), - "free must be an array or pair" + "free must be applied to an array or pair" ) ) case Return(expr) => @@ -163,6 +162,7 @@ object typeChecker { checkValue(elem, Constraint.Is(KnownType.Int, "array index must be an int")) acc match { case KnownType.Array(innerTy) => Some(innerTy) + case ? => Some(?) case nonArrayTy => ctx.error( Error.TypeMismatch(elem.pos, KnownType.Array(?), acc, "cannot index into a non-array") @@ -189,9 +189,9 @@ object typeChecker { ) .satisfies(constraint, l.pos) case Call(id, args) => - val FuncType(retTy, paramTys) = ctx.funcType(id) + val funcTy @ FuncType(retTy, paramTys) = ctx.funcType(id) if (args.length != paramTys.length) { - ctx.error(Error.FunctionParamsMismatch(id.pos, paramTys.length, args.length)) + ctx.error(Error.FunctionParamsMismatch(id, paramTys.length, args.length, funcTy)) } args.zip(paramTys).foreach { case (arg, paramTy) => checkValue(arg, Constraint.Is(paramTy, s"argument type mismatch in function ${id.v}")) @@ -246,18 +246,17 @@ object typeChecker { ) KnownType.Bool.satisfies(constraint, op.pos) case op: (Less | LessEq | Greater | GreaterEq) => - val xTy = checkValue( - op.x, - Constraint.IsEither( - KnownType.Int, - KnownType.Char, - s"${op.name} operator must be applied to an int or char" - ) - ) - checkValue( - op.y, - Constraint.Is(xTy, s"${op.name} operator must be applied to values of the same type") + val xConstraint = Constraint.IsEither( + KnownType.Int, + KnownType.Char, + s"${op.name} operator must be applied to an int or char" ) + val yConstraint = checkValue(op.x, xConstraint) match { + case ? => xConstraint + case xTy => + Constraint.Is(xTy, s"${op.name} operator must be applied to values of the same type") + } + checkValue(op.y, yConstraint) KnownType.Bool.satisfies(constraint, op.pos) case op: (And | Or) => val operand = Constraint.Is(KnownType.Bool, s"${op.name} operator must be applied to a bool")