Added Compiler.check(), made type constraints mandatory in generics, reverted '\n' being converted to a semicolon, minor refactoring

This commit is contained in:
Mattia Giambirtone 2022-07-16 13:04:00 +02:00
parent 2072f34d4c
commit b40275b52f
4 changed files with 50 additions and 45 deletions

View File

@ -773,6 +773,22 @@ proc matchImpl(self: Compiler, name: string, kind: Type): Name =
return impl[0]
proc check(self: Compiler, term: Expression, kind: Type) =
## Checks the type of term against a known type.
## Raises an error if appropriate and returns
## otherwise
let k = self.inferType(term)
if k.isNil():
if term.kind == identExpr:
self.error(&"reference to undeclared name '{term.token.lexeme}'")
elif term.kind == callExpr and CallExpr(term).callee.kind == identExpr:
self.error(&"call to undeclared function '{CallExpr(term).callee.token.lexeme}'")
self.error(&"expecting value of type '{self.typeToStr(kind)}', but expression has no type")
elif not self.compareTypes(k, kind):
self.error(&"expecting value of type '{self.typeToStr(k)}', got '{self.typeToStr(k)}' instead")
proc emitFunction(self: Compiler, name: Name) =
## Wrapper to emit LoadFunction instructions
if name.isFunDecl:
@ -1271,13 +1287,8 @@ proc blockStmt(self: Compiler, node: BlockStmt) =
proc ifStmt(self: Compiler, node: IfStmt) =
## Compiles if/else statements for conditional
## execution of code
var cond = self.inferType(node.condition)
self.check(node.condition, Type(kind: Bool))
self.expression(node.condition)
if not self.compareTypes(cond, Type(kind: Bool)):
if cond.isNil():
self.error(&"expecting value of type 'bool', but expression has no type")
else:
self.error(&"expecting value of type 'bool', got '{self.typeToStr(cond)}' instead")
let jump = self.emitJump(JumpIfFalsePop)
self.statement(node.thenBranch)
let jump2 = self.emitJump(JumpForwards)
@ -1290,7 +1301,7 @@ proc ifStmt(self: Compiler, node: IfStmt) =
proc emitLoop(self: Compiler, begin: int) =
## Emits a JumpBackwards instruction with the correct
## jump offset
var offset = self.chunk.code.len() - begin + 4
let offset = self.chunk.code.len() - begin + 4
if offset > 16777215:
self.error("cannot jump more than 16777215 bytecode instructions")
self.emitByte(JumpBackwards)
@ -1300,15 +1311,10 @@ proc emitLoop(self: Compiler, begin: int) =
proc whileStmt(self: Compiler, node: WhileStmt) =
## Compiles C-style while loops and
## desugared C-style for loops
var cond = self.inferType(node.condition)
self.check(node.condition, Type(kind: Bool))
self.expression(node.condition)
if not self.compareTypes(cond, Type(kind: Bool)):
if cond.isNil():
self.error(&"expecting value of type 'bool', but expression has no type")
else:
self.error(&"expecting value of type 'bool', got '{self.typeToStr(cond)}' instead")
let start = self.chunk.code.len()
var jump = self.emitJump(JumpIfFalsePop)
let jump = self.emitJump(JumpIfFalsePop)
self.statement(node.body)
self.patchJump(jump)
self.emitLoop(start)
@ -1329,7 +1335,7 @@ proc callExpr(self: Compiler, node: CallExpr) =
kind = self.inferType(argument)
if kind.isNil():
if argument.kind == identExpr:
self.error(&"reference to undeclared identifier '{IdentExpr(argument).name.lexeme}'")
self.error(&"reference to undeclared name '{IdentExpr(argument).name.lexeme}'")
self.error(&"cannot infer the type of argument {i + 1} in function call")
args.add(("", kind))
argExpr.add(argument)
@ -1459,7 +1465,7 @@ proc returnStmt(self: Compiler, node: ReturnStmt) =
if actual.isNil() and not expected.isNil():
if not node.value.isNil():
if node.value.kind == identExpr:
self.error(&"reference to undeclared identifier '{node.value.token.lexeme}'")
self.error(&"reference to undeclared name '{node.value.token.lexeme}'")
elif node.value.kind == callExpr and CallExpr(node.value).callee.kind == identExpr:
self.error(&"call to undeclared function '{CallExpr(node.value).callee.token.lexeme}'")
self.error(&"expected return value of type '{self.typeToStr(expected)}', but expression has no type")
@ -1478,12 +1484,13 @@ proc returnStmt(self: Compiler, node: ReturnStmt) =
self.emitByte(0)
# TODO: Implement this as a custom operator
proc yieldStmt(self: Compiler, node: YieldStmt) =
## Compiles yield statements
self.expression(node.expression)
self.emitByte(OpCode.Yield)
# TODO: Implement this as a custom operator
proc raiseStmt(self: Compiler, node: RaiseStmt) =
## Compiles raise statements
self.expression(node.exception)
@ -1551,11 +1558,10 @@ proc statement(self: Compiler, node: Statement) =
# The expression has no type, so we don't have to
# pop anything
discard
elif self.replMode:
self.emitByte(PopRepl)
else:
if self.replMode:
self.emitByte(PopRepl)
else:
self.emitByte(Pop)
self.emitByte(Pop)
of NodeKind.ifStmt:
self.ifStmt(IfStmt(node))
of NodeKind.assertStmt:
@ -1572,7 +1578,7 @@ proc statement(self: Compiler, node: Statement) =
self.importStmt(ImportStmt(node))
of NodeKind.whileStmt:
# Note: Our parser already desugars
# for loops to while loops!
# for loops to while loops
let loop = self.currentLoop
self.currentLoop = Loop(start: self.chunk.code.len(),
depth: self.scopeDepth, breakPos: @[])
@ -1604,7 +1610,7 @@ proc varDecl(self: Compiler, node: VarDecl) =
var name = node.value.token.lexeme
if node.value.kind == callExpr:
name = CallExpr(node.value).callee.token.lexeme
self.error(&"reference to undeclared identifier '{name}'")
self.error(&"reference to undeclared name '{name}'")
self.error(&"'{node.name.token.lexeme}' has no type")
elif not expected.isNil() and expected.mutable: # I mean, variables *are* already mutable (some of them anyway)
self.error(&"invalid type '{self.typeToStr(expected)}' for var")

View File

@ -565,8 +565,9 @@ proc next(self: Lexer) =
elif self.match("\n"):
# New line
self.incLine()
if not self.getToken("\n").isNil():
self.createToken(Semicolon)
# TODO: Broken
#[if not self.getToken("\n").isNil():
self.createToken(Semicolon)]#
elif self.match("`"):
# Stropped token
self.parseBackticks()

View File

@ -155,11 +155,11 @@ proc getCurrentFunction*(self: Parser): Declaration {.inline.} = self.currentFun
proc getFile*(self: Parser): string {.inline.} = self.file
proc getModule*(self: Parser): string {.inline.} = self.getFile().extractFilename()
# Handy templates to make our life easier, thanks nim!
template endOfFile: Token = Token(kind: EndOfFile, lexeme: "", line: -1)
template endOfLine(msg: string) = self.expect(Semicolon, msg)
proc peek(self: Parser, distance: int = 0): Token =
## Peeks at the token at the given distance.
## If the distance is out of bounds, an EOF
@ -251,8 +251,7 @@ proc match[T: TokenType or string](self: Parser, kind: openarray[T]): bool =
result = false
proc expect[T: TokenType or string](self: Parser, kind: T,
message: string = "") =
proc expect[T: TokenType or string](self: Parser, kind: T, message: string = "") =
## Behaves like self.match(), except that
## when a token doesn't match, an error
## is raised. If no error message is
@ -264,8 +263,7 @@ proc expect[T: TokenType or string](self: Parser, kind: T,
self.error(message)
proc expect[T: TokenType or string](self: Parser, kind: openarray[T],
message: string = "") =
proc expect[T: TokenType or string](self: Parser, kind: openarray[T], message: string = "") =
## Behaves like self.expect(), except that
## an error is raised only if none of the
## given token kinds matches
@ -273,7 +271,7 @@ proc expect[T: TokenType or string](self: Parser, kind: openarray[T],
if self.match(kind):
return
if message.len() == 0:
self.error(&"""expecting any of the following tokens: {kinds.join(", ")}, but got {self.peek().kind} instead""")
self.error(&"""expecting any of the following tokens: {kind.join(", ")}, but got {self.peek().kind} instead""")
# Forward declarations
@ -526,7 +524,7 @@ proc assertStmt(self: Parser): Statement =
## fed into them is falsey
let tok = self.peek(-1)
var expression = self.expression()
endOfLine("missing semicolon after assert statement")
endOfLine("missing statement terminator after 'assert'")
result = newAssertStmt(expression, tok)
@ -561,7 +559,7 @@ proc breakStmt(self: Parser): Statement =
let tok = self.peek(-1)
if self.currentLoop != Loop:
self.error("'break' cannot be used outside loops")
endOfLine("missing semicolon after break statement")
endOfLine("missing statement terminator after 'break'")
result = newBreakStmt(tok)
@ -571,7 +569,7 @@ proc deferStmt(self: Parser): Statement =
if self.currentFunction.isNil():
self.error("'defer' cannot be used outside functions")
result = newDeferStmt(self.expression(), tok)
endOfLine("missing semicolon after defer statement")
endOfLine("missing statement terminator after 'defer'")
proc continueStmt(self: Parser): Statement =
@ -579,7 +577,7 @@ proc continueStmt(self: Parser): Statement =
let tok = self.peek(-1)
if self.currentLoop != Loop:
self.error("'continue' cannot be used outside loops")
endOfLine("missing semicolon after continue statement")
endOfLine("missing statement terminator after 'continue'")
result = newContinueStmt(tok)
@ -594,7 +592,7 @@ proc returnStmt(self: Parser): Statement =
# we need to check if there's an actual value
# to return or not
value = self.expression()
endOfLine("missing semicolon after return statement")
endOfLine("missing statement terminator after 'return'")
result = newReturnStmt(value, tok)
case self.currentFunction.kind:
of NodeKind.funDecl:
@ -614,7 +612,7 @@ proc yieldStmt(self: Parser): Statement =
result = newYieldStmt(self.expression(), tok)
else:
result = newYieldStmt(newNilExpr(Token(lexeme: "nil")), tok)
endOfLine("missing semicolon after yield statement")
endOfLine("missing statement terminator after 'yield'")
proc awaitStmt(self: Parser): Statement =
@ -625,7 +623,7 @@ proc awaitStmt(self: Parser): Statement =
if self.currentFunction.token.kind != Coroutine:
self.error("'await' can only be used inside coroutines")
result = newAwaitStmt(self.expression(), tok)
endOfLine("missing semicolon after await statement")
endOfLine("missing statement terminator after 'await'")
proc raiseStmt(self: Parser): Statement =
@ -636,7 +634,7 @@ proc raiseStmt(self: Parser): Statement =
# Raise can be used on its own, in which
# case it re-raises the last active exception
exception = self.expression()
endOfLine("missing semicolon after raise statement")
endOfLine("missing statement terminator after 'raise'")
result = newRaiseStmt(exception, tok)
@ -666,7 +664,7 @@ proc importStmt(self: Parser, fromStmt: bool = false): Statement =
# TODO: New AST node
self.expect(Identifier, "expecting module name(s) after import statement")
result = newImportStmt(newIdentExpr(self.peek(-1), self.scopeDepth), tok)
endOfLine("missing semicolon after import statement")
endOfLine("missing statement terminator after 'import'")
@ -932,8 +930,8 @@ proc parseGenerics(self: Parser, decl: Declaration) =
while not self.check(RightBracket) and not self.done():
self.expect(Identifier, "expecting generic type name")
gen.name = newIdentExpr(self.peek(-1), self.scopeDepth)
if self.match(":"):
gen.cond = self.expression()
self.expect(":", "expecting type constraint after generic name")
gen.cond = self.expression()
decl.generics.add(gen)
if not self.match(Comma):
break
@ -1054,7 +1052,7 @@ proc expressionStatement(self: Parser): Statement =
## Parses expression statements, which
## are expressions followed by a semicolon
var expression = self.expression()
endOfLine("missing semicolon after expression")
endOfLine("missing expression terminator")
result = Statement(newExprStmt(expression, expression.token))

View File

@ -380,8 +380,8 @@ proc fillSymbolTable(tokenizer: Lexer) =
tokenizer.symbols.addSymbol("]", RightBracket)
tokenizer.symbols.addSymbol(".", Dot)
tokenizer.symbols.addSymbol(",", Comma)
# tokenizer.symbols.addSymbol(";", Semicolon)
tokenizer.symbols.addSymbol("\n", Semicolon)
tokenizer.symbols.addSymbol(";", Semicolon)
# tokenizer.symbols.addSymbol("\n", Semicolon) # TODO: Broken
# Keywords
tokenizer.symbols.addKeyword("type", TokenType.Type)
tokenizer.symbols.addKeyword("enum", Enum)