Added tagged type unions, moved intrinsics to peon's stdlib and fixed issues with generics and parametric polymorphism
This commit is contained in:
parent
382a6c141b
commit
63cdda42ce
11
README.md
11
README.md
|
@ -77,6 +77,7 @@ Type system:
|
|||
|
||||
- Custom types
|
||||
- Intrinsics ✅
|
||||
- Type unions ✅
|
||||
- Generics ✅
|
||||
- Functions ✅
|
||||
- Closures ✅
|
||||
|
@ -85,8 +86,8 @@ Misc:
|
|||
|
||||
- Pragmas ✅
|
||||
- Attribute resolution ✅
|
||||
- UFCS
|
||||
- Import system with namespaces and visibility control ✅
|
||||
- UFCS (Universal Function Call Syntax)
|
||||
- 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
|
||||
|
@ -101,9 +102,9 @@ have been implemented alredady):
|
|||
- Easy C/Nim interop via FFI
|
||||
- C/C++ backend
|
||||
- Nim backend
|
||||
- Structured concurrency
|
||||
- Capability-based programming (i.e. functions are passed objects to act on the real world)
|
||||
- Parametric Polymorphism (with untagged typed unions) ✅
|
||||
- Structured concurrency (must-have!)
|
||||
- Capability-based programming (i.e. functions are passed objects to act on the real world. This is just a maybe)
|
||||
- Parametric Polymorphism ✅
|
||||
- Simple OOP (with multiple dispatch!)
|
||||
- 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)
|
||||
|
|
|
@ -135,7 +135,6 @@ genericSum(3.14, 0.1);
|
|||
genericSum(1'u8, 250'u8);
|
||||
```
|
||||
|
||||
__Note__: The generic `Number` type is currently not defined. You can use type unions instead
|
||||
|
||||
#### More generics!
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ type
|
|||
UInt32, Int64, UInt64, Float32, Float64,
|
||||
Char, Byte, String, Function, CustomType,
|
||||
Nil, Nan, Bool, Inf, Typevar, Generic,
|
||||
Reference, Pointer, Any, All
|
||||
Reference, Pointer, Any, All, Union
|
||||
Type = ref object
|
||||
## A wrapper around
|
||||
## compile-time types
|
||||
|
@ -77,6 +77,8 @@ type
|
|||
# would map to [(true, int), (false, uint)]
|
||||
cond: seq[tuple[match: bool, kind: Type]]
|
||||
name: string
|
||||
of Union:
|
||||
types: seq[tuple[match: bool, kind: Type]]
|
||||
else:
|
||||
discard
|
||||
|
||||
|
@ -261,14 +263,17 @@ proc expression(self: Compiler, node: Expression)
|
|||
proc statement(self: Compiler, node: Statement)
|
||||
proc declaration(self: Compiler, node: Declaration)
|
||||
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 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 call(self: Compiler, node: CallExpr, compile: bool = true): Name {.discardable.}
|
||||
proc getItemExpr(self: Compiler, node: GetItemExpr, compile: bool = true): Type {.discardable.}
|
||||
proc infer(self: Compiler, node: LiteralExpr, allowGeneric: bool = false): Type
|
||||
proc infer(self: Compiler, node: Expression, allowGeneric: bool = false): Type
|
||||
proc inferOrError(self: Compiler, node: Expression, allowGeneric: bool = false): Type
|
||||
proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Name {.discardable.}
|
||||
proc binary(self: Compiler, node: BinaryExpr, compile: bool = true): Name {.discardable.}
|
||||
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 findInModule(self: Compiler, name: string, module: Name): 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
|
||||
# temporaries. There is no stack frame for builtins, so we skip
|
||||
# these names too
|
||||
elif variable.kind == Argument and variable.belongsTo.valueType.isBuiltinFunction:
|
||||
continue
|
||||
elif variable.kind == Argument:
|
||||
if variable.belongsTo.valueType.isBuiltinFunction:
|
||||
continue
|
||||
elif not variable.belongsTo.resolved:
|
||||
continue
|
||||
# This variable isn't in declared in our module,
|
||||
# but we may still have access to it
|
||||
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:
|
||||
inc(result)
|
||||
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 not name.belongsTo.isNil() and not variable.belongsTo.isNil():
|
||||
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)
|
||||
continue
|
||||
found = true
|
||||
|
@ -758,6 +768,28 @@ proc getClosurePos(self: Compiler, name: Name): int =
|
|||
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 =
|
||||
## Compares two type objects
|
||||
## 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
|
||||
# we compare their return types)
|
||||
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():
|
||||
result = b.isNil() or b.kind == All
|
||||
elif b.isNil():
|
||||
result = a.isNil() or a.kind == All
|
||||
elif a.kind == All:
|
||||
result = true
|
||||
elif b.kind == All:
|
||||
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:
|
||||
return b.isNil() or b.kind == All
|
||||
if b.isNil():
|
||||
return a.isNil() or a.kind == All
|
||||
if a.kind == All or b.kind == All:
|
||||
return true
|
||||
if a.kind != Generic and b.kind != Generic and a.kind == b.kind:
|
||||
# Here we make sure to compare only concrete
|
||||
# types
|
||||
case a.kind:
|
||||
|
@ -803,7 +818,9 @@ proc compare(self: Compiler, a, b: Type): bool =
|
|||
Char, Byte, String, Nil, Nan, Bool, Inf:
|
||||
# A value's type is always equal to
|
||||
# another one's
|
||||
result = true
|
||||
return true
|
||||
of Union:
|
||||
return self.compareUnions(a.types, b.types)
|
||||
of Reference, Pointer:
|
||||
# Here we already know that both
|
||||
# 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):
|
||||
temp = false
|
||||
break
|
||||
result = temp
|
||||
return temp
|
||||
else:
|
||||
discard # TODO: Custom types
|
||||
elif a.kind == Generic:
|
||||
discard # TODO: Custom types, enums
|
||||
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:
|
||||
for c1 in a.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
|
||||
return self.compareUnions(a.cond, b.cond)
|
||||
else:
|
||||
for constraint in a.cond:
|
||||
if self.compare(constraint.kind, b):
|
||||
|
@ -882,16 +903,9 @@ proc compare(self: Compiler, a, b: Type): bool =
|
|||
else:
|
||||
return false
|
||||
return true
|
||||
elif b.kind == Generic:
|
||||
if b.kind == Generic:
|
||||
if a.kind == Generic:
|
||||
for c1 in a.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
|
||||
return self.compareUnions(a.cond, b.cond)
|
||||
else:
|
||||
for constraint in b.cond:
|
||||
if self.compare(constraint.kind, a):
|
||||
|
@ -900,9 +914,18 @@ proc compare(self: Compiler, a, b: Type): bool =
|
|||
else:
|
||||
return false
|
||||
return true
|
||||
# TODO: Is this ok?
|
||||
else:
|
||||
result = false
|
||||
if a.kind == Any:
|
||||
# Why this difference? The reason is that
|
||||
# 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 =
|
||||
|
@ -951,7 +974,7 @@ proc toIntrinsic(name: string): Type =
|
|||
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
|
||||
## (if the expression is nil, nil is returned)
|
||||
if node.isNil():
|
||||
|
@ -995,88 +1018,26 @@ proc infer(self: Compiler, node: LiteralExpr, allowGeneric: bool = false): Type
|
|||
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
|
||||
## returns it (if the node is nil, nil is
|
||||
## returned). Always returns a concrete type
|
||||
## unless allowGeneric is set to true
|
||||
## returned)
|
||||
if node.isNil():
|
||||
return nil
|
||||
if node.isLiteral():
|
||||
return self.infer(LiteralExpr(node), allowGeneric)
|
||||
case node.kind:
|
||||
of identExpr:
|
||||
let node = IdentExpr(node)
|
||||
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()
|
||||
result = self.identifier(IdentExpr(node), compile=false).valueType
|
||||
of unaryExpr:
|
||||
let node = UnaryExpr(node)
|
||||
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
|
||||
result = self.unary(UnaryExpr(node), compile=false).valueType.returnType
|
||||
of binaryExpr:
|
||||
let node = BinaryExpr(node)
|
||||
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
|
||||
result = self.binary(BinaryExpr(node), compile=false).valueType.returnType
|
||||
of {intExpr, hexExpr, binExpr, octExpr,
|
||||
strExpr, falseExpr, trueExpr, infExpr,
|
||||
nanExpr, floatExpr, nilExpr
|
||||
}:
|
||||
result = self.infer(LiteralExpr(node))
|
||||
of lambdaExpr:
|
||||
var node = LambdaExpr(node)
|
||||
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 NodeKind.callExpr:
|
||||
result = self.call(CallExpr(node), compile=false).valueType.returnType
|
||||
of varExpr:
|
||||
result = self.infer(Var(node).value)
|
||||
result.mutable = true
|
||||
|
@ -1089,15 +1050,15 @@ proc infer(self: Compiler, node: Expression, allowGeneric: bool = false): Type =
|
|||
of NodeKind.getItemExpr:
|
||||
result = self.getItemExpr(GetItemExpr(node), compile=false)
|
||||
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
|
||||
## the given expression and raises an
|
||||
## error with an appropriate message if
|
||||
## it fails
|
||||
result = self.infer(node, allowGeneric)
|
||||
result = self.infer(node)
|
||||
if result.isNil():
|
||||
self.error("expression has no type", node)
|
||||
|
||||
|
@ -1145,6 +1106,15 @@ proc typeToStr(self: Compiler, typ: Type): string =
|
|||
result &= ", "
|
||||
else:
|
||||
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:
|
||||
for i, condition in typ.cond:
|
||||
if i > 0:
|
||||
|
@ -1204,12 +1174,12 @@ proc match(self: Compiler, name: string, kind: Type, node: ASTNode = nil, args:
|
|||
var impl: seq[Name] = @[]
|
||||
var temp: Name
|
||||
for obj in self.findByName(name):
|
||||
if obj.isGeneric:
|
||||
if obj.isGeneric and args.len() > 0:
|
||||
temp = self.specialize(obj, args)
|
||||
else:
|
||||
temp = obj
|
||||
if self.compare(kind, temp.valueType):
|
||||
impl.add(temp)
|
||||
impl.add(self.match(name, temp.valueType, node))
|
||||
if impl.len() == 0:
|
||||
var msg = &"failed to find a suitable implementation for '{name}'"
|
||||
let names = self.findByName(name)
|
||||
|
@ -1423,7 +1393,7 @@ proc endScope(self: Compiler) =
|
|||
# Arguments to builtin functions become temporaries on the
|
||||
# stack and are popped automatically
|
||||
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,
|
||||
# so we can't get rid of its arguments
|
||||
# (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)
|
||||
|
||||
|
||||
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.
|
||||
## "Declaring" a name only means updating our internal
|
||||
## 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:
|
||||
var node = ImportStmt(node)
|
||||
# We change the name of the module internally so that
|
||||
# if you do import /path/to/mod, then doing mod.f()
|
||||
# will work without any extra work on our end. Note how
|
||||
# if you import /path/to/mod, then doing mod.f() will
|
||||
# still work without any extra work on our end. Note how
|
||||
# we don't change the metadata about the identifier's
|
||||
# position so that error messages still highlight the
|
||||
# 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()
|
||||
self.names.add(Name(depth: self.depth,
|
||||
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,
|
||||
ident: node.moduleName,
|
||||
line: node.moduleName.token.line,
|
||||
|
@ -1574,8 +1568,34 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name {.d
|
|||
))
|
||||
n = self.names[^1]
|
||||
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:
|
||||
discard # TODO: Types, enums
|
||||
discard # TODO: enums
|
||||
self.dispatchPragmas(n)
|
||||
case n.kind:
|
||||
of NameKind.Function:
|
||||
|
@ -1629,12 +1649,43 @@ proc handleMagicPragma(self: Compiler, pragma: Pragma, name: Name) =
|
|||
self.error("'magic' pragma: wrong number of arguments")
|
||||
elif pragma.args[0].kind != strExpr:
|
||||
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")
|
||||
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) =
|
||||
|
@ -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!)")
|
||||
|
||||
|
||||
proc unary(self: Compiler, node: UnaryExpr) {.inline.} =
|
||||
proc unary(self: Compiler, node: UnaryExpr, compile: bool = true): Name {.discardable.} =
|
||||
## Compiles all unary expressions
|
||||
var default: Expression
|
||||
let fn = Type(kind: Function,
|
||||
returnType: Type(kind: Any),
|
||||
args: @[("", self.inferOrError(node.a), default)])
|
||||
let impl = self.match(node.token.lexeme, fn, node, @[node.a])
|
||||
self.generateCall(impl, @[node.a], impl.line)
|
||||
result = self.match(node.token.lexeme, fn, node, @[node.a])
|
||||
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
|
||||
var default: Expression
|
||||
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])
|
||||
self.generateCall(impl, @[node.a, node.b], impl.line)
|
||||
result = self.match(node.token.lexeme, fn, node, @[node.a, node.b])
|
||||
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
|
||||
var s = name
|
||||
if s.isNil():
|
||||
s = self.resolveOrError(node)
|
||||
var t = self.findByType(s.ident.token.lexeme, Type(kind: All))
|
||||
s = t[0] # Shadowing!
|
||||
result = s
|
||||
if not compile:
|
||||
return
|
||||
if s.isConst:
|
||||
# Constants are always emitted as Load* instructions
|
||||
# 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)
|
||||
|
||||
|
||||
proc assignment(self: Compiler, node: ASTNode) =
|
||||
proc assignment(self: Compiler, node: ASTNode, compile: bool = true): Name {.discardable.} =
|
||||
## Compiles assignment expressions
|
||||
case node.kind:
|
||||
of assignExpr:
|
||||
|
@ -1956,6 +2012,8 @@ proc assignment(self: Compiler, node: ASTNode) =
|
|||
self.error(&"cannot reassign '{name.token.lexeme}' (value is immutable)", name)
|
||||
self.check(node.value, r.valueType)
|
||||
self.expression(node.value)
|
||||
if not compile:
|
||||
return
|
||||
if not r.isClosedOver:
|
||||
self.emitByte(StoreVar, 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.emitBytes(self.getClosurePos(r).toTriple(), node.token.line)
|
||||
of setItemExpr:
|
||||
let typ = self.inferOrError(SetItemExpr(node))
|
||||
|
||||
discard # TODO
|
||||
else:
|
||||
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) =
|
||||
## "Prepares" a function declaration by declaring
|
||||
## its arguments and typechecking it
|
||||
|
||||
# First we declare the function's generics, if it has any.
|
||||
# This is because the function's return type may in itself
|
||||
# be a generic, so it needs to exist first
|
||||
|
@ -2074,7 +2134,7 @@ proc prepareFunction(self: Compiler, fn: Name) =
|
|||
file: fn.file,
|
||||
isConst: false,
|
||||
ident: argument.name,
|
||||
valueType: self.inferOrError(argument.valueType, allowGeneric=true),
|
||||
valueType: self.inferOrError(argument.valueType),
|
||||
codePos: 0,
|
||||
isLet: false,
|
||||
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))
|
||||
# The function needs a return type too!
|
||||
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) =
|
||||
|
@ -2108,7 +2168,7 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression], line: int) =
|
|||
self.emitByte(LoadUInt64, line)
|
||||
self.emitBytes(self.chunk.writeConstant(fn.codePos.toLong()), line)
|
||||
else:
|
||||
discard
|
||||
discard # Unreachable
|
||||
if fn.valueType.forwarded:
|
||||
self.forwarded.add((fn, self.chunk.consts.high() - 7))
|
||||
self.emitByte(LoadUInt64, line)
|
||||
|
@ -2172,14 +2232,14 @@ proc specialize(self: Compiler, name: Name, args: seq[Expression]): Name =
|
|||
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
|
||||
var args: seq[tuple[name: string, kind: Type, default: Expression]] = @[]
|
||||
var argExpr: seq[Expression] = @[]
|
||||
var default: Expression
|
||||
var kind: Type
|
||||
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 argument.kind == NodeKind.identExpr:
|
||||
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))
|
||||
argExpr.add(argument)
|
||||
for i, argument in node.arguments.keyword:
|
||||
kind = self.infer(argument.value, allowGeneric=false)
|
||||
kind = self.infer(argument.value)
|
||||
if kind.isNil():
|
||||
if argument.value.kind == NodeKind.identExpr:
|
||||
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()
|
||||
result = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node, argExpr)
|
||||
# Now we call it
|
||||
self.generateCall(result, argExpr, node.token.line)
|
||||
if compile:
|
||||
self.generateCall(result, argExpr, node.token.line)
|
||||
of NodeKind.callExpr:
|
||||
# Calling a call expression, like hello()()
|
||||
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
|
||||
# our way to the innermost call
|
||||
for exp in reversed(all):
|
||||
self.callExpr(exp)
|
||||
self.call(exp, compile)
|
||||
# TODO: Calling lambdas on-the-fly (i.e. on the same line)
|
||||
else:
|
||||
let typ = self.infer(node)
|
||||
|
@ -2252,7 +2313,7 @@ proc expression(self: Compiler, node: Expression) =
|
|||
## Compiles all expressions
|
||||
case node.kind:
|
||||
of NodeKind.callExpr:
|
||||
self.callExpr(CallExpr(node))
|
||||
self.call(CallExpr(node))
|
||||
of NodeKind.getItemExpr:
|
||||
self.getItemExpr(GetItemExpr(node))
|
||||
of NodeKind.pragmaExpr:
|
||||
|
@ -2377,8 +2438,7 @@ proc breakStmt(self: Compiler, node: BreakStmt) =
|
|||
proc assertStmt(self: Compiler, node: AssertStmt) =
|
||||
## Compiles assert statements (raise
|
||||
## AssertionError if the expression is falsey)
|
||||
self.expression(node.expression)
|
||||
self.emitByte(OpCode.Assert, node.token.line)
|
||||
# TODO
|
||||
|
||||
|
||||
proc forEachStmt(self: Compiler, node: ForEachStmt) =
|
||||
|
@ -2388,7 +2448,7 @@ proc forEachStmt(self: Compiler, node: ForEachStmt) =
|
|||
|
||||
proc importStmt(self: Compiler, node: ImportStmt) =
|
||||
## Imports a module at compile time
|
||||
self.declareName(node)
|
||||
self.declare(node)
|
||||
var module = self.names[^1]
|
||||
try:
|
||||
self.compileModule(module)
|
||||
|
@ -2540,7 +2600,7 @@ proc varDecl(self: Compiler, node: VarDecl) =
|
|||
typ = expected
|
||||
self.expression(node.value)
|
||||
self.emitByte(AddVar, node.token.line)
|
||||
self.declareName(node)
|
||||
self.declare(node)
|
||||
var name = self.names[^1]
|
||||
name.valueType = typ
|
||||
|
||||
|
@ -2587,7 +2647,6 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
|
|||
else:
|
||||
self.chunk.functions.add(0.toDouble())
|
||||
if BlockStmt(node.body).code.len() == 0:
|
||||
raise newException(IndexDefect, "")
|
||||
self.error("cannot declare function with empty body")
|
||||
# Since the deferred array is a linear
|
||||
# sequence of instructions and we want
|
||||
|
@ -2655,12 +2714,12 @@ proc declaration(self: Compiler, node: Declaration) =
|
|||
## the first time
|
||||
case node.kind:
|
||||
of NodeKind.funDecl:
|
||||
var name = self.declareName(node)
|
||||
var name = self.declare(node)
|
||||
if name.isGeneric:
|
||||
# We typecheck generics immediately
|
||||
self.funDecl(FunDecl(node), name)
|
||||
of NodeKind.typeDecl:
|
||||
self.declareName(node)
|
||||
self.declare(node)
|
||||
of NodeKind.varDecl:
|
||||
# We compile this immediately because we
|
||||
# need to keep the stack in the right state
|
||||
|
@ -2708,7 +2767,7 @@ proc compileModule(self: Compiler, module: Name) =
|
|||
var moduleName = module.path & ".pn"
|
||||
for i, searchPath in moduleLookupPaths:
|
||||
if searchPath == "":
|
||||
path = joinPath(getCurrentDir(), joinPath(splitPath(self.file).head, moduleName))
|
||||
path = absolutePath(joinPath(splitPath(self.file).head, moduleName))
|
||||
else:
|
||||
path = joinPath(searchPath, moduleName)
|
||||
if fileExists(path):
|
||||
|
|
|
@ -276,6 +276,7 @@ type
|
|||
isEnum*: bool
|
||||
isRef*: bool
|
||||
parent*: IdentExpr
|
||||
value*: Expression
|
||||
|
||||
Pragma* = ref object of Expression
|
||||
name*: IdentExpr
|
||||
|
|
|
@ -139,9 +139,8 @@ proc newOperatorTable: OperatorTable =
|
|||
result.tokens = @[]
|
||||
for prec in Precedence:
|
||||
result.precedence[prec] = @[]
|
||||
# Assignment and attribute accessing are (currently)
|
||||
# the only builtins that cannot be easily implemented
|
||||
# from within peon itself
|
||||
# These operators are currently not built-in
|
||||
# due to compiler limitations
|
||||
result.addOperator("=")
|
||||
result.addOperator(".")
|
||||
|
||||
|
@ -735,7 +734,7 @@ proc importStmt(self: Parser, fromStmt: bool = false): Statement =
|
|||
var path = ""
|
||||
for i, searchPath in moduleLookupPaths:
|
||||
if searchPath == "":
|
||||
path = joinPath(getCurrentDir(), joinPath(splitPath(self.file).head, moduleName))
|
||||
path = absolutePath(joinPath(splitPath(self.file).head, moduleName))
|
||||
else:
|
||||
path = joinPath(searchPath, moduleName)
|
||||
if fileExists(path):
|
||||
|
@ -986,6 +985,8 @@ proc parseGenericConstraint(self: Parser): Expression =
|
|||
result = newBinaryExpr(result, self.step(), self.parseGenericConstraint())
|
||||
of "~":
|
||||
result = newUnaryExpr(self.step(), result)
|
||||
of ",":
|
||||
discard # Comma is handled in parseGenerics()
|
||||
else:
|
||||
self.error("invalid type constraint in generic declaration")
|
||||
|
||||
|
@ -1183,9 +1184,9 @@ proc typeDecl(self: Parser): TypeDecl =
|
|||
## Parses type declarations
|
||||
let token = self.peek(-1)
|
||||
self.expect(Identifier, "expecting type name after 'type'")
|
||||
var name = newIdentExpr(self.peek(-1), self.scopeDepth)
|
||||
let isPrivate = not self.match("*")
|
||||
self.checkDecl(isPrivate)
|
||||
var name = newIdentExpr(self.peek(-1), self.scopeDepth)
|
||||
var fields: seq[tuple[name: IdentExpr, valueType: Expression, isPrivate: bool]] = @[]
|
||||
var defaults: seq[Expression] = @[]
|
||||
var generics: seq[tuple[name: IdentExpr, cond: Expression]] = @[]
|
||||
|
@ -1194,44 +1195,58 @@ proc typeDecl(self: Parser): TypeDecl =
|
|||
if self.match(LeftBracket):
|
||||
self.parseGenerics(result)
|
||||
self.expect("=", "expecting '=' after type name")
|
||||
case self.step().lexeme:
|
||||
var hasNone = false
|
||||
case self.peek().lexeme:
|
||||
of "ref":
|
||||
discard self.step()
|
||||
self.expect("object", "expecting 'object' after 'ref'")
|
||||
result.isRef = true
|
||||
of "enum":
|
||||
discard self.step()
|
||||
result.isEnum = true
|
||||
of "object":
|
||||
discard self.step()
|
||||
discard # Default case
|
||||
else:
|
||||
self.error("invalid syntax")
|
||||
if not result.isEnum and self.match("of"):
|
||||
self.expect(Identifier, "expecting parent type name after 'of'")
|
||||
result.parent = newIdentExpr(self.peek(-1))
|
||||
self.expect(LeftBrace, "expecting '{' after type declaration")
|
||||
if self.match(TokenType.Pragma):
|
||||
for pragma in self.parsePragmas():
|
||||
pragmas.add(pragma)
|
||||
var
|
||||
argName: IdentExpr
|
||||
argPrivate: bool
|
||||
argType: Expression
|
||||
while not self.match(RightBrace) and not self.done():
|
||||
self.expect(Identifier, "expecting field name")
|
||||
argName = newIdentExpr(self.peek(-1), self.scopeDepth)
|
||||
if not result.isEnum:
|
||||
argPrivate = not self.match("*")
|
||||
self.expect(":", "expecting ':' after field name")
|
||||
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")
|
||||
hasNone = true
|
||||
if hasNone:
|
||||
result.value = self.expression()
|
||||
while not self.check(";"):
|
||||
case self.peek().lexeme:
|
||||
of "|": # Type unions!
|
||||
result.value = newBinaryExpr(result.value, self.step(), self.expression())
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
if not result.isEnum and not hasNone and self.match("of"):
|
||||
self.expect(Identifier, "expecting parent type name after 'of'")
|
||||
result.parent = newIdentExpr(self.peek(-1))
|
||||
if not self.match(";"): # Type has no fields. Usually for unions or aliases
|
||||
self.expect(LeftBrace, "expecting '{' after type declaration")
|
||||
if self.match(TokenType.Pragma):
|
||||
for pragma in self.parsePragmas():
|
||||
pragmas.add(pragma)
|
||||
var
|
||||
argName: IdentExpr
|
||||
argPrivate: bool
|
||||
argType: Expression
|
||||
while not self.match(RightBrace) and not self.done():
|
||||
self.expect(Identifier, "expecting field name")
|
||||
argName = newIdentExpr(self.peek(-1), self.scopeDepth)
|
||||
if not result.isEnum:
|
||||
argPrivate = not self.match("*")
|
||||
self.expect(":", "expecting ':' after field name")
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -85,6 +85,8 @@ proc repl =
|
|||
elif input == "#clear":
|
||||
stdout.write("\x1Bc")
|
||||
continue
|
||||
elif input == "":
|
||||
continue
|
||||
else:
|
||||
current &= input & "\n"
|
||||
if current.len() == 0:
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
# 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
|
||||
# instructions. They're implemented this way because:
|
||||
# - They tie into the existing type system nicely
|
||||
# - 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]
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
}
|
||||
|
||||
|
||||
operator `-`*[T: uint64 | uint32 | uint16 | uint8](a: T): T {
|
||||
operator `-`*[T: UnsignedInteger](a: T): T {
|
||||
#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]
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
}
|
||||
|
||||
|
||||
operator `/`*[T: uint64 | uint32 | uint16 | uint8](a, b: T): T {
|
||||
operator `/`*[T: UnsignedInteger](a, b: T): T {
|
||||
#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]
|
||||
}
|
||||
|
||||
|
||||
operator `**`*[T: uint64 | uint32 | uint16 | uint8](a, b: T): T {
|
||||
operator `**`*[T: UnsignedInteger](a, b: T): T {
|
||||
#pragma[magic: "Pow", pure]
|
||||
}
|
||||
|
||||
|
||||
operator `%`*(a, b: int64): int64 {
|
||||
operator `%`*[T: SignedInteger](a, b: T): T {
|
||||
#pragma[magic: "SignedMod", pure]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# Bitwise operations on primitive types
|
||||
import values;
|
||||
|
||||
|
||||
operator `&`*[T: int | uint64 | int32 | uint32 | int16 | uint16 | int8 | uint8](a, b: T): T {
|
||||
#pragma[magic: "And", pure]
|
||||
|
|
|
@ -1,30 +1,31 @@
|
|||
# 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]
|
||||
}
|
||||
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
|
||||
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]
|
||||
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
|
||||
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]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# Logical operators
|
||||
import values;
|
||||
|
||||
|
||||
operator `and`*(a, b: bool): bool {
|
||||
#pragma[magic: "LogicalAnd", pure]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Various miscellaneous utilities
|
||||
import values;
|
||||
|
||||
|
||||
# Some useful builtins
|
||||
|
|
|
@ -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;
|
|
@ -1,18 +1,20 @@
|
|||
## The peon standard library
|
||||
|
||||
import builtins/values;
|
||||
import builtins/arithmetics;
|
||||
import builtins/bitwise;
|
||||
import builtins/logical;
|
||||
import builtins/misc;
|
||||
import builtins/comparisons;
|
||||
|
||||
|
||||
export values;
|
||||
export arithmetics;
|
||||
export bitwise;
|
||||
export logical;
|
||||
export misc;
|
||||
export comparisons;
|
||||
|
||||
|
||||
var version* = 1;
|
||||
var _private = 5; # Invisible outside the module (underscore is to silence warning)
|
||||
var test* = 0x60;
|
|
@ -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:
|
||||
stderr.styledWrite(fgRed, styleBright, " in function ", fgYellow, FunDecl(fn).name.token.lexeme)
|
||||
stderr.styledWriteLine(styleBright, fgDefault, ": ", msg)
|
||||
stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start])
|
||||
stderr.styledWrite(fgRed, styleUnderscore, line[pos.start..pos.stop])
|
||||
stderr.styledWriteLine(fgDefault, line[pos.stop + 1..^1])
|
||||
if line.len() > 0:
|
||||
stderr.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start])
|
||||
stderr.styledWrite(fgRed, styleUnderscore, line[pos.start..pos.stop])
|
||||
stderr.styledWriteLine(fgDefault, line[pos.stop + 1..^1])
|
||||
|
||||
|
||||
proc print*(exc: CompileError) =
|
||||
|
|
|
@ -5,15 +5,18 @@ import std;
|
|||
fn noReturn(n: int) {
|
||||
var n = n;
|
||||
var `17` = 17;
|
||||
print(n == n); # true
|
||||
print(`17` == 17); # true
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
fn fooBar(a, b: int): int {
|
||||
var baz = 38;
|
||||
print(baz == 38);
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
noReturn(1);
|
||||
print(fooBar(1, 3) == 1); # true
|
||||
print(fooBar(1, 3) == 1); # true
|
|
@ -1,12 +1,12 @@
|
|||
# Tests generic functions
|
||||
# Another test for generic functions
|
||||
import std;
|
||||
|
||||
|
||||
fn sum[T: any](a, b: T): T {
|
||||
fn sum[T: int | int32](a, b: T): T {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
|
||||
print(sum(1, 2)); # 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
|
|
@ -0,0 +1,7 @@
|
|||
# Another test for generic functions
|
||||
import std;
|
||||
|
||||
|
||||
fn sum[T: any](a: T, b: T): T {
|
||||
return a + b;
|
||||
}
|
Loading…
Reference in New Issue