Skip to content

Commit

Permalink
Forbid function template overloading. Lazily resolve param types. Rev…
Browse files Browse the repository at this point in the history
…ise function and value template syntax.
  • Loading branch information
fubark committed Sep 2, 2024
1 parent c32e6fc commit 27a6379
Show file tree
Hide file tree
Showing 27 changed files with 966 additions and 777 deletions.
62 changes: 30 additions & 32 deletions docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1561,7 +1561,7 @@ Note that invoking the template again with the same argument(s) returns the same
### Type specialization.
[Value templates](#value-templates) can be used to specialize type templates:
```cy
func List[T type] type:
def List[T type] type:
if T == dyn:
return DynList
else:
Expand Down Expand Up @@ -2024,7 +2024,6 @@ The `try catch` statement, `try else` and `try` expressions provide a way to cat
* [Explicit template call.](#explicit-template-call)
* [Expand function.](#expand-function)
* [Infer param type.](#infer-param-type)
* [Value templates.](#value-templates)
</td>
</tr></table>

Expand Down Expand Up @@ -2274,31 +2273,31 @@ There is no need for `#` if the caller is already in a compile-time context: *Co
## Function templates.
Function declarations can include template parameters to create a function template:
```cy
func add(#T type, a T, b T) T:
func add[T](T type, a T, b T) T:
return a + b
```
Template parameters are prefixed with `#`.
Unlike type templates, function templates allow template parameters to be declared alongside runtime parameters.
Only the template parameter names are declared.
Their types are then inferred from the function signature.
### Explicit template call.
When the function is invoked with template argument(s), a special version of the function is generated and used:
When the function is invoked with template argument(s), a new runtime function is generated and used:
```cy
print add(int, 1, 2) --> 3
print add(float, 1, 2) --> 3.0
```
Note that invoking the function again with the same argument(s) uses the same generated function. In other words, the generated function is always memoized from the template arguments.
Note that invoking the function again with the same template argument(s) uses the same generated function. In other words, the generated function is always memoized from the template arguments.
### Expand function.
Since functions may contain template and runtime parameters, the index operator is used to expand the function with just template arguments and return the generated function:
The function template can be explicitly expanded to a runtime function:
```cy
var addInt = add[int]
print addInt(1, 2) --> 3
```
### Infer param type.
When a template parameter is declared in the type specifier, it's inferred from the argument's type:
When a template parameter is first encountered in a function parameter's type specifier, it's inferred from the argument's type:
```cy
func add(a #T, b T) T:
func add[T](a T, b T) T:
return a + b

print add(1, 2) --> 3
Expand All @@ -2308,31 +2307,10 @@ In the above example, `add[int]` and `add[float]` were inferred from the functio
Nested template parameters can also be inferred:
```cy
func set(m Map[#K, #V], key K, val V):
func set[K, V](m Map[K, V], key K, val V):
m.set(key, val)
```
## Value templates.
A value template returns a memoized value after being invoked with template arguments just once. It's declared with `func` but the template parameters are delimited with brackets.
This is different from a function template which generates multiple runtime functions based on its template parameters. A value template does not generate a runtime function and instead expands to a value at compile-time:
```cy
func GetType[ID String] type:
if ID == 'bool':
return bool
else ID == 'int':
return int
else ID == 'String':
return String
else
throw error.Unsupported

var a GetType['int'] = 123
print a --> 123
```
This can be useful to evaluate compile-time logic to create new types or specialize other templates.
Any compile-time compatible type can also be returned.
# Modules.
<table><tr>
<td valign="top">
Expand Down Expand Up @@ -3400,6 +3378,7 @@ print str.trim(.left, ' ')
* [Reflection.](#reflection)
* [Attributes.](#attributes)
* [Templates.](#templates)
* [Value templates.](#value-templates)
* [Macros.](#macros)
* [Compile-time execution.](#compile-time-execution)
* [Builtin types.](#builtin-types)
Expand Down Expand Up @@ -3570,6 +3549,25 @@ Templates enables parametric polymorphism for types and functions. Template argu
See [Type Declarations / Type templates](#type-templates) and [Functions / Function templates](#function-templates).
### Value templates.
A value template returns a memoized value after being invoked with template arguments at compile-time. It's declared with `def`:
```cy
def StrType[ID String] type:
if ID == 'bool':
return bool
else ID == 'int':
return int
else ID == 'String':
return String
else
throw error.Unsupported

var a StrType['int'] = 123
print a --> 123
```
This can be useful to evaluate compile-time logic to create new types or specialize other templates.
Any compile-time compatible type can also be returned.
## Macros.
> _Planned Feature_
Expand Down
2 changes: 1 addition & 1 deletion exts/sublime/cyber.sublime-syntax
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ contexts:
- match: '\b(or|and|not)\b'
scope: keyword.operator.cyber

- match: '\b(var|const|as|context|dyn)\b'
- match: '\b(var|def|as|context|dyn)\b'
scope: keyword.variable.cyber

- match: '\b(func|return|self)\b'
Expand Down
2 changes: 1 addition & 1 deletion exts/vim/syntax/cyber.vim
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
syntax keyword cyberKeyword
\ if else switch while for each break continue pass
\ or and not is
\ var as dyn
\ var as dyn def
\ func return
\ coinit coyield coresume
\ type object enum true false none
Expand Down
16 changes: 10 additions & 6 deletions src/ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,10 @@ pub const FuncDecl = struct {
pub const FuncParam = struct {
name_type: *Node align(8),
type: ?*Node,
template: bool,

// Used by sema to indicate that the parameter's type is inferred.
sema_infer_tparam: bool = false,
sema_tparam: bool = false,
};

pub const UseAlias = struct {
Expand Down Expand Up @@ -592,9 +595,10 @@ pub const StringTemplate = struct {
parts: []*Node align(8),
};

const FuncSigType = enum(u8) {
pub const FuncSigType = enum(u8) {
func,
infer,
template,
};

fn NodeData(comptime node_t: NodeType) type {
Expand Down Expand Up @@ -778,10 +782,7 @@ pub const Node = struct {
.forIterStmt => self.cast(.forIterStmt).pos,
.forRangeStmt => self.cast(.forRangeStmt).pos,
.funcDecl => self.cast(.funcDecl).pos,
.func_param => {
const param = self.cast(.func_param);
return param.name_type.pos() - @intFromBool(param.template);
},
.func_param => self.cast(.func_param).name_type.pos(),
.func_type => self.cast(.func_type).pos,
.group => self.cast(.group).pos,
.hexLit => self.cast(.hexLit).pos,
Expand Down Expand Up @@ -1139,6 +1140,9 @@ pub const AstView = struct {
if (decl.params.len == 0) {
return false;
}
if (decl.params[0].name_type.type() != .ident) {
return false;
}
const param_name = self.nodeString(decl.params[0].name_type);
return std.mem.eql(u8, param_name, "self");
}
Expand Down
4 changes: 2 additions & 2 deletions src/bc_gen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn genAll(c: *cy.Compiler) !void {
log.tracev("prep type: {s}", .{stype.sym.name()});
const sym = stype.sym;

if (stype.info.ct_infer or stype.info.ct_ref) {
if (stype.info.ct_ref) {
continue;
}

Expand Down Expand Up @@ -210,6 +210,7 @@ fn prepareSym(c: *cy.Compiler, sym: *cy.Sym) !void {
},
.context_var,
.template,
.func_template,
.hostobj_t,
.type,
.chunk,
Expand All @@ -233,7 +234,6 @@ fn prepareSym(c: *cy.Compiler, sym: *cy.Sym) !void {

pub fn prepareFunc(c: *cy.Compiler, opt_group: ?rt.FuncGroupId, func: *cy.Func) !void {
switch (func.type) {
.template,
.trait => return,
.userLambda => {
if (cy.Trace) {
Expand Down
4 changes: 2 additions & 2 deletions src/builtins/builtins.zig
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const func = cy.hostFuncEntry;
const funcs = [_]C.HostFuncEntry{
// Utils.
func("bitcast_", zErrFunc(bitcast)),
func("copy", zErrFunc(copy)),
func("copy_", zErrFunc(copy)),
func("dump", zErrFunc(dump)),
func("eprint", eprint),
func("errorReport", zErrFunc(errorReport)),
Expand Down Expand Up @@ -129,7 +129,7 @@ const funcs = [_]C.HostFuncEntry{
func("List.remove", bindings.listRemove),
func("List.resize_", zErrFunc(bindings.listResize)),
// .{"sort", bindings.listSort, .standard},
func("List.fill", listFill),
func("List.fill_", listFill),

// ListIterator
func("ListIterator.next_", zErrFunc(bindings.listIteratorNext)),
Expand Down
64 changes: 33 additions & 31 deletions src/builtins/builtins_vm.cy
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
--| Functions.
--|

func bitcast(#D type, val #S) D:
func bitcast[D, S](D type, val S) D:
return bitcast_(D, typeid[D], val, typeid[S])

@host -func bitcast_(#D type, dst_t int, val #S, src_t int) D
@host -func bitcast_[D, S](D type, dst_t int, val S, src_t int) D

--| Copies a primitive value or creates a shallow copy of an object value.
func copy(val #T) T:
return copy(typeid[T], val)
func copy[T](val T) T:
return copy_(typeid[T], val)

@host -func copy(type_id int, val #T) T
@host -func copy_[T](type_id int, val T) T

--| Dumps a detailed description of a value.
@host func dump(val any) void
Expand Down Expand Up @@ -47,23 +47,23 @@ func copy(val #T) T:
--| Returns the statistics of the run in a map value.
@host func performGC() Map

func ptrcast(#D type, val #S) *D:
func ptrcast[D, S](D type, val S) *D:
return ptrcast_(D, typeid[S], val)

@host -func ptrcast_(#D type, src_t int, val #S) *D
@host -func ptrcast_[D, S](D type, src_t int, val S) *D

--| Prints a value. The host determines how it is printed.
@host func print(str any) void

--| Queues a callback function as an async task.
@host func queueTask(fn Func() void) void

@host func refcast(#T type, ptr *T) &T
@host func refcast[T](T type, ptr *T) &T

--| Converts a rune to a string.
@host func runestr(val int) String

func sizeof(#T type) int:
func sizeof[T](T type) int:
return sizeof_(typeid[T])

@host func sizeof_(type_id int) int
Expand All @@ -77,7 +77,7 @@ func typeof(t ExprType) type:
--|

--| Returns the type ID of a type.
func typeid[T type] int:
def typeid[T type] int:
return (T).id()

--|
Expand Down Expand Up @@ -287,10 +287,10 @@ type float #float64_t:

--| Creates a list with initial capacity of `n` and values set to `val`.
--| If the value is an object, it is shallow copied `n` times.
func List.fill(val #T, n int) List[T]:
return List.fill(typeid[List[T]], typeid[T], val, n)
func List.fill[T](val T, n int) List[T]:
return List.fill_(typeid[List[T]], typeid[T], val, n)

@host -func List.fill(list_t int, val_t int, val #T, n int) List[T]
@host -func List.fill_[T](list_t int, val_t int, val T, n int) List[T]

@host type ListIterator[T type] _:
func next(self) ?T:
Expand Down Expand Up @@ -534,7 +534,7 @@ type pointer[T type] #int64_t:
@host func set(self, offset int, ctype symbol, val any) void

--| Converts an `int` to a `pointer` value.
@host func pointer.$call(#T type, addr int) *T
@host func pointer.$call[T](T type, addr int) *T

@host
type ExternFunc _:
Expand Down Expand Up @@ -562,24 +562,24 @@ type Option[T type] enum:
@host type Future[T type] _

--| Returns a `Future[T]` that has a completed value.
func Future.complete(val #T) Future[T]:
func Future.complete[T](val T) Future[T]:
return Future.complete_(val, typeid[Future[T]])

@host -func Future.complete_(val #T, ret_type int) Future[T]
@host -func Future.complete_[T](val T, ret_type int) Future[T]

func Future.new(#T type) Future[T]:
func Future.new[T](T type) Future[T]:
return Future.new_(T, typeid[Future[T]])

@host -func Future.new_(#T type, ret_type int) Future[T]
@host -func Future.new_[T](T type, ret_type int) Future[T]

@host type FutureResolver[T type] _:
@host func complete(self, val T) void
@host func future(self) Future[T]

func FutureResolver.new(#T type) FutureResolver[T]:
func FutureResolver.new[T](T type) FutureResolver[T]:
return FutureResolver.new_(T, typeid[Future[T]], typeid[FutureResolver[T]])

@host -func FutureResolver.new_(#T type, future_t int, ret_t int) FutureResolver[T]
@host -func FutureResolver.new_[T](T type, future_t int, ret_t int) FutureResolver[T]

type Ref[T type] #int64_t

Expand Down Expand Up @@ -710,20 +710,22 @@ type IMemory trait:
type Memory:
iface IMemory

func new(self, #T type) *T:
var bytes = self.iface.alloc(sizeof(T))
return ptrcast(T, bytes.ptr)
func Memory.new[T](self, T type) *T:
var bytes = self.iface.alloc(sizeof(T))
var ptr = ptrcast(T, bytes.ptr)
return ptr

func alloc(self, #T type, n int) [*]T:
var bytes = self.iface.alloc(sizeof(T) * n)
return ptrcast(T, bytes.ptr)[0..n]
func Memory.alloc[T](self, T type, n int) [*]T:
var bytes = self.iface.alloc(sizeof(T) * n)
var slice = ptrcast(T, bytes.ptr)[0..n]
return slice

func free(self, ptr *#T):
self.iface.free(ptrcast(byte, ptr)[0..sizeof(T)])
func Memory.free[T](self, ptr *T):
self.iface.free(ptrcast(byte, ptr)[0..sizeof(T)])

func free(self, slice [*]#T):
var bytes = ptrcast(byte, slice.ptr)[0..sizeof(T)*slice.len()]
self.iface.free(bytes)
func Memory.free_[T](self, slice [*]T):
var bytes = ptrcast(byte, slice.ptr)[0..sizeof(T)*slice.len()]
self.iface.free(bytes)

type DefaultMemory:
with IMemory
Expand Down
2 changes: 0 additions & 2 deletions src/chunk.zig
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,6 @@ pub const Chunk = struct {
self.use_alls.deinit(self.alloc);

for (self.funcs.items) |func| {
func.deinit(self.alloc);
self.alloc.destroy(func);
}
self.funcs.deinit(self.alloc);
Expand All @@ -334,7 +333,6 @@ pub const Chunk = struct {
self.syms.deinit(self.alloc);

for (self.deferred_funcs.items) |func| {
func.deinit(self.alloc);
self.alloc.destroy(func);
}
self.deferred_funcs.deinit(self.alloc);
Expand Down
Loading

0 comments on commit 27a6379

Please sign in to comment.