Further work on porting the bytecode target

This commit is contained in:
Mattia Giambirtone 2023-11-21 09:17:43 +01:00
parent 6181c49f1f
commit 83051d67f8
Signed by: nocturn9x
GPG Key ID: 8270F9F467971E59
12 changed files with 534 additions and 138 deletions

View File

@ -1,10 +1,46 @@
import frontend/compiler/typechecker
import backend/bytecode/opcodes
import backend/bytecode/tooling/multibyte
import errors
import std/strutils
import std/parseutils
import std/tables
import std/strformat
type
FunctionWrapper = ref object
## A wrapper around a typed function
## declaration. This is necessary to
## carry bytecode-specific information
## regarding this function along with
## the typed declaration itself
decl: TypedFunDecl
location: int
BytecodeGenerator* = ref object
## A bytecode generator
# The piece of code we compile into
chunk: Chunk
# The current size of the call
# stack (which is always known
# statically)
stackSize: int
# Stores the position of all jumps
jumps: seq[tuple[patched: bool, offset: int]]
# Metadata regarding function locations (used to construct
# the debugging fields in the resulting bytecode)
functions: seq[tuple[start, stop, pos: int, fn: Name]]
# Used for error reporting
currentFile: string
currentNode: TypedNode
# The typechecker used to validate the peon code we're generating
# bytecode for
typeChecker: TypeChecker
proc newBytecodeGenerator*: BytecodeGenerator =
@ -13,18 +49,384 @@ proc newBytecodeGenerator*: BytecodeGenerator =
result = BytecodeGenerator()
proc generateExpression(self: BytecodeGenerator, node: TypedExpr) =
## Emits code for expressions
proc generateExpression(self: BytecodeGenerator, expression: TypedExpr)
proc generate*(self: BytecodeGenerator, compiled: seq[TypedNode]): Chunk =
proc error(self: BytecodeGenerator, msg: string, typedNode: TypedNode = nil) {.raises: [CodeGenError].} =
## Raises a generic peon exception
var typedNode = typedNode
var file = self.currentFile
if typedNode.isNil():
typedNode = self.currentNode
if file == "" and typedNode.node.isDecl():
file = TypedDecl(typedNode).name.owner.ident.token.lexeme
raise CodeGenError(msg: msg, line: typedNode.node.token.line, file: file)
proc emitByte(self: BytecodeGenerator, byt: OpCode | uint8, line: int) {.inline.} =
## Emits a single byte, writing it to
## the current chunk being compiled
self.chunk.write(uint8(byt), line)
proc emitBytes(self: BytecodeGenerator, bytarr: openarray[OpCode | uint8], line: int) {.inline.} =
## Handy helper method to write arbitrary bytes into
## the current chunk, calling emitByte on each of its
## elements
for b in bytarr:
self.emitByte(b, line)
proc makeConstant(self: BytecodeGenerator, value: TypedExpr): array[3, uint8] =
## Adds a constant to the current chunk's constant table
## and returns its index as a 3-byte array of uint8s
var lit: string
if value.kind.kind == Integer:
lit = value.node.token.lexeme
if lit.contains("'"):
var idx = lit.high()
while lit[idx] != '\'':
lit = lit[0..^2]
dec(idx)
lit = lit[0..^2]
case value.kind.kind:
of Integer:
case value.kind.size:
of Tiny:
result = self.chunk.writeConstant([uint8(parseInt(lit))])
of Short:
result = self.chunk.writeConstant(parseInt(lit).toDouble())
of Long:
result = self.chunk.writeConstant(parseInt(lit).toQuad())
of LongLong:
if not value.kind.signed:
result = self.chunk.writeConstant(parseInt(lit).toLong())
else:
result = self.chunk.writeConstant(parseBiggestUInt(lit).toLong())
of String:
result = self.chunk.writeConstant(value.node.token.lexeme[1..^1].toBytes())
of Float:
case value.kind.width:
of Half:
var f: float = 0.0
discard parseFloat(value.node.token.lexeme, f)
result = self.chunk.writeConstant(cast[array[4, uint8]](float32(f)))
of Full:
var f: float = 0.0
discard parseFloat(value.node.token.lexeme, f)
result = self.chunk.writeConstant(cast[array[8, uint8]](f))
else:
discard
proc emitConstant(self: BytecodeGenerator, expression: TypedExpr) =
## Emits a constant instruction along
## with its operand
let
typ = expression.kind
node = expression.node
case typ.kind:
of Integer:
case typ.size:
of LongLong:
if typ.signed:
self.emitByte(LoadInt64, node.token.line)
else:
self.emitByte(LoadUInt64, node.token.line)
of Long:
if typ.signed:
self.emitByte(LoadInt32, node.token.line)
else:
self.emitByte(LoadUInt32, node.token.line)
of Short:
if typ.signed:
self.emitByte(LoadInt16, node.token.line)
else:
self.emitByte(LoadUInt16, node.token.line)
of Tiny:
if typ.signed:
self.emitByte(LoadInt8, node.token.line)
else:
self.emitByte(LoadUInt8, node.token.line)
of String:
self.emitByte(LoadString, node.token.line)
let str = LiteralExpr(node).literal.lexeme
if str.len() >= 16777216:
self.error("string constants cannot be larger than 16777215 bytes", expression)
self.emitBytes((str.len() - 2).toTriple(), node.token.line)
of Float:
case typ.width:
of Half:
self.emitByte(LoadFloat32, node.token.line)
of Full:
self.emitByte(LoadFloat64, node.token.line)
else:
discard # TODO
self.emitBytes(self.makeConstant(expression), node.token.line)
proc setJump(self: BytecodeGenerator, offset: int, jmp: array[3, uint8]) =
## Sets a jump at the given
## offset to the given value
self.chunk.code[offset + 1] = jmp[0]
self.chunk.code[offset + 2] = jmp[1]
self.chunk.code[offset + 3] = jmp[2]
proc setJump(self: BytecodeGenerator, offset: int, jmp: seq[uint8]) =
## Sets a jump at the given
## offset to the given value
self.chunk.code[offset + 1] = jmp[0]
self.chunk.code[offset + 2] = jmp[1]
self.chunk.code[offset + 3] = jmp[2]
proc emitJump(self: BytecodeGenerator, opcode: OpCode, line: int): int =
## Emits a dummy jump offset to be patched later
## and returns a unique identifier for that jump
## to be passed to patchJump
self.emitByte(opcode, line)
self.jumps.add((patched: false, offset: self.chunk.code.high()))
self.emitBytes(0.toTriple(), line)
result = self.jumps.high()
proc patchJump(self: BytecodeGenerator, offset: int) =
## Patches a previously emitted relative
## jump using emitJump
var jump: int = self.chunk.code.len() - self.jumps[offset].offset
if jump < 0:
self.error("jump size cannot be negative (This is an internal error and most likely a bug)")
if jump > 16777215:
# TODO: Emit consecutive jumps using insertAt
self.error("cannot jump more than 16777215 instructions")
if jump > 0:
self.setJump(self.jumps[offset].offset, (jump - 4).toTriple())
self.jumps[offset].patched = true
proc handleBuiltinFunction(self: BytecodeGenerator, fn: FunctionWrapper, args: seq[TypedExpr], line: int) =
## Emits instructions for builtin functions
## such as addition or subtraction
if fn.decl.name.valueType.builtinOp notin ["LogicalOr", "LogicalAnd"]:
if len(args) == 2:
self.generateExpression(args[1])
self.generateExpression(args[0])
elif len(args) == 1:
self.generateExpression(args[0])
const codes: Table[string, OpCode] = {"Negate": Negate,
"NegateFloat32": NegateFloat32,
"NegateFloat64": NegateFloat64,
"Add": Add,
"Subtract": Subtract,
"Divide": Divide,
"Multiply": Multiply,
"SignedDivide": SignedDivide,
"AddFloat64": AddFloat64,
"SubtractFloat64": SubtractFloat64,
"DivideFloat64": DivideFloat64,
"MultiplyFloat64": MultiplyFloat64,
"AddFloat32": AddFloat32,
"SubtractFloat32": SubtractFloat32,
"DivideFloat32": DivideFloat32,
"MultiplyFloat32": MultiplyFloat32,
"Pow": Pow,
"SignedPow": SignedPow,
"PowFloat32": PowFloat32,
"PowFloat64": PowFloat64,
"Mod": Mod,
"SignedMod": SignedMod,
"ModFloat32": ModFloat32,
"ModFloat64": ModFloat64,
"Or": Or,
"And": And,
"Xor": Xor,
"Not": Not,
"LShift": LShift,
"RShift": RShift,
"Equal": Equal,
"NotEqual": NotEqual,
"LessThan": LessThan,
"GreaterThan": GreaterThan,
"LessOrEqual": LessOrEqual,
"GreaterOrEqual": GreaterOrEqual,
"SignedLessThan": SignedLessThan,
"SignedGreaterThan": SignedGreaterThan,
"SignedLessOrEqual": SignedLessOrEqual,
"SignedGreaterOrEqual": SignedGreaterOrEqual,
"Float32LessThan": Float32LessThan,
"Float32GreaterThan": Float32GreaterThan,
"Float32LessOrEqual": Float32LessOrEqual,
"Float32GreaterOrEqual": Float32GreaterOrEqual,
"Float64LessThan": Float64LessThan,
"Float64GreaterThan": Float64GreaterThan,
"Float64LessOrEqual": Float64LessOrEqual,
"Float64GreaterOrEqual": Float64GreaterOrEqual,
"PrintString": PrintString,
"SysClock64": SysClock64,
"LogicalNot": LogicalNot,
"NegInf": LoadNInf,
"Identity": Identity
}.toTable()
if fn.decl.name.valueType.builtinOp == "print":
let typ = args[0].kind
case typ.kind:
of Integer:
case typ.size:
of LongLong:
if typ.signed:
self.emitByte(PrintInt64, line)
else:
self.emitByte(PrintUInt64, line)
of Long:
if typ.signed:
self.emitByte(PrintInt32, line)
else:
self.emitByte(PrintUInt32, line)
of Short:
if typ.signed:
self.emitByte(PrintInt16, line)
else:
self.emitByte(PrintUInt16, line)
of Tiny:
if typ.signed:
self.emitByte(PrintInt8, line)
else:
self.emitByte(PrintUInt8, line)
of Float:
case typ.width:
of Full:
self.emitByte(PrintFloat64, line)
of Half:
self.emitByte(PrintFloat32, line)
of String:
self.emitByte(PrintString, line)
of Boolean:
self.emitByte(PrintBool, line)
of TypeKind.Nan:
self.emitByte(PrintNan, line)
of TypeKind.Infinity:
self.emitByte(PrintInf, line)
of Function:
self.emitByte(LoadString, line)
var loc: string = fn.location.toHex()
while loc[0] == '0' and loc.len() > 1:
loc = loc[1..^1]
var str: string
if typ.isLambda:
str = &"anonymous function at 0x{loc}"
else:
str = &"function '{FunDecl(typ.fun).name.token.lexeme}' at 0x{loc}"
self.emitBytes(str.len().toTriple(), line)
self.emitBytes(self.chunk.writeConstant(str.toBytes()), line)
self.emitByte(PrintString, line)
else:
self.error(&"invalid type {self.typechecker.stringify(typ)} for built-in 'print'", args[0])
return
if fn.decl.name.valueType.builtinOp in codes:
self.emitByte(codes[fn.decl.name.valueType.builtinOp], line)
return
# Some builtin operations are slightly more complex
# so we handle them separately
case fn.decl.name.valueType.builtinOp:
of "LogicalOr":
self.generateExpression(args[0])
let jump = self.emitJump(JumpIfTrue, line)
self.generateExpression(args[1])
self.patchJump(jump)
of "LogicalAnd":
self.generateExpression(args[0])
let jump = self.emitJump(JumpIfFalseOrPop, line)
self.generateExpression(args[1])
self.patchJump(jump)
of "cast":
# Type casts are a merely compile-time construct:
# they don't produce any code at runtime because
# the underlying data representation does not change!
# The only reason why there's a "cast" pragma is to
# make it so that the peon stub can have no body
discard
else:
self.error(&"unknown built-in: '{fn.decl.name.valueType.builtinOp}'")
proc patchReturnAddress(self: BytecodeGenerator, pos: int) =
## Patches the return address of a function
## call
let address = self.chunk.code.len().toLong()
self.chunk.consts[pos] = address[0]
self.chunk.consts[pos + 1] = address[1]
self.chunk.consts[pos + 2] = address[2]
self.chunk.consts[pos + 3] = address[3]
self.chunk.consts[pos + 4] = address[4]
self.chunk.consts[pos + 5] = address[5]
self.chunk.consts[pos + 6] = address[6]
self.chunk.consts[pos + 7] = address[7]
proc generateExpression(self: BytecodeGenerator, expression: TypedExpr) =
## Emits code for expressions
let
typ = expression.kind
node = expression.node
case typ.kind:
of Integer, Float:
# No need to do any input validation here: the typechecker
# has graciously done all the work for us! :)
self.emitConstant(expression)
of Infinity:
if typ.positive:
self.emitByte(LoadInf, node.token.line)
else:
self.emitByte(LoadNInf, node.token.line)
of NaN:
self.emitByte(LoadNaN, node.token.line)
else:
discard # TODO
proc beginProgram(self: BytecodeGenerator): int =
## Emits boilerplate code to set up
## a peon program
self.emitByte(LoadUInt64, 1)
# The initial jump address is always the same
self.emitBytes(self.chunk.writeConstant(12.toLong()), 1)
self.emitByte(LoadUInt64, 1)
# We emit a dummy return address which is patched later
self.emitBytes(self.chunk.writeConstant(0.toLong()), 1)
result = self.chunk.consts.len() - 8
self.emitByte(Call, 1)
self.emitBytes(0.toTriple(), 1)
proc endProgram(self: BytecodeGenerator, pos: int) =
## Emits boilerplate code to tear down
## a peon program
self.emitByte(OpCode.Return, self.currentNode.node.token.line)
# Entry point has no return value
self.emitByte(0, self.currentNode.node.token.line)
# Patch the return address now that we know the boundaries
# of the function
self.patchReturnAddress(pos)
proc generate*(self: BytecodeGenerator, compiled: seq[TypedNode], typeChecker: TypeChecker): Chunk =
## Turn the given compilation output
## into a bytecode chunk
self.chunk = newChunk()
self.typeChecker = typeChecker
let offset = self.beginProgram()
for typedNode in compiled:
self.currentNode = typedNode
let currentFile = self.currentFile
if self.currentNode.node.isDecl():
self.currentFile = TypedDecl(typedNode).name.ident.token.lexeme
case typedNode.node.kind:
of exprStmt:
self.generateExpression(TypedExpr(typedNode))
self.generateExpression(TypedExprStmt(typedNode).expression)
self.emitByte(Pop, typedNode.node.token.line)
else:
discard # TODO
self.currentFile = currentFile
self.endProgram(offset)
result = self.chunk

View File

@ -39,6 +39,7 @@ type
commit*: string
compileDate*: int
chunk*: Chunk
size*: int
SerializationError* = ref object of PeonException
@ -221,6 +222,7 @@ proc loadBytes*(self: BytecodeSerializer, stream: seq[byte]): SerializedBytecode
discard self.newBytecodeSerializer()
new(result)
result.chunk = newChunk()
result.size = stream.len()
self.chunk = result.chunk
var stream = stream
try:

View File

@ -333,22 +333,11 @@ proc collect(self: var PeonVM) =
# Implementation of the peon VM
proc initCache*(self: var PeonVM) =
## Initializes the VM's
## singletons cache
self.cache[0] = 0x0 # False
self.cache[1] = 0x1 # True
self.cache[2] = 0x2 # Nil
self.cache[3] = 0x3 # Positive inf
self.cache[4] = 0x4 # Negative inf
self.cache[5] = 0x5 # NaN
proc newPeonVM*: PeonVM =
## Initializes a new, blank VM
## for executing Peon bytecode
result.ip = 0
result.initCache()
result.gc = newPeonGC()
result.frames = @[]
result.operands = @[]
@ -363,15 +352,16 @@ func getNil*(self: var PeonVM): uint64 = self.cache[2]
func getBool*(self: var PeonVM, value: bool): uint64 =
if value:
return self.cache[1]
return self.cache[0]
return 0
return 1
func getInf*(self: var PeonVM, positive: bool): uint64 =
if positive:
return self.cache[3]
return self.cache[4]
return cast[uint64](Inf)
return cast[uint64](-Inf)
func getNan*(self: var PeonVM): uint64 = self.cache[5]
func getNan*(self: var PeonVM): uint64 = cast[uint64](NaN)
# Thanks to nim's *genius* idea of making x > y a template
@ -1042,11 +1032,11 @@ proc run*(self: var PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[], repl:
try:
self.dispatch()
except Defect as e:
stderr.writeLine(&"Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}")
stderr.writeLine(&"VM: Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}")
except CatchableError as e:
stderr.writeLine(&"Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}")
stderr.writeLine(&"VM: Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}")
except NilAccessDefect:
stderr.writeLine(&"Memory Access Violation (bytecode offset {self.ip}): SIGSEGV")
stderr.writeLine(&"VM: Memory Access Violation (bytecode offset {self.ip}): SIGSEGV")
quit(1)
if not repl:
# We clean up after ourselves!

View File

@ -83,7 +83,8 @@ Options
-s, --string Run the given string as if it were a file (the filename is set to '<string>')
The following options are specific to the 'bytecode' backend:
-n, --noDump Do not dump bytecode files to the source directory
-n, --noDump Do not dump bytecode files to the source directory. Note that
no files are dumped when using -s/--string
--breakpoints Set debugging breakpoints at the given bytecode offsets.
Input should be a comma-separated list of positive integers
(spacing is irrelevant). Only works if peon was compiled with

View File

@ -19,3 +19,6 @@ type
## peon failure (not to be used directly)
file*: string # The file where the error occurred
line*: int # The line where the error occurred
CodeGenError* = ref object of PeonException
## An exception for a code generation failure

View File

@ -313,11 +313,11 @@ proc toIntrinsic(name: string): Type =
return Type(kind: Byte, intrinsic: true)
elif name in ["char", "c"]:
return Type(kind: Char, intrinsic: true)
elif name == "nan":
elif name == "NaN":
return Type(kind: TypeKind.Nan, intrinsic: true)
elif name == "nil":
return Type(kind: Nil, intrinsic: true)
elif name == "inf":
elif name == "Inf":
return Type(kind: Infinity, intrinsic: true, positive: true)
elif name == "NegInf":
return Type(kind: Infinity, intrinsic: true)
elif name == "bool":
return Type(kind: Boolean, intrinsic: true)
@ -342,6 +342,10 @@ proc infer(self: TypeChecker, node: LiteralExpr): TypedExpr =
return newTypedExpr(node, "bool".toIntrinsic())
of strExpr:
return newTypedExpr(node, "string".toIntrinsic())
of nanExpr:
return newTypedExpr(node, "NaN".toIntrinsic())
of infExpr:
return newTypedExpr(node, "Inf".toIntrinsic())
of intExpr, binExpr, octExpr, hexExpr:
let size = node.token.lexeme.split("'")
if size.len() == 1:
@ -442,9 +446,10 @@ proc compare(self: TypeChecker, a, b: Type): bool =
# b has a parent and a doesn't
if a.parent.isNil() and not b.parent.isNil():
return false
if not self.compare(a.parent, b.parent):
# Different parent
return false
if not a.parent.isNil() and not b.parent.isNil():
if not self.compare(a.parent, b.parent):
# Different parent
return false
# Then the field names
var fields: HashSet[string] = initHashSet[string]()
for fieldName in a.fields.keys():
@ -463,7 +468,7 @@ proc compare(self: TypeChecker, a, b: Type): bool =
# far as the type system is concerned
return true
of Boolean, Infinity, Any,
Auto, Char, Byte, String, Nil:
Auto, Char, Byte, String:
return true
of Integer:
return a.size == b.size and a.signed == b.signed
@ -517,75 +522,82 @@ proc compare(self: TypeChecker, a, b: Type): bool =
return false
proc compare*(self: TypeChecker, a, b: Name): bool =
## Compares two names. In addition to checking
## that their types match, this function makes
## sure the two given objects actually come from
## the same module (and so are literally the same
## exact object)
if not self.compare(a.valueType, b.valueType):
return false
return a.owner == b.owner
proc literal(self: TypeChecker, node: LiteralExpr): TypedExpr =
case node.kind:
of trueExpr, falseExpr:
result = self.infer(node)
of strExpr:
of trueExpr, falseExpr, strExpr, infExpr, nanExpr:
result = self.infer(node)
of intExpr:
let y = IntExpr(node)
result = self.infer(y)
result = self.infer(node)
if result.kind.kind == Integer:
var x: int
try:
discard parseInt(y.literal.lexeme, x)
discard parseInt(node.literal.lexeme, x)
except ValueError:
self.error("integer value out of range")
else:
var x: uint64
try:
discard parseBiggestUInt(y.literal.lexeme, x)
discard parseBiggestUInt(node.literal.lexeme, x)
except ValueError:
self.error("integer value out of range")
of hexExpr:
var x: int
var y = HexExpr(node)
result = self.infer(y)
result = self.infer(node)
try:
discard parseHex(y.literal.lexeme, x)
discard parseHex(node.literal.lexeme, x)
except ValueError:
self.error("integer value out of range")
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
pos: (start: y.token.pos.start,
stop: y.token.pos.start + len($x)),
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
let node = newIntExpr(Token(lexeme: $x, line: node.token.line,
pos: (start: node.token.pos.start,
stop: node.token.pos.start + len($x)),
relPos: (start: node.token.relPos.start, stop: node.token.relPos.start + len($x))
)
)
result.node = node
of binExpr:
var x: int
var y = BinExpr(node)
result = self.infer(y)
result = self.infer(node)
try:
discard parseBin(y.literal.lexeme, x)
discard parseBin(node.literal.lexeme, x)
except ValueError:
self.error("integer value out of range")
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
pos: (start: y.token.pos.start,
stop: y.token.pos.start + len($x)),
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
let node = newIntExpr(Token(lexeme: $x, line: node.token.line,
pos: (start: node.token.pos.start,
stop: node.token.pos.start + len($x)),
relPos: (start: node.token.relPos.start, stop: node.token.relPos.start + len($x))
)
)
result.node = node
of octExpr:
var x: int
var y = OctExpr(node)
result = self.infer(y)
result = self.infer(node)
try:
discard parseOct(y.literal.lexeme, x)
discard parseOct(node.literal.lexeme, x)
except ValueError:
self.error("integer value out of range")
let node = newIntExpr(Token(lexeme: $x, line: y.token.line,
pos: (start: y.token.pos.start,
stop: y.token.pos.start + len($x)),
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
let node = newIntExpr(Token(lexeme: $x, line: node.token.line,
pos: (start: node.token.pos.start,
stop: node.token.pos.start + len($x)),
relPos: (start: node.token.relPos.start, stop: node.token.relPos.start + len($x))
)
)
result.node = node
of floatExpr:
var x: float
var y = FloatExpr(node)
result = self.infer(y)
result = self.infer(node)
try:
discard parseFloat(y.literal.lexeme, x)
discard parseFloat(node.literal.lexeme, x)
except ValueError:
self.error("floating point value out of range")
else:
@ -680,7 +692,7 @@ proc stringify*(self: TypeChecker, typ: Type): string =
if typ.isNil():
return "void"
case typ.kind:
of Char, Byte, String, Nil, TypeKind.Nan,
of Char, Byte, String, TypeKind.Nan,
Auto, Any:
result &= ($typ.kind).toLowerAscii()
of CustomType:
@ -1001,6 +1013,10 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
for (a, b) in zip(result.valueType.args, sig):
if not a.kind.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node)
of CustomType:
for (a, b) in zip(result.valueType.fields.values().toSeq(), sig):
if not a.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node)
else:
# TODO
discard
@ -1705,10 +1721,12 @@ proc validate(self: TypeChecker, node: ASTNode): TypedNode =
case node.kind:
of binaryExpr, unaryExpr, NodeKind.genericExpr, identExpr,
groupingExpr, callExpr, intExpr, floatExpr, octExpr,
binExpr, hexExpr, trueExpr, falseExpr:
binExpr, hexExpr, trueExpr, falseExpr, nanExpr, infExpr:
result = self.expression(Expression(node))
of exprStmt:
result = self.expression(ExprStmt(node).expression)
let statement = ExprStmt(node)
result = self.expression(statement.expression)
result = TypedExprStmt(node: statement, expression: TypedExpr(result))
of NodeKind.whileStmt:
result = self.whileStmt(WhileStmt(node))
of NodeKind.blockStmt:

View File

@ -35,7 +35,6 @@ type
Auto,
Byte,
Char,
Nil,
CustomType,
EnumEntry,
Reference,
@ -215,6 +214,9 @@ type
TypedStmt* = ref object of TypedNode
## A typed statement node
TypedExprStmt* = ref object of TypedStmt
expression*: TypedExpr
TypedBlockStmt* = ref object of TypedStmt
## A typed block statement
body*: seq[TypedNode]

View File

@ -75,6 +75,8 @@ type
hexExpr,
octExpr,
binExpr,
nanExpr,
infExpr,
identExpr, # Identifier
pragmaExpr,
refExpr,
@ -124,17 +126,6 @@ type
# although arguably less useful (and probably significantly slower than bigints)
literal*: Token
IntExpr* = ref object of LiteralExpr
OctExpr* = ref object of LiteralExpr
HexExpr* = ref object of LiteralExpr
BinExpr* = ref object of LiteralExpr
FloatExpr* = ref object of LiteralExpr
StrExpr* = ref object of LiteralExpr
CharExpr* = ref object of LiteralExpr
TrueExpr* = ref object of LiteralExpr
FalseExpr* = ref object of LiteralExpr
IdentExpr* = ref object of Expression
name*: Token
depth*: int
@ -314,12 +305,18 @@ proc isConst*(self: ASTNode): bool =
## constants
case self.kind:
of intExpr, hexExpr, binExpr, octExpr, strExpr, falseExpr, trueExpr,
floatExpr:
floatExpr, nanExpr, infExpr:
return true
else:
return false
proc isDecl*(self: ASTNode): bool =
## Returns true if the given AST node
## represents a declaration
return self.kind in [typeDecl, funDecl, varDecl]
## AST node constructors
proc newASTNode*(kind: NodeKind, token: Token): ASTNode =
## Initializes a new generic ASTNode object
@ -374,50 +371,49 @@ proc newSwitchStmt*(switch: Expression, branches: seq[tuple[cond: Expression, bo
result.default = default
proc newIntExpr*(literal: Token): IntExpr =
result = IntExpr(kind: intExpr)
proc newIntExpr*(literal: Token): LiteralExpr =
result = LiteralExpr(kind: intExpr)
result.literal = literal
result.token = literal
proc newOctExpr*(literal: Token): OctExpr =
result = OctExpr(kind: octExpr)
proc newOctExpr*(literal: Token): LiteralExpr =
result = LiteralExpr(kind: octExpr)
result.literal = literal
result.token = literal
proc newHexExpr*(literal: Token): HexExpr =
result = HexExpr(kind: hexExpr)
proc newHexExpr*(literal: Token): LiteralExpr =
result = LiteralExpr(kind: hexExpr)
result.literal = literal
result.token = literal
proc newBinExpr*(literal: Token): BinExpr =
result = BinExpr(kind: binExpr)
proc newBinExpr*(literal: Token): LiteralExpr =
result = LiteralExpr(kind: binExpr)
result.literal = literal
result.token = literal
proc newFloatExpr*(literal: Token): FloatExpr =
result = FloatExpr(kind: floatExpr)
proc newFloatExpr*(literal: Token): LiteralExpr =
result = LiteralExpr(kind: floatExpr)
result.literal = literal
result.token = literal
proc newTrueExpr*(token: Token): LiteralExpr = LiteralExpr(kind: trueExpr,
token: token, literal: token)
proc newFalseExpr*(token: Token): LiteralExpr = LiteralExpr(kind: falseExpr,
token: token, literal: token)
proc newTrueExpr*(token: Token): LiteralExpr = LiteralExpr(kind: trueExpr, token: token, literal: token)
proc newFalseExpr*(token: Token): LiteralExpr = LiteralExpr(kind: falseExpr, token: token, literal: token)
proc newNanExpr*(token: Token): LiteralExpr = LiteralExpr(kind: nanExpr, token: token, literal: token)
proc newInfExpr*(token: Token): LiteralExpr = LiteralExpr(kind: infExpr, token: token, literal: token)
proc newStrExpr*(literal: Token): StrExpr =
result = StrExpr(kind: strExpr)
proc newStrExpr*(literal: Token): LiteralExpr =
result = LiteralExpr(kind: strExpr)
result.literal = literal
result.token = literal
proc newCharExpr*(literal: Token): CharExpr =
result = CharExpr(kind: charExpr)
proc newCharExpr*(literal: Token): LiteralExpr =
result = LiteralExpr(kind: charExpr)
result.literal = literal
result.token = literal

View File

@ -377,6 +377,10 @@ proc primary(self: Parser): Expression =
result = newBinExpr(self.step())
of String:
result = newStrExpr(self.step())
of TokenType.Inf:
result = newInfExpr(self.step())
of TokenType.Nan:
result = newNanExpr(self.step())
of Function:
discard self.step()
result = Expression(self.funDecl(isLambda=true))

View File

@ -41,7 +41,7 @@ type
# Literal types
Integer, Float, String, Identifier,
Binary, Octal, Hex, Char
Binary, Octal, Hex, Char, Nan, Inf
# Brackets, parentheses,
# operators and others

View File

@ -111,7 +111,14 @@ proc runFile(filename: string, fromString: bool = false, dump: bool = true, brea
if debugTypeChecker:
styledEcho fgCyan, "Typechecker output:"
for typedNode in typedNodes:
styledEcho fgGreen, &"\t{typedNode.node} -> {typeChecker.stringify(typedNode)}\n"
case typedNode.node.kind:
of exprStmt:
# *Technically* an expression statement has no type, but that isn't really useful for debug
# purposes, so we print the type of the expression within it instead
let exprNode = TypedExprStmt(typedNode).expression
styledEcho fgGreen, &"\t{typedNode.node} -> {exprNode.node} -> {typeChecker.stringify(TypedExprStmt(typedNode).expression)}\n"
else:
styledEcho fgGreen, &"\t{typedNode.node} -> {typeChecker.stringify(typedNode)}\n"
case backend:
of PeonBackend.Bytecode:
var
@ -122,22 +129,24 @@ proc runFile(filename: string, fromString: bool = false, dump: bool = true, brea
chunk: Chunk = newChunk()
serialized: SerializedBytecode
if not isBinary:
chunk = generator.generate(typedNodes)
chunk = generator.generate(typedNodes, typeChecker)
serialized = serializer.loadBytes(serializer.dumpBytes(chunk, filename))
else:
serialized = serializer.loadFile(filename)
chunk = serialized.chunk
if dump:
if dump and not fromString:
if not output.endsWith(".pbc"):
output.add(".pbc")
serializer.dumpFile(chunk, filename, output)
if debugCompiler:
styledEcho fgCyan, "Disassembler output: "
styledEcho fgCyan, "Disassembler output below"
debugger.disassembleChunk(chunk, filename)
if debugSerializer:
styledEcho fgCyan, "Serializer output: "
styledEcho fgCyan, "Serializer checks: "
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
stdout.styledWriteLine(fgBlue, "\t- Compilation date & time: ", fgYellow, fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss"))
stdout.styledWriteLine(fgBlue, "\t- Total binary size: ", fgYellow, formatSize(serialized.size))
stdout.styledWrite(fgBlue, &"\t- Constants segment: ")
if serialized.chunk.consts == chunk.consts:
styledEcho fgGreen, "OK"
@ -245,39 +254,6 @@ proc repl(warnings: seq[WarningKind] = @[], showMismatches: bool = false) =
except TypeCheckError:
print(TypeCheckError(getCurrentException()))
quit(0)
#[
var
lexer = newLexer()
parser = newParser()
compiler = newPeonCompiler()
source: string
file = "test.pn"
warnings: seq[WarningKind] = @[]
for i in WarningKind.low()..WarningKind.high():
warnings.add(WarningKind(i))
lexer.fillSymbolTable()
while true:
stdout.write(">>> ")
stdout.flushFile()
try:
source = stdin.readLine()
for typedNode in compiler.compile(parser.parse(lexer.lex(source, file), file, lexer.getLines(), lexer.getSource()), lexer.getFile(), lexer.getSource(),
showshowMismatches=true, disabledWarnings=warnings):
echo &"{typedNode.node} -> {compiler.stringify(typedNode)}\n"
except IOError:
echo ""
break
except LexingError as exc:
print(exc)
except ParseError as exc:
print(exc)
except CompileError as exc:
print(exc)
when isMainModule:
setControlCHook(proc () {.noconv.} = echo ""; quit(0))
main()
]#
]#

View File

@ -55,6 +55,8 @@ proc fillSymbolTable*(tokenizer: Lexer) =
tokenizer.symbols.addKeyword("false", TokenType.False)
tokenizer.symbols.addKeyword("ref", TokenType.Ref)
tokenizer.symbols.addKeyword("ptr", TokenType.Ptr)
tokenizer.symbols.addKeyword("nan", TokenType.Nan)
tokenizer.symbols.addKeyword("inf", TokenType.Inf)
for sym in [">", "<", "=", "~", "/", "+", "-", "_", "*", "?", "@", ":", "==", "!=",
">=", "<=", "+=", "-=", "/=", "*=", "**=", "!", "%", "&", "|", "^",
">>", "<<"]: