Fixed a bug with the compiler where it would crash with incomplete assignment expressions

This commit is contained in:
nocturn9x 2021-01-11 08:06:55 +01:00
parent 87fa674b15
commit 410a557ce5
3 changed files with 43 additions and 44 deletions

View File

@ -27,7 +27,6 @@ import types/baseObject
import types/function
import types/numbers
import types/japlString
import types/methods
import tables
import config
import memory
@ -88,7 +87,7 @@ proc makeRule(prefix, infix: ParseFn, precedence: Precedence): ParseRule =
result = ParseRule(prefix: prefix, infix: infix, precedence: precedence)
proc advance(self: var Parser): Token =
proc advance(self: Parser): Token =
## Steps forward by one in the tokens' list and
## increments the current token index
result = self.tokens[self.current]
@ -111,7 +110,7 @@ proc check(self: Parser, kind: TokenType): bool =
return self.peek().kind == kind
proc match(self: var Parser, kind: TokenType): bool =
proc match(self: Parser, kind: TokenType): bool =
## Calls self.check() and consumes a token if the expected
## token type is encountered, in which case true
## is returned. False is returned otherwise
@ -120,7 +119,39 @@ proc match(self: var Parser, kind: TokenType): bool =
return true
proc parseError(self: var Parser, token: Token, message: string) =
proc synchronize(self: Parser) =
## Synchronizes the parser's state. This is useful when
## dealing with parsing errors. When an error occurs, we
## note it with our nice panicMode and hadError fields, but
## that in itself doesn't allow the parser to go forward
## in the code and report other possible errors. On the
## other hand, attempting to start parsing the source
## right after an error has occurred could lead to a
## cascade of unhelpful error messages that complicate
## debugging issues. So, when an error occurs, we try
## to get back into a state that at least allows us to keep
## parsing and pretend the error never happened (the code
## would not be compiled anyway so we might as well tell the
## user if anything else is wrong with their code). The parser
## will skip to the next valid token for a statement, like an
## if or a for loop or a class declaration, and then keep
## parsing from there. Note that hadError is never reset, but
## panicMode is
self.panicMode = false
while self.peek().kind != TokenType.EOF: # Infinite loops are bad, so we must take EOF into account
if self.previous().kind == TokenType.SEMICOLON:
return
case self.peek().kind:
of TokenType.CLASS, TokenType.FUN, TokenType.VAR,
TokenType.FOR, TokenType.IF, TokenType.WHILE,
TokenType.RETURN: # We found a statement boundary, so the parser bails out
return
else:
discard
discard self.advance()
proc parseError(self: Parser, token: Token, message: string) =
## Notifies the user about parsing errors, writing them to
## the standard error file. This parser is designed to report
## all syntatical errors inside a file in one go, rather than
@ -132,9 +163,10 @@ proc parseError(self: var Parser, token: Token, message: string) =
self.panicMode = true
self.hadError = true
stderr.write(&"A fatal error occurred while parsing '{self.file}', line {token.line}, at '{token.lexeme}' -> {message}\n")
self.synchronize()
proc consume(self: var Parser, expected: TokenType, message: string) =
proc consume(self: Parser, expected: TokenType, message: string) =
## Attempts to consume a token if it is of the expected type
## or raises a parsing error with the given message otherwise
if self.check(expected):
@ -212,7 +244,11 @@ proc initCompiler*(context: FunctionType, enclosing: Compiler = nil, parser: Par
proc parsePrecedence(self: Compiler, precedence: Precedence) =
## Parses expressions using pratt's elegant algorithm to precedence parsing
discard self.parser.advance()
if self.parser.peek().kind == TokenType.EOF:
self.parser.parseError(self.parser.peek(), "Expecting expression")
return
else:
discard self.parser.advance()
var prefixRule = getRule(self.parser.previous.kind).prefix
if prefixRule == nil: # If there is no prefix rule than an expression is expected
self.parser.parseError(self.parser.previous, "Expecting expression")
@ -421,38 +457,6 @@ proc grouping(self: Compiler, canAssign: bool) =
self.parser.consume(TokenType.RP, "Expecting ')' after parentheszed expression")
proc synchronize(self: Compiler) =
## Synchronizes the parser's state. This is useful when
## dealing with parsing errors. When an error occurs, we
## note it with our nice panicMode and hadError fields, but
## that in itself doesn't allow the parser to go forward
## in the code and report other possible errors. On the
## other hand, attempting to start parsing the source
## right after an error has occurred could lead to a
## cascade of unhelpful error messages that complicate
## debugging issues. So, when an error occurs, we try
## to get back into a state that at least allows us to keep
## parsing and pretend the error never happened (the code
## would not be compiled anyway so we might as well tell the
## user if anything else is wrong with their code). The parser
## will skip to the next valid token for a statement, like an
## if or a for loop or a class declaration, and then keep
## parsing from there. Note that hadError is never reset, but
## panicMode is
self.parser.panicMode = false
while self.parser.peek().kind != TokenType.EOF: # Infinite loops are bad, so we must take EOF into account
if self.parser.previous().kind == TokenType.SEMICOLON:
return
case self.parser.peek().kind:
of TokenType.CLASS, TokenType.FUN, TokenType.VAR,
TokenType.FOR, TokenType.IF, TokenType.WHILE,
TokenType.RETURN: # We found a statement boundary, so the parser bails out
return
else:
discard
discard self.parser.advance()
proc identifierConstant(self: Compiler, tok: Token): uint8 =
## Emits instructions for identifiers
return self.makeConstant(self.markObject(asStr(tok.lexeme)))
@ -682,9 +686,7 @@ proc endScope(self: Compiler) =
## Ends a scope, popping off any local that
## was created inside it along the way
self.scopeDepth = self.scopeDepth - 1
var start: Natural = self.localCount
while self.localCount > 0 and self.locals[self.localCount - 1].depth > self.scopeDepth:
self.emitByte(OpCode.Pop)
self.localCount = self.localCount - 1
@ -1039,8 +1041,6 @@ proc declaration(self: Compiler) =
self.varDeclaration()
else:
self.statement()
if self.parser.panicMode:
self.synchronize()
proc freeObject(self: Compiler, obj: ptr Obj) =

View File

@ -17,7 +17,6 @@
import vm
import types/native
import types/baseObject
import types/japlNil
import types/numbers
import types/methods
import types/japlString

View File

@ -509,7 +509,7 @@ proc run(self: VM, repl: bool): InterpretResult =
# reasons and works on any pair of objects
var right = self.pop()
var left = self.pop()
self.push(self.getBoolean(addr(left) == addr(right)))
self.push(self.getBoolean(left == right))
of OpCode.GetItem:
# TODO: More generic method
if not self.slice():