Compare commits

...

5 Commits

Author SHA1 Message Date
Nocturn9x 6e2070899b Minor fixes to error reporting 2022-01-31 15:21:46 +01:00
Nocturn9x 8ed3cc7550 Fixed imports (whoops) 2022-01-31 15:18:06 +01:00
Nocturn9x 776a2241f7 Readded frontend 2022-01-31 15:14:26 +01:00
Nocturn9x a545341428 Swapped frontend and backend directories (wrong naming) 2022-01-31 15:14:08 +01:00
Nocturn9x 633c032601 Added Ctrl+E and Ctrl+A bindings 2022-01-31 15:10:53 +01:00
15 changed files with 338 additions and 248 deletions

View File

@ -33,7 +33,7 @@ export multibyte
type
Name = ref object
## A compile-time wrapper around
## A compile-time wrapper around
## statically resolved names.
## Depth indicates to which scope
## the variable belongs, zero meaning
@ -43,12 +43,12 @@ type
depth: int
isPrivate: bool
isConst: bool
Loop = object
## A "loop object" used
## by the compiler to emit
## appropriate jump offsets
## for continue and break
## for continue and break
## statements
start: int
depth: int
@ -68,17 +68,17 @@ type
# Each time a defer statement is
# compiled, its code is emitted
# here. Later, if there is any code
# to defer in the current function,
# to defer in the current function,
# funDecl will wrap the function's code
# inside an implicit try/finally block
# and add this code in the finally branch.
# This sequence is emptied each time a
# This sequence is emptied each time a
# fun declaration is compiled and stores only
# deferred code for the current function (may
# be empty)
deferred: seq[uint8]
proc initCompiler*(enableOptimizations: bool = true): Compiler =
## Initializes a new Compiler object
@ -100,23 +100,21 @@ proc declaration(self: Compiler, node: ASTNode)
proc peek(self: Compiler, distance: int = 0): ASTNode
## End of forward declarations
## Public getters for nicer error formatting
proc getCurrentNode*(self: Compiler): ASTNode = (if self.current >=
self.ast.len(): self.ast[^1] else: self.ast[self.current - 1])
## Utility functions
proc error(self: Compiler, message: string) =
## Raises a formatted CompileError exception
let tok = self.peek().token
raise newException(CompileError, &"A fatal error occurred while compiling '{self.file}', line {tok.line} at '{tok.lexeme}' -> {message}")
proc peek(self: Compiler, distance: int = 0): ASTNode =
## Peeks at the AST node at the given distance.
## If the distance is out of bounds, the last
## AST node in the tree is returned. A negative
## distance may be used to retrieve previously
## consumed AST nodes
if self.ast.high() == -1 or self.current + distance > self.ast.high() or self.current + distance < 0:
if self.ast.high() == -1 or self.current + distance > self.ast.high() or
self.current + distance < 0:
result = self.ast[^1]
else:
result = self.ast[self.current + distance]
@ -128,6 +126,12 @@ proc done(self: Compiler): bool =
result = self.current > self.ast.high()
proc error(self: Compiler, message: string) =
## Raises a formatted CompileError exception
var tok = self.getCurrentNode().token
raise newException(CompileError, &"A fatal error occurred while compiling '{self.file}', line {tok.line} at '{tok.lexeme}' -> {message}")
proc step(self: Compiler): ASTNode =
## Steps to the next node and returns
## the consumed one
@ -222,10 +226,10 @@ proc patchJump(self: Compiler, offset: int) =
of LongJumpIfFalse:
self.chunk.code[offset] = JumpIfFalse.uint8()
of LongJumpIfFalsePop:
self.chunk.code[offset] = JumpIfFalsePop.uint8()
self.chunk.code[offset] = JumpIfFalsePop.uint8()
else:
discard # Unreachable
self.chunk.code.delete(offset + 1) # Discards the 24 bit integer
self.error(&"invalid opcode {self.chunk.code[offset]} in patchJump (This is an internal error and most likely a bug)")
self.chunk.code.delete(offset + 1) # Discards the 24 bit integer
let offsetArray = jump.toDouble()
self.chunk.code[offset + 1] = offsetArray[0]
self.chunk.code[offset + 2] = offsetArray[1]
@ -238,9 +242,9 @@ proc patchJump(self: Compiler, offset: int) =
of JumpIfFalse:
self.chunk.code[offset] = LongJumpIfFalse.uint8()
of JumpIfFalsePop:
self.chunk.code[offset] = LongJumpIfFalsePop.uint8()
self.chunk.code[offset] = LongJumpIfFalsePop.uint8()
else:
discard # Unreachable
self.error(&"invalid opcode {self.chunk.code[offset]} in patchJump (This is an internal error and most likely a bug)")
let offsetArray = jump.toTriple()
self.chunk.code[offset + 1] = offsetArray[0]
self.chunk.code[offset + 2] = offsetArray[1]
@ -288,7 +292,9 @@ proc literal(self: Compiler, node: ASTNode) =
assert parseHex(y.literal.lexeme, x) == len(y.literal.lexeme)
except ValueError:
self.error("integer value out of range")
self.emitConstant(newIntExpr(Token(lexeme: $x, line: y.token.line, pos: (start: y.token.pos.start, stop: y.token.pos.start + len($x)))))
self.emitConstant(newIntExpr(Token(lexeme: $x, line: y.token.line,
pos: (start: y.token.pos.start, stop: y.token.pos.start +
len($x)))))
of binExpr:
var x: int
var y = BinExpr(node)
@ -296,7 +302,9 @@ proc literal(self: Compiler, node: ASTNode) =
assert parseBin(y.literal.lexeme, x) == len(y.literal.lexeme)
except ValueError:
self.error("integer value out of range")
self.emitConstant(newIntExpr(Token(lexeme: $x, line: y.token.line, pos: (start: y.token.pos.start, stop: y.token.pos.start + len($x)))))
self.emitConstant(newIntExpr(Token(lexeme: $x, line: y.token.line,
pos: (start: y.token.pos.start, stop: y.token.pos.start +
len($x)))))
of octExpr:
var x: int
var y = OctExpr(node)
@ -304,7 +312,9 @@ proc literal(self: Compiler, node: ASTNode) =
assert parseOct(y.literal.lexeme, x) == len(y.literal.lexeme)
except ValueError:
self.error("integer value out of range")
self.emitConstant(newIntExpr(Token(lexeme: $x, line: y.token.line, pos: (start: y.token.pos.start, stop: y.token.pos.start + len($x)))))
self.emitConstant(newIntExpr(Token(lexeme: $x, line: y.token.line,
pos: (start: y.token.pos.start, stop: y.token.pos.start +
len($x)))))
of floatExpr:
var x: float
var y = FloatExpr(node)
@ -318,7 +328,7 @@ proc literal(self: Compiler, node: ASTNode) =
for member in y.members:
self.expression(member)
self.emitByte(BuildList)
self.emitBytes(y.members.len().toTriple()) # 24-bit integer, meaning list literals can have up to 2^24 elements
self.emitBytes(y.members.len().toTriple()) # 24-bit integer, meaning list literals can have up to 2^24 elements
of tupleExpr:
var y = TupleExpr(node)
for member in y.members:
@ -337,7 +347,7 @@ proc literal(self: Compiler, node: ASTNode) =
self.expression(key)
self.expression(value)
self.emitByte(BuildDict)
self.emitBytes(y.keys.len().toTriple())
self.emitBytes(y.keys.len().toTriple())
of awaitExpr:
var y = AwaitExpr(node)
self.expression(y.awaitee)
@ -349,12 +359,12 @@ proc literal(self: Compiler, node: ASTNode) =
proc unary(self: Compiler, node: UnaryExpr) =
## Compiles unary expressions such as negation or
## bitwise inversion
self.expression(node.a) # Pushes the operand onto the stack
self.expression(node.a) # Pushes the operand onto the stack
case node.operator.kind:
of Minus:
self.emitByte(UnaryNegate)
of Plus:
discard # Unary + does nothing
discard # Unary + does nothing
of TokenType.LogicalNot:
self.emitByte(OpCode.LogicalNot)
of Tilde:
@ -446,18 +456,21 @@ proc declareName(self: Compiler, node: ASTNode) =
# slap myself 100 times with a sign saying "I'm dumb". Mark my words
self.error("cannot declare more than 16777215 static variables at a time")
self.names.add(Name(depth: self.scopeDepth, name: IdentExpr(node.name),
isPrivate: node.isPrivate, owner: node.owner, isConst: node.isConst))
isPrivate: node.isPrivate,
owner: node.owner,
isConst: node.isConst))
else:
discard # TODO: Classes, functions
discard # TODO: Classes, functions
proc varDecl(self: Compiler, node: VarDecl) =
proc varDecl(self: Compiler, node: VarDecl) =
## Compiles variable declarations
self.expression(node.value)
self.declareName(node)
proc resolveStatic(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth): Name =
proc resolveStatic(self: Compiler, name: IdentExpr,
depth: int = self.scopeDepth): Name =
## Traverses self.staticNames backwards and returns the
## first name object with the given name at the given
## depth. The default depth is the current one. Returns
@ -468,7 +481,8 @@ proc resolveStatic(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth
return nil
proc deleteStatic(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth) =
proc deleteStatic(self: Compiler, name: IdentExpr,
depth: int = self.scopeDepth) =
## Traverses self.staticNames backwards and returns the
## deletes name object with the given name at the given
## depth. The default depth is the current one. Does
@ -502,10 +516,10 @@ proc identifier(self: Compiler, node: IdentExpr) =
else:
let index = self.getStaticIndex(node)
if index != -1:
self.emitByte(LoadFast) # Static name resolution, loads value at index in the stack
self.emitByte(LoadFast) # Static name resolution, loads value at index in the stack
self.emitBytes(index.toTriple())
else:
self.emitByte(LoadName) # Resolves by name, at runtime, in a global hashmap
self.emitByte(LoadName) # Resolves by name, at runtime, in a global hashmap
self.emitBytes(self.identifierConstant(node))
@ -545,7 +559,7 @@ proc assignment(self: Compiler, node: ASTNode) =
of InplaceLeftShift:
self.emitByte(BinaryShiftLeft)
else:
discard # Unreachable
discard # Unreachable
# In-place operators just change
# what values is set to a given
# stack offset/name, so we only
@ -578,7 +592,7 @@ proc beginScope(self: Compiler) =
inc(self.scopeDepth)
proc endScope(self: Compiler) =
proc endScope(self: Compiler) =
## Ends the current local scope
if self.scopeDepth < 0:
self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)")
@ -648,8 +662,9 @@ proc emitLoop(self: Compiler, begin: int) =
## Emits a JumpBackwards instruction with the correct
## jump offset
var offset: int
case OpCode(self.chunk.code[begin + 1]): # The jump instruction
of LongJumpForwards, LongJumpBackwards, LongJumpIfFalse, LongJumpIfFalsePop, LongJumpIfTrue:
case OpCode(self.chunk.code[begin + 1]): # The jump instruction
of LongJumpForwards, LongJumpBackwards, LongJumpIfFalse,
LongJumpIfFalsePop, LongJumpIfTrue:
offset = self.chunk.code.len() - begin + 4
else:
offset = self.chunk.code.len() - begin
@ -661,7 +676,7 @@ proc emitLoop(self: Compiler, begin: int) =
else:
self.emitByte(JumpBackwards)
self.emitBytes(offset.toDouble())
proc whileStmt(self: Compiler, node: WhileStmt) =
## Compiles C-style while loops
@ -671,7 +686,7 @@ proc whileStmt(self: Compiler, node: WhileStmt) =
self.statement(node.body)
self.patchJump(jump)
self.emitLoop(start)
proc expression(self: Compiler, node: ASTNode) =
## Compiles all expressions
@ -695,7 +710,8 @@ proc expression(self: Compiler, node: ASTNode) =
of binaryExpr:
# Binary expressions such as 2 ^ 5 and 0.66 * 3.14
self.binary(BinaryExpr(node))
of intExpr, hexExpr, binExpr, octExpr, strExpr, falseExpr, trueExpr, infExpr, nanExpr, floatExpr, nilExpr,
of intExpr, hexExpr, binExpr, octExpr, strExpr, falseExpr, trueExpr,
infExpr, nanExpr, floatExpr, nilExpr,
tupleExpr, setExpr, listExpr, dictExpr:
# Since all of these AST nodes mostly share
# the same overall structure, and the kind
@ -711,17 +727,6 @@ proc delStmt(self: Compiler, node: ASTNode) =
## Compiles del statements, which unbind
## a name from the current scope
case node.kind:
of intExpr, hexExpr, binExpr, octExpr, strExpr, falseExpr, trueExpr, infExpr, nanExpr, floatExpr, nilExpr,
tupleExpr, setExpr, listExpr, dictExpr, groupingExpr:
# We disallow grouping expressions because the parser
# already gets rid of redundant parentheses, so if
# there is a grouping expression left it can't be
# deleted because it's not just a bare identifier
self.error("cannot delete literals")
of binaryExpr, unaryExpr:
self.error("cannot delete operator")
of setItemExpr, assignExpr:
self.error("cannot delete assignment")
of identExpr:
var node = IdentExpr(node)
let i = self.getStaticIndex(node)
@ -733,7 +738,7 @@ proc delStmt(self: Compiler, node: ASTNode) =
self.emitByte(DeleteName)
self.emitBytes(self.identifierConstant(node))
else:
discard # Unreachable
discard # The parser already handles the other cases
proc awaitStmt(self: Compiler, node: AwaitStmt) =
@ -783,7 +788,7 @@ proc raiseStmt(self: Compiler, node: RaiseStmt) =
proc continueStmt(self: Compiler, node: ContinueStmt) =
## Compiles continue statements. A continue statements
## jumps to the next iteration in a loop
if self.currentLoop.start > 65535:
if self.currentLoop.start <= 65535:
self.emitByte(Jump)
self.emitBytes(self.currentLoop.start.toDouble())
else:
@ -792,15 +797,15 @@ proc continueStmt(self: Compiler, node: ContinueStmt) =
proc breakStmt(self: Compiler, node: BreakStmt) =
## Compiles break statements. A continue statements
## Compiles break statements. A continue statement
## jumps to the next iteration in a loop
# Emits dummy jump offset, this is
# patched later!
# patched later
discard self.emitJump(OpCode.Break)
self.currentLoop.breakPos.add(self.chunk.code.high() - 4) # 3 is the size of the jump offset!
self.currentLoop.breakPos.add(self.chunk.code.high() - 4)
if self.currentLoop.depth > self.scopeDepth:
# Breaking out of a loop closes its scope!
# Breaking out of a loop closes its scope
self.endScope()
@ -827,7 +832,7 @@ proc statement(self: Compiler, node: ASTNode) =
case node.kind:
of exprStmt:
self.expression(ExprStmt(node).expression)
self.emitByte(Pop) # Expression statements discard their value. Their main use case is side effects in function calls
self.emitByte(Pop) # Expression statements discard their value. Their main use case is side effects in function calls
of NodeKind.ifStmt:
self.ifStmt(IfStmt(node))
of NodeKind.delStmt:
@ -850,7 +855,8 @@ proc statement(self: Compiler, node: ASTNode) =
## Our parser already desugars for loops to
## while loops!
let loop = self.currentLoop
self.currentLoop = Loop(start: self.chunk.code.len(), depth: self.scopeDepth, breakPos: @[])
self.currentLoop = Loop(start: self.chunk.code.len(),
depth: self.scopeDepth, breakPos: @[])
self.whileStmt(WhileStmt(node))
self.patchBreaks()
self.currentLoop = loop
@ -876,7 +882,7 @@ proc declaration(self: Compiler, node: ASTNode) =
of NodeKind.varDecl:
self.varDecl(VarDecl(node))
of funDecl, classDecl:
discard # TODO
discard # TODO
else:
self.statement(node)
@ -896,7 +902,7 @@ proc compile*(self: Compiler, ast: seq[ASTNode], file: string): Chunk =
if self.ast.len() > 0:
# *Technically* an empty program is a valid program
self.endScope()
self.emitByte(OpCode.Return) # Exits the VM's main loop when used at the global scope
self.emitByte(OpCode.Return) # Exits the VM's main loop when used at the global scope
result = self.chunk
if self.scopeDepth != -1:
if self.ast.len() > 0 and self.scopeDepth != -1:
self.error(&"internal error: invalid scopeDepth state (expected -1, got {self.scopeDepth}), did you forget to call endScope/beginScope?")

View File

@ -33,7 +33,7 @@ export errors
const tokens = to_table({
'(': LeftParen, ')': RightParen,
'{': LeftBrace, '}': RightBrace,
'.': Dot, ',': Comma, '-': Minus,
'.': Dot, ',': Comma, '-': Minus,
'+': Plus, '*': Asterisk,
'>': GreaterThan, '<': LessThan, '=': Equal,
'~': Tilde, '/': Slash, '%': Percentage,
@ -103,6 +103,16 @@ type
start: int
current: int
file: string
lines: seq[tuple[start, stop: int]]
lastLine: int
# Simple public getters
proc getStart*(self: Lexer): int = self.start
proc getCurrent*(self: Lexer): int = self.current
proc getLine*(self: Lexer): int = self.line
proc getSource*(self: Lexer): string = self.source
proc getRelPos*(self: Lexer, line: int): tuple[start, stop: int] = (if line > 1: self.lines[line - 2] else: (start: 0, stop: self.current))
proc initLexer*(self: Lexer = nil): Lexer =
@ -117,6 +127,8 @@ proc initLexer*(self: Lexer = nil): Lexer =
result.start = 0
result.current = 0
result.file = ""
result.lines = @[]
result.lastLine = 0
proc done(self: Lexer): bool =
@ -124,6 +136,15 @@ proc done(self: Lexer): bool =
result = self.current >= self.source.len
proc incLine(self: Lexer) =
## Increments the lexer's line
## and updates internal line
## metadata
self.lines.add((start: self.lastLine, stop: self.current))
self.line += 1
self.lastLine = self.current
proc step(self: Lexer, n: int = 1): char =
## Steps n characters forward in the
## source file (default = 1). A null
@ -156,6 +177,7 @@ proc peek(self: Lexer, distance: int = 0): char =
proc error(self: Lexer, message: string) =
## Raises a lexing error with a formatted
## error message
raise newException(LexingError, &"A fatal error occurred while parsing '{self.file}', line {self.line} at '{self.peek()}' -> {message}")
@ -230,7 +252,7 @@ proc createToken(self: Lexer, tokenType: TokenType) =
tok.kind = tokenType
tok.lexeme = self.source[self.start..<self.current]
tok.line = self.line
tok.pos = (start: self.start, stop: self.current)
tok.pos = (start: self.start, stop: self.current)
self.tokens.add(tok)
@ -255,13 +277,12 @@ proc parseEscape(self: Lexer) =
# We natively convert LF to CRLF on Windows, and
# gotta thank Microsoft for the extra boilerplate!
self.source[self.current] = cast[char](0x0D)
self.source &= cast[char](0X0A)
else:
when defined(darwin):
# Thanks apple, lol
self.source[self.current] = cast[char](0x0A)
else:
self.source[self.current] = cast[char](0X0D)
self.source.insert(self.current + 1, 0X0A)
when defined(darwin):
# Thanks apple, lol
self.source[self.current] = cast[char](0x0A)
when defined(linux):
self.source[self.current] = cast[char](0X0D)
of 'r':
self.source[self.current] = cast[char](0x0D)
of 't':
@ -278,23 +299,27 @@ proc parseEscape(self: Lexer) =
var code = ""
var value = 0
var i = self.current
while i < self.source.high() and (let c = self.source[i].toLowerAscii(); c in '0'..'7') and len(code) < 3:
while i < self.source.high() and (let c = self.source[
i].toLowerAscii(); c in '0'..'7') and len(code) < 3:
code &= self.source[i]
i += 1
assert parseOct(code, value) == code.len()
if value > uint8.high().int:
self.error("escape sequence value too large (> 255)")
self.source[self.current] = cast[char](value)
of 'u':
self.error("unicode escape sequences are not supported (yet)")
of 'U':
of 'u', 'U':
self.error("unicode escape sequences are not supported (yet)")
of 'x':
var code = ""
var value = 0
var i = self.current
while i < self.source.high() and (let c = self.source[i].toLowerAscii(); c in 'a'..'f' or c in '0'..'9'):
while i < self.source.high() and (let c = self.source[
i].toLowerAscii(); c in 'a'..'f' or c in '0'..'9'):
code &= self.source[i]
i += 1
assert parseHex(code, value) == code.len()
if value > uint8.high().int:
self.error("escape sequence value too large (> 255)")
self.source[self.current] = cast[char](value)
else:
self.error(&"invalid escape sequence '\\{self.peek()}'")
@ -317,25 +342,27 @@ proc parseString(self: Lexer, delimiter: char, mode: string = "single") =
## either single or double quotes. They can span across multiple
## lines and escape sequences in them are not parsed, like in raw
## strings, so a multi-line string prefixed with the "r" modifier
## is redundant, although multi-line byte strings are supported
## is redundant, although multi-line byte/format strings are supported
while not self.check(delimiter) and not self.done():
if self.check('\n'):
if mode == "multi":
self.line = self.line + 1
self.incLine()
else:
self.error("unexpected EOL while parsing string literal")
if mode in ["raw", "multi"]:
discard self.step()
if self.check('\\'):
# This madness here serves to get rid of the slash, since \x is mapped
# to a one-byte sequence but the string '\x' actually 2 bytes (or more,
# to a one-byte sequence but the string '\x' actually 2 bytes (or more,
# depending on the specific escape sequence)
self.source = self.source[0..<self.current] & self.source[self.current + 1..^1]
self.source = self.source[0..<self.current] & self.source[
self.current + 1..^1]
self.parseEscape()
if mode == "format" and self.check('{'):
discard self.step()
if self.check('{'):
self.source = self.source[0..<self.current] & self.source[self.current + 1..^1]
self.source = self.source[0..<self.current] & self.source[
self.current + 1..^1]
continue
while not self.check(['}', '"']):
discard self.step()
@ -345,14 +372,15 @@ proc parseString(self: Lexer, delimiter: char, mode: string = "single") =
if not self.check('}', 1):
self.error("unmatched '}' in format string")
else:
self.source = self.source[0..<self.current] & self.source[self.current + 1..^1]
self.source = self.source[0..<self.current] & self.source[
self.current + 1..^1]
discard self.step()
if self.done():
self.error("unexpected EOF while parsing string literal")
return
if mode == "multi":
if not self.match(delimiter.repeat(3)):
self.error("unexpected EOL while parsing multi-line string literal")
if self.done():
self.error("unexpected EOF while parsing string literal")
return
else:
discard self.step()
self.createToken(String)
@ -369,6 +397,7 @@ proc parseBinary(self: Lexer) =
while (self.tokens[^1].lexeme.len() - 2) mod 8 != 0:
self.tokens[^1].lexeme = "0b" & "0" & self.tokens[^1].lexeme[2..^1]
proc parseOctal(self: Lexer) =
## Parses octal numbers
while self.peek().isDigit():
@ -399,7 +428,7 @@ proc parseNumber(self: Lexer) =
## is case-insensitive. Binary number literals are
## expressed using the prefix 0b, hexadecimal
## numbers with the prefix 0x and octal numbers
## with the prefix 0o
## with the prefix 0o
case self.peek():
of 'b':
discard self.step()
@ -460,7 +489,7 @@ proc next(self: Lexer) =
'\e']: # We skip whitespaces, tabs and other useless characters
return
elif single == '\n':
self.line += 1
self.incLine()
elif single in ['"', '\'']:
if self.check(single) and self.check(single, 1):
# Multiline strings start with 3 quotes
@ -494,7 +523,7 @@ proc next(self: Lexer) =
# to the pair of tokens (//, =) for example
for key in triple.keys():
if key[0] == single and self.check(key[1..^1]):
discard self.step(2) # We step 2 characters
discard self.step(2) # We step 2 characters
self.createToken(triple[key])
return
for key in double.keys():
@ -511,10 +540,7 @@ proc next(self: Lexer) =
proc lex*(self: Lexer, source, file: string): seq[Token] =
## Lexes a source file, converting a stream
## of characters into a series of tokens.
## If an error occurs, this procedure
## returns an empty sequence and the lexer's
## errored and errorMessage fields will be set
## of characters into a series of tokens
discard self.initLexer()
self.source = source
self.file = file

View File

@ -34,7 +34,7 @@ type
funDecl,
varDecl,
# Statements
forStmt, # Unused for now (for loops are compiled to while loops)
forStmt, # Unused for now (for loops are compiled to while loops)
ifStmt,
returnStmt,
breakStmt,
@ -58,14 +58,14 @@ type
lambdaExpr,
awaitExpr,
yieldExpr,
setItemExpr, # Set expressions like a.b = "c"
setItemExpr, # Set expressions like a.b = "c"
binaryExpr,
unaryExpr,
sliceExpr,
callExpr,
getItemExpr, # Get expressions like a.b
# Primary expressions
groupingExpr, # Parenthesized expressions such as (true) and (3 + 4)
getItemExpr, # Get expressions like a.b
# Primary expressions
groupingExpr, # Parenthesized expressions such as (true) and (3 + 4)
trueExpr,
listExpr,
tupleExpr,
@ -81,7 +81,7 @@ type
nilExpr,
nanExpr,
infExpr,
identExpr, # Identifier
identExpr, # Identifier
ASTNode* = ref object of RootObj
@ -129,7 +129,7 @@ type
# a tough luck for us
ListExpr* = ref object of ASTNode
members*: seq[ASTNode]
SetExpr* = ref object of ListExpr
TupleExpr* = ref object of ListExpr
@ -137,17 +137,17 @@ type
DictExpr* = ref object of ASTNode
keys*: seq[ASTNode]
values*: seq[ASTNode]
IdentExpr* = ref object of ASTNode
name*: Token
GroupingExpr* = ref object of ASTNode
expression*: ASTNode
GetItemExpr* = ref object of ASTNode
obj*: ASTNode
name*: ASTNode
SetItemExpr* = ref object of GetItemExpr
# Since a setItem expression is just
# a getItem one followed by an assignment,
@ -155,8 +155,9 @@ type
value*: ASTNode
CallExpr* = ref object of ASTNode
callee*: ASTNode # The thing being called
arguments*: tuple[positionals: seq[ASTNode], keyword: seq[tuple[name: ASTNode, value: ASTNode]]]
callee*: ASTNode # The thing being called
arguments*: tuple[positionals: seq[ASTNode], keyword: seq[tuple[
name: ASTNode, value: ASTNode]]]
UnaryExpr* = ref object of ASTNode
operator*: Token
@ -216,8 +217,8 @@ type
code*: seq[ASTNode]
ForStmt* = ref object of ASTNode
discard # Unused
discard # Unused
ForEachStmt* = ref object of ASTNode
identifier*: ASTNode
expression*: ASTNode
@ -225,7 +226,7 @@ type
DeferStmt* = ref object of ASTNode
deferred*: ASTNode
TryStmt* = ref object of ASTNode
body*: ASTNode
handlers*: seq[tuple[body: ASTNode, exc: ASTNode, name: ASTNode]]
@ -235,12 +236,12 @@ type
WhileStmt* = ref object of ASTNode
condition*: ASTNode
body*: ASTNode
AwaitStmt* = ref object of ASTNode
awaitee*: ASTNode
BreakStmt* = ref object of ASTNode
ContinueStmt* = ref object of ASTNode
ReturnStmt* = ref object of ASTNode
@ -253,9 +254,9 @@ type
YieldStmt* = ref object of ASTNode
expression*: ASTNode
Declaration* = ref object of ASTNode
owner*: string # Used for determining if a module can access a given field
owner*: string # Used for determining if a module can access a given field
VarDecl* = ref object of Declaration
name*: ASTNode
@ -287,7 +288,8 @@ type
isPrivate*: bool
Expression* = LiteralExpr | ListExpr | GetItemExpr | SetItemExpr | UnaryExpr | BinaryExpr | CallExpr | AssignExpr |
GroupingExpr | IdentExpr | DictExpr | TupleExpr | SetExpr | TrueExpr | FalseExpr | NilExpr |
GroupingExpr | IdentExpr | DictExpr | TupleExpr | SetExpr |
TrueExpr | FalseExpr | NilExpr |
NanExpr | InfExpr
Statement* = ExprStmt | ImportStmt | FromImportStmt | DelStmt | AssertStmt | RaiseStmt | BlockStmt | ForStmt | WhileStmt |
@ -304,11 +306,14 @@ proc newASTNode*(kind: NodeKind, token: Token): ASTNode =
proc isConst*(self: ASTNode): bool {.inline.} = self.kind in {intExpr, hexExpr, binExpr, octExpr, strExpr,
falseExpr, trueExpr, infExpr, nanExpr,
floatExpr}
falseExpr,
trueExpr, infExpr,
nanExpr,
floatExpr, nilExpr}
proc isLiteral*(self: ASTNode): bool {.inline.} = self.isConst() or self.kind in {tupleExpr, dictExpr, setExpr, listExpr}
proc isLiteral*(self: ASTNode): bool {.inline.} = self.isConst() or self.kind in
{tupleExpr, dictExpr, setExpr, listExpr}
proc newIntExpr*(literal: Token): IntExpr =
@ -366,7 +371,8 @@ proc newGroupingExpr*(expression: ASTNode, token: Token): GroupingExpr =
result.token = token
proc newLambdaExpr*(arguments, defaults: seq[ASTNode], body: ASTNode, isGenerator: bool, token: Token): LambdaExpr =
proc newLambdaExpr*(arguments, defaults: seq[ASTNode], body: ASTNode,
isGenerator: bool, token: Token): LambdaExpr =
result = LambdaExpr(kind: lambdaExpr)
result.body = body
result.arguments = arguments
@ -415,14 +421,17 @@ proc newSetItemExpr*(obj, name, value: ASTNode, token: Token): SetItemExpr =
result.token = token
proc newCallExpr*(callee: ASTNode, arguments: tuple[positionals: seq[ASTNode], keyword: seq[tuple[name: ASTNode, value: ASTNode]]], token: Token): CallExpr =
proc newCallExpr*(callee: ASTNode, arguments: tuple[positionals: seq[ASTNode],
keyword: seq[tuple[name: ASTNode, value: ASTNode]]],
token: Token): CallExpr =
result = CallExpr(kind: callExpr)
result.callee = callee
result.arguments = arguments
result.token = token
proc newSliceExpr*(slicee: ASTNode, ends: seq[ASTNode], token: Token): SliceExpr =
proc newSliceExpr*(slicee: ASTNode, ends: seq[ASTNode],
token: Token): SliceExpr =
result = SliceExpr(kind: sliceExpr)
result.slicee = slicee
result.ends = ends
@ -475,7 +484,8 @@ proc newImportStmt*(moduleName: ASTNode, token: Token): ImportStmt =
result.token = token
proc newFromImportStmt*(fromModule: ASTNode, fromAttributes: seq[ASTNode], token: Token): FromImportStmt =
proc newFromImportStmt*(fromModule: ASTNode, fromAttributes: seq[ASTNode],
token: Token): FromImportStmt =
result = FromImportStmt(kind: fromImportStmt)
result.fromModule = fromModule
result.fromAttributes = fromAttributes
@ -542,7 +552,8 @@ proc newWhileStmt*(condition: ASTNode, body: ASTNode, token: Token): WhileStmt =
result.token = token
proc newForEachStmt*(identifier: ASTNode, expression, body: ASTNode, token: Token): ForEachStmt =
proc newForEachStmt*(identifier: ASTNode, expression, body: ASTNode,
token: Token): ForEachStmt =
result = ForEachStmt(kind: forEachStmt)
result.identifier = identifier
result.expression = expression
@ -550,7 +561,7 @@ proc newForEachStmt*(identifier: ASTNode, expression, body: ASTNode, token: Toke
result.token = token
proc newBreakStmt*(token: Token): BreakStmt =
proc newBreakStmt*(token: Token): BreakStmt =
result = BreakStmt(kind: breakStmt)
result.token = token
@ -566,7 +577,8 @@ proc newReturnStmt*(value: ASTNode, token: Token): ReturnStmt =
result.token = token
proc newIfStmt*(condition: ASTNode, thenBranch, elseBranch: ASTNode, token: Token): IfStmt =
proc newIfStmt*(condition: ASTNode, thenBranch, elseBranch: ASTNode,
token: Token): IfStmt =
result = IfStmt(kind: ifStmt)
result.condition = condition
result.thenBranch = thenBranch
@ -589,7 +601,8 @@ proc newVarDecl*(name: ASTNode, value: ASTNode = newNilExpr(Token()),
proc newFunDecl*(name: ASTNode, arguments, defaults: seq[ASTNode],
body: ASTNode, isStatic: bool = true, isAsync,
isGenerator: bool, isPrivate: bool = true, token: Token, owner: string): FunDecl =
isGenerator: bool, isPrivate: bool = true, token: Token,
owner: string): FunDecl =
result = FunDecl(kind: funDecl)
result.name = name
result.arguments = arguments
@ -605,7 +618,8 @@ proc newFunDecl*(name: ASTNode, arguments, defaults: seq[ASTNode],
proc newClassDecl*(name: ASTNode, body: ASTNode,
parents: seq[ASTNode], isStatic: bool = true,
isPrivate: bool = true, token: Token, owner: string): ClassDecl =
isPrivate: bool = true, token: Token,
owner: string): ClassDecl =
result = ClassDecl(kind: classDecl)
result.name = name
result.body = body
@ -616,15 +630,16 @@ proc newClassDecl*(name: ASTNode, body: ASTNode,
result.owner = owner
proc `$`*(self: ASTNode): string =
proc `$`*(self: ASTNode): string =
if self == nil:
return "nil"
case self.kind:
of intExpr, floatExpr, hexExpr, binExpr, octExpr, strExpr, trueExpr, falseExpr, nanExpr, nilExpr, infExpr:
of intExpr, floatExpr, hexExpr, binExpr, octExpr, strExpr, trueExpr,
falseExpr, nanExpr, nilExpr, infExpr:
if self.kind in {trueExpr, falseExpr, nanExpr, nilExpr, infExpr}:
result &= &"Literal({($self.kind)[0..^5]})"
elif self.kind == strExpr:
result &= &"Literal({LiteralExpr(self).literal.lexeme})"
result &= &"Literal({LiteralExpr(self).literal.lexeme[1..^2].escape()})"
else:
result &= &"Literal({LiteralExpr(self).literal.lexeme})"
of identExpr:
@ -742,4 +757,4 @@ proc `$`*(self: ASTNode): string =
result &= ", elseClause=nil"
result &= ")"
else:
discard
discard

View File

@ -35,7 +35,7 @@ type
## - The second integer represents the count of whatever comes after it
## (let's call it c)
## - After c, a sequence of c integers follows
##
##
## A visual representation may be easier to understand: [1, 2, 3, 4]
## This is to be interpreted as "there are 2 instructions at line 1 whose values
## are 3 and 4"
@ -56,37 +56,37 @@ type
# used for more complex opcodes. All
# arguments to opcodes (if they take
# arguments) come from popping off the
# stack
LoadConstant = 0u8, # Pushes constant at position x in the constant table onto the stack
# Binary operators
UnaryNegate, # Pushes the result of -x onto the stack
BinaryAdd, # Pushes the result of a + b onto the stack
BinarySubtract, # Pushes the result of a - b onto the stack
BinaryDivide, # Pushes the result of a / b onto the stack (true division). The result is a float
BinaryFloorDiv, # Pushes the result of a // b onto the stack (integer division). The result is always an integer
BinaryMultiply, # Pushes the result of a * b onto the stack
BinaryPow, # Pushes the result of a ** b (a to the power of b) onto the stack
BinaryMod, # Pushes the result of a % b onto the stack (modulo division)
BinaryShiftRight, # Pushes the result of a >> b (a with bits shifted b times to the right) onto the stack
BinaryShiftLeft, # Pushes the result of a << b (a with bits shifted b times to the left) onto the stack
BinaryXor, # Pushes the result of a ^ b (bitwise exclusive or) onto the stack
BinaryOr, # Pushes the result of a | b (bitwise or) onto the stack
BinaryAnd, # Pushes the result of a & b (bitwise and) onto the stack
UnaryNot, # Pushes the result of ~x (bitwise not) onto the stack
BinaryAs, # Pushes the result of a as b onto the stack (converts a to the type of b. Explicit support from a is required)
BinaryIs, # Pushes the result of a is b onto the stack (true if a and b point to the same object, false otherwise)
BinaryIsNot, # Pushes the result of not (a is b). This could be implemented in terms of BinaryIs, but it's more efficient this way
BinaryOf, # Pushes the result of a of b onto the stack (true if a is a subclass of b, false otherwise)
# stack
LoadConstant = 0u8, # Pushes constant at position x in the constant table onto the stack
# Binary operators
UnaryNegate, # Pushes the result of -x onto the stack
BinaryAdd, # Pushes the result of a + b onto the stack
BinarySubtract, # Pushes the result of a - b onto the stack
BinaryDivide, # Pushes the result of a / b onto the stack (true division). The result is a float
BinaryFloorDiv, # Pushes the result of a // b onto the stack (integer division). The result is always an integer
BinaryMultiply, # Pushes the result of a * b onto the stack
BinaryPow, # Pushes the result of a ** b (a to the power of b) onto the stack
BinaryMod, # Pushes the result of a % b onto the stack (modulo division)
BinaryShiftRight, # Pushes the result of a >> b (a with bits shifted b times to the right) onto the stack
BinaryShiftLeft, # Pushes the result of a << b (a with bits shifted b times to the left) onto the stack
BinaryXor, # Pushes the result of a ^ b (bitwise exclusive or) onto the stack
BinaryOr, # Pushes the result of a | b (bitwise or) onto the stack
BinaryAnd, # Pushes the result of a & b (bitwise and) onto the stack
UnaryNot, # Pushes the result of ~x (bitwise not) onto the stack
BinaryAs, # Pushes the result of a as b onto the stack (converts a to the type of b. Explicit support from a is required)
BinaryIs, # Pushes the result of a is b onto the stack (true if a and b point to the same object, false otherwise)
BinaryIsNot, # Pushes the result of not (a is b). This could be implemented in terms of BinaryIs, but it's more efficient this way
BinaryOf, # Pushes the result of a of b onto the stack (true if a is a subclass of b, false otherwise)
BinarySlice, # Perform slicing on supported objects (like "hello"[0:2], which yields "he"). The result is pushed onto the stack
BinarySubscript, # Subscript operator, like "hello"[0] (which pushes 'h' onto the stack)
# Binary comparison operators
GreaterThan, # Pushes the result of a > b onto the stack
LessThan, # Pushes the result of a < b onto the stack
EqualTo, # Pushes the result of a == b onto the stack
NotEqualTo, # Pushes the result of a != b onto the stack (optimization for not (a == b))
GreaterOrEqual, # Pushes the result of a >= b onto the stack
LessOrEqual, # Pushes the result of a <= b onto the stack
# Logical operators
BinarySubscript, # Subscript operator, like "hello"[0] (which pushes 'h' onto the stack)
# Binary comparison operators
GreaterThan, # Pushes the result of a > b onto the stack
LessThan, # Pushes the result of a < b onto the stack
EqualTo, # Pushes the result of a == b onto the stack
NotEqualTo, # Pushes the result of a != b onto the stack (optimization for not (a == b))
GreaterOrEqual, # Pushes the result of a >= b onto the stack
LessOrEqual, # Pushes the result of a <= b onto the stack
# Logical operators
LogicalNot,
LogicalAnd,
LogicalOr,
@ -99,24 +99,24 @@ type
# Basic stack operations
Pop,
Push,
PopN, # Pops N elements off the stack (optimization for exiting scopes and returning from functions)
# Name resolution/handling
LoadAttribute,
DeclareName, # Declares a global dynamically bound name in the current scope
LoadName, # Loads a dynamically bound variable
LoadFast, # Loads a statically bound variable
StoreName, # Sets/updates a dynamically bound variable's value
PopN, # Pops N elements off the stack (optimization for exiting scopes and returning from functions)
# Name resolution/handling
LoadAttribute,
DeclareName, # Declares a global dynamically bound name in the current scope
LoadName, # Loads a dynamically bound variable
LoadFast, # Loads a statically bound variable
StoreName, # Sets/updates a dynamically bound variable's value
StoreFast, # Sets/updates a statically bound variable's value
DeleteName, # Unbinds a dynamically bound variable's name from the current scope
DeleteFast, # Unbinds a statically bound variable's name from the current scope
# Looping and jumping
Jump, # Absolute and unconditional jump into the bytecode
JumpIfFalse, # Jumps to an absolute index in the bytecode if the value at the top of the stack is falsey
JumpIfTrue, # Jumps to an absolute index in the bytecode if the value at the top of the stack is truthy
JumpIfFalsePop, # Like JumpIfFalse, but it also pops off the stack (regardless of truthyness). Optimization for if statements
JumpForwards, # Relative, unconditional, positive jump in the bytecode
JumpBackwards, # Relative, unconditional, negative jump into the bytecode
Break, # Temporary opcode used to signal exiting out of loop
DeleteName, # Unbinds a dynamically bound variable's name from the current scope
DeleteFast, # Unbinds a statically bound variable's name from the current scope
# Looping and jumping
Jump, # Absolute and unconditional jump into the bytecode
JumpIfFalse, # Jumps to an absolute index in the bytecode if the value at the top of the stack is falsey
JumpIfTrue, # Jumps to an absolute index in the bytecode if the value at the top of the stack is truthy
JumpIfFalsePop, # Like JumpIfFalse, but it also pops off the stack (regardless of truthyness). Optimization for if statements
JumpForwards, # Relative, unconditional, positive jump in the bytecode
JumpBackwards, # Relative, unconditional, negative jump into the bytecode
Break, # Temporary opcode used to signal exiting out of loop
## Long variants of jumps (they use a 24-bit operand instead of a 16-bit one)
LongJump,
LongJumpIfFalse,
@ -130,9 +130,9 @@ type
Return
# Exception handling
Raise,
ReRaise, # Re-raises active exception
ReRaise, # Re-raises active exception
BeginTry,
FinishTry,
FinishTry,
# Generators
Yield,
# Coroutines
@ -179,8 +179,9 @@ const stackDoubleInstructions* = {}
const argumentDoubleInstructions* = {PopN, }
# Jump instructions jump at relative or absolute bytecode offsets
const jumpInstructions* = {JumpIfFalse, JumpIfFalsePop, JumpForwards, JumpBackwards,
LongJumpIfFalse, LongJumpIfFalsePop, LongJumpForwards,
const jumpInstructions* = {JumpIfFalse, JumpIfFalsePop, JumpForwards, JumpBackwards,
LongJumpIfFalse, LongJumpIfFalsePop,
LongJumpForwards,
LongJumpBackwards, JumpIfTrue, LongJumpIfTrue}
# Collection instructions push a built-in collection type onto the stack
@ -228,7 +229,7 @@ proc write*(self: Chunk, bytes: openarray[OpCode], line: int) =
proc getLine*(self: Chunk, idx: int): int =
## Returns the associated line of a given
## Returns the associated line of a given
## instruction index
if self.lines.len < 2:
raise newException(IndexDefect, "the chunk object is empty")
@ -265,7 +266,7 @@ proc findOrAddConstant(self: Chunk, constant: ASTNode): int =
var c = IdentExpr(c)
var constant = IdentExpr(constant)
if c.name.lexeme == constant.name.lexeme:
return i
return i
else:
continue
self.consts.add(constant)
@ -282,4 +283,4 @@ proc addConstant*(self: Chunk, constant: ASTNode): array[3, uint8] =
# to our stack by the way). Not that anyone's ever gonna hit this
# limit in the real world, but you know, just in case
raise newException(CompileError, "cannot encode more than 16777215 constants")
result = self.findOrAddConstant(constant).toTriple()
result = self.findOrAddConstant(constant).toTriple()

View File

@ -33,6 +33,7 @@ type
Parser* = ref object
## A recursive-descent top-down
## parser implementation
# Index into self.tokens
current: int
# The name of the file being parsed.
@ -83,6 +84,9 @@ proc initParser*(): Parser =
result.currentLoop = None
result.scopeDepth = 0
# Public getters for improved error formatting
proc getCurrent*(self: Parser): int = self.current
proc getCurrentToken*(self: Parser): Token = (if self.getCurrent() >= self.tokens.len(): self.tokens[^1] else: self.tokens[self.current - 1])
# Handy templates to make our life easier, thanks nim!
@ -109,7 +113,7 @@ proc done(self: Parser): bool =
## EOF token to signal the end
## of the file (unless the token
## list is empty)
result = self.peek().kind == EndOfFile
result = self.tokens.len() == 0 or self.peek().kind == EndOfFile
proc step(self: Parser, n: int = 1): Token =
@ -124,8 +128,8 @@ proc step(self: Parser, n: int = 1): Token =
proc error(self: Parser, message: string) =
## Raises a formatted ParseError exception
var lexeme = if not self.done(): self.peek().lexeme else: self.step().lexeme
var errorMessage = &"A fatal error occurred while parsing '{self.file}', line {self.peek().line} at '{lexeme}' {message}"
var lexeme = self.getCurrentToken().lexeme
var errorMessage = &"A fatal error occurred while parsing '{self.file}', line {self.peek().line} at '{lexeme}' -> {message}"
raise newException(ParseError, errorMessage)
@ -517,6 +521,8 @@ proc delStmt(self: Parser): ASTNode =
self.error("cannot delete operator")
elif temp.kind == callExpr:
self.error("cannot delete function call")
elif temp.kind == assignExpr:
self.error("cannot delete assignment")
else:
result = newDelStmt(expression, tok)

View File

@ -13,16 +13,21 @@
# limitations under the License.
## Test module to wire up JAPL components
import backend/lexer
import backend/parser
import backend/optimizer
import backend/compiler
import frontend/lexer
import frontend/parser
import frontend/optimizer
import frontend/compiler
import frontend/serializer
import util/debugger
import backend/serializer
import jale/editor
import jale/templates
import jale/plugin/defaults
import jale/plugin/editor_history
import jale/keycodes
import jale/multiline
import config
@ -32,6 +37,12 @@ import sequtils
import times
import nimSHA2
const debugLexer = false
const debugParser = false
const debugOptimizer = false
const debugCompiler = true
const debugSerializer = true
proc getLineEditor: LineEditor =
result = newLineEditor()
@ -60,7 +71,10 @@ proc main =
let lineEditor = getLineEditor()
lineEditor.bindEvent(jeQuit):
keep = false
lineEditor.bindKey("ctrl+a"):
lineEditor.content.home()
lineEditor.bindKey("ctrl+e"):
lineEditor.content.`end`()
echo JAPL_VERSION_STRING
while keep:
try:
@ -77,69 +91,91 @@ proc main =
except IOError:
echo ""
break
echo &"Processing: '{source}'\n"
try:
tokens = lexer.lex(source, filename)
echo "Tokenization step: "
for token in tokens:
echo "\t", token
echo ""
when debugLexer:
echo "Tokenization step: "
for token in tokens:
echo "\t", token
echo ""
tree = parser.parse(tokens, filename)
echo "Parsing step: "
for node in tree:
echo "\t", node
echo ""
when debugParser:
echo "Parsing step: "
for node in tree:
echo "\t", node
echo ""
optimized = optimizer.optimize(tree)
echo &"Optimization step (constant folding enabled: {optimizer.foldConstants}):"
for node in optimized.tree:
echo "\t", node
echo ""
stdout.write(&"Produced warnings: ")
if optimized.warnings.len() > 0:
when debugOptimizer:
echo &"Optimization step (constant folding enabled: {optimizer.foldConstants}):"
for node in optimized.tree:
echo "\t", node
echo ""
stdout.write(&"Produced warnings: ")
if optimized.warnings.len() > 0:
echo ""
for warning in optimized.warnings:
echo "\t", warning
else:
stdout.write("No warnings produced\n")
echo ""
for warning in optimized.warnings:
echo "\t", warning
else:
stdout.write("No warnings produced\n")
echo ""
compiled = compiler.compile(optimized.tree, filename)
echo "Compilation step:"
stdout.write("\t")
echo &"""Raw byte stream: [{compiled.code.join(", ")}]"""
echo "\nBytecode disassembler output below:\n"
disassembleChunk(compiled, filename)
echo ""
when debugCompiler:
echo "Compilation step:"
stdout.write("\t")
echo &"""Raw byte stream: [{compiled.code.join(", ")}]"""
echo "\nBytecode disassembler output below:\n"
disassembleChunk(compiled, filename)
echo ""
serializedRaw = serializer.dumpBytes(compiled, source, filename)
echo "Serialization step: "
stdout.write("\t")
echo &"""Raw hex output: {serializedRaw.mapIt(toHex(it)).join("").toLowerAscii()}"""
echo ""
when debugSerializer:
serializedRaw = serializer.dumpBytes(compiled, source, filename)
echo "Serialization step: "
stdout.write("\t")
echo &"""Raw hex output: {serializedRaw.mapIt(toHex(it)).join("").toLowerAscii()}"""
echo ""
serialized = serializer.loadBytes(serializedRaw)
echo "Deserialization step:"
echo &"\t- File hash: {serialized.fileHash} (matches: {computeSHA256(source).toHex().toLowerAscii() == serialized.fileHash})"
echo &"\t- JAPL version: {serialized.japlVer.major}.{serialized.japlVer.minor}.{serialized.japlVer.patch} (commit {serialized.commitHash[0..8]} on branch {serialized.japlBranch})"
stdout.write("\t")
echo &"""- Compilation date & time: {fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss")}"""
stdout.write(&"\t- Reconstructed constants table: [")
for i, e in serialized.chunk.consts:
stdout.write(e)
if i < len(serialized.chunk.consts) - 1:
stdout.write(", ")
stdout.write("]\n")
stdout.write(&"\t- Reconstructed bytecode: [")
for i, e in serialized.chunk.code:
stdout.write($e)
if i < len(serialized.chunk.code) - 1:
stdout.write(", ")
stdout.write(&"] (matches: {serialized.chunk.code == compiled.code})\n")
except:
echo &"A Nim runtime exception occurred: {getCurrentExceptionMsg()}"
serialized = serializer.loadBytes(serializedRaw)
echo "Deserialization step:"
echo &"\t- File hash: {serialized.fileHash} (matches: {computeSHA256(source).toHex().toLowerAscii() == serialized.fileHash})"
echo &"\t- JAPL version: {serialized.japlVer.major}.{serialized.japlVer.minor}.{serialized.japlVer.patch} (commit {serialized.commitHash[0..8]} on branch {serialized.japlBranch})"
stdout.write("\t")
echo &"""- Compilation date & time: {fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss")}"""
stdout.write(&"\t- Reconstructed constants table: [")
for i, e in serialized.chunk.consts:
stdout.write(e)
if i < len(serialized.chunk.consts) - 1:
stdout.write(", ")
stdout.write("]\n")
stdout.write(&"\t- Reconstructed bytecode: [")
for i, e in serialized.chunk.code:
stdout.write($e)
if i < len(serialized.chunk.code) - 1:
stdout.write(", ")
stdout.write(&"] (matches: {serialized.chunk.code == compiled.code})\n")
except LexingError:
let lineNo = lexer.getLine()
let relPos = lexer.getRelPos(lineNo)
let line = lexer.getSource().splitLines()[lineNo - 1].strip()
echo getCurrentExceptionMsg()
echo &"Source line: {line}"
echo " ".repeat(relPos.start + len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except ParseError:
let lineNo = parser.getCurrentToken().line
let relPos = lexer.getRelPos(lineNo)
let line = lexer.getSource().splitLines()[lineNo - 1].strip()
echo getCurrentExceptionMsg()
echo &"Source line: {line}"
echo " ".repeat(relPos.start + len("Source line: ")) & "^".repeat(relPos.stop - parser.getCurrentToken().lexeme.len())
except CompileError:
let lineNo = compiler.getCurrentNode().token.line
let relPos = lexer.getRelPos(lineNo)
let line = lexer.getSource().splitLines()[lineNo - 1].strip()
echo getCurrentExceptionMsg()
echo &"Source line: {line}"
echo " ".repeat(relPos.start + len("Source line: ")) & "^".repeat(relPos.stop - compiler.getCurrentNode().token.lexeme.len())
when isMainModule:

View File

@ -12,8 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import ../backend/meta/bytecode
import ../backend/meta/ast
import ../frontend/meta/bytecode
import ../frontend/meta/ast
import multibyte