Added tagged type unions, moved intrinsics to peon's stdlib and fixed issues with generics and parametric polymorphism

This commit is contained in:
Mattia Giambirtone 2022-11-29 16:48:05 +01:00
parent 382a6c141b
commit 63cdda42ce
17 changed files with 367 additions and 245 deletions

View File

@ -77,6 +77,7 @@ Type system:
- Custom types - Custom types
- Intrinsics ✅ - Intrinsics ✅
- Type unions ✅
- Generics ✅ - Generics ✅
- Functions ✅ - Functions ✅
- Closures ✅ - Closures ✅
@ -85,8 +86,8 @@ Misc:
- Pragmas ✅ - Pragmas ✅
- Attribute resolution ✅ - Attribute resolution ✅
- UFCS - UFCS (Universal Function Call Syntax)
- Import system with namespaces and visibility control ✅ - Import system (with namespaces and visibility control)
__Note__: Since peon isn't complete yet, the toolchain (lexer, parser, compiler and VM) may change, but since they __Note__: Since peon isn't complete yet, the toolchain (lexer, parser, compiler and VM) may change, but since they
@ -101,9 +102,9 @@ have been implemented alredady):
- Easy C/Nim interop via FFI - Easy C/Nim interop via FFI
- C/C++ backend - C/C++ backend
- Nim backend - Nim backend
- Structured concurrency - Structured concurrency (must-have!)
- Capability-based programming (i.e. functions are passed objects to act on the real world) - Capability-based programming (i.e. functions are passed objects to act on the real world. This is just a maybe)
- Parametric Polymorphism (with untagged typed unions) - Parametric Polymorphism ✅
- Simple OOP (with multiple dispatch!) - Simple OOP (with multiple dispatch!)
- RTTI, with methods that dispatch at runtime based on the true type of a value - RTTI, with methods that dispatch at runtime based on the true type of a value
- Limited compile-time evaluation (embed the Peon VM in the C/C++/Nim backend and use that to execute peon code at compile time) - Limited compile-time evaluation (embed the Peon VM in the C/C++/Nim backend and use that to execute peon code at compile time)

View File

@ -135,7 +135,6 @@ genericSum(3.14, 0.1);
genericSum(1'u8, 250'u8); genericSum(1'u8, 250'u8);
``` ```
__Note__: The generic `Number` type is currently not defined. You can use type unions instead
#### More generics! #### More generics!

View File

@ -46,7 +46,7 @@ type
UInt32, Int64, UInt64, Float32, Float64, UInt32, Int64, UInt64, Float32, Float64,
Char, Byte, String, Function, CustomType, Char, Byte, String, Function, CustomType,
Nil, Nan, Bool, Inf, Typevar, Generic, Nil, Nan, Bool, Inf, Typevar, Generic,
Reference, Pointer, Any, All Reference, Pointer, Any, All, Union
Type = ref object Type = ref object
## A wrapper around ## A wrapper around
## compile-time types ## compile-time types
@ -77,6 +77,8 @@ type
# would map to [(true, int), (false, uint)] # would map to [(true, int), (false, uint)]
cond: seq[tuple[match: bool, kind: Type]] cond: seq[tuple[match: bool, kind: Type]]
name: string name: string
of Union:
types: seq[tuple[match: bool, kind: Type]]
else: else:
discard discard
@ -261,14 +263,17 @@ proc expression(self: Compiler, node: Expression)
proc statement(self: Compiler, node: Statement) proc statement(self: Compiler, node: Statement)
proc declaration(self: Compiler, node: Declaration) proc declaration(self: Compiler, node: Declaration)
proc peek(self: Compiler, distance: int = 0): ASTNode proc peek(self: Compiler, distance: int = 0): ASTNode
proc identifier(self: Compiler, node: IdentExpr, name: Name = nil) proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool = true): Name {.discardable.}
proc varDecl(self: Compiler, node: VarDecl) proc varDecl(self: Compiler, node: VarDecl)
proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name
proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, args: seq[Expression] = @[], allowFwd: bool = true): Name proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, args: seq[Expression] = @[], allowFwd: bool = true): Name
proc call(self: Compiler, node: CallExpr, compile: bool = true): Name {.discardable.}
proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type {.discardable.} proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type {.discardable.}
proc infer(self: Compiler, node: LiteralExpr, allowGeneric: bool = false): Type proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Name {.discardable.}
proc infer(self: Compiler, node: Expression, allowGeneric: bool = false): Type proc binary(self: Compiler, node: BinaryExpr, compile: bool = true): Name {.discardable.}
proc inferOrError(self: Compiler, node: Expression, allowGeneric: bool = false): Type proc infer(self: Compiler, node: LiteralExpr): Type
proc infer(self: Compiler, node: Expression): Type
proc inferOrError(self: Compiler, node: Expression): Type
proc findByName(self: Compiler, name: string): seq[Name] proc findByName(self: Compiler, name: string): seq[Name]
proc findInModule(self: Compiler, name: string, module: Name): seq[Name] proc findInModule(self: Compiler, name: string, module: Name): seq[Name]
proc findByType(self: Compiler, name: string, kind: Type): seq[Name] proc findByType(self: Compiler, name: string, kind: Type): seq[Name]
@ -723,8 +728,11 @@ proc getStackPos(self: Compiler, name: Name): int =
# Arguments to builtin functions are optimized away to stack # Arguments to builtin functions are optimized away to stack
# temporaries. There is no stack frame for builtins, so we skip # temporaries. There is no stack frame for builtins, so we skip
# these names too # these names too
elif variable.kind == Argument and variable.belongsTo.valueType.isBuiltinFunction: elif variable.kind == Argument:
continue if variable.belongsTo.valueType.isBuiltinFunction:
continue
elif not variable.belongsTo.resolved:
continue
# This variable isn't in declared in our module, # This variable isn't in declared in our module,
# but we may still have access to it # but we may still have access to it
elif variable.owner != name.owner: elif variable.owner != name.owner:
@ -734,9 +742,11 @@ proc getStackPos(self: Compiler, name: Name): int =
if variable.isPrivate or name.owner notin variable.exportedTo: if variable.isPrivate or name.owner notin variable.exportedTo:
inc(result) inc(result)
continue continue
# Note: this MUST be an if, NOT an elif!
if name.ident == variable.ident and variable.depth == name.depth and name.owner == variable.owner: if name.ident == variable.ident and variable.depth == name.depth and name.owner == variable.owner:
if not name.belongsTo.isNil() and not variable.belongsTo.isNil(): if not name.belongsTo.isNil() and not variable.belongsTo.isNil():
if name.belongsTo != variable.belongsTo and variable.belongsTo.depth > name.belongsTo.depth: if name.belongsTo != variable.belongsTo and variable.belongsTo.depth > name.belongsTo.depth:
# Argument with the same name but in a different function: ignore it
dec(result) dec(result)
continue continue
found = true found = true
@ -758,6 +768,28 @@ proc getClosurePos(self: Compiler, name: Name): int =
return -1 return -1
proc compareUnions(self: Compiler, a, b: seq[tuple[match: bool, kind: Type]]): bool =
## Compares type unions between each other
var
long = a
short = b
if b.len() > a.len():
long = b
short = a
var matched = false
for cond1 in long:
for cond2 in short:
if not self.compare(cond2.kind, cond1.kind) or cond2.match != cond1.match:
continue
else:
matched = true
break
if not matched:
return false
matched = false
return true
proc compare(self: Compiler, a, b: Type): bool = proc compare(self: Compiler, a, b: Type): bool =
## Compares two type objects ## Compares two type objects
## for equality (works with nil!) ## for equality (works with nil!)
@ -765,36 +797,19 @@ proc compare(self: Compiler, a, b: Type): bool =
# The nil code here is for void functions (when # The nil code here is for void functions (when
# we compare their return types) # we compare their return types)
result = false 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
if a.isNil(): if a.isNil():
result = b.isNil() or b.kind == All return b.isNil() or b.kind == All
elif b.isNil(): if b.isNil():
result = a.isNil() or a.kind == All return a.isNil() or a.kind == All
elif a.kind == All: if a.kind == All or b.kind == All:
result = true return true
elif b.kind == All: if a.kind != Generic and b.kind != Generic and a.kind == b.kind:
result = true
# 'Any' is an type internal to
# the peon compiler that cannot
# be produced from regular user
# code. We use it mostly when
# resolving function calls (where
# we don't care about the type of
# the function's return value, as
# long as it has one) because peon
# doesn't have return type inference
# (yet :)). A seemingly identical type
# 'All' also exists, but while 'Any'
# means 'any type', 'All' means 'any
# type or no type at all'
elif a.kind == Any:
result = not b.isNil()
elif b.kind == Any:
result = not a.isNil()
# Note: the elif chain here is important!
# If we used regular if statements, we'd run
# the risk of running more checks than we
# need to and incorrectly modifying the result
elif a.kind != Generic and b.kind != Generic and a.kind == b.kind:
# Here we make sure to compare only concrete # Here we make sure to compare only concrete
# types # types
case a.kind: case a.kind:
@ -803,7 +818,9 @@ proc compare(self: Compiler, a, b: Type): bool =
Char, Byte, String, Nil, Nan, Bool, Inf: Char, Byte, String, Nil, Nan, Bool, Inf:
# A value's type is always equal to # A value's type is always equal to
# another one's # another one's
result = true return true
of Union:
return self.compareUnions(a.types, b.types)
of Reference, Pointer: of Reference, Pointer:
# Here we already know that both # Here we already know that both
# a and b are of either of the two # a and b are of either of the two
@ -856,24 +873,28 @@ proc compare(self: Compiler, a, b: Type): bool =
if not self.compare(argA.kind, argB.kind): if not self.compare(argA.kind, argB.kind):
temp = false temp = false
break break
result = temp return temp
else: else:
discard # TODO: Custom types discard # TODO: Custom types, enums
elif a.kind == Generic: if a.kind == Union:
for constraint in a.types:
if self.compare(constraint.kind, b):
if not constraint.match:
return false
else:
return false
return true
if b.kind == Union:
for constraint in b.types:
if self.compare(constraint.kind, a):
if not constraint.match:
return false
else:
return false
return true
if a.kind == Generic:
if b.kind == Generic: if b.kind == Generic:
for c1 in a.cond: return self.compareUnions(a.cond, b.cond)
for c2 in b.cond:
if self.compare(c1.kind, c2.kind):
# Here return is fine, because there's
# no more checks after this one!
if c1.match != c2.match:
return false
else:
return false
# We only return at the end because when matching
# generics we want to match *all* constraints at
# once, not just find the first match
return true
else: else:
for constraint in a.cond: for constraint in a.cond:
if self.compare(constraint.kind, b): if self.compare(constraint.kind, b):
@ -882,16 +903,9 @@ proc compare(self: Compiler, a, b: Type): bool =
else: else:
return false return false
return true return true
elif b.kind == Generic: if b.kind == Generic:
if a.kind == Generic: if a.kind == Generic:
for c1 in a.cond: return self.compareUnions(a.cond, b.cond)
for c2 in b.cond:
if self.compare(c1.kind, c2.kind):
if c1.match != c2.match:
return false
else:
return false
return true
else: else:
for constraint in b.cond: for constraint in b.cond:
if self.compare(constraint.kind, a): if self.compare(constraint.kind, a):
@ -900,9 +914,18 @@ proc compare(self: Compiler, a, b: Type): bool =
else: else:
return false return false
return true return true
# TODO: Is this ok? if a.kind == Any:
else: # Why this difference? The reason is that
result = false # while, for example, int is a subtype of
# any, meaning that passing int to a function
# that takes any is fine, this means that any
# cannot be a subtype of int! Basically, you
# can't pass any where int is expected, because
# that would break the type system in ways I'd
# rather not talk about
return true
if b.kind == Any:
return a.kind == Any
proc toIntrinsic(name: string): Type = proc toIntrinsic(name: string): Type =
@ -951,7 +974,7 @@ proc toIntrinsic(name: string): Type =
return nil return nil
proc infer(self: Compiler, node: LiteralExpr, allowGeneric: bool = false): Type = proc infer(self: Compiler, node: LiteralExpr): Type =
## Infers the type of a given literal expression ## Infers the type of a given literal expression
## (if the expression is nil, nil is returned) ## (if the expression is nil, nil is returned)
if node.isNil(): if node.isNil():
@ -995,88 +1018,26 @@ proc infer(self: Compiler, node: LiteralExpr, allowGeneric: bool = false): Type
discard # TODO discard # TODO
proc infer(self: Compiler, node: Expression, allowGeneric: bool = false): Type = proc infer(self: Compiler, node: Expression): Type =
## Infers the type of a given expression and ## Infers the type of a given expression and
## returns it (if the node is nil, nil is ## returns it (if the node is nil, nil is
## returned). Always returns a concrete type ## returned)
## unless allowGeneric is set to true
if node.isNil(): if node.isNil():
return nil return nil
if node.isLiteral():
return self.infer(LiteralExpr(node), allowGeneric)
case node.kind: case node.kind:
of identExpr: of identExpr:
let node = IdentExpr(node) result = self.identifier(IdentExpr(node), compile=false).valueType
var name = self.resolve(node)
if not name.isNil():
result = name.valueType
if not result.isNil() and result.kind == Generic and not allowGeneric:
if name.belongsTo.isNil():
name = self.resolve(result.name)
if not name.isNil():
result = name.valueType
else:
for arg in name.belongsTo.valueType.args:
if node.token.lexeme == arg.name:
result = arg.kind
else:
result = node.name.lexeme.toIntrinsic()
of unaryExpr: of unaryExpr:
let node = UnaryExpr(node) result = self.unary(UnaryExpr(node), compile=false).valueType.returnType
var default: Expression
let impl = self.match(node.operator.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.infer(node.a), default)]), node)
result = impl.valueType.returnType
if result.kind == Generic and not allowGeneric:
result = self.specialize(impl, @[node.a]).valueType.returnType
of binaryExpr: of binaryExpr:
let node = BinaryExpr(node) result = self.binary(BinaryExpr(node), compile=false).valueType.returnType
var default: Expression
let impl = self.match(node.operator.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.infer(node.a), default), ("", self.infer(node.b), default)]), node)
result = impl.valueType.returnType
if result.kind == Generic and not allowGeneric:
result = self.specialize(impl, @[node.a, node.b]).valueType.returnType
of {intExpr, hexExpr, binExpr, octExpr, of {intExpr, hexExpr, binExpr, octExpr,
strExpr, falseExpr, trueExpr, infExpr, strExpr, falseExpr, trueExpr, infExpr,
nanExpr, floatExpr, nilExpr nanExpr, floatExpr, nilExpr
}: }:
result = self.infer(LiteralExpr(node)) result = self.infer(LiteralExpr(node))
of lambdaExpr: of NodeKind.callExpr:
var node = LambdaExpr(node) result = self.call(CallExpr(node), compile=false).valueType.returnType
result = Type(kind: Function, returnType: nil, args: @[], isLambda: true, fun: node)
var default: Expression
if not node.returnType.isNil():
result.returnType = self.infer(node.returnType)
for argument in node.arguments:
result.args.add((argument.name.token.lexeme, self.infer(argument.valueType), default))
of callExpr:
var node = CallExpr(node)
case node.callee.kind:
of identExpr:
let resolved = self.resolve(IdentExpr(node.callee))
if not resolved.isNil():
case resolved.valueType.kind:
of Function:
if resolved.isGeneric and not allowGeneric:
var args: seq[Expression] = @[]
for argument in node.arguments.positionals:
args.add(argument)
for argument in node.arguments.keyword:
args.add(argument.value)
result = self.specialize(resolved, args).valueType.returnType
else:
result = resolved.valueType.returnType
else:
result = resolved.valueType
else:
result = nil
of lambdaExpr:
result = self.infer(LambdaExpr(node.callee).returnType)
of callExpr:
result = self.infer(CallExpr(node.callee))
if not result.isNil():
result = result.returnType
else:
discard # Unreachable
of varExpr: of varExpr:
result = self.infer(Var(node).value) result = self.infer(Var(node).value)
result.mutable = true result.mutable = true
@ -1089,15 +1050,15 @@ proc infer(self: Compiler, node: Expression, allowGeneric: bool = false): Type =
of NodeKind.getItemExpr: of NodeKind.getItemExpr:
result = self.getItemExpr(GetItemExpr(node), compile=false) result = self.getItemExpr(GetItemExpr(node), compile=false)
else: else:
discard # Unreachable discard # TODO
proc inferOrError(self: Compiler, node: Expression, allowGeneric: bool = false): Type = proc inferOrError(self: Compiler, node: Expression): Type =
## Attempts to infer the type of ## Attempts to infer the type of
## the given expression and raises an ## the given expression and raises an
## error with an appropriate message if ## error with an appropriate message if
## it fails ## it fails
result = self.infer(node, allowGeneric) result = self.infer(node)
if result.isNil(): if result.isNil():
self.error("expression has no type", node) self.error("expression has no type", node)
@ -1145,6 +1106,15 @@ proc typeToStr(self: Compiler, typ: Type): string =
result &= ", " result &= ", "
else: else:
result &= "}" result &= "}"
of Any:
return "any"
of Union:
for i, condition in typ.types:
if i > 0:
result &= " | "
if not condition.match:
result &= "~"
result &= self.typeToStr(condition.kind)
of Generic: of Generic:
for i, condition in typ.cond: for i, condition in typ.cond:
if i > 0: if i > 0:
@ -1204,12 +1174,12 @@ proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, args:
var impl: seq[Name] = @[] var impl: seq[Name] = @[]
var temp: Name var temp: Name
for obj in self.findByName(name): for obj in self.findByName(name):
if obj.isGeneric: if obj.isGeneric and args.len() > 0:
temp = self.specialize(obj, args) temp = self.specialize(obj, args)
else: else:
temp = obj temp = obj
if self.compare(kind, temp.valueType): if self.compare(kind, temp.valueType):
impl.add(temp) impl.add(self.match(name, temp.valueType, node))
if impl.len() == 0: if impl.len() == 0:
var msg = &"failed to find a suitable implementation for '{name}'" var msg = &"failed to find a suitable implementation for '{name}'"
let names = self.findByName(name) let names = self.findByName(name)
@ -1423,7 +1393,7 @@ proc endScope(self: Compiler) =
# Arguments to builtin functions become temporaries on the # Arguments to builtin functions become temporaries on the
# stack and are popped automatically # stack and are popped automatically
continue continue
if not name.belongsTo.resolved or not name.isReal: if not name.belongsTo.resolved or not name.belongsTo.isReal:
# Function hasn't been compiled yet, # Function hasn't been compiled yet,
# so we can't get rid of its arguments # so we can't get rid of its arguments
# (it may need them later) # (it may need them later)
@ -1492,7 +1462,31 @@ proc unpackGenerics(self: Compiler, condition: Expression, list: var seq[tuple[m
self.error("invalid type constraint in generic declaration", condition) self.error("invalid type constraint in generic declaration", condition)
proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name {.discardable.} = 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.unpackGenerics(condition.a, list)
self.unpackGenerics(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.unpackGenerics(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, mutable: bool = false): Name {.discardable.} =
## Statically declares a name into the current scope. ## Statically declares a name into the current scope.
## "Declaring" a name only means updating our internal ## "Declaring" a name only means updating our internal
## list of identifiers so that further calls to resolve() ## list of identifiers so that further calls to resolve()
@ -1555,8 +1549,8 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name {.d
of NodeKind.importStmt: of NodeKind.importStmt:
var node = ImportStmt(node) var node = ImportStmt(node)
# We change the name of the module internally so that # We change the name of the module internally so that
# if you do import /path/to/mod, then doing mod.f() # if you import /path/to/mod, then doing mod.f() will
# will work without any extra work on our end. Note how # still work without any extra work on our end. Note how
# we don't change the metadata about the identifier's # we don't change the metadata about the identifier's
# position so that error messages still highlight the # position so that error messages still highlight the
# full path # full path
@ -1564,7 +1558,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name {.d
node.moduleName.token.lexeme = node.moduleName.token.lexeme.extractFilename() node.moduleName.token.lexeme = node.moduleName.token.lexeme.extractFilename()
self.names.add(Name(depth: self.depth, self.names.add(Name(depth: self.depth,
owner: self.currentModule, owner: self.currentModule,
file: "", # The file of the module isn't known until compiled! file: "", # The file of the module isn't known until it's compiled!
path: path, path: path,
ident: node.moduleName, ident: node.moduleName,
line: node.moduleName.token.line, line: node.moduleName.token.line,
@ -1574,8 +1568,34 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name {.d
)) ))
n = self.names[^1] n = self.names[^1]
declaredName = self.names[^1].ident.token.lexeme declaredName = self.names[^1].ident.token.lexeme
of NodeKind.typeDecl:
var node = TypeDecl(node)
self.names.add(Name(kind: NameKind.CustomType,
depth: self.depth,
owner: self.currentModule,
node: node,
ident: node.name,
line: node.token.line,
isPrivate: node.isPrivate,
isReal: true,
belongsTo: self.currentFunction
)
)
n = self.names[^1]
if node.value.isNil():
discard # TODO: Fields
else:
case node.value.kind:
of identExpr:
n.valueType = self.inferOrError(node.value)
of binaryExpr:
# Type union
n.valueType = Type(kind: Union, types: @[])
self.unpackUnion(node.value, n.valueType.types)
else:
discard
else: else:
discard # TODO: Types, enums discard # TODO: enums
self.dispatchPragmas(n) self.dispatchPragmas(n)
case n.kind: case n.kind:
of NameKind.Function: of NameKind.Function:
@ -1629,12 +1649,43 @@ proc handleMagicPragma(self: Compiler, pragma: Pragma, name: Name) =
self.error("'magic' pragma: wrong number of arguments") self.error("'magic' pragma: wrong number of arguments")
elif pragma.args[0].kind != strExpr: elif pragma.args[0].kind != strExpr:
self.error("'magic' pragma: wrong type of argument (constant string expected)") self.error("'magic' pragma: wrong type of argument (constant string expected)")
elif name.node.kind != NodeKind.funDecl: elif name.node.kind == NodeKind.funDecl:
name.valueType.isBuiltinFunction = true
name.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2]
elif name.node.kind == NodeKind.typeDecl:
case pragma.args[0].token.lexeme[1..^2]:
of "int64":
name.valueType = Type(kind: Int64)
of "int32":
name.valueType = Type(kind: Int32)
of "int16":
name.valueType = Type(kind: Int16)
of "int8":
name.valueType = Type(kind: Int8)
of "uint64":
name.valueType = Type(kind: UInt64)
of "uint32":
name.valueType = Type(kind: UInt32)
of "uint16":
name.valueType = Type(kind: UInt16)
of "uint8":
name.valueType = Type(kind: UInt8)
of "float64":
name.valueType = Type(kind: Float64)
of "float32":
name.valueType = Type(kind: Float32)
of "bool":
name.valueType = Type(kind: Bool)
of "string":
name.valueType = Type(kind: String)
of "any":
name.valueType = Type(kind: Any)
of "all":
self.error("don't even think about it (compiler-chan is angry at you)", pragma)
else:
self.error("'magic' pragma: wrong argument value", pragma.args[0])
else:
self.error("'magic' pragma is not valid in this context") self.error("'magic' pragma is not valid in this context")
var node = FunDecl(name.node)
name.valueType.isBuiltinFunction = true
name.valueType.builtinOp = pragma.args[0].token.lexeme[1..^2]
proc handleErrorPragma(self: Compiler, pragma: Pragma, name: Name) = proc handleErrorPragma(self: Compiler, pragma: Pragma, name: Name) =
@ -1869,31 +1920,36 @@ proc literal(self: Compiler, node: ASTNode) =
self.error(&"invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug!)") self.error(&"invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug!)")
proc unary(self: Compiler, node: UnaryExpr) {.inline.} = proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Name {.discardable.} =
## Compiles all unary expressions ## Compiles all unary expressions
var default: Expression var default: Expression
let fn = Type(kind: Function, let fn = Type(kind: Function,
returnType: Type(kind: Any), returnType: Type(kind: Any),
args: @[("", self.inferOrError(node.a), default)]) args: @[("", self.inferOrError(node.a), default)])
let impl = self.match(node.token.lexeme, fn, node, @[node.a]) result = self.match(node.token.lexeme, fn, node, @[node.a])
self.generateCall(impl, @[node.a], impl.line) if compile:
self.generateCall(result, @[node.a], result.line)
proc binary(self: Compiler, node: BinaryExpr) {.inline.} = proc binary(self: Compiler, node: BinaryExpr, compile: bool = true): Name {.discardable.} =
## Compiles all binary expressions ## Compiles all binary expressions
var default: Expression 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: Type(kind: Any), args: @[("", self.inferOrError(node.a), default), ("", self.inferOrError(node.b), default)])
let impl = self.match(node.token.lexeme, fn, node, @[node.a, node.b]) result = self.match(node.token.lexeme, fn, node, @[node.a, node.b])
self.generateCall(impl, @[node.a, node.b], impl.line) if compile:
self.generateCall(result, @[node.a, node.b], result.line)
proc identifier(self: Compiler, node: IdentExpr, name: Name = nil) = proc identifier(self: Compiler, node: IdentExpr, name: Name = nil, compile: bool = true): Name {.discardable.} =
## Compiles access to identifiers ## Compiles access to identifiers
var s = name var s = name
if s.isNil(): if s.isNil():
s = self.resolveOrError(node) s = self.resolveOrError(node)
var t = self.findByType(s.ident.token.lexeme, Type(kind: All)) var t = self.findByType(s.ident.token.lexeme, Type(kind: All))
s = t[0] # Shadowing! s = t[0] # Shadowing!
result = s
if not compile:
return
if s.isConst: if s.isConst:
# Constants are always emitted as Load* instructions # Constants are always emitted as Load* instructions
# no matter the scope depth # no matter the scope depth
@ -1943,7 +1999,7 @@ proc identifier(self: Compiler, node: IdentExpr, name: Name = nil) =
self.emitBytes(self.getStackPos(s).toTriple(), s.ident.token.line) self.emitBytes(self.getStackPos(s).toTriple(), s.ident.token.line)
proc assignment(self: Compiler, node: ASTNode) = proc assignment(self: Compiler, node: ASTNode, compile: bool = true): Name {.discardable.} =
## Compiles assignment expressions ## Compiles assignment expressions
case node.kind: case node.kind:
of assignExpr: of assignExpr:
@ -1956,6 +2012,8 @@ proc assignment(self: Compiler, node: ASTNode) =
self.error(&"cannot reassign '{name.token.lexeme}' (value is immutable)", name) self.error(&"cannot reassign '{name.token.lexeme}' (value is immutable)", name)
self.check(node.value, r.valueType) self.check(node.value, r.valueType)
self.expression(node.value) self.expression(node.value)
if not compile:
return
if not r.isClosedOver: if not r.isClosedOver:
self.emitByte(StoreVar, node.token.line) self.emitByte(StoreVar, node.token.line)
self.emitBytes(self.getStackPos(r).toTriple(), node.token.line) self.emitBytes(self.getStackPos(r).toTriple(), node.token.line)
@ -1966,8 +2024,7 @@ proc assignment(self: Compiler, node: ASTNode) =
self.emitByte(StoreClosure, node.token.line) self.emitByte(StoreClosure, node.token.line)
self.emitBytes(self.getClosurePos(r).toTriple(), node.token.line) self.emitBytes(self.getClosurePos(r).toTriple(), node.token.line)
of setItemExpr: of setItemExpr:
let typ = self.inferOrError(SetItemExpr(node)) discard # TODO
else: else:
self.error(&"invalid AST node of kind {node.kind} at assignment(): {node} (This is an internal error and most likely a bug)") self.error(&"invalid AST node of kind {node.kind} at assignment(): {node} (This is an internal error and most likely a bug)")
@ -2043,6 +2100,9 @@ proc generateCall(self: Compiler, fn: Type, args: seq[Expression], line: int) {.
proc prepareFunction(self: Compiler, fn: Name) = proc prepareFunction(self: Compiler, fn: Name) =
## "Prepares" a function declaration by declaring
## its arguments and typechecking it
# First we declare the function's generics, if it has any. # First we declare the function's generics, if it has any.
# This is because the function's return type may in itself # This is because the function's return type may in itself
# be a generic, so it needs to exist first # be a generic, so it needs to exist first
@ -2074,7 +2134,7 @@ proc prepareFunction(self: Compiler, fn: Name) =
file: fn.file, file: fn.file,
isConst: false, isConst: false,
ident: argument.name, ident: argument.name,
valueType: self.inferOrError(argument.valueType, allowGeneric=true), valueType: self.inferOrError(argument.valueType),
codePos: 0, codePos: 0,
isLet: false, isLet: false,
line: argument.name.token.line, line: argument.name.token.line,
@ -2091,7 +2151,7 @@ proc prepareFunction(self: Compiler, fn: Name) =
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, self.names[^1].valueType, default))
# The function needs a return type too! # The function needs a return type too!
if not FunDecl(fn.node).returnType.isNil(): if not FunDecl(fn.node).returnType.isNil():
fn.valueType.returnType = self.inferOrError(FunDecl(fn.node).returnType, allowGeneric=true) fn.valueType.returnType = self.inferOrError(FunDecl(fn.node).returnType)
proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) = proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) =
@ -2108,7 +2168,7 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) =
self.emitByte(LoadUInt64, line) self.emitByte(LoadUInt64, line)
self.emitBytes(self.chunk.writeConstant(fn.codePos.toLong()), line) self.emitBytes(self.chunk.writeConstant(fn.codePos.toLong()), line)
else: else:
discard discard # Unreachable
if fn.valueType.forwarded: if fn.valueType.forwarded:
self.forwarded.add((fn, self.chunk.consts.high() - 7)) self.forwarded.add((fn, self.chunk.consts.high() - 7))
self.emitByte(LoadUInt64, line) self.emitByte(LoadUInt64, line)
@ -2172,14 +2232,14 @@ proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name =
discard # TODO: Custom user-defined types discard # TODO: Custom user-defined types
proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} = proc call(self: Compiler, node: CallExpr, compile: bool = true): Name {.discardable.} =
## Compiles code to call a chain of function calls ## Compiles code to call a chain of function calls
var args: seq[tuple[name: string, kind: Type, default: Expression]] = @[] var args: seq[tuple[name: string, kind: Type, default: Expression]] = @[]
var argExpr: seq[Expression] = @[] var argExpr: seq[Expression] = @[]
var default: Expression var default: Expression
var kind: Type var kind: Type
for i, argument in node.arguments.positionals: for i, argument in node.arguments.positionals:
kind = self.infer(argument, allowGeneric=false) # We don't use inferOrError so that we can raise a more appropriate error message kind = self.infer(argument) # We don't use inferOrError so that we can raise a more appropriate error message
if kind.isNil(): if kind.isNil():
if argument.kind == NodeKind.identExpr: if argument.kind == NodeKind.identExpr:
self.error(&"reference to undeclared name '{argument.token.lexeme}'", argument) self.error(&"reference to undeclared name '{argument.token.lexeme}'", argument)
@ -2187,7 +2247,7 @@ proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} =
args.add(("", kind, default)) args.add(("", kind, default))
argExpr.add(argument) argExpr.add(argument)
for i, argument in node.arguments.keyword: for i, argument in node.arguments.keyword:
kind = self.infer(argument.value, allowGeneric=false) kind = self.infer(argument.value)
if kind.isNil(): if kind.isNil():
if argument.value.kind == NodeKind.identExpr: if argument.value.kind == NodeKind.identExpr:
self.error(&"reference to undeclared name '{argument.value.token.lexeme}'", argument.value) self.error(&"reference to undeclared name '{argument.value.token.lexeme}'", argument.value)
@ -2199,7 +2259,8 @@ proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} =
# Calls like hi() # Calls like hi()
result = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node, argExpr) result = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node, argExpr)
# Now we call it # Now we call it
self.generateCall(result, argExpr, node.token.line) if compile:
self.generateCall(result, argExpr, node.token.line)
of NodeKind.callExpr: of NodeKind.callExpr:
# Calling a call expression, like hello()() # Calling a call expression, like hello()()
var node: Expression = node var node: Expression = node
@ -2215,7 +2276,7 @@ proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} =
# the way back to the first one earlier) and work # the way back to the first one earlier) and work
# our way to the innermost call # our way to the innermost call
for exp in reversed(all): for exp in reversed(all):
self.callExpr(exp) self.call(exp, compile)
# TODO: Calling lambdas on-the-fly (i.e. on the same line) # TODO: Calling lambdas on-the-fly (i.e. on the same line)
else: else:
let typ = self.infer(node) let typ = self.infer(node)
@ -2252,7 +2313,7 @@ proc expression(self: Compiler, node: Expression) =
## Compiles all expressions ## Compiles all expressions
case node.kind: case node.kind:
of NodeKind.callExpr: of NodeKind.callExpr:
self.callExpr(CallExpr(node)) self.call(CallExpr(node))
of NodeKind.getItemExpr: of NodeKind.getItemExpr:
self.getItemExpr(GetItemExpr(node)) self.getItemExpr(GetItemExpr(node))
of NodeKind.pragmaExpr: of NodeKind.pragmaExpr:
@ -2377,8 +2438,7 @@ proc breakStmt(self: Compiler, node: BreakStmt) =
proc assertStmt(self: Compiler, node: AssertStmt) = proc assertStmt(self: Compiler, node: AssertStmt) =
## Compiles assert statements (raise ## Compiles assert statements (raise
## AssertionError if the expression is falsey) ## AssertionError if the expression is falsey)
self.expression(node.expression) # TODO
self.emitByte(OpCode.Assert, node.token.line)
proc forEachStmt(self: Compiler, node: ForEachStmt) = proc forEachStmt(self: Compiler, node: ForEachStmt) =
@ -2388,7 +2448,7 @@ proc forEachStmt(self: Compiler, node: ForEachStmt) =
proc importStmt(self: Compiler, node: ImportStmt) = proc importStmt(self: Compiler, node: ImportStmt) =
## Imports a module at compile time ## Imports a module at compile time
self.declareName(node) self.declare(node)
var module = self.names[^1] var module = self.names[^1]
try: try:
self.compileModule(module) self.compileModule(module)
@ -2540,7 +2600,7 @@ proc varDecl(self: Compiler, node: VarDecl) =
typ = expected typ = expected
self.expression(node.value) self.expression(node.value)
self.emitByte(AddVar, node.token.line) self.emitByte(AddVar, node.token.line)
self.declareName(node) self.declare(node)
var name = self.names[^1] var name = self.names[^1]
name.valueType = typ name.valueType = typ
@ -2587,7 +2647,6 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
else: else:
self.chunk.functions.add(0.toDouble()) self.chunk.functions.add(0.toDouble())
if BlockStmt(node.body).code.len() == 0: if BlockStmt(node.body).code.len() == 0:
raise newException(IndexDefect, "")
self.error("cannot declare function with empty body") self.error("cannot declare function with empty body")
# Since the deferred array is a linear # Since the deferred array is a linear
# sequence of instructions and we want # sequence of instructions and we want
@ -2655,12 +2714,12 @@ proc declaration(self: Compiler, node: Declaration) =
## the first time ## the first time
case node.kind: case node.kind:
of NodeKind.funDecl: of NodeKind.funDecl:
var name = self.declareName(node) var name = self.declare(node)
if name.isGeneric: if name.isGeneric:
# We typecheck generics immediately # We typecheck generics immediately
self.funDecl(FunDecl(node), name) self.funDecl(FunDecl(node), name)
of NodeKind.typeDecl: of NodeKind.typeDecl:
self.declareName(node) self.declare(node)
of NodeKind.varDecl: of NodeKind.varDecl:
# We compile this immediately because we # We compile this immediately because we
# need to keep the stack in the right state # need to keep the stack in the right state
@ -2708,7 +2767,7 @@ proc compileModule(self: Compiler, module: Name) =
var moduleName = module.path & ".pn" var moduleName = module.path & ".pn"
for i, searchPath in moduleLookupPaths: for i, searchPath in moduleLookupPaths:
if searchPath == "": if searchPath == "":
path = joinPath(getCurrentDir(), joinPath(splitPath(self.file).head, moduleName)) path = absolutePath(joinPath(splitPath(self.file).head, moduleName))
else: else:
path = joinPath(searchPath, moduleName) path = joinPath(searchPath, moduleName)
if fileExists(path): if fileExists(path):

View File

@ -276,6 +276,7 @@ type
isEnum*: bool isEnum*: bool
isRef*: bool isRef*: bool
parent*: IdentExpr parent*: IdentExpr
value*: Expression
Pragma* = ref object of Expression Pragma* = ref object of Expression
name*: IdentExpr name*: IdentExpr

View File

@ -139,9 +139,8 @@ proc newOperatorTable: OperatorTable =
result.tokens = @[] result.tokens = @[]
for prec in Precedence: for prec in Precedence:
result.precedence[prec] = @[] result.precedence[prec] = @[]
# Assignment and attribute accessing are (currently) # These operators are currently not built-in
# the only builtins that cannot be easily implemented # due to compiler limitations
# from within peon itself
result.addOperator("=") result.addOperator("=")
result.addOperator(".") result.addOperator(".")
@ -735,7 +734,7 @@ proc importStmt(self: Parser, fromStmt: bool = false): Statement =
var path = "" var path = ""
for i, searchPath in moduleLookupPaths: for i, searchPath in moduleLookupPaths:
if searchPath == "": if searchPath == "":
path = joinPath(getCurrentDir(), joinPath(splitPath(self.file).head, moduleName)) path = absolutePath(joinPath(splitPath(self.file).head, moduleName))
else: else:
path = joinPath(searchPath, moduleName) path = joinPath(searchPath, moduleName)
if fileExists(path): if fileExists(path):
@ -986,6 +985,8 @@ proc parseGenericConstraint(self: Parser): Expression =
result = newBinaryExpr(result, self.step(), self.parseGenericConstraint()) result = newBinaryExpr(result, self.step(), self.parseGenericConstraint())
of "~": of "~":
result = newUnaryExpr(self.step(), result) result = newUnaryExpr(self.step(), result)
of ",":
discard # Comma is handled in parseGenerics()
else: else:
self.error("invalid type constraint in generic declaration") self.error("invalid type constraint in generic declaration")
@ -1183,9 +1184,9 @@ proc typeDecl(self: Parser): TypeDecl =
## Parses type declarations ## Parses type declarations
let token = self.peek(-1) let token = self.peek(-1)
self.expect(Identifier, "expecting type name after 'type'") self.expect(Identifier, "expecting type name after 'type'")
var name = newIdentExpr(self.peek(-1), self.scopeDepth)
let isPrivate = not self.match("*") let isPrivate = not self.match("*")
self.checkDecl(isPrivate) self.checkDecl(isPrivate)
var name = newIdentExpr(self.peek(-1), self.scopeDepth)
var fields: seq[tuple[name: IdentExpr, valueType: Expression, isPrivate: bool]] = @[] var fields: seq[tuple[name: IdentExpr, valueType: Expression, isPrivate: bool]] = @[]
var defaults: seq[Expression] = @[] var defaults: seq[Expression] = @[]
var generics: seq[tuple[name: IdentExpr, cond: Expression]] = @[] var generics: seq[tuple[name: IdentExpr, cond: Expression]] = @[]
@ -1194,44 +1195,58 @@ proc typeDecl(self: Parser): TypeDecl =
if self.match(LeftBracket): if self.match(LeftBracket):
self.parseGenerics(result) self.parseGenerics(result)
self.expect("=", "expecting '=' after type name") self.expect("=", "expecting '=' after type name")
case self.step().lexeme: var hasNone = false
case self.peek().lexeme:
of "ref": of "ref":
discard self.step()
self.expect("object", "expecting 'object' after 'ref'") self.expect("object", "expecting 'object' after 'ref'")
result.isRef = true result.isRef = true
of "enum": of "enum":
discard self.step()
result.isEnum = true result.isEnum = true
of "object": of "object":
discard self.step()
discard # Default case discard # Default case
else: else:
self.error("invalid syntax") hasNone = true
if not result.isEnum and self.match("of"): if hasNone:
self.expect(Identifier, "expecting parent type name after 'of'") result.value = self.expression()
result.parent = newIdentExpr(self.peek(-1)) while not self.check(";"):
self.expect(LeftBrace, "expecting '{' after type declaration") case self.peek().lexeme:
if self.match(TokenType.Pragma): of "|": # Type unions!
for pragma in self.parsePragmas(): result.value = newBinaryExpr(result.value, self.step(), self.expression())
pragmas.add(pragma) else:
var discard
argName: IdentExpr else:
argPrivate: bool if not result.isEnum and not hasNone and self.match("of"):
argType: Expression self.expect(Identifier, "expecting parent type name after 'of'")
while not self.match(RightBrace) and not self.done(): result.parent = newIdentExpr(self.peek(-1))
self.expect(Identifier, "expecting field name") if not self.match(";"): # Type has no fields. Usually for unions or aliases
argName = newIdentExpr(self.peek(-1), self.scopeDepth) self.expect(LeftBrace, "expecting '{' after type declaration")
if not result.isEnum: if self.match(TokenType.Pragma):
argPrivate = not self.match("*") for pragma in self.parsePragmas():
self.expect(":", "expecting ':' after field name") pragmas.add(pragma)
argType = self.expression() var
result.fields.add((argName, argType, argPrivate)) argName: IdentExpr
if self.match("="): argPrivate: bool
result.defaults.add(self.expression()) argType: Expression
else: while not self.match(RightBrace) and not self.done():
result.fields.add((argName, nil, false)) self.expect(Identifier, "expecting field name")
if not result.isEnum: argName = newIdentExpr(self.peek(-1), self.scopeDepth)
self.expect(";", "expecting semicolon after type field declaration") if not result.isEnum:
else: argPrivate = not self.match("*")
if not self.check(RightBrace): self.expect(":", "expecting ':' after field name")
self.expect(",", "expecting comma after enum field declaration") argType = self.expression()
result.fields.add((argName, argType, argPrivate))
if self.match("="):
result.defaults.add(self.expression())
else:
result.fields.add((argName, nil, false))
if not result.isEnum:
self.expect(";", "expecting semicolon after type field declaration")
else:
if not self.check(RightBrace):
self.expect(",", "expecting comma after enum field declaration")
result.pragmas = pragmas result.pragmas = pragmas

View File

@ -85,6 +85,8 @@ proc repl =
elif input == "#clear": elif input == "#clear":
stdout.write("\x1Bc") stdout.write("\x1Bc")
continue continue
elif input == "":
continue
else: else:
current &= input & "\n" current &= input & "\n"
if current.len() == 0: if current.len() == 0:

View File

@ -1,12 +1,14 @@
# Builtin arithmetic operators for Peon # Builtin arithmetic operators for Peon
# Note: Most of these do nothing on their own. All they do
# Note: These stubs do nothing on their own. All they do
# is serve as placeholders for emitting specific VM # is serve as placeholders for emitting specific VM
# instructions. They're implemented this way because: # instructions. They're implemented this way because:
# - They tie into the existing type system nicely # - They tie into the existing type system nicely
# - It makes the implementation easier and more flexible # - It makes the implementation easier and more flexible
import values;
operator `+`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8](a, b: T): T { operator `+`*[T: Integer](a, b: T): T {
#pragma[magic: "Add", pure] #pragma[magic: "Add", pure]
} }
@ -21,7 +23,7 @@ operator `+`(a, b: float32): float32 {
} }
operator `-`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8](a, b: T): T { operator `-`*[T: Integer](a, b: T): T {
#pragma[magic: "Subtract", pure] #pragma[magic: "Subtract", pure]
} }
@ -36,12 +38,12 @@ operator `-`*(a, b: float32): float32 {
} }
operator `-`*[T: int | int32 | int16 | int8](a: T): T { operator `-`*[T: SignedInteger](a: T): T {
#pragma[magic: "Negate", pure] #pragma[magic: "Negate", pure]
} }
operator `-`*[T: uint64 | uint32 | uint16 | uint8](a: T): T { operator `-`*[T: UnsignedInteger](a: T): T {
#pragma[magic: "", error: "unsigned integer cannot be negative"] #pragma[magic: "", error: "unsigned integer cannot be negative"]
} }
@ -61,7 +63,7 @@ operator `-`*(a: inf): inf {
} }
operator `*`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8](a, b: T): T { operator `*`*[T: Integer](a, b: T): T {
#pragma[magic: "Multiply", pure] #pragma[magic: "Multiply", pure]
} }
@ -76,12 +78,12 @@ operator `*`*(a, b: float32): float32 {
} }
operator `/`*[T: int | int32 | int16 | int8](a, b: T): T { operator `/`*[T: SignedInteger](a, b: T): T {
#pragma[magic: "SignedDivide", pure] #pragma[magic: "SignedDivide", pure]
} }
operator `/`*[T: uint64 | uint32 | uint16 | uint8](a, b: T): T { operator `/`*[T: UnsignedInteger](a, b: T): T {
#pragma[magic: "Divide", pure] #pragma[magic: "Divide", pure]
} }
@ -96,16 +98,16 @@ operator `/`*(a, b: float32): float32 {
} }
operator `**`*[T: int | int32 | int16 | int8](a, b: T): T { operator `**`*[T: SignedInteger](a, b: T): T {
#pragma[magic: "SignedPow", pure] #pragma[magic: "SignedPow", pure]
} }
operator `**`*[T: uint64 | uint32 | uint16 | uint8](a, b: T): T { operator `**`*[T: UnsignedInteger](a, b: T): T {
#pragma[magic: "Pow", pure] #pragma[magic: "Pow", pure]
} }
operator `%`*(a, b: int64): int64 { operator `%`*[T: SignedInteger](a, b: T): T {
#pragma[magic: "SignedMod", pure] #pragma[magic: "SignedMod", pure]
} }

View File

@ -1,4 +1,6 @@
# Bitwise operations on primitive types # Bitwise operations on primitive types
import values;
operator `&`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8](a, b: T): T { operator `&`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8](a, b: T): T {
#pragma[magic: "And", pure] #pragma[magic: "And", pure]

View File

@ -1,30 +1,31 @@
# Comparison operators # Comparison operators
import values;
operator `>`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool {
operator `>`*[T: Number](a, b: T): bool {
#pragma[magic: "GreaterThan", pure] #pragma[magic: "GreaterThan", pure]
} }
operator `<`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool { operator `<`*[T: Number](a, b: T): bool {
#pragma[magic: "LessThan", pure] #pragma[magic: "LessThan", pure]
} }
operator `==`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool { operator `==`*[T: Number](a, b: T): bool {
#pragma[magic: "Equal", pure] #pragma[magic: "Equal", pure]
} }
operator `!=`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool { operator `!=`*[T: Number](a, b: T): bool {
#pragma[magic: "NotEqual", pure] #pragma[magic: "NotEqual", pure]
} }
operator `>=`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool { operator `>=`*[T: Number](a, b: T): bool {
#pragma[magic: "GreaterOrEqual", pure] #pragma[magic: "GreaterOrEqual", pure]
} }
operator `<=`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8 | float | float32](a, b: T): bool { operator `<=`*[T: Number](a, b: T): bool {
#pragma[magic: "LessOrEqual", pure] #pragma[magic: "LessOrEqual", pure]
} }

View File

@ -1,4 +1,6 @@
# Logical operators # Logical operators
import values;
operator `and`*(a, b: bool): bool { operator `and`*(a, b: bool): bool {
#pragma[magic: "LogicalAnd", pure] #pragma[magic: "LogicalAnd", pure]

View File

@ -1,4 +1,5 @@
# Various miscellaneous utilities # Various miscellaneous utilities
import values;
# Some useful builtins # Some useful builtins

View File

@ -0,0 +1,24 @@
# Stub type declarations for peon's intrinsic types
type int64* = object {#pragma[magic: "int64"]}
type int32* = object {#pragma[magic: "int32"]}
type int16* = object {#pragma[magic: "int16"]}
type int8* = object {#pragma[magic: "int8"]}
type uint64* = object {#pragma[magic: "uint64"]}
type uint32* = object {#pragma[magic: "uint32"]}
type uint16* = object {#pragma[magic: "uint16"]}
type uint8* = object {#pragma[magic: "uint8"]}
type float64* = object {#pragma[magic: "float64"]}
type float32* = object {#pragma[magic: "float32"]}
type bool* = object {#pragma[magic: "bool"]}
type string* = object {#pragma[magic: "string"]}
type any* = object {#pragma[magic: "any"]}
# Some convenience aliases
type int* = int64;
type float* = float64;
type SignedInteger* = int64 | int32 | int16 | int8;
type UnsignedInteger* = uint64 | uint32 | uint16 | uint8;
type Integer* = SignedInteger | UnsignedInteger;
type Number* = Integer | float64 | float32;

View File

@ -1,18 +1,20 @@
## The peon standard library ## The peon standard library
import builtins/values;
import builtins/arithmetics; import builtins/arithmetics;
import builtins/bitwise; import builtins/bitwise;
import builtins/logical; import builtins/logical;
import builtins/misc; import builtins/misc;
import builtins/comparisons; import builtins/comparisons;
export values;
export arithmetics; export arithmetics;
export bitwise; export bitwise;
export logical; export logical;
export misc; export misc;
export comparisons; export comparisons;
var version* = 1; var version* = 1;
var _private = 5; # Invisible outside the module (underscore is to silence warning) var _private = 5; # Invisible outside the module (underscore is to silence warning)
var test* = 0x60; var test* = 0x60;

View File

@ -32,9 +32,10 @@ proc printError(file, line: string, lineNo: int, pos: tuple[start, stop: int], f
if not fn.isNil() and fn.kind == funDecl: if not fn.isNil() and fn.kind == funDecl:
stderr.styledWrite(fgRed, styleBright, " in function ", fgYellow, FunDecl(fn).name.token.lexeme) stderr.styledWrite(fgRed, styleBright, " in function ", fgYellow, FunDecl(fn).name.token.lexeme)
stderr.styledWriteLine(styleBright, fgDefault, ": ", msg) stderr.styledWriteLine(styleBright, fgDefault, ": ", msg)
stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start]) if line.len() > 0:
stderr.styledWrite(fgRed, styleUnderscore, line[pos.start..pos.stop]) stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start])
stderr.styledWriteLine(fgDefault, line[pos.stop + 1..^1]) stderr.styledWrite(fgRed, styleUnderscore, line[pos.start..pos.stop])
stderr.styledWriteLine(fgDefault, line[pos.stop + 1..^1])
proc print*(exc: CompileError) = proc print*(exc: CompileError) =

View File

@ -5,15 +5,18 @@ import std;
fn noReturn(n: int) { fn noReturn(n: int) {
var n = n; var n = n;
var `17` = 17; var `17` = 17;
print(n == n); # true
print(`17` == 17); # true
return; return;
} }
fn fooBar(a, b: int): int { fn fooBar(a, b: int): int {
var baz = 38; var baz = 38;
print(baz == 38);
return a; return a;
} }
noReturn(1); noReturn(1);
print(fooBar(1, 3) == 1); # true print(fooBar(1, 3) == 1); # true

View File

@ -1,12 +1,12 @@
# Tests generic functions # Another test for generic functions
import std; import std;
fn sum[T: any](a, b: T): T { fn sum[T: int | int32](a, b: T): T {
return a + b; return a + b;
} }
print(sum(1, 2)); # Prints 3 print(sum(1, 2)); # Prints 3
print(sum(1'i32, 2'i32)); # Also prints 3! print(sum(1'i32, 2'i32)); # Also prints 3!
# print(sum(1'i16, 2'i16)); # Will not work if uncommented: print is not defined for i16! #print(sum(1.0, 2.0)); # Will fail to compile

7
tests/generics3.pn Normal file
View File

@ -0,0 +1,7 @@
# Another test for generic functions
import std;
fn sum[T: any](a: T, b: T): T {
return a + b;
}