Initial experimental work on function declarations. Minor formatting fixes
This commit is contained in:
parent
8ceea4e84f
commit
43f5cea905
|
@ -56,15 +56,33 @@ type
|
|||
|
||||
Compiler* = ref object
|
||||
## A wrapper around the compiler's state
|
||||
|
||||
# The bytecode chunk where we write code to
|
||||
chunk: Chunk
|
||||
# The output of our parser (AST)
|
||||
ast: seq[ASTNode]
|
||||
# The current AST node we're looking at
|
||||
current: int
|
||||
# The current file being compiled (used only for
|
||||
# error reporting)
|
||||
file: string
|
||||
# Compile-time "simulation" of the stack at
|
||||
# runtime to load variables that have stack
|
||||
# behavior more efficiently
|
||||
names: seq[Name]
|
||||
# The current scope depth. If > 0, we're
|
||||
# in a local scope, otherwise it's global
|
||||
scopeDepth: int
|
||||
# The current function being compiled
|
||||
currentFunction: FunDecl
|
||||
# Are optimizations turned on?
|
||||
enableOptimizations*: bool
|
||||
# The current loop being compiled (used to
|
||||
# keep track of where to jump)
|
||||
currentLoop: Loop
|
||||
# The current module being compiled
|
||||
# (used to restrict access to statically
|
||||
# defined variables at compile time)
|
||||
currentModule: string
|
||||
# Each time a defer statement is
|
||||
# compiled, its code is emitted
|
||||
|
@ -130,7 +148,7 @@ proc done(self: Compiler): bool =
|
|||
proc error(self: Compiler, message: string) =
|
||||
## Raises a formatted CompileError exception
|
||||
var tok = self.getCurrentNode().token
|
||||
raise newException(CompileError, &"A fatal error occurred while compiling '{self.file}', line {tok.line} at '{tok.lexeme}' -> {message}")
|
||||
raise newException(CompileError, &"A fatal error occurred while compiling '{self.file}', module '{self.currentModule}' line {tok.line} at '{tok.lexeme}' -> {message}")
|
||||
|
||||
|
||||
proc step(self: Compiler): ASTNode =
|
||||
|
@ -229,7 +247,7 @@ proc patchJump(self: Compiler, offset: int) =
|
|||
of LongJumpIfFalsePop:
|
||||
self.chunk.code[offset] = JumpIfFalsePop.uint8()
|
||||
else:
|
||||
self.error(&"invalid opcode {self.chunk.code[offset]} in patchJump (This is an internal error and most likely a bug)")
|
||||
discard
|
||||
self.chunk.code.delete(offset + 1) # Discards the 24 bit integer
|
||||
let offsetArray = jump.toDouble()
|
||||
self.chunk.code[offset + 1] = offsetArray[0]
|
||||
|
@ -245,7 +263,7 @@ proc patchJump(self: Compiler, offset: int) =
|
|||
of JumpIfFalsePop:
|
||||
self.chunk.code[offset] = LongJumpIfFalsePop.uint8()
|
||||
else:
|
||||
self.error(&"invalid opcode {self.chunk.code[offset]} in patchJump (This is an internal error and most likely a bug)")
|
||||
discard
|
||||
let offsetArray = jump.toTriple()
|
||||
self.chunk.code[offset + 1] = offsetArray[0]
|
||||
self.chunk.code[offset + 2] = offsetArray[1]
|
||||
|
@ -496,7 +514,8 @@ proc deleteStatic(self: Compiler, name: IdentExpr,
|
|||
proc getStaticIndex(self: Compiler, name: IdentExpr): int =
|
||||
## Gets the predicted stack position of the given variable
|
||||
## if it is static, returns -1 if it is to be bound dynamically
|
||||
## or it does not exist at all
|
||||
## or it does not exist at all and returns -pos if the variable
|
||||
## is outside of the current local scope (is emitted as a closure)
|
||||
var i: int = self.names.high()
|
||||
for variable in reversed(self.names):
|
||||
if name.name.lexeme == variable.name.name.lexeme:
|
||||
|
@ -517,8 +536,12 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
|||
else:
|
||||
let index = self.getStaticIndex(node)
|
||||
if index != -1:
|
||||
self.emitByte(LoadFast) # Static name resolution, loads value at index in the stack. Very fast. Much wow.
|
||||
self.emitBytes(index.toTriple())
|
||||
if index >= 0:
|
||||
self.emitByte(LoadFast) # Static name resolution, loads value at index in the stack. Very fast. Much wow.
|
||||
self.emitBytes(index.toTriple())
|
||||
else:
|
||||
self.emitByte(LoadHeap)
|
||||
self.emitBytes(self.identifierConstant(node)) # Closed-over variable!
|
||||
else:
|
||||
self.emitByte(LoadName) # Resolves by name, at runtime, in a global hashmap. Slower
|
||||
self.emitBytes(self.identifierConstant(node))
|
||||
|
@ -878,7 +901,13 @@ proc statement(self: Compiler, node: ASTNode) =
|
|||
|
||||
proc funDecl(self: Compiler, node: FunDecl) =
|
||||
## Compiles function declarations
|
||||
# We store the current function...
|
||||
var function = self.currentFunction
|
||||
self.currentFunction = node
|
||||
self.declareName(node.name)
|
||||
# A function's code is just compiled linearly
|
||||
# and then jumped over
|
||||
let jmp = self.emitJump(JumpForwards)
|
||||
# Since the deferred array is a linear
|
||||
# sequence of instructions and we want
|
||||
# to keep track to whose function's each
|
||||
|
@ -903,11 +932,15 @@ proc funDecl(self: Compiler, node: FunDecl) =
|
|||
# just pop the instructions
|
||||
for i in countup(deferStart, self.deferred.len(), 1):
|
||||
self.deferred.delete(i)
|
||||
self.patchJump(jmp)
|
||||
# ... and restore it later!
|
||||
self.currentFunction = function
|
||||
|
||||
|
||||
proc classDecl(self: Compiler, node: ClassDecl) =
|
||||
## Compiles class declarations
|
||||
self.declareName(node.name)
|
||||
self.emitByte(MakeClass)
|
||||
self.blockStmt(BlockStmt(node.body))
|
||||
|
||||
|
||||
|
|
|
@ -11,11 +11,12 @@
|
|||
# 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.
|
||||
|
||||
## Low level bytecode implementation details
|
||||
import ast
|
||||
import ../../util/multibyte
|
||||
import errors
|
||||
|
||||
|
||||
import strutils
|
||||
import strformat
|
||||
|
||||
|
@ -56,67 +57,71 @@ type
|
|||
# used for more complex opcodes. All
|
||||
# arguments to opcodes (if they take
|
||||
# arguments) come from popping off the
|
||||
# stack
|
||||
# stack. Unsupported operations will
|
||||
# raise TypeError or ValueError exceptions
|
||||
# and never fail silently
|
||||
LoadConstant = 0u8, # Pushes constant at position x in the constant table onto the stack
|
||||
# Binary operators
|
||||
UnaryNegate, # Pushes the result of -x onto the stack
|
||||
BinaryAdd, # Pushes the result of a + b onto the stack
|
||||
BinarySubtract, # Pushes the result of a - b onto the stack
|
||||
BinaryDivide, # Pushes the result of a / b onto the stack (true division). The result is a float
|
||||
BinaryFloorDiv, # Pushes the result of a // b onto the stack (integer division). The result is always an integer
|
||||
BinaryMultiply, # Pushes the result of a * b onto the stack
|
||||
BinaryPow, # Pushes the result of a ** b (a to the power of b) onto the stack
|
||||
BinaryMod, # Pushes the result of a % b onto the stack (modulo division)
|
||||
BinaryShiftRight, # Pushes the result of a >> b (a with bits shifted b times to the right) onto the stack
|
||||
BinaryShiftLeft, # Pushes the result of a << b (a with bits shifted b times to the left) onto the stack
|
||||
BinaryXor, # Pushes the result of a ^ b (bitwise exclusive or) onto the stack
|
||||
BinaryOr, # Pushes the result of a | b (bitwise or) onto the stack
|
||||
BinaryAnd, # Pushes the result of a & b (bitwise and) onto the stack
|
||||
UnaryNot, # Pushes the result of ~x (bitwise not) onto the stack
|
||||
BinaryAs, # Pushes the result of a as b onto the stack (converts a to the type of b. Explicit support from a is required)
|
||||
BinaryIs, # Pushes the result of a is b onto the stack (true if a and b point to the same object, false otherwise)
|
||||
BinaryIsNot, # Pushes the result of not (a is b). This could be implemented in terms of BinaryIs, but it's more efficient this way
|
||||
BinaryOf, # Pushes the result of a of b onto the stack (true if a is a subclass of b, false otherwise)
|
||||
BinarySlice, # Perform slicing on supported objects (like "hello"[0:2], which yields "he"). The result is pushed onto the stack
|
||||
BinarySubscript, # Subscript operator, like "hello"[0] (which pushes 'h' onto the stack)
|
||||
# Binary comparison operators
|
||||
GreaterThan, # Pushes the result of a > b onto the stack
|
||||
LessThan, # Pushes the result of a < b onto the stack
|
||||
EqualTo, # Pushes the result of a == b onto the stack
|
||||
NotEqualTo, # Pushes the result of a != b onto the stack (optimization for not (a == b))
|
||||
GreaterOrEqual, # Pushes the result of a >= b onto the stack
|
||||
LessOrEqual, # Pushes the result of a <= b onto the stack
|
||||
# Logical operators
|
||||
LogicalNot,
|
||||
## Binary operators
|
||||
UnaryNegate, # Pushes the result of -x onto the stack
|
||||
BinaryAdd, # Pushes the result of a + b onto the stack
|
||||
BinarySubtract, # Pushes the result of a - b onto the stack
|
||||
BinaryDivide, # Pushes the result of a / b onto the stack (true division). The result is a float
|
||||
BinaryFloorDiv, # Pushes the result of a // b onto the stack (integer division). The result is always an integer
|
||||
BinaryMultiply, # Pushes the result of a * b onto the stack
|
||||
BinaryPow, # Pushes the result of a ** b (a to the power of b) onto the stack
|
||||
BinaryMod, # Pushes the result of a % b onto the stack (modulo division)
|
||||
BinaryShiftRight, # Pushes the result of a >> b (a with bits shifted b times to the right) onto the stack
|
||||
BinaryShiftLeft, # Pushes the result of a << b (a with bits shifted b times to the left) onto the stack
|
||||
BinaryXor, # Pushes the result of a ^ b (bitwise exclusive or) onto the stack
|
||||
BinaryOr, # Pushes the result of a | b (bitwise or) onto the stack
|
||||
BinaryAnd, # Pushes the result of a & b (bitwise and) onto the stack
|
||||
UnaryNot, # Pushes the result of ~x (bitwise not) onto the stack
|
||||
BinaryAs, # Pushes the result of a as b onto the stack (converts a to the type of b. Explicit support from a is required)
|
||||
BinaryIs, # Pushes the result of a is b onto the stack (true if a and b point to the same object, false otherwise)
|
||||
BinaryIsNot, # Pushes the result of not (a is b). This could be implemented in terms of BinaryIs, but it's more efficient this way
|
||||
BinaryOf, # Pushes the result of a of b onto the stack (true if a is a subclass of b, false otherwise)
|
||||
BinarySlice, # Perform slicing on supported objects (like "hello"[0:2], which yields "he"). The result is pushed onto the stack
|
||||
BinarySubscript, # Subscript operator, like "hello"[0] (which pushes 'h' onto the stack)
|
||||
## Binary comparison operators
|
||||
GreaterThan, # Pushes the result of a > b onto the stack
|
||||
LessThan, # Pushes the result of a < b onto the stack
|
||||
EqualTo, # Pushes the result of a == b onto the stack
|
||||
NotEqualTo, # Pushes the result of a != b onto the stack (optimization for not (a == b))
|
||||
GreaterOrEqual, # Pushes the result of a >= b onto the stack
|
||||
LessOrEqual, # Pushes the result of a <= b onto the stack
|
||||
## Logical operators
|
||||
LogicalNot, # Pushes true if
|
||||
LogicalAnd,
|
||||
LogicalOr,
|
||||
# Constants/singletons
|
||||
## Constant opcodes (each of them pushes a singleton on the stack)
|
||||
Nil,
|
||||
True,
|
||||
False,
|
||||
Nan,
|
||||
Inf,
|
||||
# Basic stack operations
|
||||
Pop,
|
||||
Push,
|
||||
PopN, # Pops N elements off the stack (optimization for exiting scopes and returning from functions)
|
||||
# Name resolution/handling
|
||||
## Basic stack operations
|
||||
Pop, # Pops an element off the stack and discards it
|
||||
Push, # Pushes x onto the stack
|
||||
PopN, # Pops x elements off the stack (optimization for exiting scopes and returning from functions)
|
||||
## Name resolution/handling
|
||||
LoadAttribute,
|
||||
DeclareName, # Declares a global dynamically bound name in the current scope
|
||||
LoadName, # Loads a dynamically bound variable
|
||||
LoadFast, # Loads a statically bound variable
|
||||
StoreName, # Sets/updates a dynamically bound variable's value
|
||||
StoreFast, # Sets/updates a statically bound variable's value
|
||||
DeleteName, # Unbinds a dynamically bound variable's name from the current scope
|
||||
DeleteFast, # Unbinds a statically bound variable's name from the current scope
|
||||
# Looping and jumping
|
||||
Jump, # Absolute and unconditional jump into the bytecode
|
||||
JumpIfFalse, # Jumps to an absolute index in the bytecode if the value at the top of the stack is falsey
|
||||
JumpIfTrue, # Jumps to an absolute index in the bytecode if the value at the top of the stack is truthy
|
||||
JumpIfFalsePop, # Like JumpIfFalse, but it also pops off the stack (regardless of truthyness). Optimization for if statements
|
||||
JumpForwards, # Relative, unconditional, positive jump in the bytecode
|
||||
JumpBackwards, # Relative, unconditional, negative jump into the bytecode
|
||||
Break, # Temporary opcode used to signal exiting out of loop
|
||||
DeclareName, # Declares a global dynamically bound name in the current scope
|
||||
LoadName, # Loads a dynamically bound variable
|
||||
LoadFast, # Loads a statically bound variable
|
||||
StoreName, # Sets/updates a dynamically bound variable's value
|
||||
StoreFast, # Sets/updates a statically bound variable's value
|
||||
DeleteName, # Unbinds a dynamically bound variable's name from the current scope
|
||||
DeleteFast, # Unbinds a statically bound variable's name from the current scope
|
||||
LoadHeap, # Loads a closed-over variable
|
||||
StoreHeap, # Stores a closed-over variable
|
||||
## Looping and jumping
|
||||
Jump, # Absolute and unconditional jump into the bytecode
|
||||
JumpIfFalse, # Jumps to an absolute index in the bytecode if the value at the top of the stack is falsey
|
||||
JumpIfTrue, # Jumps to an absolute index in the bytecode if the value at the top of the stack is truthy
|
||||
JumpIfFalsePop, # Like JumpIfFalse, but it also pops off the stack (regardless of truthyness). Optimization for if statements
|
||||
JumpForwards, # Relative, unconditional, positive jump in the bytecode
|
||||
JumpBackwards, # Relative, unconditional, negative jump into the bytecode
|
||||
Break, # Temporary opcode used to signal exiting out of loop
|
||||
## Long variants of jumps (they use a 24-bit operand instead of a 16-bit one)
|
||||
LongJump,
|
||||
LongJumpIfFalse,
|
||||
|
@ -124,32 +129,32 @@ type
|
|||
LongJumpIfFalsePop,
|
||||
LongJumpForwards,
|
||||
LongJumpBackwards,
|
||||
# Functions
|
||||
MakeFunction,
|
||||
Call,
|
||||
Return
|
||||
# Exception handling
|
||||
Raise,
|
||||
ReRaise, # Re-raises active exception
|
||||
BeginTry,
|
||||
FinishTry,
|
||||
# Generators
|
||||
## Functions
|
||||
Call, # Calls a callable object
|
||||
Return # Returns from the current function
|
||||
## Exception handling
|
||||
Raise, # Raises exception x
|
||||
ReRaise, # Re-raises active exception
|
||||
BeginTry, # Initiates an exception handling context
|
||||
FinishTry, # Closes the current exception handling context
|
||||
## Generators
|
||||
Yield,
|
||||
# Coroutines
|
||||
## Coroutines
|
||||
Await,
|
||||
# Collection literals
|
||||
## Collection literals
|
||||
BuildList,
|
||||
BuildDict,
|
||||
BuildSet,
|
||||
BuildTuple,
|
||||
# Misc
|
||||
## Misc
|
||||
Assert,
|
||||
MakeClass
|
||||
|
||||
|
||||
# We group instructions by their operation/operand types for easier handling when debugging
|
||||
|
||||
# Simple instructions encompass:
|
||||
# - Instructions that push onto/pop off the stack unconditionally (True, False, PopN, Pop, etc.)
|
||||
# - Instructions that push onto/pop off the stack unconditionally (True, False, Pop, etc.)
|
||||
# - Unary and binary operators
|
||||
const simpleInstructions* = {Return, BinaryAdd, BinaryMultiply,
|
||||
BinaryDivide, BinarySubtract,
|
||||
|
@ -162,7 +167,8 @@ const simpleInstructions* = {Return, BinaryAdd, BinaryMultiply,
|
|||
BinaryIs, BinaryAs, GreaterOrEqual,
|
||||
LessOrEqual, BinaryOr, BinaryAnd,
|
||||
UnaryNot, BinaryFloorDiv, BinaryOf, Raise,
|
||||
ReRaise, BeginTry, FinishTry, Yield, Await}
|
||||
ReRaise, BeginTry, FinishTry, Yield, Await,
|
||||
MakeClass}
|
||||
|
||||
# Constant instructions are instructions that operate on the bytecode constant table
|
||||
const constantInstructions* = {LoadConstant, DeclareName, LoadName, StoreName, DeleteName}
|
||||
|
@ -171,7 +177,7 @@ const constantInstructions* = {LoadConstant, DeclareName, LoadName, StoreName, D
|
|||
# of 24 bit integers
|
||||
const stackTripleInstructions* = {Call, StoreFast, DeleteFast, LoadFast}
|
||||
|
||||
# Stack Double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
|
||||
# Stack double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
|
||||
# of 16 bit integers
|
||||
const stackDoubleInstructions* = {}
|
||||
|
||||
|
@ -180,8 +186,7 @@ const argumentDoubleInstructions* = {PopN, }
|
|||
|
||||
# Jump instructions jump at relative or absolute bytecode offsets
|
||||
const jumpInstructions* = {JumpIfFalse, JumpIfFalsePop, JumpForwards, JumpBackwards,
|
||||
LongJumpIfFalse, LongJumpIfFalsePop,
|
||||
LongJumpForwards,
|
||||
LongJumpIfFalse, LongJumpIfFalsePop, LongJumpForwards,
|
||||
LongJumpBackwards, JumpIfTrue, LongJumpIfTrue}
|
||||
|
||||
# Collection instructions push a built-in collection type onto the stack
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# limitations under the License.
|
||||
|
||||
type
|
||||
## Nim exceptions for internal JAPL failures
|
||||
NimVMException* = object of CatchableError
|
||||
LexingError* = object of NimVMException
|
||||
ParseError* = object of NimVMException
|
||||
|
|
|
@ -26,7 +26,7 @@ type
|
|||
# Other singleton types
|
||||
Infinity, NotANumber, Nil
|
||||
|
||||
# Control-flow statements
|
||||
# Control flow statements
|
||||
If, Else,
|
||||
|
||||
# Looping statements
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Makes our short integers platform-independent (big vs little endian)
|
||||
## Utilities to convert from/to our 16-bit and 24-bit representations
|
||||
## of numbers
|
||||
|
||||
|
||||
proc toDouble*(input: int | uint | uint16): array[2, uint8] =
|
||||
|
|
Loading…
Reference in New Issue