Various fixes to stack frame alignment and added incremental compilation to REPL
This commit is contained in:
parent
0887dab246
commit
df105125c4
|
@ -21,6 +21,8 @@ import ../frontend/meta/bytecode
|
||||||
import ../util/multibyte
|
import ../util/multibyte
|
||||||
|
|
||||||
|
|
||||||
|
export types
|
||||||
|
|
||||||
type
|
type
|
||||||
PeonVM* = ref object
|
PeonVM* = ref object
|
||||||
## The Peon Virtual Machine
|
## The Peon Virtual Machine
|
||||||
|
@ -268,7 +270,7 @@ proc dispatch*(self: PeonVM) =
|
||||||
of Pop:
|
of Pop:
|
||||||
self.lastPop = self.pop()
|
self.lastPop = self.pop()
|
||||||
of PopN:
|
of PopN:
|
||||||
for _ in 0..<int(self.readLong()):
|
for _ in 0..<int(self.readShort()):
|
||||||
discard self.pop()
|
discard self.pop()
|
||||||
of Jump:
|
of Jump:
|
||||||
self.ip = int(self.readShort())
|
self.ip = int(self.readShort())
|
||||||
|
@ -319,4 +321,5 @@ proc run*(self: PeonVM, chunk: Chunk) =
|
||||||
self.frames = @[0]
|
self.frames = @[0]
|
||||||
self.stack = @[]
|
self.stack = @[]
|
||||||
self.ip = 0
|
self.ip = 0
|
||||||
|
self.lastPop = self.getNil()
|
||||||
self.dispatch()
|
self.dispatch()
|
||||||
|
|
|
@ -27,7 +27,7 @@ when len(PEON_COMMIT_HASH) != 40:
|
||||||
const PEON_BRANCH* = "master"
|
const PEON_BRANCH* = "master"
|
||||||
when len(PEON_BRANCH) > 255:
|
when len(PEON_BRANCH) > 255:
|
||||||
{.fatal: "The git branch name's length must be less than or equal to 255 characters".}
|
{.fatal: "The git branch name's length must be less than or equal to 255 characters".}
|
||||||
const DEBUG_TRACE_VM* = false # Traces VM execution
|
const DEBUG_TRACE_VM* = true # Traces VM execution
|
||||||
const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO)
|
const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO)
|
||||||
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation
|
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation
|
||||||
const DEBUG_TRACE_COMPILER* = false # Traces the compiler
|
const DEBUG_TRACE_COMPILER* = false # Traces the compiler
|
||||||
|
|
|
@ -403,7 +403,7 @@ proc detectClosureVariable(self: Compiler, name: Name,
|
||||||
## unpredictably or crash
|
## unpredictably or crash
|
||||||
if name == nil:
|
if name == nil:
|
||||||
return
|
return
|
||||||
if name.depth < depth:
|
if name.depth > 0 and name.depth < depth:
|
||||||
# Ding! The given name is closed over: we need to
|
# Ding! The given name is closed over: we need to
|
||||||
# change the NoOp instructions that self.declareName
|
# change the NoOp instructions that self.declareName
|
||||||
# put in place for us into a StoreHeap. We don't need to change
|
# put in place for us into a StoreHeap. We don't need to change
|
||||||
|
@ -595,7 +595,11 @@ proc inferType(self: Compiler, node: Expression): Type =
|
||||||
var node = CallExpr(node)
|
var node = CallExpr(node)
|
||||||
case node.callee.kind:
|
case node.callee.kind:
|
||||||
of identExpr:
|
of identExpr:
|
||||||
result = self.resolve(IdentExpr(node.callee)).valueType.returnType
|
let resolved = self.resolve(IdentExpr(node.callee))
|
||||||
|
if resolved != nil:
|
||||||
|
result = resolved.valueType.returnType
|
||||||
|
else:
|
||||||
|
result = nil
|
||||||
of lambdaExpr:
|
of lambdaExpr:
|
||||||
result = self.inferType(LambdaExpr(node.callee).returnType)
|
result = self.inferType(LambdaExpr(node.callee).returnType)
|
||||||
else:
|
else:
|
||||||
|
@ -962,7 +966,9 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
||||||
if not t.closedOver:
|
if not t.closedOver:
|
||||||
# Static name resolution, loads value at index in the stack. Very fast. Much wow.
|
# Static name resolution, loads value at index in the stack. Very fast. Much wow.
|
||||||
self.emitByte(LoadVar)
|
self.emitByte(LoadVar)
|
||||||
self.emitBytes((index - self.frames[self.scopeDepth]).toTriple())
|
if self.frames.len() > 1:
|
||||||
|
inc(index, 2)
|
||||||
|
self.emitBytes((index - self.frames[^1]).toTriple())
|
||||||
else:
|
else:
|
||||||
# Heap-allocated closure variable. Stored in a separate "closure array" in the VM that does not have stack semantics.
|
# Heap-allocated closure variable. Stored in a separate "closure array" in the VM that does not have stack semantics.
|
||||||
# This makes closures work as expected and is not much slower than indexing our stack (since they're both
|
# This makes closures work as expected and is not much slower than indexing our stack (since they're both
|
||||||
|
@ -1019,7 +1025,7 @@ proc endScope(self: Compiler) =
|
||||||
var names: seq[Name] = @[]
|
var names: seq[Name] = @[]
|
||||||
for name in self.names:
|
for name in self.names:
|
||||||
if name.depth > self.scopeDepth:
|
if name.depth > self.scopeDepth:
|
||||||
if name.valueType.kind != Function and OpCode(self.chunk.code[name.codePos]) == NoOp:
|
if name.valueType.kind != Function and OpCode(self.chunk.code[name.codePos]) == NoOp:
|
||||||
for _ in countup(0, 3):
|
for _ in countup(0, 3):
|
||||||
# Since by deleting it the size of the
|
# Since by deleting it the size of the
|
||||||
# sequence decreases, we don't need to
|
# sequence decreases, we don't need to
|
||||||
|
@ -1369,10 +1375,11 @@ proc funDecl(self: Compiler, node: FunDecl) =
|
||||||
## Compiles function declarations
|
## Compiles function declarations
|
||||||
# A function's code is just compiled linearly
|
# A function's code is just compiled linearly
|
||||||
# and then jumped over
|
# and then jumped over
|
||||||
|
self.emitByte(LoadNil) # Aligns the stack
|
||||||
let jmp = self.emitJump(JumpForwards)
|
let jmp = self.emitJump(JumpForwards)
|
||||||
var function = self.currentFunction
|
var function = self.currentFunction
|
||||||
self.declareName(node)
|
self.declareName(node)
|
||||||
self.frames.add(self.names.high())
|
self.frames.add(self.names.len())
|
||||||
# TODO: Forward declarations
|
# TODO: Forward declarations
|
||||||
if node.body != nil:
|
if node.body != nil:
|
||||||
if BlockStmt(node.body).code.len() == 0:
|
if BlockStmt(node.body).code.len() == 0:
|
||||||
|
@ -1384,7 +1391,7 @@ proc funDecl(self: Compiler, node: FunDecl) =
|
||||||
# the same function with the same name! Error!
|
# the same function with the same name! Error!
|
||||||
var msg = &"multiple matching implementations of '{node.name.token.lexeme}' found:\n"
|
var msg = &"multiple matching implementations of '{node.name.token.lexeme}' found:\n"
|
||||||
for fn in reversed(impl):
|
for fn in reversed(impl):
|
||||||
msg &= &"- '{fn.name}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
|
msg &= &"- '{fn.name.token.lexeme}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
|
||||||
self.error(msg)
|
self.error(msg)
|
||||||
# We store the current function
|
# We store the current function
|
||||||
self.currentFunction = node
|
self.currentFunction = node
|
||||||
|
@ -1490,8 +1497,9 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string): Chunk =
|
||||||
self.declaration(Declaration(self.step()))
|
self.declaration(Declaration(self.step()))
|
||||||
if self.ast.len() > 0:
|
if self.ast.len() > 0:
|
||||||
# *Technically* an empty program is a valid program
|
# *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
|
result = self.chunk
|
||||||
if self.ast.len() > 0 and self.scopeDepth != 0:
|
if self.ast.len() > 0 and self.scopeDepth != -1:
|
||||||
self.error(&"invalid state: invalid scopeDepth value (expected 0, got {self.scopeDepth}), did you forget to call endScope/beginScope?")
|
self.error(&"invalid state: invalid scopeDepth value (expected -1, got {self.scopeDepth}), did you forget to call endScope/beginScope?")
|
||||||
self.endScope()
|
echo self.chunk.code
|
48
src/main.nim
48
src/main.nim
|
@ -53,20 +53,33 @@ proc repl =
|
||||||
vm = newPeonVM()
|
vm = newPeonVM()
|
||||||
editor = getLineEditor()
|
editor = getLineEditor()
|
||||||
input: string
|
input: string
|
||||||
|
current: string
|
||||||
tokenizer.fillSymbolTable()
|
tokenizer.fillSymbolTable()
|
||||||
editor.bindEvent(jeQuit):
|
editor.bindEvent(jeQuit):
|
||||||
stdout.styledWriteLine(fgGreen, "Goodbye!")
|
stdout.styledWriteLine(fgGreen, "Goodbye!")
|
||||||
editor.prompt = ""
|
editor.prompt = ""
|
||||||
keep = false
|
keep = false
|
||||||
|
input = ""
|
||||||
editor.bindKey("ctrl+a"):
|
editor.bindKey("ctrl+a"):
|
||||||
editor.content.home()
|
editor.content.home()
|
||||||
editor.bindKey("ctrl+e"):
|
editor.bindKey("ctrl+e"):
|
||||||
editor.content.`end`()
|
editor.content.`end`()
|
||||||
while keep:
|
while keep:
|
||||||
try:
|
try:
|
||||||
input = editor.read()
|
# We incrementally add content to the input
|
||||||
if input.len() == 0:
|
# so that you can, for example, define a function
|
||||||
|
# then press enter and use it at the next iteration
|
||||||
|
# of the read loop
|
||||||
|
current = editor.read()
|
||||||
|
if current.len() == 0:
|
||||||
continue
|
continue
|
||||||
|
elif current == "#clearInput":
|
||||||
|
input = ""
|
||||||
|
continue
|
||||||
|
elif current == "#clear":
|
||||||
|
stdout.write("\x1Bc")
|
||||||
|
continue
|
||||||
|
input &= &"\n{current}"
|
||||||
tokens = tokenizer.lex(input, "stdin")
|
tokens = tokenizer.lex(input, "stdin")
|
||||||
if tokens.len() == 0:
|
if tokens.len() == 0:
|
||||||
continue
|
continue
|
||||||
|
@ -123,8 +136,35 @@ proc repl =
|
||||||
when debugRuntime:
|
when debugRuntime:
|
||||||
styledEcho fgCyan, "\n\nExecution step: "
|
styledEcho fgCyan, "\n\nExecution step: "
|
||||||
vm.run(serialized.chunk)
|
vm.run(serialized.chunk)
|
||||||
echo vm.lastPop
|
var popped = vm.lastPop
|
||||||
|
case popped.kind:
|
||||||
|
of Int64:
|
||||||
|
echo &"{popped.long}'i64"
|
||||||
|
of UInt64:
|
||||||
|
echo &"{popped.uLong}'u64"
|
||||||
|
of Int32:
|
||||||
|
echo &"{popped.`int`}'i32"
|
||||||
|
of UInt32:
|
||||||
|
echo &"{popped.uInt}'u32"
|
||||||
|
of Int16:
|
||||||
|
echo &"{popped.short}'i16"
|
||||||
|
of UInt16:
|
||||||
|
echo &"{popped.uShort}'u16"
|
||||||
|
of Int8:
|
||||||
|
echo &"{popped.tiny}'i8"
|
||||||
|
of UInt8:
|
||||||
|
echo &"{popped.uTiny}'u8"
|
||||||
|
of ObjectKind.Inf:
|
||||||
|
if popped.positive:
|
||||||
|
echo "inf"
|
||||||
|
else:
|
||||||
|
echo "-inf"
|
||||||
|
of ObjectKind.Nan, Nil:
|
||||||
|
echo ($popped.kind).toLowerAscii()
|
||||||
|
else:
|
||||||
|
discard
|
||||||
except LexingError:
|
except LexingError:
|
||||||
|
input = ""
|
||||||
let exc = LexingError(getCurrentException())
|
let exc = LexingError(getCurrentException())
|
||||||
let relPos = tokenizer.getRelPos(exc.line)
|
let relPos = tokenizer.getRelPos(exc.line)
|
||||||
let line = tokenizer.getSource().splitLines()[exc.line - 1].strip()
|
let line = tokenizer.getSource().splitLines()[exc.line - 1].strip()
|
||||||
|
@ -134,6 +174,7 @@ proc repl =
|
||||||
styledEcho fgBlue, "Source line: " , fgDefault, line
|
styledEcho fgBlue, "Source line: " , fgDefault, line
|
||||||
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
|
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
|
||||||
except ParseError:
|
except ParseError:
|
||||||
|
input = ""
|
||||||
let exc = ParseError(getCurrentException())
|
let exc = ParseError(getCurrentException())
|
||||||
let lexeme = exc.token.lexeme
|
let lexeme = exc.token.lexeme
|
||||||
let lineNo = exc.token.line
|
let lineNo = exc.token.line
|
||||||
|
@ -149,6 +190,7 @@ proc repl =
|
||||||
styledEcho fgBlue, "Source line: " , fgDefault, line
|
styledEcho fgBlue, "Source line: " , fgDefault, line
|
||||||
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
|
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
|
||||||
except CompileError:
|
except CompileError:
|
||||||
|
input = ""
|
||||||
let exc = CompileError(getCurrentException())
|
let exc = CompileError(getCurrentException())
|
||||||
let lexeme = exc.node.token.lexeme
|
let lexeme = exc.node.token.lexeme
|
||||||
let lineNo = exc.node.token.line
|
let lineNo = exc.node.token.line
|
||||||
|
|
|
@ -63,7 +63,8 @@ proc printInstruction(instruction: OpCode, newline: bool = false) =
|
||||||
|
|
||||||
|
|
||||||
proc checkFrameStart(self: Debugger, n: int) =
|
proc checkFrameStart(self: Debugger, n: int) =
|
||||||
return
|
## Todo: Checking frame end offsets needs
|
||||||
|
## fixes
|
||||||
for i, e in self.cfiData:
|
for i, e in self.cfiData:
|
||||||
if n == e.start and not (e.started or e.stopped):
|
if n == e.start and not (e.started or e.stopped):
|
||||||
e.started = true
|
e.started = true
|
||||||
|
@ -131,7 +132,7 @@ proc callInstruction(self: Debugger, instruction: OpCode) =
|
||||||
printInstruction(instruction)
|
printInstruction(instruction)
|
||||||
stdout.styledWrite(fgGreen, &", jumps to address ", fgYellow, $slot, fgGreen, " with ", fgYellow, $args, fgGreen, " argument")
|
stdout.styledWrite(fgGreen, &", jumps to address ", fgYellow, $slot, fgGreen, " with ", fgYellow, $args, fgGreen, " argument")
|
||||||
if args > 1:
|
if args > 1:
|
||||||
stdout.styledWrite(fgYellow, "s")
|
stdout.styledWrite(fgGreen, "s")
|
||||||
nl()
|
nl()
|
||||||
self.current += 7
|
self.current += 7
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue