Basic support for actual incremental compilation
This commit is contained in:
parent
26c55c403e
commit
13b432b2d2
|
@ -26,6 +26,7 @@ import algorithm
|
||||||
import parseutils
|
import parseutils
|
||||||
import strutils
|
import strutils
|
||||||
import sequtils
|
import sequtils
|
||||||
|
import sets
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
@ -180,6 +181,8 @@ type
|
||||||
lines: seq[tuple[start, stop: int]]
|
lines: seq[tuple[start, stop: int]]
|
||||||
# The source of the current module
|
# The source of the current module
|
||||||
source: string
|
source: string
|
||||||
|
# Currently imported modules
|
||||||
|
modules: HashSet[string]
|
||||||
CompileError* = ref object of PeonException
|
CompileError* = ref object of PeonException
|
||||||
compiler*: Compiler
|
compiler*: Compiler
|
||||||
node*: ASTNode
|
node*: ASTNode
|
||||||
|
@ -189,7 +192,7 @@ type
|
||||||
|
|
||||||
## Forward declarations
|
## Forward declarations
|
||||||
proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tuple[start, stop: int]], source: string, chunk: Chunk = nil,
|
proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tuple[start, stop: int]], source: string, chunk: Chunk = nil,
|
||||||
standalone: bool = true): Chunk
|
terminateScope: bool = true, incremental: bool = false): Chunk
|
||||||
proc expression(self: Compiler, node: Expression)
|
proc expression(self: Compiler, node: Expression)
|
||||||
proc statement(self: Compiler, node: Statement)
|
proc statement(self: Compiler, node: Statement)
|
||||||
proc declaration(self: Compiler, node: Declaration)
|
proc declaration(self: Compiler, node: Declaration)
|
||||||
|
@ -1302,7 +1305,6 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) =
|
||||||
name.valueType = self.inferType(argument.valueType)
|
name.valueType = self.inferType(argument.valueType)
|
||||||
# If it's still nil, it's an error!
|
# If it's still nil, it's an error!
|
||||||
if name.valueType.isNil():
|
if name.valueType.isNil():
|
||||||
echo argument.name.token
|
|
||||||
self.error(&"cannot determine the type of argument '{argument.name.token.lexeme}'", argument.name)
|
self.error(&"cannot determine the type of argument '{argument.name.token.lexeme}'", argument.name)
|
||||||
fn.valueType.args.add((argument.name.token.lexeme, name.valueType))
|
fn.valueType.args.add((argument.name.token.lexeme, name.valueType))
|
||||||
else:
|
else:
|
||||||
|
@ -1936,18 +1938,18 @@ proc declaration(self: Compiler, node: Declaration) =
|
||||||
self.statement(Statement(node))
|
self.statement(Statement(node))
|
||||||
|
|
||||||
|
|
||||||
proc terminateProgram(self: Compiler, pos: int) =
|
proc terminateProgram(self: Compiler, pos: int, terminateScope: bool = true) =
|
||||||
## Utility to terminate a peon program
|
## Utility to terminate a peon program
|
||||||
## compiled as the main module
|
if terminateScope:
|
||||||
self.endScope()
|
self.endScope()
|
||||||
self.patchReturnAddress(pos)
|
self.patchReturnAddress(pos)
|
||||||
self.emitByte(OpCode.Return)
|
self.emitByte(OpCode.Return)
|
||||||
self.emitByte(0) # Entry point has no return value (TODO: Add easter eggs, cuz why not)
|
self.emitByte(0) # Entry point has no return value (TODO: Add easter eggs, cuz why not)
|
||||||
|
|
||||||
|
|
||||||
proc beginProgram(self: Compiler): int =
|
proc beginProgram(self: Compiler, incremental: bool = false): int =
|
||||||
## Utility to begin a peon program
|
## Utility to begin a peon program
|
||||||
## compiled as the main module.
|
## compiled.
|
||||||
## Returns a dummy return address of
|
## Returns a dummy return address of
|
||||||
## the implicit main to be patched by
|
## the implicit main to be patched by
|
||||||
## terminateProgram
|
## terminateProgram
|
||||||
|
@ -1962,7 +1964,11 @@ proc beginProgram(self: Compiler): int =
|
||||||
# of where our program ends (which we don't know yet).
|
# of where our program ends (which we don't know yet).
|
||||||
# To fix this, we emit dummy offsets and patch them
|
# To fix this, we emit dummy offsets and patch them
|
||||||
# later, once we know the boundaries of our hidden main()
|
# later, once we know the boundaries of our hidden main()
|
||||||
var main = Name(depth: 0,
|
var main: Name
|
||||||
|
if incremental:
|
||||||
|
main = self.names[0]
|
||||||
|
else:
|
||||||
|
main = Name(depth: 0,
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
isConst: false,
|
isConst: false,
|
||||||
isLet: false,
|
isLet: false,
|
||||||
|
@ -1977,18 +1983,19 @@ proc beginProgram(self: Compiler): int =
|
||||||
name: newIdentExpr(Token(lexeme: "", kind: Identifier)),
|
name: newIdentExpr(Token(lexeme: "", kind: Identifier)),
|
||||||
isFunDecl: true,
|
isFunDecl: true,
|
||||||
line: -1)
|
line: -1)
|
||||||
self.names.add(main)
|
self.names.add(main)
|
||||||
self.emitByte(LoadFunction)
|
self.emitByte(LoadFunction)
|
||||||
self.emitBytes(main.codePos.toTriple())
|
self.emitBytes(main.codePos.toTriple())
|
||||||
self.emitByte(LoadReturnAddress)
|
self.emitByte(LoadReturnAddress)
|
||||||
result = self.chunk.code.len()
|
# result = self.chunk.code.len()
|
||||||
self.emitBytes(0.toQuad())
|
self.emitBytes(0.toQuad())
|
||||||
self.emitByte(Call)
|
self.emitByte(Call)
|
||||||
self.emitBytes(0.toTriple())
|
self.emitBytes(0.toTriple())
|
||||||
|
result = 5
|
||||||
|
|
||||||
|
|
||||||
proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tuple[start, stop: int]], source: string, chunk: Chunk = nil,
|
proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tuple[start, stop: int]], source: string, chunk: Chunk = nil,
|
||||||
standalone: bool = true): Chunk =
|
terminateScope: bool = true, incremental: bool = false): Chunk =
|
||||||
## Compiles a sequence of AST nodes into a chunk
|
## Compiles a sequence of AST nodes into a chunk
|
||||||
## object
|
## object
|
||||||
if chunk.isNil():
|
if chunk.isNil():
|
||||||
|
@ -1997,7 +2004,9 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tu
|
||||||
self.chunk = chunk
|
self.chunk = chunk
|
||||||
self.ast = ast
|
self.ast = ast
|
||||||
self.file = file
|
self.file = file
|
||||||
self.names = @[]
|
var terminateScope = terminateScope
|
||||||
|
if incremental:
|
||||||
|
terminateScope = false
|
||||||
self.scopeDepth = 0
|
self.scopeDepth = 0
|
||||||
self.currentFunction = nil
|
self.currentFunction = nil
|
||||||
self.currentModule = self.file.extractFilename()
|
self.currentModule = self.file.extractFilename()
|
||||||
|
@ -2005,13 +2014,13 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tu
|
||||||
self.frames = @[0]
|
self.frames = @[0]
|
||||||
self.lines = lines
|
self.lines = lines
|
||||||
self.source = source
|
self.source = source
|
||||||
var pos: int
|
let pos = self.beginProgram(incremental=incremental)
|
||||||
if standalone:
|
if incremental:
|
||||||
pos = self.beginProgram()
|
for i in countup(1, 2):
|
||||||
|
discard self.chunk.code.pop()
|
||||||
while not self.done():
|
while not self.done():
|
||||||
self.declaration(Declaration(self.step()))
|
self.declaration(Declaration(self.step()))
|
||||||
if standalone:
|
self.terminateProgram(pos, terminateScope)
|
||||||
self.terminateProgram(pos)
|
|
||||||
result = self.chunk
|
result = self.chunk
|
||||||
|
|
||||||
|
|
||||||
|
@ -2025,7 +2034,7 @@ proc compileModule(self: Compiler, filename: string) =
|
||||||
let source = readFile(joinPath(splitPath(self.file).head, filename))
|
let source = readFile(joinPath(splitPath(self.file).head, filename))
|
||||||
let tokens = lexer.lex(source, filename)
|
let tokens = lexer.lex(source, filename)
|
||||||
let ast = parser.parse(tokens, filename, lexer.getLines(), source)
|
let ast = parser.parse(tokens, filename, lexer.getLines(), source)
|
||||||
discard compiler.compile(ast, filename, lexer.getLines(), source, standalone=false)
|
discard compiler.compile(ast, filename, lexer.getLines(), source, terminateScope=false)
|
||||||
self.names &= compiler.names
|
self.names &= compiler.names
|
||||||
self.closedOver &= compiler.closedOver
|
self.closedOver &= compiler.closedOver
|
||||||
compiler.endScope()
|
compiler.endScope()
|
|
@ -293,7 +293,7 @@ const simpleInstructions* = {Return, LoadNil,
|
||||||
LessOrEqualFloat64,
|
LessOrEqualFloat64,
|
||||||
GreaterOrEqualFloat32,
|
GreaterOrEqualFloat32,
|
||||||
LessOrEqualFloat32,
|
LessOrEqualFloat32,
|
||||||
SysClock64,
|
SysClock64, NegInt16
|
||||||
}
|
}
|
||||||
|
|
||||||
# Constant instructions are instructions that operate on the bytecode constant table
|
# Constant instructions are instructions that operate on the bytecode constant table
|
||||||
|
|
|
@ -277,7 +277,7 @@ proc expect[T: TokenType or string](self: Parser, kind: T, message: string = "")
|
||||||
self.error(message)
|
self.error(message)
|
||||||
|
|
||||||
|
|
||||||
proc expect[T: TokenType or string](self: Parser, kind: openarray[T], message: string = "") =
|
proc expect[T: TokenType or string](self: Parser, kind: openarray[T], message: string = "") {.used.} =
|
||||||
## Behaves like self.expect(), except that
|
## Behaves like self.expect(), except that
|
||||||
## an error is raised only if none of the
|
## an error is raised only if none of the
|
||||||
## given token kinds matches
|
## given token kinds matches
|
||||||
|
@ -450,6 +450,7 @@ proc call(self: Parser): Expression =
|
||||||
## Operator parsing handlers
|
## Operator parsing handlers
|
||||||
|
|
||||||
proc unary(self: Parser): Expression =
|
proc unary(self: Parser): Expression =
|
||||||
|
## Parses unary expressions
|
||||||
if self.peek().kind == Symbol and self.peek().lexeme in self.operators.tokens:
|
if self.peek().kind == Symbol and self.peek().lexeme in self.operators.tokens:
|
||||||
result = newUnaryExpr(self.step(), self.unary())
|
result = newUnaryExpr(self.step(), self.unary())
|
||||||
else:
|
else:
|
||||||
|
@ -457,6 +458,7 @@ proc unary(self: Parser): Expression =
|
||||||
|
|
||||||
|
|
||||||
proc parsePow(self: Parser): Expression =
|
proc parsePow(self: Parser): Expression =
|
||||||
|
## Parses power expressions
|
||||||
result = self.unary()
|
result = self.unary()
|
||||||
var operator: Token
|
var operator: Token
|
||||||
var right: Expression
|
var right: Expression
|
||||||
|
@ -467,6 +469,8 @@ proc parsePow(self: Parser): Expression =
|
||||||
|
|
||||||
|
|
||||||
proc parseMul(self: Parser): Expression =
|
proc parseMul(self: Parser): Expression =
|
||||||
|
## Parses multiplication and division
|
||||||
|
## expressions
|
||||||
result = self.parsePow()
|
result = self.parsePow()
|
||||||
var operator: Token
|
var operator: Token
|
||||||
var right: Expression
|
var right: Expression
|
||||||
|
@ -477,6 +481,8 @@ proc parseMul(self: Parser): Expression =
|
||||||
|
|
||||||
|
|
||||||
proc parseAdd(self: Parser): Expression =
|
proc parseAdd(self: Parser): Expression =
|
||||||
|
## Parses addition and subtraction
|
||||||
|
## expressions
|
||||||
result = self.parseMul()
|
result = self.parseMul()
|
||||||
var operator: Token
|
var operator: Token
|
||||||
var right: Expression
|
var right: Expression
|
||||||
|
@ -487,6 +493,7 @@ proc parseAdd(self: Parser): Expression =
|
||||||
|
|
||||||
|
|
||||||
proc parseCmp(self: Parser): Expression =
|
proc parseCmp(self: Parser): Expression =
|
||||||
|
## Parses comparison expressions
|
||||||
result = self.parseAdd()
|
result = self.parseAdd()
|
||||||
var operator: Token
|
var operator: Token
|
||||||
var right: Expression
|
var right: Expression
|
||||||
|
@ -497,6 +504,7 @@ proc parseCmp(self: Parser): Expression =
|
||||||
|
|
||||||
|
|
||||||
proc parseAnd(self: Parser): Expression =
|
proc parseAnd(self: Parser): Expression =
|
||||||
|
## Parses logical and expressions
|
||||||
result = self.parseCmp()
|
result = self.parseCmp()
|
||||||
var operator: Token
|
var operator: Token
|
||||||
var right: Expression
|
var right: Expression
|
||||||
|
@ -507,6 +515,7 @@ proc parseAnd(self: Parser): Expression =
|
||||||
|
|
||||||
|
|
||||||
proc parseOr(self: Parser): Expression =
|
proc parseOr(self: Parser): Expression =
|
||||||
|
## Parses logical or expressions
|
||||||
result = self.parseAnd()
|
result = self.parseAnd()
|
||||||
var operator: Token
|
var operator: Token
|
||||||
var right: Expression
|
var right: Expression
|
||||||
|
@ -517,6 +526,7 @@ proc parseOr(self: Parser): Expression =
|
||||||
|
|
||||||
|
|
||||||
proc parseAssign(self: Parser): Expression =
|
proc parseAssign(self: Parser): Expression =
|
||||||
|
## Parses assignment expressions
|
||||||
result = self.parseOr()
|
result = self.parseOr()
|
||||||
if self.peek().kind == Symbol and self.operators.getPrecedence(self.peek().lexeme) == Assign:
|
if self.peek().kind == Symbol and self.operators.getPrecedence(self.peek().lexeme) == Assign:
|
||||||
let tok = self.step()
|
let tok = self.step()
|
||||||
|
@ -531,6 +541,7 @@ proc parseAssign(self: Parser): Expression =
|
||||||
|
|
||||||
|
|
||||||
proc parseArrow(self: Parser): Expression =
|
proc parseArrow(self: Parser): Expression =
|
||||||
|
## Parses arrow expressions
|
||||||
result = self.parseAssign()
|
result = self.parseAssign()
|
||||||
var operator: Token
|
var operator: Token
|
||||||
var right: Expression
|
var right: Expression
|
||||||
|
@ -546,7 +557,7 @@ proc parseArrow(self: Parser): Expression =
|
||||||
proc assertStmt(self: Parser): Statement =
|
proc assertStmt(self: Parser): Statement =
|
||||||
## Parses "assert" statements, which
|
## Parses "assert" statements, which
|
||||||
## raise an error if the expression
|
## raise an error if the expression
|
||||||
## fed into them is falsey
|
## fed into them is false
|
||||||
let tok = self.peek(-1)
|
let tok = self.peek(-1)
|
||||||
var expression = self.expression()
|
var expression = self.expression()
|
||||||
endOfLine("missing statement terminator after 'assert'")
|
endOfLine("missing statement terminator after 'assert'")
|
||||||
|
|
32
src/main.nim
32
src/main.nim
|
@ -35,9 +35,6 @@ const debugSerializer {.booldefine.} = false
|
||||||
const debugRuntime {.booldefine.} = false
|
const debugRuntime {.booldefine.} = false
|
||||||
|
|
||||||
|
|
||||||
when debugSerializer:
|
|
||||||
import nimSHA2
|
|
||||||
|
|
||||||
|
|
||||||
proc repl(vm: PeonVM = newPeonVM()) =
|
proc repl(vm: PeonVM = newPeonVM()) =
|
||||||
styledEcho fgMagenta, "Welcome into the peon REPL!"
|
styledEcho fgMagenta, "Welcome into the peon REPL!"
|
||||||
|
@ -45,7 +42,7 @@ proc repl(vm: PeonVM = newPeonVM()) =
|
||||||
keep = true
|
keep = true
|
||||||
tokens: seq[Token] = @[]
|
tokens: seq[Token] = @[]
|
||||||
tree: seq[Declaration] = @[]
|
tree: seq[Declaration] = @[]
|
||||||
compiled: Chunk
|
compiled: Chunk = newChunk()
|
||||||
serialized: Serialized
|
serialized: Serialized
|
||||||
tokenizer = newLexer()
|
tokenizer = newLexer()
|
||||||
parser = newParser()
|
parser = newParser()
|
||||||
|
@ -55,6 +52,7 @@ proc repl(vm: PeonVM = newPeonVM()) =
|
||||||
editor = getLineEditor()
|
editor = getLineEditor()
|
||||||
input: string
|
input: string
|
||||||
current: string
|
current: string
|
||||||
|
incremental: bool = false
|
||||||
tokenizer.fillSymbolTable()
|
tokenizer.fillSymbolTable()
|
||||||
editor.bindEvent(jeQuit):
|
editor.bindEvent(jeQuit):
|
||||||
stdout.styledWriteLine(fgGreen, "Goodbye!")
|
stdout.styledWriteLine(fgGreen, "Goodbye!")
|
||||||
|
@ -71,19 +69,16 @@ proc repl(vm: PeonVM = newPeonVM()) =
|
||||||
# so that you can, for example, define a function
|
# so that you can, for example, define a function
|
||||||
# then press enter and use it at the next iteration
|
# then press enter and use it at the next iteration
|
||||||
# of the read loop
|
# of the read loop
|
||||||
current = editor.read()
|
input = editor.read()
|
||||||
if current.len() == 0:
|
if input.len() == 0:
|
||||||
continue
|
continue
|
||||||
elif current == "#clearInput":
|
elif input == "#reset":
|
||||||
input = ""
|
compiled = newChunk()
|
||||||
|
incremental = false
|
||||||
continue
|
continue
|
||||||
elif current == "#clear":
|
elif input == "#clear":
|
||||||
stdout.write("\x1Bc")
|
stdout.write("\x1Bc")
|
||||||
continue
|
continue
|
||||||
elif current == "#showInput":
|
|
||||||
echo input
|
|
||||||
continue
|
|
||||||
input &= &"{current}\n"
|
|
||||||
tokens = tokenizer.lex(input, "stdin")
|
tokens = tokenizer.lex(input, "stdin")
|
||||||
if tokens.len() == 0:
|
if tokens.len() == 0:
|
||||||
continue
|
continue
|
||||||
|
@ -103,14 +98,14 @@ proc repl(vm: PeonVM = newPeonVM()) =
|
||||||
for node in tree:
|
for node in tree:
|
||||||
styledEcho fgGreen, "\t", $node
|
styledEcho fgGreen, "\t", $node
|
||||||
echo ""
|
echo ""
|
||||||
compiled = compiler.compile(tree, "stdin", tokenizer.getLines(), input)
|
discard compiler.compile(tree, "stdin", tokenizer.getLines(), input, chunk=compiled, incremental=incremental, terminateScope=false)
|
||||||
|
incremental = true
|
||||||
when debugCompiler:
|
when debugCompiler:
|
||||||
styledEcho fgCyan, "Compilation step:\n"
|
styledEcho fgCyan, "Compilation step:\n"
|
||||||
debugger.disassembleChunk(compiled, "stdin")
|
debugger.disassembleChunk(compiled, "stdin")
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
serializer.dumpFile(compiled, "stdin", "stdin.pbc")
|
serialized = serializer.loadBytes(serializer.dumpBytes(compiled, "stdin"))
|
||||||
serialized = serializer.loadFile("stdin.pbc")
|
|
||||||
when debugSerializer:
|
when debugSerializer:
|
||||||
styledEcho fgCyan, "Serialization step: "
|
styledEcho fgCyan, "Serialization step: "
|
||||||
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch
|
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch
|
||||||
|
@ -207,6 +202,7 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
|
||||||
input = readFile(f)
|
input = readFile(f)
|
||||||
else:
|
else:
|
||||||
input = f
|
input = f
|
||||||
|
f = "<string>"
|
||||||
tokens = tokenizer.lex(input, f)
|
tokens = tokenizer.lex(input, f)
|
||||||
if tokens.len() == 0:
|
if tokens.len() == 0:
|
||||||
return
|
return
|
||||||
|
@ -237,6 +233,8 @@ proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
|
||||||
path &= splitFile(f).name & ".pbc"
|
path &= splitFile(f).name & ".pbc"
|
||||||
serializer.dumpFile(compiled, f, path)
|
serializer.dumpFile(compiled, f, path)
|
||||||
serialized = serializer.loadFile(path)
|
serialized = serializer.loadFile(path)
|
||||||
|
if fromString:
|
||||||
|
discard tryRemoveFile("<string>.pbc")
|
||||||
when debugSerializer:
|
when debugSerializer:
|
||||||
styledEcho fgCyan, "Serialization step: "
|
styledEcho fgCyan, "Serialization step: "
|
||||||
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch
|
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch
|
||||||
|
@ -361,7 +359,7 @@ when isMainModule:
|
||||||
else:
|
else:
|
||||||
echo "usage: peon [options] [filename.pn]"
|
echo "usage: peon [options] [filename.pn]"
|
||||||
quit()
|
quit()
|
||||||
# TODO: Use interactive/fromString options
|
# TODO: Use interactive
|
||||||
if file == "":
|
if file == "":
|
||||||
repl()
|
repl()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -222,6 +222,7 @@ operator `<`*(a, b: int): bool {
|
||||||
|
|
||||||
operator `==`*(a, b: int): bool {
|
operator `==`*(a, b: int): bool {
|
||||||
#pragma[magic: "EqualInt64", pure]
|
#pragma[magic: "EqualInt64", pure]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
operator `!=`*(a, b: int): bool {
|
operator `!=`*(a, b: int): bool {
|
||||||
|
|
Loading…
Reference in New Issue