Basic support for actual incremental compilation

This commit is contained in:
Mattia Giambirtone 2022-08-16 12:20:07 +02:00
parent 26c55c403e
commit 13b432b2d2
5 changed files with 64 additions and 45 deletions

View File

@ -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()

View File

@ -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

View File

@ -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'")

View File

@ -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:

View File

@ -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 {