Compare commits

...

2 Commits

11 changed files with 285 additions and 217 deletions

View File

@ -690,7 +690,7 @@ when debugVM: # So nim shuts up
styledEcho(fgRed, "Unknown command ", fgYellow, &"'{command}'")
proc dispatch*(self: var PeonVM) =
proc dispatch*(self: var PeonVM) {.inline.} =
## Main bytecode dispatch loop
var instruction {.register.}: OpCode
while true:
@ -764,10 +764,6 @@ proc dispatch*(self: var PeonVM) =
# not needed there anymore
discard self.pop()
discard self.pop()
of ReplExit:
# Preserves the VM's state for the next
# execution. Used in the REPL
return
of Return:
# Returns from a function.
# Every peon program is wrapped
@ -1079,14 +1075,16 @@ proc run*(self: var PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[], repl:
proc resume*(self: var PeonVM, chunk: Chunk) =
## Resumes execution of the given chunk (which
## may have changed since the last call to run()).
## No other state mutation occurs and all stacks as
## well as other metadata are left intact. This should
## not be used directly unless you know what you're
## doing, as incremental compilation support is very
## experimental and highly unstable
self.chunk = chunk
## may have changed since the last call to run()). No other
## state mutation occurs and all stacks as well as other
## metadata are left intact. This should not be used directly
## unless you know what you're doing, as incremental compilation
## support is very experimental and highly unstable
try:
self.chunk = chunk
when debugVM:
if self.breakpoints == @[0'u64]:
self.debugNext = true
self.dispatch()
except NilAccessDefect:
stderr.writeLine("Memory Access Violation: SIGSEGV")

View File

@ -59,6 +59,9 @@ type
Type* = ref object
## A wrapper around
## compile-time types
# Is this type a builtin?
isBuiltin*: bool
case kind*: TypeKind:
of Function:
isLambda*: bool
@ -86,6 +89,9 @@ type
name*: string
of Union:
types*: seq[tuple[match: bool, kind: Type]]
of Typevar:
# What type do we represent?
wrapped*: Type
else:
discard
@ -141,8 +147,6 @@ type
# Has the compiler generated this name internally or
# does it come from user code?
isReal*: bool
# Is this name a builtin?
isBuiltin*: bool
## BACKEND-SPECIFIC FIELDS
@ -234,6 +238,7 @@ proc getSource*(self: Compiler): string {.inline.} = self.source
## implemented in the same module). They are methods because we need to dispatch to their actual specific
## implementations inside each target module, so we need the runtime type of the compiler object to be
## taken into account
method makeConcrete(self: Compiler, node: GenericExpr, compile: bool = true): Type {.base.} = nil
method expression*(self: Compiler, node: Expression, compile: bool = true): Type {.discardable, base.} = nil
method identifier*(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool = true, strict: bool = true): Type {.discardable, base.} = nil
method call*(self: Compiler, node: CallExpr, compile: bool = true): Type {.discardable, base.} = nil
@ -258,7 +263,7 @@ method dispatchDelayedPragmas(self: Compiler, name: Name) {.base.} = discard
## Utility functions
proc `$`*(self: Name): string = $(self[])
proc `$`(self: Type): string = $(self[])
proc `$`*(self: Type): string = $(self[])
proc hash(self: Name): Hash = self.ident.token.lexeme.hash()
@ -421,20 +426,22 @@ method compare*(self: Compiler, a, b: Type): bool =
# 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
# type, including nil (nim's nil, not our nil)
if a.isNil():
return b.isNil() or b.kind == All
elif b.isNil():
return a.isNil() or a.kind == All
elif a.kind == All or b.kind == All:
return true
elif a.kind == b.kind:
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:
return true
of Typevar:
return self.compare(a.wrapped, b.wrapped)
of Union:
return self.compareUnions(a.types, b.types)
of Generic:
@ -455,36 +462,22 @@ method compare*(self: Compiler, a, b: Type): bool =
return false
var i = 0
for (argA, argB) in zip(a.args, b.args):
# When we compare functions with forward
# declarations, or forward declarations
# between each other, we need to be more
# strict (as in: check argument names and
# their default values, any pragma associated
# with the function, and whether they are pure)
if a.forwarded:
if b.forwarded:
if argA.name != argB.name:
return false
else:
if argB.name == "":
# An empty argument name means
# we crafted this type object
# manually, so we don't need
# to match the argument name
continue
if argA.name != argB.name:
return false
elif b.forwarded:
if a.forwarded:
if argA.name != argB.name:
return false
else:
if argA.name == "":
continue
if argA.name != argB.name:
return false
if argA.name == "":
continue
if argB.name == "":
continue
if argA.name != argB.name:
return false
if not self.compare(argA.kind, argB.kind):
return false
if a.forwarded or b.forwarded:
# We need to be more strict when checking forward
# declarations
if b.fun.pragmas.len() != a.fun.pragmas.len():
return false
for (pragA, pragB) in zip(a.fun.pragmas, b.fun.pragmas):
if pragA != pragB:
return false
return true
else:
discard # TODO: Custom types, enums
@ -615,6 +608,8 @@ method infer*(self: Compiler, node: Expression): Type =
if node.isNil():
return nil
case node.kind:
of NodeKind.genericExpr:
result = self.makeConcrete(GenericExpr(node), compile=false)
of NodeKind.identExpr:
result = self.identifier(IdentExpr(node), compile=false, strict=false)
of NodeKind.unaryExpr:
@ -659,16 +654,30 @@ method stringify*(self: Compiler, typ: Type): string =
of Int8, UInt8, Int16, UInt16, Int32,
UInt32, Int64, UInt64, Float32, Float64,
Char, Byte, String, Nil, TypeKind.Nan, Bool,
TypeKind.Inf, Auto:
TypeKind.Inf, Auto, Any:
result &= ($typ.kind).toLowerAscii()
of Typevar:
result = self.stringify(typ.wrapped)
of Pointer:
result &= &"ptr {self.stringify(typ.value)}"
of Reference:
result &= &"ref {self.stringify(typ.value)}"
of Function:
result &= "fn ("
result &= "fn "
if typ.fun.generics.len() > 0:
result &= "["
for i, gen in typ.fun.generics:
result &= &"{gen.name.token.lexeme}: {self.stringify(self.inferOrError(gen.cond))}"
if i < typ.fun.generics.len() - 1:
result &= ", "
result &= "]"
result &= "("
for i, (argName, argType, argDefault) in typ.args:
result &= &"{argName}: {self.stringify(argType)}"
result &= &"{argName}: "
if argType.kind == Generic:
result &= argType.name
else:
result &= self.stringify(argType)
if not argDefault.isNil():
result &= &" = {argDefault}"
if i < typ.args.len() - 1:
@ -690,8 +699,6 @@ method stringify*(self: Compiler, typ: Type): string =
result &= ", "
else:
result &= "}"
of Any:
return "any"
of Union:
for i, condition in typ.types:
if i > 0:
@ -725,9 +732,10 @@ method findInModule*(self: Compiler, name: string, module: Name): seq[Name] =
## Looks for objects that have been already declared as
## public within the given module with the given name.
## Returns all objects that apply. If the name is an
## empty string, returns all objects within the given
## module, regardless of whether they are exported to
## the current one or not
## empty string, returns all public names within the
## given module, regardless of whether they are exported
## to the current one or not (this is used at import time
## and for the export statement)
if name == "":
for obj in reversed(self.names):
if obj.owner.isNil():
@ -765,13 +773,14 @@ proc check*(self: Compiler, term: Expression, kind: Type) {.inline.} =
let k = self.inferOrError(term)
if not self.compare(k, kind):
self.error(&"expecting value of type {self.stringify(kind)}, got {self.stringify(k)}", term)
elif k.kind == Any and kind.kind != Any:
if k.kind == Any and kind.kind notin [Any, Generic]:
self.error(&"any is not a valid type in this context")
proc isAny*(typ: Type): bool =
## Returns true if the given type is
## of (or contains) the any type
## of (or contains) the any type. Not
## applicable to typevars
case typ.kind:
of Any:
return true
@ -791,10 +800,7 @@ method match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, all
## Tries to find a matching function implementation
## compatible with the given type and returns its
## name object
var impl: seq[Name] = @[]
for obj in self.findByName(name):
if self.compare(kind, obj.valueType):
impl.add(obj)
var impl: seq[Name] = self.findByType(name, kind)
if impl.len() == 0:
let names = self.findByName(name)
var msg = &"failed to find a suitable implementation for '{name}'"
@ -812,6 +818,8 @@ method match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, all
msg &= &": wrong number of arguments (expected {name.valueType.args.len()}, got {kind.args.len()})"
else:
for i, arg in kind.args:
if arg.name != "" and name.valueType.args[i].name != "" and arg.name != name.valueType.args[i].name:
msg &= &": unexpected argument '{arg.name}' at position {i + 1}"
if not self.compare(arg.kind, name.valueType.args[i].kind):
msg &= &": first mismatch at position {i + 1}: (expected {self.stringify(name.valueType.args[i].kind)}, got {self.stringify(arg.kind)})"
break
@ -838,6 +846,7 @@ method match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, all
if impl[0].valueType.forwarded and not allowFwd:
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
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)
@ -849,56 +858,36 @@ proc beginScope*(self: Compiler) =
inc(self.depth)
proc unpackGenerics*(self: Compiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) =
proc unpackTypes*(self: Compiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) =
## Recursively unpacks a type constraint in a generic type
case condition.kind:
of identExpr:
list.add((accept, self.inferOrError(condition)))
if list[^1].kind.kind == Auto:
var typ = self.inferOrError(condition)
if typ.kind != Typevar:
self.error(&"expecting a type name, got value of type {self.stringify(typ)} instead", condition)
typ = typ.wrapped
if typ.kind == Auto:
self.error("automatic types cannot be used within generics", condition)
list.add((accept, typ))
of binaryExpr:
let condition = BinaryExpr(condition)
case condition.operator.lexeme:
of "|":
self.unpackGenerics(condition.a, list)
self.unpackGenerics(condition.b, list)
self.unpackTypes(condition.a, list)
self.unpackTypes(condition.b, list)
else:
self.error("invalid type constraint in generic declaration", condition)
of unaryExpr:
let condition = UnaryExpr(condition)
case condition.operator.lexeme:
of "~":
self.unpackGenerics(condition.a, list, accept=false)
self.unpackTypes(condition.a, list, accept=false)
else:
self.error("invalid type constraint in generic declaration", condition)
else:
self.error("invalid type constraint in generic declaration", condition)
proc unpackUnion*(self: Compiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) =
## Recursively unpacks a type union
case condition.kind:
of identExpr:
list.add((accept, self.inferOrError(condition)))
of binaryExpr:
let condition = BinaryExpr(condition)
case condition.operator.lexeme:
of "|":
self.unpackUnion(condition.a, list)
self.unpackUnion(condition.b, list)
else:
self.error("invalid type constraint in type union", condition)
of unaryExpr:
let condition = UnaryExpr(condition)
case condition.operator.lexeme:
of "~":
self.unpackUnion(condition.a, list, accept=false)
else:
self.error("invalid type constraint in type union", condition)
else:
self.error("invalid type constraint in type union", condition)
proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} =
## Statically declares a name into the current scope.
## "Declaring" a name only means updating our internal
@ -957,19 +946,6 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} =
fn.valueType.compiled = true
if node.generics.len() > 0:
fn.isGeneric = true
var typ: Type
for argument in node.arguments:
typ = self.infer(argument.valueType)
if not typ.isNil() and typ.kind == Auto:
fn.valueType.isAuto = true
if fn.isGeneric:
self.error("automatic types cannot be used within generics", argument.valueType)
break
typ = self.infer(node.returnType)
if not typ.isNil() and typ.kind == Auto:
fn.valueType.isAuto = true
if fn.isGeneric:
self.error("automatic types cannot be used within generics", node.returnType)
self.names.add(fn)
self.prepareFunction(fn)
n = fn
@ -1017,10 +993,13 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} =
case node.value.kind:
of identExpr:
n.valueType = self.inferOrError(node.value)
if n.valueType.kind == Typevar:
# Type alias!
n.valueType = n.valueType.wrapped
of binaryExpr:
# Type union
n.valueType = Type(kind: Union, types: @[])
self.unpackUnion(node.value, n.valueType.types)
self.unpackTypes(node.value, n.valueType.types)
else:
discard
else:
@ -1043,7 +1022,7 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} =
continue
if name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum]:
if name.depth < n.depth:
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' at depth {name.depth} shadows a name from an outer scope ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n)
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer scope ({name.owner.file}:{name.ident.token.line}:{name.ident.token.relPos.start})", node=n.ident)
if name.owner != n.owner:
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' at depth {name.depth} shadows a name from an outer module ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n)
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer module ({name.owner.file}:{name.ident.token.line}:{name.ident.token.relPos.start})", node=n.ident)
return n

View File

@ -201,7 +201,6 @@ type
SysClock64, # Pushes the output of a monotonic clock on the stack
LoadTOS, # Pushes the top of the call stack onto the operand stack
DupTop, # Duplicates the top of the operand stack onto the operand stack
ReplExit, # Exits the VM immediately, leaving its state intact. Used in the REPL
LoadGlobal # Loads a global variable
@ -282,7 +281,6 @@ const simpleInstructions* = {Return, LoadNil,
Float32GreaterOrEqual,
Float32LessOrEqual,
DupTop,
ReplExit,
Identity
}

View File

@ -466,7 +466,7 @@ proc handleBuiltinFunction(self: BytecodeCompiler, fn: Type, args: seq[Expressio
"Identity": Identity
}.to_table()
if fn.builtinOp == "print":
let typ = self.inferOrError(args[0])
var typ = self.inferOrError(args[0])
case typ.kind:
of Int64:
self.emitByte(PrintInt64, line)
@ -528,6 +528,13 @@ proc handleBuiltinFunction(self: BytecodeCompiler, fn: Type, args: seq[Expressio
let jump = self.emitJump(JumpIfFalseOrPop, line)
self.expression(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.builtinOp}'", fn.fun)
@ -543,6 +550,9 @@ proc patchForwardDeclarations(self: BytecodeCompiler) =
if forwarded.isPrivate != impl.isPrivate:
self.error(&"implementation of '{impl.ident.token.lexeme}' has a mismatching visibility modifier from its forward declaration", impl.ident)
if position == 0:
# Forward declaration created by funDecl (it's
# necessary to make sure that there's no unimplemented
# forward declarations)
continue
pos = impl.codePos.toLong()
self.chunk.consts[position] = pos[0]
@ -585,7 +595,7 @@ proc endScope(self: BytecodeCompiler) =
if name.kind notin [NameKind.Var, NameKind.Argument]:
continue
elif name.kind == NameKind.Argument and not name.belongsTo.isNil():
if name.belongsTo.isBuiltin:
if name.belongsTo.valueType.isBuiltin:
# Arguments to builtin functions become temporaries on the
# stack and are popped automatically
continue
@ -607,7 +617,7 @@ proc endScope(self: BytecodeCompiler) =
self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used (add '_' prefix to silence warning)", name)
of NameKind.Argument:
if not name.ident.token.lexeme.startsWith("_") and name.isPrivate:
if not name.belongsTo.isNil() and not name.belongsTo.isBuiltin and name.belongsTo.isReal and name.belongsTo.resolved:
if not name.belongsTo.isNil() and not name.belongsTo.valueType.isBuiltin and name.belongsTo.isReal and name.belongsTo.resolved:
# Builtin functions never use their arguments. We also don't emit this
# warning if the function was generated internally by the compiler (for
# example as a result of generic specialization) because such objects do
@ -639,56 +649,6 @@ proc endScope(self: BytecodeCompiler) =
inc(idx)
proc unpackGenerics(self: BytecodeCompiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) =
## Recursively unpacks a type constraint in a generic type
case condition.kind:
of identExpr:
list.add((accept, self.inferOrError(condition)))
if list[^1].kind.kind == Auto:
self.error("automatic types cannot be used within generics", condition)
of binaryExpr:
let condition = BinaryExpr(condition)
case condition.operator.lexeme:
of "|":
self.unpackGenerics(condition.a, list)
self.unpackGenerics(condition.b, list)
else:
self.error("invalid type constraint in generic declaration", condition)
of unaryExpr:
let condition = UnaryExpr(condition)
case condition.operator.lexeme:
of "~":
self.unpackGenerics(condition.a, list, accept=false)
else:
self.error("invalid type constraint in generic declaration", condition)
else:
self.error("invalid type constraint in generic declaration", condition)
proc unpackUnion(self: BytecodeCompiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) =
## Recursively unpacks a type union
case condition.kind:
of identExpr:
list.add((accept, self.inferOrError(condition)))
of binaryExpr:
let condition = BinaryExpr(condition)
case condition.operator.lexeme:
of "|":
self.unpackUnion(condition.a, list)
self.unpackUnion(condition.b, list)
else:
self.error("invalid type constraint in type union", condition)
of unaryExpr:
let condition = UnaryExpr(condition)
case condition.operator.lexeme:
of "~":
self.unpackUnion(condition.a, list, accept=false)
else:
self.error("invalid type constraint in type union", condition)
else:
self.error("invalid type constraint in type union", condition)
proc emitLoop(self: BytecodeCompiler, begin: int, line: int) =
## Emits a JumpBackwards instruction with the correct
## jump offset
@ -717,19 +677,20 @@ proc handleMagicPragma(self: BytecodeCompiler, pragma: Pragma, name: Name) =
## Handles the "magic" pragma. Assumes the given name is already
## declared
if pragma.args.len() != 1:
self.error("'magic' pragma: wrong number of arguments")
self.error(&"'magic' pragma: wrong number of arguments (expected 1, got {len(pragma.args)})")
elif pragma.args[0].kind != strExpr:
self.error("'magic' pragma: wrong type of argument (constant string expected)")
self.error(&"'magic' pragma: wrong argument type (constant string expected, got {self.stringify(self.inferOrError(pragma.args[0]))})")
elif name.node.kind == NodeKind.funDecl:
name.isBuiltin = true
name.valueType.isBuiltin = true
name.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2]
name.valueType.compiled = true
elif name.node.kind == NodeKind.typeDecl:
name.valueType = pragma.args[0].token.lexeme[1..^2].toIntrinsic()
if name.valueType.kind == All:
self.error("don't even think about it (compiler-chan is angry at you)", pragma)
self.error("don't even think about it (compiler-chan is angry at you :/)", pragma)
if name.valueType.isNil():
self.error("'magic' pragma: wrong argument value", pragma.args[0])
name.isBuiltin = true
name.valueType.isBuiltin = true
else:
self.error("'magic' pragma is not valid in this context")
@ -814,6 +775,9 @@ proc generateCall(self: BytecodeCompiler, fn: Type, args: seq[Expression], line:
## instead of Name objects (used for lambdas and
## consequent calls). The function's address is
## assumed to be on the stack
if fn.isBuiltin:
self.handleBuiltinFunction(fn, args, line)
return
self.emitByte(LoadUInt64, line)
self.emitBytes(self.chunk.writeConstant(0.toLong()), line)
let pos = self.chunk.consts.len() - 8
@ -840,7 +804,7 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) =
# be a generic, so it needs to exist first
var constraints: seq[tuple[match: bool, kind: Type]] = @[]
for gen in fn.node.generics:
self.unpackGenerics(gen.cond, constraints)
self.unpackTypes(gen.cond, constraints)
self.names.add(Name(depth: fn.depth + 1,
isPrivate: true,
valueType: Type(kind: Generic, name: gen.name.token.lexeme, cond: constraints),
@ -857,19 +821,23 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) =
let idx = self.stackIndex
self.stackIndex = 1
var default: Expression
var node = FunDecl(fn.node)
let node = FunDecl(fn.node)
var i = 0
var typ: Type
for argument in node.arguments:
if self.names.high() > 16777215:
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
self.names.add(Name(depth: fn.depth + 1,
isPrivate: true,
owner: fn.owner,
file: fn.file,
isConst: false,
ident: argument.name,
valueType: if not fn.valueType.isAuto: self.inferOrError(argument.valueType) else: Type(kind: Any),
valueType: typ,
codePos: 0,
isLet: false,
line: argument.name.token.line,
@ -881,14 +849,16 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) =
))
if node.arguments.high() - node.defaults.high() <= node.arguments.high():
# There's a default argument!
fn.valueType.args.add((self.names[^1].ident.token.lexeme, self.names[^1].valueType, node.defaults[i]))
fn.valueType.args.add((self.names[^1].ident.token.lexeme, typ, node.defaults[i]))
inc(i)
else:
# This argument has no default
fn.valueType.args.add((self.names[^1].ident.token.lexeme, self.names[^1].valueType, default))
fn.valueType.args.add((self.names[^1].ident.token.lexeme, typ, default))
# The function needs a return type too!
if not FunDecl(fn.node).returnType.isNil():
fn.valueType.returnType = self.inferOrError(FunDecl(fn.node).returnType)
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
fn.position = self.stackIndex
self.stackIndex = idx
if node.isTemplate:
@ -941,8 +911,7 @@ proc prepareAutoFunction(self: BytecodeCompiler, fn: Name, args: seq[tuple[name:
proc generateCall(self: BytecodeCompiler, fn: Name, args: seq[Expression], line: int) =
## Small wrapper that abstracts emitting a call instruction
## for a given function
self.dispatchDelayedPragmas(fn)
if fn.isBuiltin:
if fn.valueType.isBuiltin:
self.handleBuiltinFunction(fn.valueType, args, line)
return
case fn.kind:
@ -969,15 +938,13 @@ proc generateCall(self: BytecodeCompiler, fn: Name, args: seq[Expression], line:
proc specialize(self: BytecodeCompiler, typ: Type, args: seq[Expression]): Type {.discardable.} =
## Specializes a generic type.
## Used for typechecking at the
## call site
## Instantiates a generic type
var mapping: TableRef[string, Type] = newTable[string, Type]()
var kind: Type
result = deepCopy(typ)
case result.kind:
of TypeKind.Function:
# This first loop checks if a user tries to reassign a generic's
# This loop checks if a user tries to reassign a generic's
# name to a different type
for i, (name, typ, default) in result.args:
if typ.kind != Generic:
@ -990,6 +957,17 @@ proc specialize(self: BytecodeCompiler, typ: Type, args: seq[Expression]): Type
if not result.returnType.isNil() and result.returnType.kind == Generic:
if result.returnType.name in mapping:
result.returnType = mapping[result.returnType.name]
elif mapping.len() == 0:
# The function has no generic arguments,
# just a generic return type
var typ: Type
for i, gen in result.fun.generics:
if gen.name.token.lexeme == result.returnType.name:
typ = result.args[i].kind
break
if typ.isNil():
self.error(&"unknown generic argument name '{result.returnType.name}'", result.fun)
result.returnType = typ
else:
self.error(&"unknown generic argument name '{result.returnType.name}'", result.fun)
else:
@ -1000,12 +978,10 @@ proc terminateProgram(self: BytecodeCompiler, pos: int) =
## Utility to terminate a peon program
self.patchForwardDeclarations()
self.endScope()
if self.replMode:
self.emitByte(ReplExit, self.peek().token.line)
else:
self.emitByte(OpCode.Return, self.peek().token.line)
self.emitByte(0, self.peek().token.line) # Entry point has no return value
self.patchReturnAddress(pos)
self.emitByte(OpCode.Return, self.peek().token.line)
self.emitByte(0, self.peek().token.line) # Entry point has no return value
self.patchReturnAddress(pos)
proc beginProgram(self: BytecodeCompiler): int =
@ -1207,6 +1183,10 @@ method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, com
if s.isNil() and not strict:
return nil
result = s.valueType
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)
if not compile:
return result
var node = s.ident
@ -1224,7 +1204,12 @@ method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, com
# they're referenced
self.emitByte(LoadUInt64, node.token.line)
self.emitBytes(self.chunk.writeConstant(s.codePos.toLong()), node.token.line)
elif s.isBuiltin:
elif s.kind == NameKind.CustomType:
# Types have no runtime representation either, but we need
# 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)
@ -1284,6 +1269,44 @@ method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true):
self.error(&"invalid AST node of kind {node.kind} at assignment(): {node} (This is an internal error and most likely a bug)")
method makeConcrete(self: BytecodeCompiler, node: GenericExpr, compile: bool = true): Type =
## Builds a concrete type from the given generic
## instantiation
var name = self.resolveOrError(node.ident)
if not name.isGeneric:
self.error(&"cannot instantiate concrete type from {self.stringify(name.valueType)}: a generic is required")
var fun = FunDecl(name.node)
if fun.generics.len() != node.args.len():
self.error(&"wrong number of types supplied for generic instantiation (expected {fun.generics.len()}, got {node.args.len()} instead)")
var concrete = deepCopy(name.valueType)
var types: seq[Type] = @[]
var map = newTable[string, Type]()
for arg in node.args:
types.add(self.inferOrError(arg))
if types[^1].kind != Typevar:
self.error(&"expecting type name during generic instantiation, got {self.stringify(types[^1])} instead", arg)
for (gen, value) in zip(fun.generics, node.args):
map[gen.name.token.lexeme] = self.inferOrError(value)
for i, argument in concrete.args:
if argument.kind.kind != Generic:
continue
elif argument.name in map:
concrete.args[i].kind = map[argument.name]
else:
self.error(&"unknown generic argument name '{argument.name}'", concrete.fun)
if not concrete.returnType.isNil() and concrete.returnType.kind == Generic:
if concrete.returnType.name in map:
concrete.returnType = map[concrete.returnType.name]
else:
self.error(&"unknown generic argument name '{concrete.returnType.name}'", concrete.fun)
if compile:
# Types don't exist at runtime, but if you want to
# assign them to variables then you need *something*
# to pop off the stack, so we just push a nil
self.emitByte(LoadNil, node.token.line)
result = concrete
method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type {.discardable.} =
## Compiles function calls
var args: seq[tuple[name: string, kind: Type, default: Expression]] = @[]
@ -1321,7 +1344,9 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type
if not impl.valueType.compiled:
self.funDecl(FunDecl(result.fun), impl)
result = result.returnType
self.dispatchDelayedPragmas(impl)
if compile:
# Lambdas can't be templates :P
if impl.valueType.fun.kind == funDecl and FunDecl(impl.valueType.fun).isTemplate:
for arg in reversed(argExpr):
self.expression(arg)
@ -1357,7 +1382,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type
self.generateCall(result, argExpr, node.token.line)
result = result.returnType
of NodeKind.getItemExpr:
var node = GetItemExpr(node.callee)
let node = GetItemExpr(node.callee)
result = self.getItemExpr(node, compile=false, matching=Type(kind: Function, args: args, returnType: Type(kind: All)))
var fn: Name
# getItemExpr returns a Type object, but
@ -1375,17 +1400,29 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type
if compile:
self.generateCall(fn, argExpr, node.token.line)
of NodeKind.lambdaExpr:
# Calling a lambda
var node = LambdaExpr(node.callee)
var impl = self.lambdaExpr(node, compile=compile)
let impl = self.lambdaExpr(node, compile=compile)
result = impl.returnType
if compile:
self.generateCall(impl, argExpr, node.token.line)
of NodeKind.genericExpr:
# Instantiating a generic type
let node = GenericExpr(node.callee)
let concrete = self.makeConcrete(node)
var impl = self.resolve(node.ident).deepCopy()
impl.valueType = concrete
result = impl.valueType.returnType
if compile:
self.generateCall(impl, argExpr, node.token.line)
else:
let typ = self.infer(node)
if typ.isNil():
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.} =
@ -1547,6 +1584,8 @@ method lambdaExpr(self: BytecodeCompiler, node: LambdaExpr, compile: bool = true
method expression(self: BytecodeCompiler, node: Expression, compile: bool = true): Type {.discardable.} =
## Compiles all expressions
case node.kind:
of NodeKind.genericExpr:
return self.makeConcrete(GenericExpr(node))
of NodeKind.callExpr:
return self.call(CallExpr(node), compile)
of NodeKind.getItemExpr:
@ -1796,7 +1835,7 @@ proc namedBlock(self: BytecodeCompiler, node: NamedBlockStmt) =
proc switchStmt(self: BytecodeCompiler, node: SwitchStmt) =
## Compiles C-style switch statements
## Compiles switch statements
self.expression(node.switch)
let typeOfA = self.inferOrError(node.switch)
var ifJump: int = -1
@ -1895,9 +1934,11 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) =
if node.value.isNil():
# Variable has no value: the type declaration
# takes over
typ = self.inferOrError(node.valueType)
if typ.kind == Auto:
self.error("automatic types require initialization", node)
typ = self.inferOrError(node.valueType)
if typ.kind != Typevar:
self.error(&"expecting type name, got value of type {self.stringify(typ)} instead", node.name)
elif node.valueType.isNil():
# Variable has no type declaration: the type
# of its value takes over
@ -1915,7 +1956,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.infer(node.value)
typ = self.inferOrError(node.value)
self.expression(node.value)
self.emitByte(AddVar, node.token.line)
inc(self.stackIndex)
@ -1945,7 +1986,7 @@ proc funDecl(self: BytecodeCompiler, node: FunDecl, name: Name) =
self.forwarded.add((name, 0))
name.valueType.forwarded = true
return
if name.isBuiltin:
if name.valueType.isBuiltin:
# Builtins are handled at call time
return
self.currentFunction = name
@ -2087,8 +2128,6 @@ proc compile*(self: BytecodeCompiler, ast: seq[Declaration], file: string, lines
else:
self.ast = ast
self.current = 0
if not incremental:
self.stackIndex = 1
self.lines = lines
self.source = source
self.isMainModule = isMainModule
@ -2099,9 +2138,8 @@ proc compile*(self: BytecodeCompiler, ast: seq[Declaration], file: string, lines
if not incremental:
self.jumps = @[]
self.modules = newTable[string, Name]()
self.stackIndex = 1
let pos = self.beginProgram()
let idx = self.stackIndex
self.stackIndex = idx
while not self.done():
self.declaration(Declaration(self.step()))
self.terminateProgram(pos)

View File

@ -79,6 +79,7 @@ type
pragmaExpr,
refExpr,
ptrExpr,
genericExpr,
switchStmt
# Here I would've rather used object variants, and in fact that's what was in
@ -155,6 +156,11 @@ type
name: IdentExpr, value: Expression]]]
closeParen*: Token # Needed for error reporting
GenericExpr* = ref object of Expression
ident*: IdentExpr
args*: seq[Expression]
UnaryExpr* = ref object of Expression
operator*: Token
a*: Expression
@ -187,7 +193,7 @@ type
ends*: seq[Expression]
AssignExpr* = ref object of Expression
name*: Expression
name*: IdentExpr
value*: Expression
ExprStmt* = ref object of Statement
@ -459,6 +465,13 @@ proc newCallExpr*(callee: Expression, arguments: tuple[positionals: seq[
result.token = token
proc newGenericExpr*(ident: IdentExpr, args: seq[Expression]): GenericExpr =
result = GenericExpr(kind: genericExpr)
result.ident = ident
result.args = args
result.token = ident.token
proc newSliceExpr*(expression: Expression, ends: seq[Expression], token: Token): SliceExpr =
result = SliceExpr(kind: sliceExpr)
result.expression = expression
@ -487,7 +500,7 @@ proc newYieldExpr*(expression: Expression, token: Token): YieldExpr =
result.token = token
proc newAssignExpr*(name: Expression, value: Expression,
proc newAssignExpr*(name: IdentExpr, value: Expression,
token: Token): AssignExpr =
result = AssignExpr(kind: assignExpr)
result.name = name
@ -786,6 +799,9 @@ proc `$`*(self: ASTNode): string =
result &= &"Ref({Ref(self).value})"
of ptrExpr:
result &= &"Ptr({Ptr(self).value})"
of genericExpr:
var self = GenericExpr(self)
result &= &"Generic(ident={self.ident}, args={self.args})"
else:
discard
@ -843,6 +859,13 @@ proc getRelativeBoundaries*(self: ASTNode): tuple[start, stop: int] =
stop = self.token.relPos.stop + 1
# -8 so the error highlights the #pragma[ part as well
result = (self.token.relPos.start - 8, stop)
of genericExpr:
var self = GenericExpr(self)
let ident = getRelativeBoundaries(self.ident)
var stop: int = ident.stop + 2
if self.args.len() > 0:
stop = getRelativeBoundaries(self.args[^1]).stop
result = (ident.start, stop)
else:
result = (0, 0)

View File

@ -418,11 +418,12 @@ proc makeCall(self: Parser, callee: Expression): CallExpr =
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 == "=":
if IdentExpr(BinaryExpr(argument).a) in argNames:
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))
if argument.kind == assignExpr:
var assign = AssignExpr(argument)
if assign.name in argNames:
self.error("duplicate keyword arguments are not allowed", assign.name.token)
argNames.add(assign.name)
arguments.keyword.add((name: assign.name, value: assign.value))
elif arguments.keyword.len() == 0:
arguments.positionals.add(argument)
else:
@ -436,10 +437,18 @@ proc makeCall(self: Parser, callee: Expression): CallExpr =
result.closeParen = self.peek(-1)
proc parseGenericArgs(self: Parser) =
## Parses function generic arguments
## like function[type](arg)
discard # TODO
proc parseGenericArgs(self: Parser): Expression =
## Parses expressions like someType[someGeneric]
## that are needed to instantiate generics
var item = newIdentExpr(self.peek(-2), self.scopeDepth)
var types: seq[Expression] = @[]
while not self.check(RightBracket) and not self.done():
self.expect(Identifier)
types.add(newIdentExpr(self.peek(-1), self.scopeDepth))
if not self.match(Comma):
break
self.expect(RightBracket)
return newGenericExpr(item, types)
proc call(self: Parser): Expression =
@ -454,8 +463,9 @@ proc call(self: Parser): Expression =
result = newGetItemExpr(result, newIdentExpr(self.peek(-1), self.scopeDepth), self.peek(-1))
result.file = self.file
elif self.match(LeftBracket):
self.parseGenericArgs() # TODO
result = self.makeCall(result)
if self.peek(-2).kind != Identifier:
self.error("invalid syntax")
result = self.parseGenericArgs()
else:
break
@ -564,7 +574,7 @@ proc parseAssign(self: Parser): Expression =
var value = self.expression()
case result.kind:
of identExpr, sliceExpr:
result = newAssignExpr(result, value, tok)
result = newAssignExpr(IdentExpr(result), value, tok)
result.file = self.file
of getItemExpr:
result = newSetItemExpr(GetItemExpr(result).obj, GetItemExpr(result).name, value, tok)
@ -839,7 +849,7 @@ proc tryStmt(self: Parser): Statement =
var elseClause: Statement
while self.match(Except):
if self.match(LeftBrace):
handlers.add((body: self.blockStmt(), exc: newIdentExpr(self.peek(-1))))
handlers.add((body: self.blockStmt(), exc: newIdentExpr(self.peek(-1), self.scopeDepth)))
else:
self.expect(Identifier, "expecting exception name after 'except'")
self.expect(LeftBrace, "expecting '{' after exception name")

View File

@ -50,7 +50,7 @@ proc getLineEditor: LineEditor =
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
@ -104,7 +104,7 @@ proc repl(warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: Comp
for node in tree:
styledEcho fgGreen, "\t", $node
echo ""
compiled = compiler.compile(tree, "stdin", tokenizer.getLines(), input, chunk=compiled, showMismatches=mismatches, disabledWarnings=warnings, mode=mode, incremental=true)
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")
@ -152,6 +152,7 @@ proc repl(warnings: seq[WarningKind] = @[], mismatches: bool = false, mode: Comp
file = relativePath(file, getCurrentDir())
stderr.styledWriteLine(fgRed, styleBright, "Error while (de-)serializing ", fgYellow, file, fgDefault, &": {getCurrentException().msg}")
quit(0)
]#
proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[],
@ -219,6 +220,9 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
stderr.styledWriteLine(fgRed, styleBright, "Error: ", fgDefault, "the selected backend is not implemented yet")
elif backend == PeonBackend.Bytecode:
serialized = serializer.loadFile(f)
if debugCompiler:
styledEcho fgCyan, "Compilation step:\n"
debugger.disassembleChunk(serialized.chunk, f)
if backend == PeonBackend.Bytecode and 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
@ -415,6 +419,7 @@ when isMainModule:
if breaks.len() == 0 and debugVM:
breaks.add(0)
if file == "":
repl(warnings, mismatches, mode, breaks)
echo "Sorry, the REPL is currently broken :("
#repl(warnings, mismatches, mode, breaks)
else:
runFile(file, fromString, dump, breaks, warnings, mismatches, mode, run, backend, output)

View File

@ -68,6 +68,12 @@ type auto* = object {
#pragma[magic: "auto"]
}
type typevar* = object {
#pragma[magic: "typevar"]
}
# Some convenience aliases
type int* = int64;
type float* = float64;

View File

@ -21,4 +21,9 @@ var test* = 0x60;
fn testGlobals*: bool {
return version == 1 and _private == 5 and test == 0x60;
}
fn cast*[T: any](x: any): T {
#pragma[magic: "cast"]
}

View File

@ -49,9 +49,9 @@ proc print*(exc: CompileError) =
var contents: string
if file notin ["<string>", "", "stdin"]:
file = relativePath(exc.file, getCurrentDir())
contents = readFile(file).splitLines()[exc.line - 1].strip(chars={'\n'})
contents = readFile(file).strip(chars={'\n'}).splitLines()[exc.line - 1]
else:
contents = exc.compiler.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
contents = exc.compiler.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1]
printError(file, contents, exc.line, exc.node.getRelativeBoundaries(), exc.function,
exc.msg)
@ -62,7 +62,7 @@ proc print*(exc: ParseError) =
var file = exc.file
if file notin ["<string>", ""]:
file = relativePath(exc.file, getCurrentDir())
printError(file, exc.parser.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}),
printError(file, exc.parser.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1],
exc.line, exc.token.relPos, exc.parser.getCurrentFunction(),
exc.msg)
@ -73,6 +73,6 @@ proc print*(exc: LexingError) =
var file = exc.file
if file notin ["<string>", ""]:
file = relativePath(exc.file, getCurrentDir())
printError(file, exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'}),
printError(file, exc.lexer.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1],
exc.line, exc.pos, nil, exc.msg)

6
tests/cast.pn Normal file
View File

@ -0,0 +1,6 @@
import std;
var x = int;
var caster = cast[x];
print(caster(2.0) == 4611686018427387904);