Compare commits
5 Commits
29df939df3
...
6e2070899b
Author | SHA1 | Date |
---|---|---|
Nocturn9x | 6e2070899b | |
Nocturn9x | 8ed3cc7550 | |
Nocturn9x | 776a2241f7 | |
Nocturn9x | a545341428 | |
Nocturn9x | 633c032601 |
|
@ -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?")
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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)
|
||||
|
152
src/main.nim
152
src/main.nim
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue