Added attribute resolution, fixed closures, fixed cross-shadowing issues

This commit is contained in:
Mattia Giambirtone 2022-11-27 13:39:41 +01:00
parent b99be47556
commit 7ab757074f
17 changed files with 904 additions and 689 deletions

View File

@ -44,32 +44,29 @@ below:
## Length Encoding. Instructions are encoded in groups whose structure
## follows the following schema:
## - The first integer represents the line number
## - 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"
## - The second integer represents the number of
## instructions on that line
## For example, if lines equals [1, 5], it means that there are 5 instructions
## at line 1, meaning that all instructions in code[0..4] belong to the same line.
## This is more efficient than using the naive approach, which would encode
## the same line number multiple times and waste considerable amounts of space.
[...]
```
### CFI segment
### Functions segment
The CFI segment (where CFI stands for **C**all **F**rame **I**nformation), contains details about each function in
This segment , contains details about each function in
the original file. The segment's size is fixed and is encoded at the beginning as a sequence of 4 bytes (i.e. a single 32 bit integer).
The data in this segment can be decoded as explained in [this file](../src/frontend/meta/bytecode.nim#L41), which is quoted
below:
```
[...]
## cfi represents Call Frame Information and encodes the following information:
## [...] encodes the following information:
## - Function name
## - Argument count
## - Function boundaries
## The encoding for CFI data is the following:
## The encoding for is the following:
## - First, the position into the bytecode where the function begins is encoded (as a 3 byte integer)
## - Second, the position into the bytecode where the function ends is encoded (as a 3 byte integer)
## - After that follows the argument count as a 1 byte integer

View File

@ -39,7 +39,7 @@ Peon ~~steals~~ borrows many ideas from Python, Nim (the the language peon itsel
Here follow a few examples of peon code to make it clear what the end product should look like. Note that
not all examples represent working functionality and some of these examples might not be up to date either.
For somewhat updated tests, check the [tests](../tests/) directory.
For somewhat more updated code snippets, check the [tests](../tests/) directory.
### Variable declarations
@ -48,10 +48,10 @@ var x = 5; # Inferred type is int64
var y = 3'u16; # Type is specified as uint16
x = 6; # Works: type matches
x = 3.0; # Error: Cannot assign float64 to x
var x = 3.14; # Error: Cannot re-declare x
var x = 3.14; # Error: cannot re-declare x
const z = 6.28; # Constant declaration
let a = "hi!"; # Cannot be reassigned/mutated
var b: int32 = 5; # Explicit type declaration
var b: int32 = 5; # Explicit type declaration (TODO)
```
__Note__: Peon supports [name stropping](https://en.wikipedia.org/wiki/Stropping_(syntax)), meaning
@ -71,7 +71,7 @@ fn fib(n: int): int {
fib(30);
```
### Type declarations
### Type declarations (TODO)
```
type Foo = object { # Can also be "ref object" for reference types (managed automatically)
@ -80,7 +80,7 @@ type Foo = object { # Can also be "ref object" for reference types (managed auto
}
```
### Enumeration types
### Enumeration types (TODO)
```
type SomeEnum = enum { # Can be mapped to an integer
@ -135,6 +135,8 @@ genericSum(3.14, 0.1);
genericSum(1'u8, 250'u8);
```
__Note__: The generic `Number` type is currently not defined. You can use type unions instead
#### More generics!
```
@ -146,7 +148,7 @@ genericSth(1, 3.0);
```
#### Even more generics?
#### Even more generics? (TODO)
```
type Box*[T: SomeNumber] = object {
@ -172,7 +174,10 @@ fn someF: int {
}
```
### Generators
__Note__: A function that is forward-declared __must__ be implemented in the same
module as the forward declaration
### Generators (TODO)
```
generator count(n: int): int {
@ -188,7 +193,7 @@ foreach (n: count(10)) {
```
### Coroutines
### Coroutines (TODO)
```
import concur;

View File

@ -405,9 +405,9 @@ proc `!>=`[T](a, b: T): auto {.inline, used.} =
b <= a
# Stack primitives. Note: all accesses to the call stack
# that go through the getc/setc wrappers are frame-relative,
# meaning that the index is added to the current stack frame's
# Stack primitives. Note that all accesses to the call stack
# that go through the (get|set|peek)c wrappers are frame-relative,
# meaning that the given index is added to the current stack frame's
# bottom to obtain an absolute stack index
proc push(self: var PeonVM, obj: uint64) =
## Pushes a value object onto the
@ -835,8 +835,23 @@ proc dispatch*(self: var PeonVM) =
# in a hidden function, so this
# will also exit the VM if we're
# at the end of the program
let ret = self.popc() # Return address
discard self.popc() # Function address
# The reason why we don't just
# call popc() twice might not be
# immediately apparent: after all,
# if all modules are enclosed in an
# implicit function, the stack frame
# of the module will be empty when it
# exits, right? Well, not really! A module
# needs to retain its variables throughout
# the entire exeuction of the program, which
# means the call stack might have spurious
# data at the top that is not a return address!
let ret = self.calls[self.frames[^1] + 1] # Return address
# We discard the return address and the
# original function's address
self.calls.delete(self.frames[^1])
self.calls.delete(self.frames[^1])
if self.readByte() == 1:
# Function is non-void!
self.push(self.results.pop())
@ -866,24 +881,33 @@ proc dispatch*(self: var PeonVM) =
# into the given call stack index
let idx = self.readLong()
when debugVM:
assert idx.int - self.calls.high() <= 1, "StoreVar index is bigger than the length of the call stack"
if idx + self.frames[^1] <= self.calls.high().uint:
self.setc(idx.int, self.pop())
else:
self.pushc(self.pop())
assert idx.int in 0..self.calls.high(), "StoreVar index is out of bounds"
self.setc(idx.int, self.pop())
of AddVar:
# Adds a new variable to the call stack. This is just
# an optimization for StoreVar that avoids using an if
# condition in the VM's bytecode dispatch loop (which is
# not a great idea)
self.pushc(self.pop())
of LoadClosure:
# Loads a closed-over variable from the current
# environment onto the operand stack
self.push(self.getClosure(self.readLong().int))
of PopClosure:
# Discards a closed-over variable from the
# current environment
discard self.popClosure(self.readLong().int)
of StoreClosure:
# Stores/updates the value of a closed-over
# Updates the value of a closed-over
# variable
let item = self.getc(self.readLong().int)
self.setClosure(self.readLong().int, item)
of LoadTos:
# Copies the top of the call stack
# (TOS: Top of Stack) onto the
# operand stack
self.push(self.peekc())
of AddClosure:
# Stores the value at the top of the
# operand stack in the topmost closure
# environment
self.envs.add(self.pop())
of LoadVar:
# Pushes a variable from the call stack
# onto the operand stack

File diff suppressed because it is too large Load Diff

View File

@ -162,6 +162,7 @@ proc incLine(self: Lexer)
proc getStart*(self: Lexer): int = self.start
proc getFile*(self: Lexer): string = self.file
proc getCurrent*(self: Lexer): int = self.current
proc getCurrentLinePos*(self: Lexer): tuple[start, stop: int] = (self.lastLine, self.linePos)
proc getLine*(self: Lexer): int = self.line
proc getLines*(self: Lexer): seq[tuple[start, stop: int]] = self.lines
proc getSource*(self: Lexer): string = self.source

View File

@ -662,7 +662,7 @@ proc newTypeDecl*(name: IdentExpr, fields: seq[tuple[name: IdentExpr, valueType:
proc `$`*(self: ASTNode): string =
if self == nil:
if self.isNil():
return "nil"
case self.kind:
of intExpr, floatExpr, hexExpr, binExpr, octExpr, strExpr, trueExpr,
@ -786,8 +786,22 @@ proc `==`*(self, other: IdentExpr): bool {.inline.} = self.token == other.token
proc getRelativeBoundaries*(self: ASTNode): tuple[start, stop: int] =
## Gets the location of a node relative to its line
## Recursively computes the position of a node relative
## to its containing line
case self.kind:
of varDecl:
var self = VarDecl(self)
let start = self.token.relPos.start
var stop = self.name.token.relPos.stop
if not self.value.isNil():
stop = self.value.token.relPos.stop
if self.pragmas.len() > 0:
stop = getRelativeBoundaries(self.pragmas[^1]).stop
result = (start, stop)
of breakStmt:
result = self.token.relPos
of importStmt:
result = (self.token.relPos.start, getRelativeBoundaries(ImportStmt(self).moduleName).stop)
of exprStmt:
result = getRelativeBoundaries(ExprStmt(self).expression)
of unaryExpr:
@ -808,6 +822,9 @@ proc getRelativeBoundaries*(self: ASTNode): tuple[start, stop: int] =
of callExpr:
var self = CallExpr(self)
result = (getRelativeBoundaries(self.callee).start, self.closeParen.relPos.stop)
of getItemExpr:
var self = GetItemExpr(self)
result = (getRelativeBoundaries(self.obj).start, getRelativeBoundaries(self.name).stop)
of pragmaExpr:
var self = Pragma(self)
let start = self.token.relPos.start
@ -816,6 +833,7 @@ proc getRelativeBoundaries*(self: ASTNode): tuple[start, stop: int] =
stop = self.args[^1].token.relPos.stop + 1
else:
stop = self.token.relPos.stop + 1
# -8 so the error highlights the #pragma[ part as well
result = (self.token.relPos.start - 8, stop)
else:
result = (0, 0)

View File

@ -35,11 +35,11 @@ type
## at line 1, meaning that all instructions in code[0..4] belong to the same line.
## This is more efficient than using the naive approach, which would encode
## the same line number multiple times and waste considerable amounts of space.
## cfi represents Call Frame Information and encodes the following information:
## functions encodes the following information:
## - Function name
## - Argument count
## - Function boundaries
## The encoding for CFI data is the following:
## The encoding is the following:
## - First, the position into the bytecode where the function begins is encoded (as a 3 byte integer)
## - Second, the position into the bytecode where the function ends is encoded (as a 3 byte integer)
## - After that follows the argument count as a 1 byte integer
@ -48,7 +48,7 @@ type
consts*: seq[uint8]
code*: seq[uint8]
lines*: seq[int]
cfi*: seq[uint8]
functions*: seq[uint8]
OpCode* {.pure.} = enum
## Enum of Peon's bytecode opcodes
@ -122,6 +122,7 @@ type
LessThan,
GreaterOrEqual,
LessOrEqual,
LogicalNot,
## Print opcodes
PrintInt64,
PrintUInt64,
@ -146,9 +147,10 @@ type
LoadAttribute, # Pushes the attribute b of object a onto the stack
LoadVar, # Pushes the object at position x in the stack onto the stack
StoreVar, # Stores the value of b at position a in the stack
AddVar, # An optimization for StoreVar (used when the variable is first declared)
LoadClosure, # Pushes the object position x in the closure array onto the stack
StoreClosure, # Stores the value of b at position a in the closure array
PopClosure,
AddClosure, # This is the same optimization of AddVar, but applied to StoreClosure instead
## Looping and jumping
Jump, # Absolute, unconditional jump into the bytecode
JumpForwards, # Relative, unconditional, positive jump in the bytecode
@ -171,12 +173,12 @@ type
## Coroutines
Await, # Calls an asynchronous function
## Misc
Assert, # Raises an AssertionFailed exception if x is false
Assert, # Raises an exception if x is false
NoOp, # Just a no-op
PopC, # Pop off the call stack onto the operand stack
PushC, # Pop off the operand stack onto the call stack
SysClock64, # Pushes the output of a monotonic clock on the stack
LogicalNot
LoadTos # Pushes the top of the call stack onto the operand stack
# We group instructions by their operation/operand types for easier handling when debugging
@ -240,7 +242,10 @@ const simpleInstructions* = {Return, LoadNil,
PrintNan,
PrintInf,
PrintString,
LogicalNot
LogicalNot,
AddVar,
AddClosure,
LoadTos
}
# Constant instructions are instructions that operate on the bytecode constant table
@ -253,7 +258,7 @@ const constantInstructions* = {LoadInt64, LoadUInt64,
# Stack triple instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 24 bit integers
const stackTripleInstructions* = {StoreVar, LoadVar, LoadCLosure, PopClosure}
const stackTripleInstructions* = {StoreVar, LoadVar, LoadCLosure, }
# Stack double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 16 bit integers
@ -271,10 +276,7 @@ const jumpInstructions* = {Jump, JumpIfFalse, JumpIfFalsePop,
proc newChunk*: Chunk =
## Initializes a new, empty chunk
result = Chunk(consts: @[], code: @[], lines: @[], cfi: @[])
proc `$`*(self: Chunk): string = &"""Chunk(consts=[{self.consts.join(", ")}], code=[{self.code.join(", ")}], lines=[{self.lines.join(", ")}])"""
result = Chunk(consts: @[], code: @[], lines: @[], functions: @[])
proc write*(self: Chunk, newByte: uint8, line: int) =
@ -290,8 +292,8 @@ proc write*(self: Chunk, newByte: uint8, line: int) =
proc write*(self: Chunk, bytes: openarray[uint8], line: int) =
## Calls write in a loop with all members of the given
## array
## Calls self.write() in a loop with all members of the
## given array
for cByte in bytes:
self.write(cByte, line)

View File

@ -101,7 +101,6 @@ type
## A parsing exception
parser*: Parser
token*: Token
module*: string
proc addOperator(self: OperatorTable, lexeme: string) =
@ -171,11 +170,7 @@ proc getCurrentToken*(self: Parser): Token {.inline.} = (if self.getCurrent() >=
self.tokens.high() or
self.getCurrent() - 1 < 0: self.tokens[^1] else: self.tokens[self.current - 1])
proc getCurrentFunction*(self: Parser): Declaration {.inline.} = self.currentFunction
proc getFile*(self: Parser): string {.inline.} = self.file
proc getModule*(self: Parser): string {.inline.} = self.getFile().splitFile().name
proc getLines*(self: Parser): seq[tuple[start, stop: int]] = self.lines
proc getSource*(self: Parser): string = self.source
proc getRelPos*(self: Parser, line: int): tuple[start, stop: int] = self.lines[line - 1]
template endOfFile: Token = Token(kind: EndOfFile, lexeme: "", line: -1)
template endOfLine(msg: string, tok: Token = nil) = self.expect(Semicolon, msg, tok)
@ -187,8 +182,7 @@ proc peek(self: Parser, distance: int = 0): Token =
## token is returned. A negative distance may
## be used to retrieve previously consumed
## tokens
if self.tokens.high() == -1 or self.current + distance > self.tokens.high(
) or self.current + distance < 0:
if self.tokens.high() == -1 or self.current + distance > self.tokens.high() or self.current + distance < 0:
result = endOfFile
else:
result = self.tokens[self.current + distance]
@ -216,7 +210,7 @@ proc step(self: Parser, n: int = 1): Token =
proc error(self: Parser, message: string, token: Token = nil) {.raises: [ParseError].} =
## Raises a ParseError exception
let token = if token.isNil(): self.getCurrentToken() else: token
raise ParseError(msg: message, token: token, line: token.line, file: self.file, module: self.getModule(), parser: self)
raise ParseError(msg: message, token: token, line: token.line, file: self.file, parser: self)
# Why do we allow strings or enum members of TokenType? Well, it's simple:
@ -404,22 +398,21 @@ proc makeCall(self: Parser, callee: Expression): CallExpr =
if not self.check(RightParen):
while true:
if argCount >= 255:
self.error("call can not have more than 255 arguments")
self.error("can not pass more than 255 arguments in function call")
break
argument = self.expression()
if argument.kind == binaryExpr and BinaryExpr(argument).operator.lexeme == "=":
# TODO: This will explode with slices!
if IdentExpr(BinaryExpr(argument).a) in argNames:
self.error("duplicate keyword argument in call")
self.error("duplicate keyword argument in function call is not allowed")
argNames.add(IdentExpr(BinaryExpr(argument).a))
arguments.keyword.add((name: IdentExpr(BinaryExpr(argument).a), value: BinaryExpr(argument).b))
elif arguments.keyword.len() == 0:
arguments.positionals.add(argument)
else:
self.error("positional argument cannot follow keyword argument in call")
self.error("positional arguments cannot follow keyword arguments in function calls")
if not self.match(Comma):
break
argCount += 1
argCount += 1
self.expect(RightParen)
result = newCallExpr(callee, arguments, tok)
result.closeParen = self.peek(-1)
@ -451,7 +444,7 @@ proc call(self: Parser): Expression =
proc unary(self: Parser): Expression =
## Parses unary expressions
if self.peek().kind in [Identifier, Symbol] and self.peek().lexeme in self.operators.tokens:
if self.check([Identifier, Symbol]) and self.peek().lexeme in self.operators.tokens:
result = newUnaryExpr(self.step(), self.unary())
else:
result = self.call()
@ -462,7 +455,7 @@ proc parsePow(self: Parser): Expression =
result = self.unary()
var operator: Token
var right: Expression
while self.peek().kind in [Identifier, Symbol] and self.operators.getPrecedence(self.peek().lexeme) == Power:
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Power:
operator = self.step()
right = self.unary()
result = newBinaryExpr(result, operator, right)
@ -474,7 +467,7 @@ proc parseMul(self: Parser): Expression =
result = self.parsePow()
var operator: Token
var right: Expression
while self.peek().kind in [Identifier, Symbol] and self.operators.getPrecedence(self.peek().lexeme) == Multiplication:
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Multiplication:
operator = self.step()
right = self.parsePow()
result = newBinaryExpr(result, operator, right)
@ -486,7 +479,7 @@ proc parseAdd(self: Parser): Expression =
result = self.parseMul()
var operator: Token
var right: Expression
while self.peek().kind in [Identifier, Symbol] and self.operators.getPrecedence(self.peek().lexeme) == Addition:
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Addition:
operator = self.step()
right = self.parseMul()
result = newBinaryExpr(result, operator, right)
@ -497,7 +490,7 @@ proc parseBitwise(self: Parser): Expression =
result = self.parseAdd()
var operator: Token
var right: Expression
while self.peek().kind in [Identifier, Symbol] and self.operators.getPrecedence(self.peek().lexeme) == Bitwise:
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Bitwise:
operator = self.step()
right = self.parseAdd()
result = newBinaryExpr(result, operator, right)
@ -508,7 +501,7 @@ proc parseCmp(self: Parser): Expression =
result = self.parseBitwise()
var operator: Token
var right: Expression
while self.peek().kind in [Identifier, Symbol] and self.operators.getPrecedence(self.peek().lexeme) == Compare:
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Compare:
operator = self.step()
right = self.parseAdd()
result = newBinaryExpr(result, operator, right)
@ -519,7 +512,7 @@ proc parseAnd(self: Parser): Expression =
result = self.parseCmp()
var operator: Token
var right: Expression
while self.peek().kind in [Identifier, Symbol] and self.operators.getPrecedence(self.peek().lexeme) == Precedence.And:
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Precedence.And:
operator = self.step()
right = self.parseCmp()
result = newBinaryExpr(result, operator, right)
@ -530,7 +523,7 @@ proc parseOr(self: Parser): Expression =
result = self.parseAnd()
var operator: Token
var right: Expression
while self.peek().kind in [Identifier, Symbol] and self.operators.getPrecedence(self.peek().lexeme) == Precedence.Or:
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Precedence.Or:
operator = self.step()
right = self.parseAnd()
result = newBinaryExpr(result, operator, right)
@ -539,7 +532,7 @@ proc parseOr(self: Parser): Expression =
proc parseAssign(self: Parser): Expression =
## Parses assignment expressions
result = self.parseOr()
if self.peek().kind in [Identifier, Symbol] and self.operators.getPrecedence(self.peek().lexeme) == Assign:
if self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Assign:
let tok = self.step()
var value = self.expression()
case result.kind:
@ -556,7 +549,7 @@ proc parseArrow(self: Parser): Expression =
result = self.parseAssign()
var operator: Token
var right: Expression
while self.peek().kind in [Identifier, Symbol] and self.operators.getPrecedence(self.peek().lexeme) == Precedence.Or:
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Precedence.Arrow:
operator = self.step()
right = self.parseAssign()
result = newBinaryExpr(result, operator, right)
@ -727,8 +720,12 @@ proc importStmt(self: Parser, fromStmt: bool = false): Statement =
else:
break
endOfLine("missing semicolon after import statement")
result = newImportStmt(newIdentExpr(Token(kind: Identifier, lexeme: moduleName,
line: self.peek(-1).line,
pos: (tok.pos.stop + 1, (tok.pos.stop + 1) + len(moduleName)),
relPos: (tok.relPos.stop + 1, (tok.relPos.stop + 1) + len(moduleName))),
self.scopeDepth), tok)
moduleName &= ".pn"
result = newImportStmt(newIdentExpr(Token(kind: Identifier, lexeme: moduleName, line: self.peek(-1).line), self.scopeDepth), tok)
var lexer = newLexer()
lexer.fillSymbolTable()
var path = ""
@ -736,7 +733,7 @@ proc importStmt(self: Parser, fromStmt: bool = false): Statement =
if searchPath == "":
path = joinPath(getCurrentDir(), joinPath(splitPath(self.file).head, moduleName))
else:
path = joinPath(getCurrentDir(), joinPath(searchPath, moduleName))
path = joinPath(searchPath, moduleName)
if fileExists(path):
break
elif i == searchPath.high():
@ -755,9 +752,9 @@ proc importStmt(self: Parser, fromStmt: bool = false): Statement =
self.current = current
self.tokens = tokens
except IOError:
self.error(&"""could not import '{path}': {getCurrentExceptionMsg()}""")
self.error(&"could not import '{path}': {getCurrentExceptionMsg()}")
except OSError:
self.error(&"""could not import '{path}': {getCurrentExceptionMsg()} [errno {osLastError()}]""")
self.error(&"could not import '{path}': {getCurrentExceptionMsg()} [errno {osLastError()}]")
proc tryStmt(self: Parser): Statement =
@ -1296,6 +1293,7 @@ proc parse*(self: Parser, tokens: seq[Token], file: string, lines: seq[tuple[sta
self.tree = @[]
if not persist:
self.operators = newOperatorTable()
self.operators.addOperator("=")
self.findOperators(tokens)
while not self.done():
self.tree.add(self.declaration())

View File

@ -42,7 +42,7 @@ operator `-`*[T: int | int32 | int16 | int8](a: T): T {
operator `-`*[T: uint64 | uint32 | uint16 | uint8](a: T): T {
#pragma[error: "unsigned integer cannot be negative"]
#pragma[magic: "", error: "unsigned integer cannot be negative"]
}

View File

@ -4,7 +4,7 @@
# Some useful builtins
fn clock*: float {
#pragma[magic: "SysClock64", pure]
#pragma[magic: "SysClock64"]
}

View File

@ -11,4 +11,7 @@ export arithmetics;
export bitwise;
export logical;
export misc;
export comparisons;
export comparisons;
var version* = 1;
var private = 2; # Invisible outside the module

View File

@ -22,13 +22,13 @@ import std/terminal
type
CFIElement = ref object
Function = ref object
start, stop, bottom, argc: int
name: string
started, stopped: bool
Debugger* = ref object
chunk: Chunk
cfiData: seq[CFIElement]
functions: seq[Function]
current: int
@ -36,7 +36,7 @@ proc newDebugger*: Debugger =
## Initializes a new, empty
## debugger object
new(result)
result.cfiData = @[]
result.functions = @[]
proc nl = stdout.write("\n")
@ -62,25 +62,25 @@ proc printInstruction(instruction: OpCode, newline: bool = false) =
nl()
proc checkFrameStart(self: Debugger, n: int) =
## Checks if a call frame begins at the given
proc checkFunctionStart(self: Debugger, n: int) =
## Checks if a function begins at the given
## bytecode offset
for i, e in self.cfiData:
for i, e in self.functions:
if n == e.start and not (e.started or e.stopped):
e.started = true
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - Function Start ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
styledEcho fgBlue, "\n==== Peon Bytecode Disassembler - Function Start ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
styledEcho fgGreen, "\t- Start offset: ", fgYellow, $e.start
styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop
styledEcho fgGreen, "\t- Argument count: ", fgYellow, $e.argc
proc checkFrameEnd(self: Debugger, n: int) =
## Checks if a call frame ends at the given
proc checkFunctionEnd(self: Debugger, n: int) =
## Checks if a function ends at the given
## bytecode offset
for i, e in self.cfiData:
for i, e in self.functions:
if n == e.stop and e.started and not e.stopped:
e.stopped = true
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - Function End ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
styledEcho fgBlue, "\n==== Peon Bytecode Disassembler - Function End ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
proc simpleInstruction(self: Debugger, instruction: OpCode) =
@ -94,9 +94,9 @@ proc simpleInstruction(self: Debugger, instruction: OpCode) =
else:
stdout.styledWriteLine(fgYellow, "No")
self.current += 1
self.checkFrameEnd(self.current - 2)
self.checkFrameEnd(self.current - 1)
self.checkFrameEnd(self.current)
self.checkFunctionEnd(self.current - 2)
self.checkFunctionEnd(self.current - 1)
self.checkFunctionEnd(self.current)
proc stackTripleInstruction(self: Debugger, instruction: OpCode) =
@ -189,7 +189,7 @@ proc jumpInstruction(self: Debugger, instruction: OpCode) =
while self.chunk.code[self.current] == NoOp.uint8:
inc(self.current)
for i in countup(orig, self.current + 1):
self.checkFrameStart(i)
self.checkFunctionStart(i)
proc disassembleInstruction*(self: Debugger) =
@ -223,37 +223,36 @@ proc disassembleInstruction*(self: Debugger) =
self.current += 1
proc parseCFIData(self: Debugger) =
## Parses CFI information in the chunk
proc parseFunctions(self: Debugger) =
## Parses function information in the chunk
var
start, stop, argc: int
name: string
idx = 0
size = 0
while idx < len(self.chunk.cfi) - 1:
start = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple())
while idx < len(self.chunk.functions) - 1:
start = int([self.chunk.functions[idx], self.chunk.functions[idx + 1], self.chunk.functions[idx + 2]].fromTriple())
idx += 3
stop = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple())
stop = int([self.chunk.functions[idx], self.chunk.functions[idx + 1], self.chunk.functions[idx + 2]].fromTriple())
idx += 3
argc = int(self.chunk.cfi[idx])
argc = int(self.chunk.functions[idx])
inc(idx)
size = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1]].fromDouble())
size = int([self.chunk.functions[idx], self.chunk.functions[idx + 1]].fromDouble())
idx += 2
name = self.chunk.cfi[idx..<idx + size].fromBytes()
name = self.chunk.functions[idx..<idx + size].fromBytes()
inc(idx, size)
self.cfiData.add(CFIElement(start: start, stop: stop,
argc: argc, name: name))
self.functions.add(Function(start: start, stop: stop, argc: argc, name: name))
proc disassembleChunk*(self: Debugger, chunk: Chunk, name: string) =
## Takes a chunk of bytecode and prints it
self.chunk = chunk
styledEcho fgBlue, &"==== Peon Bytecode Debugger: Session starting - Chunk '{name}' ====\n"
styledEcho fgBlue, &"==== Peon Bytecode Disassembler - Chunk '{name}' ====\n"
self.current = 0
self.parseCFIData()
self.parseFunctions()
while self.current < self.chunk.code.len:
self.disassembleInstruction()
echo ""
styledEcho fgBlue, &"==== Peon Bytecode Debugger: Session ended - Chunk '{name}' ===="
styledEcho fgBlue, &"==== Peon Bytecode Disassembler - Chunk '{name}' ===="

View File

@ -25,23 +25,27 @@ import std/strutils
import std/strformat
proc printError(file, line: string, lineNo: int, pos: tuple[start, stop: int], fn: Declaration, msg: string) =
## Internal helper to print a formatted error message
## to stderr
stderr.styledWrite(fgRed, styleBright, "Error in ", fgYellow, &"{file}:{lineNo}:{pos.start}")
if not fn.isNil() and fn.kind == funDecl:
stderr.styledWrite(fgRed, styleBright, " in function ", fgYellow, FunDecl(fn).name.token.lexeme)
stderr.styledWriteLine(styleBright, fgDefault, ": ", msg)
stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start])
stderr.styledWrite(fgRed, styleUnderscore, line[pos.start..pos.stop])
stderr.styledWriteLine(fgDefault, line[pos.stop + 1..^1])
proc print*(exc: CompileError) =
## Prints a formatted error message
## for compilation errors to stderr
var file = exc.file
if file notin ["<string>", ""]:
file = relativePath(exc.file, getCurrentDir())
let line = exc.compiler.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
let fn = exc.compiler.getCurrentFunction()
let node = exc.node
let pos = node.getRelativeBoundaries()
stderr.styledWrite(fgRed, styleBright, "Error in ", fgYellow, &"{file}:{exc.line}:{pos.start}")
if not fn.isNil() and fn.kind == funDecl:
stderr.styledWrite(fgRed, styleBright, " in function ", fgYellow, FunDecl(fn).name.token.lexeme)
stderr.styledWriteLine(styleBright, fgDefault, ": ", exc.msg)
stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start])
stderr.styledWrite(fgRed, styleUnderscore, line[pos.start..pos.stop])
stderr.styledWriteLine(fgDefault, line[pos.stop + 1..^1])
printError(file, exc.compiler.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}),
exc.line, exc.node.getRelativeBoundaries(), exc.compiler.getCurrentFunction(),
exc.msg)
proc print*(exc: ParseError) =
@ -50,12 +54,9 @@ proc print*(exc: ParseError) =
var file = exc.file
if file notin ["<string>", ""]:
file = relativePath(exc.file, getCurrentDir())
let line = exc.parser.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
let pos = exc.token.relPos
stderr.styledWriteLine(fgRed, styleBright, "Error in ", fgYellow, &"{file}:{exc.line}:{pos.start}", fgDefault, ": " & exc.msg)
stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start])
stderr.styledWrite(fgRed, styleUnderscore, line[pos.start..pos.stop])
stderr.styledWriteLine(fgDefault, line[pos.stop + 1..^1])
printError(file, exc.parser.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}),
exc.line, exc.token.relPos, exc.parser.getCurrentFunction(),
exc.msg)
proc print*(exc: LexingError) =
@ -64,12 +65,8 @@ proc print*(exc: LexingError) =
var file = exc.file
if file notin ["<string>", ""]:
file = relativePath(exc.file, getCurrentDir())
let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
let pos = exc.pos
stderr.styledWriteLine(fgRed, styleBright, "Error in ", fgYellow, &"{file}:{exc.line}:{pos.start}", fgDefault, ": " & exc.msg)
stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start])
stderr.styledWrite(fgRed, styleUnderscore, line[pos.start..<pos.stop])
stderr.styledWriteLine(fgDefault, line[pos.stop..^1])
printError(file, exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}),
exc.line, exc.pos, nil, exc.msg)
proc print*(exc: SerializationError) =

View File

@ -11,6 +11,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
## Implementation of the peon bytecode serializer
import ../frontend/meta/errors
import ../frontend/meta/bytecode
import ../frontend/compiler
@ -84,8 +87,8 @@ proc writeLineData(self: Serializer, stream: var seq[byte]) =
proc writeCFIData(self: Serializer, stream: var seq[byte]) =
## Writes Call Frame Information for debugging
## functions
stream.extend(len(self.chunk.cfi).toQuad())
stream.extend(self.chunk.cfi)
stream.extend(len(self.chunk.functions).toQuad())
stream.extend(self.chunk.functions)
proc writeConstants(self: Serializer, stream: var seq[byte]) =
@ -149,7 +152,7 @@ proc readCFIData(self: Serializer, stream: seq[byte]): int =
result += 4
var stream = stream[4..^1]
for i in countup(0, int(size) - 1):
self.chunk.cfi.add(stream[i])
self.chunk.functions.add(stream[i])
inc(result)

View File

@ -1,5 +1,5 @@
# Tests closures
import std;
# import std;
fn makeClosure(x: int): fn: int {
@ -18,9 +18,6 @@ fn makeClosureTwo(y: int): fn: int {
}
# These should all print true!
var closure = makeClosure(42);
print(closure() == 42);
print(makeClosureTwo(38)() == 38);
var closureTwo = makeClosureTwo(420);
print(closureTwo() == 420);
makeClosureTwo(38)();

View File

@ -11,9 +11,9 @@ fn first(x: int): int {
fn second(x: int): int {
var y = first(x);
y = y + 1;
return y;
var x = first(x);
x = x + 1;
return x;
}

View File

@ -19,4 +19,4 @@ fn outerTwo(n: int): int {
print(outerTwo(5)); # 5
print(outer()); # 69420
#outer(); # 69420