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
|
- 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)
|
||||||
|
|
|
@ -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!
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Various miscellaneous utilities
|
# Various miscellaneous utilities
|
||||||
|
import values;
|
||||||
|
|
||||||
|
|
||||||
# Some useful builtins
|
# 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
|
## 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;
|
|
@ -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) =
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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