Added Compiler.check(), made type constraints mandatory in generics, reverted '\n' being converted to a semicolon, minor refactoring
This commit is contained in:
parent
2072f34d4c
commit
b40275b52f
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue