as_iterator :: #match {}
-Iterable :: interface (T: type_expr) {
- as_iterator(T);
+Iterable :: interface (t: $T) {
+ as_iterator(t);
}
close :: (it: Iterator($T)) {
}
#local {
- ValidKey :: interface (T: type_expr) {
+ ValidKey :: interface (t: $T) {
// In order to use a certain type as a key in a Map, you must
// provide an implementation of core.hash.to_u32() for that type,
// and you must provide an operator overload for ==.
- hash.to_u32(T);
- T == T;
+ { hash.to_u32(t) } -> u32;
+ { t == t } -> bool;
}
}
}
use package core.intrinsics.onyx { __zero_value }
-#local SetValue :: interface (T: type_expr) {
- hash.to_u32(T);
- T == T;
+#local SetValue :: interface (t: $T) {
+ { hash.to_u32(T) } -> u32;
+ { T == T } -> bool;
}
Set :: struct (Elem_Type: type_expr) where SetValue(Elem_Type) {
(key: type_expr) -> u32 do return to_u32(cast(u32) key);
}
-Hashable :: interface (T: type_expr) {
- to_u32(T);
-}
\ No newline at end of file
+Hashable :: interface (t: $T) {
+ { to_u32(t) } -> u32;
+}
}
+// I think that interfaces should be a little bit more powerful than they are now.
-// To remedy all of this, I think Onyx should have interfaces (or constraints,
-// I'm not sure what I want to call them yet). The idea is much like
-// an type class in Haskell where you must define a set of procedure types
-// that the implementer must define in order to provide the interface.
-// With these interfaces, I would get rid of #match and only provide
-// locally overloaded procedures (like print right now), as these are still
-// useful.
-//
-// One thing I was wondering was if this is how operator overloading should
-// work as well. I don't think so, but I do think that interfaces should
-// provide a mechanism for specifying that a certain operator must be defined
-// as well.
-
-// This is how interfaces will be declared. Interfaces must have at
-// least 1 parameter, and for now, all parameters must be type_exprs.
Hashable :: interface (T: type_expr) {
- // Each "method" listed in the interface is just a symbol name
- // and a function type. It can use the parameters to the interface
- // to describe the type.
- hash :: (T) -> u32
-}
-
-// This is how you implement an interface for a certain set of type
-// arguments. All interface "methods" must be defined here and their
-// types must match the interface types listed above.
-implement Hashable(str) {
- hash :: (s: str) -> i32 {
- // ...
- }
-}
-
-Vec2 :: struct (T: type_expr) {
- x, y: T;
-}
-
-implement Hashable(Vec2($T)) {
- hash :: (v: Vec2($T)) -> i32 where H :: Hashable(T) {
- return H.hash(v.x) * H.hash(v.y);
- }
-}
-
-v : Vec2(i32);
-h := Hashable(typeof v).hash(v);
-
-// This shows two things: interfaces can have constraints, and interfaces
-// can declare that a certain operator must be overloaded with a particular
-// type.
-Mappable :: interface (T: type_expr) where Hashable(T) {
- #operator == (T, T) -> bool
-}
-
-Map :: struct (K: type_expr, V: type_expr) where Mappable(K) {
-}
-
-// This will have the "Hashable(K)" constraint of Map checked when
-// the Map(K, V) type is constructed for the parameter type.
-get :: (m: ^Map($K, $V), key: K) -> V where H :: Hashable(K) {
- // ...
- hash := H.hash(key);
-}
-
-// 'where' clauses are also allowed on procedures and can be bound
-// to a symbol in order to call the interface functions for that type.
-do_hash :: (h: $T) -> i32 where H :: Hashable(T) {
- return H.hash(h); // returns an i32;
-}
-
-// Interfaces don't have to have any methods inside of them.
-Numeric :: interface (T: type_expr) {}
-implement Numeric(i32) {}
-implement Numeric(f32) {}
-
-V2 :: struct { x, y: f32; }
-implement Numeric(V2) {}
-
-// This demonstrates the ability for a constraint to just exist as
-// a declaration that something must be numeric. The way you check
-// for something being numeric is if `impl Numeric(T) {}` has been
-// declared.
-add :: (a, b: $T) -> T where Numeric(T) {
- return a + b;
-}
-
-
-// At the moment I do not plan on forcing the programmer to declare constaints
-// where they are present. Instead, they are primarily there in order to provide
-// better error messages. For example, this function calls do_hash, which
-// requires that T is hashable, but the caller does not need to add constraints
-// to itself because of this.
-compare_things :: (s1, s2: $T) -> bool {
- h1 := do_hash(s1);
- h2 := do_hash(s2);
- return h1 == h2;
-}
-
-compare_strings :: (s1, s2: str) -> bool {
- use Hashable(str);
- return hash(s1) == hash(s2);
-}
-
-// As I'm thinking about whether or not this feature is worth implementing, I want
-// a list of interfaces that will be in the standard library.
-//
-// - Hashable (hash)
-// - Stringable (to_string)
-// - Comparable (==)
-// - Writable (write to io.Writer)
-// - Readable
-// - Iterable (as_iter :: (t: T) -> Iterator())
-
-Stringable :: interface (T: type_expr) {
- to_string :: (T, Allocator) -> str;
-}
-
-implement Stringable(i32) {
- to_string :: (v: i32, a: Allocator) -> str {
- buf: [128] u8;
- s := conv.i64_to_str(~~v, 10, buf);
- return string.alloc_copy(s, a);
- }
-}
-
-
-// This is a more complicated case because it requires pattern matching
-// on the types given.
-Iterable :: interface (T: type_expr, V: type_expr) {
- as_iter :: (T) -> Iterator(V)
-}
-
-implement Iterable([..] $T, T) {
- as_iter :: (arr: [..] $T) -> Iterator(T) {
- // ...
- }
-}
-
-implement Iterable(^List($T), T) {
- as_iter :: (package core.list).get_iterator
-}
-
-implement Iterable(range, i32) {
- as_iter :: (r: range) -> Iterator(i32) {
- }
-}
-
-iterate :: (i: $I) where Iter :: Iterable(I, $V) {
- for it: Iter.as_iter(i) {
- println(it);
- }
-}
-
-
-
-Collecter :: interface (T: type_expr, V: type_expr) {
- collect :: (T, V) -> void
-}
-
-implement Collecter(^[..] $T, T) {
- collect :: array.push
-}
-
-implement Collecter(^List($T), T) {
- collect :: (l: ^List($T), v: T) { /* .. */ }
-}
-
-gather_values :: (i: $Iter, c: $T) where I :: Iterable(Iter, $V),
- C :: Collecter(T, V) {
- for it: I.as_iter(i) {
- C.collect(c, it);
- }
-}
-
-
-
-For :: interface (T: type_expr) {
- expand :: (T, body: Code) -> void;
- expand_ptr :: (T, body: Code) -> void;
-}
-
-implement For([..] $T) {
- expand :: macro (arr: [..] $T, body: Code) {
- for it: arr {
- #insert body;
- }
- }
-
- expand_ptr :: macro (arr: [..] $T, body: Code) {
- for ^it: arr {
- #insert body;
- }
- }
-}
-
-implement For(Map($K, $V)) {
- expand :: macro (m: Map($K, $V), body: Code) {
- for ^it: m.entries {
- key := it.key;
- value := it.value;
- #insert body;
- }
- }
-
- expand_ptr :: macro (m: Map($K, $V), body: Code) {
- for ^it: m.entries {
- key := ^it.key;
- value := ^it.value;
- #insert body;
- }
- }
-}
-
-for_ :: macro (a: $T, body: Code) where F :: For(T) {
- F.expand(a, body);
-}
-
-for_ptr :: macro (a: $T, body: Code) where F :: For(T) {
- F.expand_ptr(a, body);
+ // This syntax seems pretty clean, but it introduces some complications when
+ // you actually think about using the type T in the expected type expression.
+ { hash.to_u32(T) } -> u32;
+}
+
+Ring :: interface (T: type_expr) {
+ // This syntax is very clean and easy to read; however, when you think about
+ // it for a second, the meaning of T becomes ambiguous...
+ // In the {} T is a value of type T, while in the type expression T is a type.
+ // Is this okay? Or should you have to say `typeof T` in the type expression?
+ // Or should the syntax for interfaces change?
+
+ { T + T } -> T;
+ { T - T } -> T;
+ { T * T } -> T;
+}
+
+Ring :: interface (t: $T) {
+ // This syntax is slightly better, as it makes clear where the value is being
+ // used and where the type is being used.
+
+ // They polymorphic variable "$T" is just for syntactic consistency. The "$" will
+ // always be expected before types in interfaces. Also, all types should just be
+ // a single symbol.
+
+ { t + t } -> T;
+ { t - t } -> T;
+ { t * t } -> T;
+
+ //
+ // The only problem I have with this syntax is that it is a little confusing when
+ // using the constraint syntax:
+ //
+ // foo :: (x, y: $T) -> T where Ring(T) {
+ // return x + y - x * y;
+ // }
+ //
+ // Because you are "calling" Ring with a type, not a value of type T. This might
+ // be worth giving up the syntactic consistency tho, just to avoid confusion about
+ // if T is a type or a value.
+ //
}
// This is an example of what the iter package looks like.
val := iter.as_iterator(range.{ 20, 1, -1 })
- |> iter.map((x) => x * 2)
- |> iter.map((x) => cast(f32) x)
- |> iter.filter((x) => x > 20)
+ |> iter.map(x => x * 2)
+ |> iter.map(x => cast(f32) x)
+ |> iter.filter(x => x > 20)
|> iter.take(5)
|> iter.fold(0.0f, (x, y) => x + y);
println(val);
// @CLEANUP: Is this really necessary?
typedef struct InterfaceParam {
- OnyxToken *token;
+ OnyxToken *value_token;
AstType *type_node;
- Type *type;
} InterfaceParam;
+typedef struct InterfaceConstraint {
+ AstTyped *expr;
+ AstType *expected_type_expr;
+ Type *expected_type;
+} InterfaceConstraint;
+
struct AstInterface {
AstTyped_base;
char *name;
- bh_arr(InterfaceParam) params;
- bh_arr(AstTyped *) exprs;
+ bh_arr(InterfaceParam) params;
+ bh_arr(InterfaceConstraint) exprs;
};
typedef enum ConstraintPhase {
ConstraintCheckStatus *report_status;
Scope* scope;
- bh_arr(AstTyped *) exprs;
+ bh_arr(InterfaceConstraint) exprs;
u32 expr_idx;
};
}
bh_arr_new(global_heap_allocator, constraint->exprs, bh_arr_length(constraint->interface->exprs));
- bh_arr_each(AstTyped *, expr, constraint->interface->exprs) {
- bh_arr_push(constraint->exprs, (AstTyped *) ast_clone(context.ast_alloc, (AstNode *) *expr));
+ bh_arr_each(InterfaceConstraint, ic, constraint->interface->exprs) {
+ InterfaceConstraint new_ic = {0};
+ new_ic.expr = (AstTyped *) ast_clone(context.ast_alloc, (AstNode *) ic->expr);
+ new_ic.expected_type_expr = (AstType *) ast_clone(context.ast_alloc, (AstNode *) ic->expected_type_expr);
+ bh_arr_push(constraint->exprs, new_ic);
}
assert(constraint->interface->entity && constraint->interface->entity->scope);
InterfaceParam *ip = &constraint->interface->params[i];
AstTyped *sentinel = onyx_ast_node_new(context.ast_alloc, sizeof(AstTyped), Ast_Kind_Constraint_Sentinel);
- sentinel->token = ip->token;
+ sentinel->token = ip->value_token;
sentinel->type_node = constraint->type_args[i];
- symbol_introduce(constraint->scope, ip->token, (AstNode *) sentinel);
+ assert(ip->type_node);
+ AstAlias *type_alias = onyx_ast_node_new(context.ast_alloc, sizeof(AstAlias), Ast_Kind_Alias);
+ type_alias->token = ip->type_node->token;
+ type_alias->alias = (AstTyped *) constraint->type_args[i];
+
+ symbol_introduce(constraint->scope, ip->value_token, (AstNode *) sentinel);
+ symbol_introduce(constraint->scope, ip->type_node->token, (AstNode *) type_alias);
}
assert(constraint->entity);
case Constraint_Phase_Checking_Expressions: {
fori (i, constraint->expr_idx, bh_arr_length(constraint->exprs)) {
- CheckStatus cs = check_expression(&constraint->exprs[i]);
+ InterfaceConstraint* ic = &constraint->exprs[i];
+
+ CheckStatus cs = check_expression(&ic->expr);
if (cs == Check_Return_To_Symres || cs == Check_Yield_Macro) {
return cs;
}
if (cs == Check_Error) {
- // HACK HACK HACK
- onyx_clear_errors();
+ goto constraint_error;
+ }
- *constraint->report_status = Constraint_Check_Status_Failed;
- return Check_Failed;
+ if (ic->expected_type_expr) {
+ ic->expected_type = type_build_from_ast(context.ast_alloc, ic->expected_type_expr);
+ if (ic->expected_type == NULL) {
+ printf("WEIRD CONDITION THAT SHOULD NEVER HAPPEN!\n");
+ return Check_Yield_Macro;
+ }
+
+ TYPE_CHECK(&ic->expr, ic->expected_type) {
+ goto constraint_error;
+ }
}
constraint->expr_idx++;
+ continue;
+
+ constraint_error:
+ // HACK HACK HACK
+ onyx_clear_errors();
+
+ *constraint->report_status = Constraint_Check_Status_Failed;
+ return Check_Failed;
}
*constraint->report_status = Constraint_Check_Status_Success;
fori (i, 0, bh_arr_length(constraint->type_args)) {
if (i != 0) strncat(constraint_map, ", ", 511);
- OnyxToken* symbol = constraint->interface->params[i].token;
+ OnyxToken* symbol = constraint->interface->params[i].value_token;
token_toggle_end(symbol);
strncat(constraint_map, symbol->text, 511);
token_toggle_end(symbol);
strncat(constraint_map, "'", 511);
}
- onyx_report_error(constraint->exprs[constraint->expr_idx]->token->pos, Error_Critical, "Failed to satisfy constraint where %s.", constraint_map);
+ onyx_report_error(constraint->exprs[constraint->expr_idx].expr->token->pos, Error_Critical, "Failed to satisfy constraint where %s.", constraint_map);
onyx_report_error(constraint->token->pos, Error_Critical, "Here is where the interface was used.");
onyx_report_error(pos, Error_Critical, "Here is the code that caused this constraint to be checked.");
if (parser->hit_unexpected_token) return interface;
InterfaceParam ip;
- ip.token = expect_token(parser, Token_Type_Symbol);
+ ip.value_token = expect_token(parser, Token_Type_Symbol);
expect_token(parser, ':');
- ip.type_node = parse_type(parser);
+ expect_token(parser, '$');
+
+ OnyxToken* type_sym = expect_token(parser, Token_Type_Symbol);
+ ip.type_node = (AstType *) make_symbol(parser->allocator, type_sym);
bh_arr_push(interface->params, ip);
while (!consume_token_if_next(parser, '}')) {
if (parser->hit_unexpected_token) return interface;
- AstTyped *expr = parse_expression(parser, 0);
- bh_arr_push(interface->exprs, expr);
+ InterfaceConstraint ic = {0};
+ if (consume_token_if_next(parser, '{')) {
+ ic.expr = parse_expression(parser, 0);
+
+ expect_token(parser, '}');
+ expect_token(parser, Token_Type_Right_Arrow);
+
+ ic.expected_type_expr = parse_type(parser);
+
+ } else {
+ ic.expr = parse_expression(parser, 0);
+ }
+
+ bh_arr_push(interface->exprs, ic);
expect_token(parser, ';');
}
case Constraint_Phase_Checking_Expressions: {
fori (i, constraint->expr_idx, bh_arr_length(constraint->exprs)) {
- SYMRES(expression, &constraint->exprs[i]);
+ SYMRES(expression, &constraint->exprs[i].expr);
+
+ if (constraint->exprs[i].expected_type_expr) {
+ SYMRES(type, &constraint->exprs[i].expected_type_expr);
+ }
}
return Symres_Success;
return g(f(a));
}
- Number :: interface (T: type_expr) {
- T * T;
+ Number :: interface (t: $T) {
+ { t * t } -> T;
}
dumb :: (ZZZ: $T) -> #auto where Number(T) {
use package core
-Hashable :: interface (T: type_expr) {
- hash.to_u32(T);
+Hashable :: interface (t: $T) {
+ { hash.to_u32(t) } -> u32;
}
try_hash :: #match {
},
}
-CanCastTo :: interface (T: type_expr, D: type_expr) {
- cast(typeof D) T;
+CanCastTo :: interface (t: $T, d: $D) {
+ { cast(D) t } -> D;
}
// I don't know why this doesn't work... It is complaining that it couldn't match
return x + y + x * y;
}
-SemiRing :: interface (T: type_expr) {
- T + T;
- T * T;
+SemiRing :: interface (t: $T) {
+ {t + t} -> T;
+ {t * t} -> T;
}
bit_math :: (x, y: $T) -> T where BitField(T) {
return (x & y) | (x ^ y);
}
-BitField :: interface (T: type_expr) {
- ~T;
- T & T;
- T | T;
- T ^ T;
+BitField :: interface (t: $T) {
+ { ~t } -> T;
+ { t & t } -> T;
+ { t | t } -> T;
+ { t ^ t } -> T;
}
Complex :: struct [conv.Custom_Format.{ format }] {