Compare commits

...

2 Commits

9 changed files with 172 additions and 276 deletions

View File

@ -114,21 +114,13 @@ out for yourself. Fortunately, the process is quite straightforward:
- First, you're gonna have to install [Nim](https://nim-lang.org/), the language peon is written in. I highly recommend
using [choosenim](https://github.com/dom96/choosenim) to manage your Nim installations as it makes switching between them and updating them a breeze
- Once Nim is installed, you should install [jale](https://git.nocturn9x.space/japl/jale), peon's custom line editor
library written by our beloved [Art](https://git.nocturn9x.space/art). This is needed for the REPL to work: just clone the repository, `cd` into it and run `nimble install`; this will install the library on your
system so that the Nim compiler can find it later
- After jale has been installed, clone this repository and run the REPL with `nim r src/main` (in the appropriate
directory of course). If you want to do more than play around in the REPL, I recommend compiling peon in release
mode with `nim -d:release --passC:"-flto" -o:peon`, which should produce a `peon` binary ready for you to play with
(if your C toolchain doesn't support LTO then you can just omit the `--passC` option, although that would be pretty weird
for a modern linker)
- Then, clone this repository and compile peon in release mode with `nim c -d:release --passC:"-flto" -o:peon src/main`, which should produce`peon` binary
ready for you to play with (if your C toolchain doesn't support LTO then you can just omit the `--passC` option, although that would be pretty weird for
a modern linker)
- If you want to move the executable to a different directory (say, into your `PATH`), you should copy peon's standard
library (found in `/src/peon/stdlib`) into a known folder and edit the `moduleLookupPaths` variable inside `src/config.nim`
by adding said folder to it so that the peon compiler knows where to find modules when you `import std;`. Hopefully I will
automate this soon, but as of right now the work is all manual (and it's part of the fun, IMHO ;))
library (found in `/src/peon/stdlib`) into a known folder, edit the `moduleLookupPaths` variable inside `src/config.nim`
by adding said folder to it so that the peon compiler knows where to find modules when you `import std;` and then recompile
peon. Hopefully I will automate this soon, but as of right now the work is all manual
__Note__: On Linux, peon will also look into `~/.local/peon/stdlib`
If you've done everything right, you should be able to run `peon` in your terminal and have it drop you into the REPL. Good
luck and have fun!
__Note__: On Linux, peon will also look into `~/.local/peon/stdlib` by default, so you can just create the `~/.local/peon` folder and copy `src/peon/stdlib` there

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
## The Peon runtime environment
import ../config
import config
# Sorry, but there only is enough space
# for one GC in this VM :(
@ -25,21 +25,21 @@ when not defined(gcArc) and not defined(gcOrc):
import std/math
import std/strformat
import std/segfaults
import std/strutils
import std/sets
import std/monotimes
import ../frontend/compiler/targets/bytecode/opcodes
import ../frontend/compiler/targets/bytecode/util/multibyte
when debugVM or debugMem or debugGC or debugAlloc:
import std/strformat
import std/sequtils
import std/terminal
import frontend/compiler/targets/bytecode/opcodes
import frontend/compiler/targets/bytecode/util/multibyte
when debugVM:
proc clearerr(stream: File) {.header: "stdio.h", importc.}
@ -1065,8 +1065,12 @@ proc run*(self: var PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[], repl:
self.lastDebugCommand = ""
try:
self.dispatch()
except Defect as e:
stderr.writeLine(&"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}")
except NilAccessDefect:
stderr.writeLine("Memory Access Violation: SIGSEGV")
stderr.writeLine(&"Memory Access Violation (bytecode offset {self.ip}): SIGSEGV")
quit(1)
if not repl:
# We clean up after ourselves!

View File

@ -48,7 +48,6 @@ http://www.apache.org/licenses/LICENSE-2.0 for more info.
Basic Usage
-----------
$ peon Open an interactive session (REPL)
$ peon file.pn Run the given Peon source file
$ peon file.pbc Run the given Peon bytecode file
@ -62,9 +61,9 @@ Options
-n, --noDump Don't dump the result of compilation to a file.
Note that no dump is created when using -s/--string
-b, --breakpoints Run the debugger at specific bytecode offsets (comma-separated).
Only available with --backend:bytecode and when compiled with VM
debugging on
-d, --disassemble Disassemble the output of compilation (only makes sense with --backend:bytecode)
Only available with --target:bytecode and when compiled with VM
debugging on (-d:debugVM at build time)
-d, --disassemble Disassemble the output of compilation (only makes sense with --target:bytecode)
-m, --mode Set the compilation mode. Acceptable values are 'debug' and
'release'. Defaults to 'debug'
-c, --compile Compile the code, but do not execute it. Useful along with -d
@ -72,11 +71,11 @@ Options
yes/on and no/off
--noWarn Disable a specific warning (for example, --noWarn:unusedVariable)
--showMismatches Show all mismatches when function dispatching fails (output is really verbose)
--backend Select the compilation backend (valid values are: 'c' and 'bytecode'). Note
that the REPL always uses the bytecode target. Defaults to 'bytecode'
-o, --output Rename the output file with this value (with --backend:bytecode, a '.pbc' extension
--target Select the compilation target (valid values are: 'c' and 'bytecode'). Defaults to
'bytecode'
-o, --output Rename the output file with this value (with --target:bytecode, a '.pbc' extension
is added if not already present)
--debug-dump Debug the bytecode serializer. Only makes sense with --backend:bytecode
--debug-lexer Debug the peon lexer
--debug-parser Debug the peon parser
--debug-dump Debug the bytecode serializer. Only makes sense with --target:bytecode
--debug-lexer Show the lexer's output
--debug-parser Show the parser's output
"""

View File

@ -64,6 +64,7 @@ type
isBuiltin*: bool
case kind*: TypeKind:
of Function:
nameObj*: Name
isLambda*: bool
isGenerator*: bool
isCoroutine*: bool
@ -99,7 +100,7 @@ type
## A name enumeration type
None, Module, Argument, Var, Function, CustomType, Enum
Name* = ref object of RootObj
Name* = ref object
## A generic name object
# Type of the identifier (NOT of the value!)
@ -345,6 +346,22 @@ proc step*(self: Compiler): ASTNode {.inline.} =
self.current += 1
proc wrap*(self: Type): Type =
## Wraps a type in a typevar if it's not already
## wrapped
if self.kind != Typevar:
return Type(kind: Typevar, wrapped: self)
return self
proc unwrap*(self: Type): Type =
## Unwraps a typevar if it's not already
## unwrapped
if self.kind == Typevar:
return self.wrapped
return self
# Peon's type inference and name resolution system is very flexible
# and can be reused across multiple compilation backends
@ -420,25 +437,27 @@ proc compareUnions*(self: Compiler, a, b: seq[tuple[match: bool, kind: Type]]):
proc compare*(self: Compiler, a, b: Type): bool =
## Compares two type objects
## for equality
result = false
# Note: 'All' is a type internal to the peon
# compiler that cannot be generated from user
# code in any way. It's used mostly for matching
# function return types (at least until we don't
# have return type inference) and it matches any
# type, including nil (nim's nil, not our nil)
# type, including nil (nim's nil, aka no type at all,
# not peon's nil which still has a type)
if a.isNil():
return b.isNil() or b.kind == All
elif b.isNil():
if b.isNil():
return a.isNil() or a.kind == All
elif a.kind == All or b.kind == All:
if a.kind == All or b.kind == All:
return true
if a.kind == b.kind:
# Here we compare types with the same kind discriminant
case a.kind:
of Int8, UInt8, Int16, UInt16, Int32,
UInt32, Int64, UInt64, Float32, Float64,
Char, Byte, String, Nil, TypeKind.Nan, Bool, TypeKind.Inf, Any:
Char, Byte, String, Nil, TypeKind.Nan, Bool,
TypeKind.Inf, Any, Auto:
return true
of Typevar:
return self.compare(a.wrapped, b.wrapped)
@ -481,17 +500,21 @@ proc compare*(self: Compiler, a, b: Type): bool =
return true
else:
discard # TODO: Custom types, enums
elif a.kind == Union:
if a.kind == Typevar:
return self.compare(a.wrapped, b)
if b.kind == Typevar:
return self.compare(a, b.wrapped)
if a.kind == Union:
for constraint in a.types:
if self.compare(constraint.kind, b) and constraint.match:
return true
return false
elif b.kind == Union:
if b.kind == Union:
for constraint in b.types:
if self.compare(constraint.kind, a) and constraint.match:
return true
return false
elif a.kind == Generic:
if a.kind == Generic:
if a.asUnion:
for constraint in a.cond:
if self.compare(constraint.kind, b) and constraint.match:
@ -502,7 +525,7 @@ proc compare*(self: Compiler, a, b: Type): bool =
if not self.compare(constraint.kind, b) or not constraint.match:
return false
return true
elif b.kind == Generic:
if b.kind == Generic:
if b.asUnion:
for constraint in b.cond:
if self.compare(constraint.kind, a) and constraint.match:
@ -513,7 +536,7 @@ proc compare*(self: Compiler, a, b: Type): bool =
if not self.compare(constraint.kind, a) or not constraint.match:
return false
return true
elif a.kind == Any or b.kind == Any:
if a.kind == Any or b.kind == Any:
# Here we already know that neither of
# these types are nil, so we can always
# just return true
@ -526,47 +549,47 @@ proc toIntrinsic*(name: string): Type =
## type if it is valid and returns nil
## otherwise
if name == "any":
return Type(kind: Any)
return Type(kind: Any, isBuiltin: true)
elif name == "all":
return Type(kind: All)
return Type(kind: All, isBuiltin: true)
elif name == "auto":
return Type(kind: Auto)
return Type(kind: Auto, isBuiltin: true)
elif name in ["int", "int64", "i64"]:
return Type(kind: Int64)
return Type(kind: Int64, isBuiltin: true)
elif name in ["uint64", "u64", "uint"]:
return Type(kind: UInt64)
return Type(kind: UInt64, isBuiltin: true)
elif name in ["int32", "i32"]:
return Type(kind: Int32)
return Type(kind: Int32, isBuiltin: true)
elif name in ["uint32", "u32"]:
return Type(kind: UInt32)
return Type(kind: UInt32, isBuiltin: true)
elif name in ["int16", "i16", "short"]:
return Type(kind: Int16)
return Type(kind: Int16, isBuiltin: true)
elif name in ["uint16", "u16"]:
return Type(kind: UInt16)
return Type(kind: UInt16, isBuiltin: true)
elif name in ["int8", "i8"]:
return Type(kind: Int8)
return Type(kind: Int8, isBuiltin: true)
elif name in ["uint8", "u8"]:
return Type(kind: UInt8)
return Type(kind: UInt8, isBuiltin: true)
elif name in ["f64", "float", "float64"]:
return Type(kind: Float64)
return Type(kind: Float64, isBuiltin: true)
elif name in ["f32", "float32"]:
return Type(kind: Float32)
return Type(kind: Float32, isBuiltin: true)
elif name in ["byte", "b"]:
return Type(kind: Byte)
return Type(kind: Byte, isBuiltin: true)
elif name in ["char", "c"]:
return Type(kind: Char)
return Type(kind: Char, isBuiltin: true)
elif name == "nan":
return Type(kind: TypeKind.Nan)
return Type(kind: TypeKind.Nan, isBuiltin: true)
elif name == "nil":
return Type(kind: Nil)
return Type(kind: Nil, isBuiltin: true)
elif name == "inf":
return Type(kind: TypeKind.Inf)
return Type(kind: TypeKind.Inf, isBuiltin: true)
elif name == "bool":
return Type(kind: Bool)
return Type(kind: Bool, isBuiltin: true)
elif name == "typevar":
return Type(kind: Typevar)
return Type(kind: Typevar, isBuiltin: true)
elif name == "string":
return Type(kind: String)
return Type(kind: String, isBuiltin: true)
proc infer*(self: Compiler, node: LiteralExpr): Type =
@ -577,7 +600,7 @@ proc infer*(self: Compiler, node: LiteralExpr): Type =
of intExpr, binExpr, octExpr, hexExpr:
let size = node.token.lexeme.split("'")
if size.len() == 1:
return Type(kind: Int64)
return Type(kind: Int64, isBuiltin: true)
let typ = size[1].toIntrinsic()
if not self.compare(typ, nil):
return typ
@ -586,18 +609,18 @@ proc infer*(self: Compiler, node: LiteralExpr): Type =
of floatExpr:
let size = node.token.lexeme.split("'")
if size.len() == 1:
return Type(kind: Float64)
return Type(kind: Float64, isBuiltin: true)
let typ = size[1].toIntrinsic()
if not typ.isNil():
return typ
else:
self.error(&"invalid type specifier '{size[1]}' for float", node)
of trueExpr:
return Type(kind: Bool)
return Type(kind: Bool, isBuiltin: true)
of falseExpr:
return Type(kind: Bool)
return Type(kind: Bool, isBuiltin: true)
of strExpr:
return Type(kind: String)
return Type(kind: String, isBuiltin: true)
else:
discard # Unreachable
@ -829,7 +852,6 @@ proc match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allow
msg = &"call to undefined function '{name}'"
self.error(msg, node)
elif impl.len() > 1:
echo "AAAAAA\n\n"
# If we happen to find more than one match, we try again
# and ignore forward declarations and automatic functions
impl = filterIt(impl, not it.valueType.forwarded and not it.valueType.isAuto)
@ -848,6 +870,10 @@ proc match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allow
self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner.ident.token.lexeme}' at line {impl[0].ident.token.line} of type '{self.stringify(impl[0].valueType)}'")
result = impl[0]
result.resolved = true
if result.kind == NameKind.Var:
# We found a function bound to a variable,
# so we return the original function's name object
result = result.valueType.nameObj
for (a, b) in zip(result.valueType.args, kind.args):
if not a.kind.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node)
@ -948,6 +974,7 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} =
if node.generics.len() > 0:
fn.isGeneric = true
self.names.add(fn)
fn.valueType.nameObj = fn
self.prepareFunction(fn)
n = fn
of NodeKind.importStmt:

View File

@ -106,7 +106,6 @@ method dispatchDelayedPragmas(self: BytecodeCompiler, name: Name)
proc funDecl(self: BytecodeCompiler, node: FunDecl, name: Name)
proc compileModule(self: BytecodeCompiler, module: Name)
proc generateCall(self: BytecodeCompiler, fn: Name, args: seq[Expression], line: int)
method prepareFunction(self: BytecodeCompiler, fn: Name)
# End of forward declarations
@ -466,7 +465,7 @@ proc handleBuiltinFunction(self: BytecodeCompiler, fn: Type, args: seq[Expressio
"Identity": Identity
}.to_table()
if fn.builtinOp == "print":
var typ = self.inferOrError(args[0])
var typ = self.inferOrError(args[0]).unwrap()
case typ.kind:
of Int64:
self.emitByte(PrintInt64, line)
@ -799,9 +798,7 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) =
## "Prepares" a function declaration by declaring
## its arguments and typechecking it
# First we declare the function's generics, if it has any.
# This is because the function's return type may in itself
# be a generic, so it needs to exist first
# First we declare the function's generics, if it has any
var constraints: seq[tuple[match: bool, kind: Type]] = @[]
for gen in fn.node.generics:
self.unpackTypes(gen.cond, constraints)
@ -829,8 +826,9 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) =
self.error("cannot declare more than 16777215 variables at a time")
inc(self.stackIndex)
typ = self.inferOrError(argument.valueType)
if typ.kind == Typevar:
typ = typ.wrapped
if self.compare(typ, "auto".toIntrinsic()):
fn.valueType.isAuto = true
typ = "any".toIntrinsic()
self.names.add(Name(depth: fn.depth + 1,
isPrivate: true,
owner: fn.owner,
@ -857,8 +855,8 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) =
# The function needs a return type too!
if not node.returnType.isNil():
fn.valueType.returnType = self.inferOrError(node.returnType)
if fn.valueType.returnType.kind == Typevar:
fn.valueType.returnType = fn.valueType.returnType.wrapped
if self.compare(fn.valueType.returnType, "auto".toIntrinsic()):
fn.valueType.isAuto = true
fn.position = self.stackIndex
self.stackIndex = idx
if node.isTemplate:
@ -1045,17 +1043,17 @@ method literal(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Typ
## as singletons, strings and numbers
case node.kind:
of trueExpr:
result = Type(kind: Bool)
result = "bool".toIntrinsic()
if compile:
self.emitByte(LoadTrue, node.token.line)
of falseExpr:
result = Type(kind: Bool)
result = "bool".toIntrinsic()
if compile:
self.emitByte(LoadFalse, node.token.line)
of strExpr:
result = Type(kind: String)
result = "string".toIntrinsic()
if compile:
self.emitConstant(LiteralExpr(node), Type(kind: String))
self.emitConstant(LiteralExpr(node), result)
of intExpr:
let y = IntExpr(node)
let kind = self.infer(y)
@ -1159,7 +1157,7 @@ method unary(self: BytecodeCompiler, node: UnaryExpr, compile: bool = true): Typ
method binary(self: BytecodeCompiler, node: BinaryExpr, compile: bool = true): Type {.discardable.} =
## Compiles all binary expressions
var default: Expression
let fn = Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a), default), ("", self.inferOrError(node.b), default)])
let fn = Type(kind: Function, returnType: "any".toIntrinsic(), args: @[("", self.inferOrError(node.a), default), ("", self.inferOrError(node.b), default)])
var impl = self.match(node.token.lexeme, fn, node)
result = impl.valueType
if impl.isGeneric:
@ -1186,7 +1184,7 @@ method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, com
if s.kind == NameKind.CustomType:
# This makes it so that the type of
# a type comes out as "typevar"
result = Type(kind: Typevar, wrapped: result)
result = result.wrap()
if not compile:
return result
var node = s.ident
@ -1209,29 +1207,16 @@ method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, com
# to have something on the stack to pop off (just to act as
# a placeholder)
self.emitByte(LoadNil, node.token.line)
elif s.valueType.isBuiltin:
case s.ident.token.lexeme:
of "nil":
self.emitByte(LoadNil, node.token.line)
of "nan":
self.emitByte(LoadNan, node.token.line)
of "inf":
self.emitByte(LoadInf, node.token.line)
else:
discard # Unreachable
else:
if not s.belongsTo.isNil() and s.belongsTo.valueType.fun.kind == funDecl and FunDecl(s.belongsTo.valueType.fun).isTemplate:
discard
if s.depth > 0:
# Loads a regular variable from the current frame
self.emitByte(LoadVar, s.ident.token.line)
else:
if s.depth > 0:
# Loads a regular variable from the current frame
self.emitByte(LoadVar, s.ident.token.line)
else:
# Loads a global variable from an absolute stack
# position
self.emitByte(LoadGlobal, s.ident.token.line)
# No need to check for -1 here: we already did a nil check above!
self.emitBytes(s.position.toTriple(), s.ident.token.line)
# Loads a global variable from an absolute stack
# position
self.emitByte(LoadGlobal, s.ident.token.line)
# No need to check for -1 here: we already did a nil check above!
self.emitBytes(s.position.toTriple(), s.ident.token.line)
method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Type {.discardable.} =
@ -1332,7 +1317,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type
case node.callee.kind:
of NodeKind.identExpr:
# Calls like hi()
var impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node)
var impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: "all".toIntrinsic(), args: args), node)
result = impl.valueType
if impl.isGeneric:
result = self.specialize(impl.valueType, argExpr)
@ -1421,8 +1406,6 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type
self.error(&"expression has no type", node)
else:
self.error(&"object of type '{self.stringify(typ)}' is not callable", node)
if not result.isNil() and result.kind == Typevar:
result = result.wrapped
method getItemExpr(self: BytecodeCompiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable.} =
@ -1626,7 +1609,7 @@ method expression(self: BytecodeCompiler, node: Expression, compile: bool = true
proc ifStmt(self: BytecodeCompiler, node: IfStmt) =
## Compiles if/else statements for conditional
## execution of code
self.check(node.condition, Type(kind: Bool))
self.check(node.condition, "bool".toIntrinsic())
self.expression(node.condition)
let jump = self.emitJump(JumpIfFalsePop, node.token.line)
self.statement(node.thenBranch)
@ -1640,7 +1623,7 @@ proc ifStmt(self: BytecodeCompiler, node: IfStmt) =
proc whileStmt(self: BytecodeCompiler, node: WhileStmt) =
## Compiles C-style while loops and
## desugared C-style for loops
self.check(node.condition, Type(kind: Bool))
self.check(node.condition, "bool".toIntrinsic())
let start = self.chunk.code.high()
self.expression(node.condition)
let jump = self.emitJump(JumpIfFalsePop, node.token.line)
@ -1686,7 +1669,7 @@ proc returnStmt(self: BytecodeCompiler, node: ReturnStmt) =
elif not self.currentFunction.valueType.returnType.isNil() and node.value.isNil():
self.error("bare return statement is only allowed in void functions", node)
if not node.value.isNil():
if self.currentFunction.valueType.returnType.kind == Auto:
if self.compare(self.currentFunction.valueType.returnType, "auto".toIntrinsic()):
self.currentFunction.valueType.returnType = self.inferOrError(node.value)
self.check(node.value, self.currentFunction.valueType.returnType)
self.expression(node.value)
@ -1852,7 +1835,7 @@ proc switchStmt(self: BytecodeCompiler, node: SwitchStmt) =
self.emitByte(DupTop, branch.body.token.line)
self.expression(branch.cond)
# We look for a matching equality implementation
fn = Type(kind: Function, returnType: Type(kind: Bool), args: @[("", typeOfA, default), ("", self.inferOrError(branch.cond), default)])
fn = Type(kind: Function, returnType: "bool".toIntrinsic(), args: @[("", typeOfA, default), ("", self.inferOrError(branch.cond), default)])
impl = self.match("==", fn, node)
self.generateCall(impl, @[node.switch, branch.cond], impl.line)
ifJump = self.emitJump(JumpIfFalsePop, branch.body.token.line)
@ -1934,9 +1917,13 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) =
if node.value.isNil():
# Variable has no value: the type declaration
# takes over
if typ.kind == Auto:
# TODO: Implement T.default()!
if self.compare(typ, "auto".toIntrinsic()):
self.error("automatic types require initialization", node)
typ = self.inferOrError(node.valueType)
# One of the few exceptions where we actually don't want to use
# self.compare() is this one, because that will implicitly unwrap
# the typevar and compare the wrapped type, which is not what we want
if typ.kind != Typevar:
self.error(&"expecting type name, got value of type {self.stringify(typ)} instead", node.name)
elif node.valueType.isNil():
@ -1948,7 +1935,7 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) =
# a value: the value's type must match the
# type declaration
let expected = self.inferOrError(node.valueType)
if expected.kind != Auto:
if not self.compare(expected, "auto".toIntrinsic()):
self.check(node.value, expected)
# If this doesn't fail, then we're good
typ = expected
@ -1956,7 +1943,7 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) =
# Let the compiler infer the type (this
# is the default behavior already, but
# some users may prefer to be explicit!)
typ = self.inferOrError(node.value)
typ = self.inferOrError(node.value)
self.expression(node.value)
self.emitByte(AddVar, node.token.line)
inc(self.stackIndex)

View File

@ -15,149 +15,30 @@
## Peon's main executable
# Our stuff
import frontend/parsing/lexer as l
import frontend/parsing/parser as p
import frontend/compiler/targets/bytecode/target as b
import frontend/compiler/compiler as c
import backend/vm as v
import frontend/compiler/targets/bytecode/util/serializer as s
import frontend/compiler/targets/bytecode/util/debugger
import util/symbols
import util/fmterr
import config
import util/fmterr
import util/symbols
import backend/bytecode/vm
import frontend/parsing/lexer
import frontend/parsing/parser
import frontend/compiler/compiler
import frontend/compiler/targets/bytecode/util/debugger
import frontend/compiler/targets/bytecode/util/serializer
import frontend/compiler/targets/bytecode/target as bytecode
# Builtins & external libs
import std/strformat
import std/os
import std/times
import std/strutils
import std/terminal
import std/parseopt
import std/times
import std/os
# Thanks art <3
import jale/editor as ed
import jale/templates
import jale/plugin/defaults
import jale/plugin/editor_history
import jale/keycodes
import jale/multiline
proc getLineEditor: LineEditor =
result = newLineEditor()
result.prompt = "=> "
result.populateDefaults()
let history = result.plugHistory()
result.bindHistory(history)
#[
proc repl(warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug, breakpoints: seq[uint64] = @[]) =
styledEcho fgMagenta, "Welcome into the peon REPL!"
var
keep = true
tokens: seq[Token] = @[]
tree: seq[Declaration] = @[]
compiler = newBytecodeCompiler(replMode=true)
compiled: Chunk = newChunk()
serialized: Serialized
tokenizer = newLexer()
vm = newPeonVM()
parser = newParser()
debugger = newDebugger()
serializer = newSerializer()
editor = getLineEditor()
input: string
first: bool = false
tokenizer.fillSymbolTable()
editor.bindEvent(jeQuit):
stdout.styledWriteLine(fgGreen, "Goodbye!")
keep = false
input = ""
editor.bindKey("ctrl+a"):
editor.content.home()
editor.bindKey("ctrl+e"):
editor.content.`end`()
while keep:
try:
input = editor.read()
if input == "#clear":
stdout.write("\x1Bc")
continue
elif input == "":
continue
tokens = tokenizer.lex(input, "stdin")
if tokens.len() == 0:
continue
if debugLexer:
styledEcho fgCyan, "Tokenization step:"
for i, token in tokens:
if i == tokens.high():
# Who cares about EOF?
break
styledEcho fgGreen, "\t", $token
echo ""
tree = parser.parse(tokens, "stdin", tokenizer.getLines(), input, persist=true)
if tree.len() == 0:
continue
if debugParser:
styledEcho fgCyan, "Parsing step:"
for node in tree:
styledEcho fgGreen, "\t", $node
echo ""
discard compiler.compile(tree, "stdin", tokenizer.getLines(), input, chunk=compiled, showMismatches=mismatches, disabledWarnings=warnings, mode=mode, incremental=first)
if debugCompiler:
styledEcho fgCyan, "Compilation step:\n"
debugger.disassembleChunk(compiled, "stdin")
echo ""
serialized = serializer.loadBytes(serializer.dumpBytes(compiled, "stdin"))
if debugSerializer:
styledEcho fgCyan, "Serialization step: "
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch
stdout.styledWriteLine(fgBlue, "\t- Compilation date & time: ", fgYellow, fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss"))
stdout.styledWrite(fgBlue, &"\t- Constants segment: ")
if serialized.chunk.consts == compiled.consts:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
stdout.styledWrite(fgBlue, &"\t- Code segment: ")
if serialized.chunk.code == compiled.code:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
stdout.styledWrite(fgBlue, "\t- Line info segment: ")
if serialized.chunk.lines == compiled.lines:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
stdout.styledWrite(fgBlue, "\t- Functions segment: ")
if serialized.chunk.functions == compiled.functions:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
if not first:
vm.run(serialized.chunk, repl=true, breakpoints=breakpoints)
first = true
else:
vm.resume(serialized.chunk)
except LexingError:
print(LexingError(getCurrentException()))
except ParseError:
print(ParseError(getCurrentException()))
except CompileError:
print(CompileError(getCurrentException()))
except SerializationError:
var file = SerializationError(getCurrentException()).file
if file notin ["<string>", ""]:
file = relativePath(file, getCurrentDir())
stderr.styledWriteLine(fgRed, styleBright, "Error while (de-)serializing ", fgYellow, file, fgDefault, &": {getCurrentException().msg}")
quit(0)
]#
import std/strformat
proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[],
warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug, run: bool = true,
backend: PeonBackend = PeonBackend.Bytecode, output: string) =
warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: CompileMode = Debug,
run: bool = true, backend: PeonBackend = PeonBackend.Bytecode, output: string) =
var
tokens: seq[Token] = @[]
tree: seq[Declaration] = @[]
@ -170,9 +51,9 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
serializer = newSerializer()
vm = newPeonVM()
input: string
f = f
tokenizer.fillSymbolTable()
try:
var f = f
if not fromString:
if not f.endsWith(".pn") and not f.endsWith(".pbc"):
f &= ".pn"
@ -258,22 +139,20 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
vm.run(serialized.chunk, breakpoints)
else:
discard
except LexingError:
print(LexingError(getCurrentException()))
except ParseError:
print(ParseError(getCurrentException()))
except CompileError:
print(CompileError(getCurrentException()))
except SerializationError:
var file = SerializationError(getCurrentException()).file
except LexingError as exc:
print(exc)
except ParseError as exc:
print(exc)
except CompileError as exc:
print(exc)
except SerializationError as exc:
var file = exc.file
if file notin ["<string>", ""]:
file = relativePath(file, getCurrentDir())
stderr.styledWriteLine(fgRed, styleBright, "Error while (de-)serializing ", fgYellow, file, fgDefault, &": {getCurrentException().msg}")
except IOError:
let exc = getCurrentException()
stderr.styledWriteLine(fgRed, styleBright, "Error while (de-)serializing ", fgYellow, file, fgDefault, &": {exc.msg}")
except IOError as exc:
stderr.styledWriteLine(fgRed, styleBright, "Error while trying to read ", fgYellow, f, fgDefault, &": {exc.msg}")
except OSError:
let exc = getCurrentException()
except OSError as exc:
stderr.styledWriteLine(fgRed, styleBright, "Error while trying to read ", fgYellow, f, fgDefault, &": {exc.msg} ({osErrorMsg(osLastError())})",
fgRed, "[errno ", fgYellow, $osLastError(), fgRed, "]")
@ -290,7 +169,7 @@ when isMainModule:
var mismatches: bool = false
var mode: CompileMode = CompileMode.Debug
var run: bool = true
var backend: PeonBackend
var target: PeonBackend
var output: string = ""
for kind, key, value in optParser.getopt():
case kind:
@ -307,10 +186,10 @@ when isMainModule:
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "invalid value for option 'mode' (valid options are: debug, release)")
quit()
of "help":
echo HELP_MESSAGE
echo HelpMessage
quit()
of "version":
echo PEON_VERSION_STRING
echo PeonVersionString
quit()
of "string":
file = key
@ -357,12 +236,12 @@ when isMainModule:
run = false
of "output":
output = value
of "backend":
of "target":
case value:
of "bytecode":
backend = PeonBackend.Bytecode
target = PeonBackend.Bytecode
of "c":
backend = PeonBackend.NativeC
target = PeonBackend.NativeC
of "debug-dump":
debugSerializer = true
of "debug-lexer":
@ -377,10 +256,10 @@ when isMainModule:
of "o":
output = value
of "h":
echo HELP_MESSAGE
echo HelpMessage
quit()
of "v":
echo PEON_VERSION_STRING
echo PeonVersionString
quit()
of "s":
file = key
@ -419,7 +298,6 @@ when isMainModule:
if breaks.len() == 0 and debugVM:
breaks.add(0)
if file == "":
echo "Sorry, the REPL is currently broken :("
#repl(warnings, mismatches, mode, breaks)
echo HelpMessage
else:
runFile(file, fromString, dump, breaks, warnings, mismatches, mode, run, backend, output)
runFile(file, fromString, dump, breaks, warnings, mismatches, mode, run, target, output)

View File

@ -6,6 +6,7 @@ fn sum(a, b: auto): auto {
return a + b;
}
var x: auto = 1;
print(x == 1);
print(sum(1, 2) == 3);

View File

@ -1,6 +1,13 @@
import std;
# Note: unlike C-style casts, casting in peon tells the compiler
# "hey, please reinterpret the underlying bits of data of this thing
# as the type I'm telling you, trust me bro". There is no data conversion
# occurring whatsoever! For that, use converters (once they're implemented LoL)
print(cast[int](2.0) == 4611686018427387904);
print(cast[float](4611686018427387904) == 2.0);
# If that strikes your fancy, you can do this:
var x = int;
var caster = cast[x];
print(caster(2.0) == 4611686018427387904);

View File

@ -12,3 +12,4 @@ fn identity(x: int32): int32 {
fn nope[T: int32 | int16](x: T): T {
return identity(x);
}