Added attribute resolution, fixed closures, fixed cross-shadowing issues
This commit is contained in:
parent
b99be47556
commit
7ab757074f
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# Some useful builtins
|
||||
|
||||
fn clock*: float {
|
||||
#pragma[magic: "SysClock64", pure]
|
||||
#pragma[magic: "SysClock64"]
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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}' ===="
|
||||
|
||||
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,4 +19,4 @@ fn outerTwo(n: int): int {
|
|||
|
||||
|
||||
print(outerTwo(5)); # 5
|
||||
print(outer()); # 69420
|
||||
#outer(); # 69420
|
Loading…
Reference in New Issue