diff options
| -rw-r--r-- | docs/reductions.md | 24 | ||||
| -rw-r--r-- | src/compile.c | 49 | ||||
| -rw-r--r-- | src/parse.c | 31 | ||||
| -rw-r--r-- | src/typecheck.c | 5 | ||||
| -rw-r--r-- | test/reductions.tm | 10 |
5 files changed, 81 insertions, 38 deletions
diff --git a/docs/reductions.md b/docs/reductions.md index 7aadd30b..3cbb6d5d 100644 --- a/docs/reductions.md +++ b/docs/reductions.md @@ -69,10 +69,10 @@ a collection using the `_min_` and `_max_` infix operators. = 10 ``` -The `_min_` and `_max_` operators also support field and method call suffixes, -which makes it very easy to compute the argmin/argmax (or keyed -minimum/maximum) of a collection. This is when you want to get the minimum or -maximum value _according to some feature_. +Reducers also support field and method call suffixes, which makes it very easy +to compute the argmin/argmax (or keyed minimum/maximum) of a collection. This +is when you want to get the minimum or maximum value _according to some +feature_. ```tomo # Get the longest text: @@ -84,6 +84,22 @@ maximum value _according to some feature_. = -4 ``` +You can also use suffixes on other operators: + +```tomo +texts := ["x", "y", "z"] +>> (==: texts) += no +>> (==.length: texts) += yes +>> (+.length: texts) += 3 + +nums := [1, 2, -3] +>> (+.abs(): nums) += 6 +``` + ## Comprehensions Reductions work not only with iterable values (arrays, sets, integers, etc.), diff --git a/src/compile.c b/src/compile.c index 46a0e607..3d621776 100644 --- a/src/compile.c +++ b/src/compile.c @@ -3442,21 +3442,29 @@ CORD compile(env_t *env, ast_t *ast) env_t *body_scope = for_scope(env, loop); if (op == Equals || op == NotEquals || op == LessThan || op == LessThanOrEquals || op == GreaterThan || op == GreaterThanOrEquals) { // Chained comparisons like ==, <, etc. + type_t *item_value_type = item_t; + ast_t *item_value = item; + if (reduction->key) { + set_binding(body_scope, "$", item_t, compile(body_scope, item)); + item_value = reduction->key; + item_value_type = get_type(body_scope, reduction->key); + } + CORD code = CORD_all( "({ // Reduction:\n", - compile_declaration(item_t, "prev"), ";\n" + compile_declaration(item_value_type, "prev"), ";\n" "OptionalBool_t result = NONE_BOOL;\n" ); ast_t *comparison = new(ast_t, .file=ast->file, .start=ast->start, .end=ast->end, - .tag=op, .__data.Plus.lhs=FakeAST(InlineCCode, .code="prev", .type=item_t), .__data.Plus.rhs=item); + .tag=op, .__data.Plus.lhs=FakeAST(InlineCCode, .code="prev", .type=item_value_type), .__data.Plus.rhs=item_value); body->__data.InlineCCode.code = CORD_all( "if (result == NONE_BOOL) {\n" - " prev = ", compile(body_scope, item), ";\n" + " prev = ", compile(body_scope, item_value), ";\n" " result = yes;\n" "} else {\n" " if (", compile(body_scope, comparison), ") {\n", - " prev = ", compile(body_scope, item), ";\n", + " prev = ", compile(body_scope, item_value), ";\n", " } else {\n" " result = no;\n", " break;\n", @@ -3510,43 +3518,50 @@ CORD compile(env_t *env, ast_t *ast) return code; } else { // Accumulator-style reductions like +, ++, *, etc. + type_t *reduction_type = Match(get_type(env, ast), OptionalType)->type; + ast_t *item_value = item; + if (reduction->key) { + set_binding(body_scope, "$", item_t, compile(body_scope, item)); + item_value = reduction->key; + } + CORD code = CORD_all( "({ // Reduction:\n", - compile_declaration(item_t, "reduction"), ";\n" + compile_declaration(reduction_type, "reduction"), ";\n" "Bool_t has_value = no;\n" ); // For the special case of (or)/(and), we need to early out if we can: CORD early_out = CORD_EMPTY; if (op == Compare) { - if (item_t->tag != IntType || Match(item_t, IntType)->bits != TYPE_IBITS32) + if (reduction_type->tag != IntType || Match(reduction_type, IntType)->bits != TYPE_IBITS32) code_err(ast, "<> reductions are only supported for Int32 values"); } else if (op == And) { - if (item_t->tag == BoolType) + if (reduction_type->tag == BoolType) early_out = "if (!reduction) break;"; - else if (item_t->tag == OptionalType) - early_out = CORD_all("if (", check_none(item_t, "reduction"), ") break;"); + else if (reduction_type->tag == OptionalType) + early_out = CORD_all("if (", check_none(reduction_type, "reduction"), ") break;"); } else if (op == Or) { - if (item_t->tag == BoolType) + if (reduction_type->tag == BoolType) early_out = "if (reduction) break;"; - else if (item_t->tag == OptionalType) - early_out = CORD_all("if (!", check_none(item_t, "reduction"), ") break;"); + else if (reduction_type->tag == OptionalType) + early_out = CORD_all("if (!", check_none(reduction_type, "reduction"), ") break;"); } ast_t *combination = new(ast_t, .file=ast->file, .start=ast->start, .end=ast->end, - .tag=op, .__data.Plus.lhs=FakeAST(InlineCCode, .code="reduction", .type=item_t), - .__data.Plus.rhs=item); + .tag=op, .__data.Plus.lhs=FakeAST(InlineCCode, .code="reduction", .type=reduction_type), + .__data.Plus.rhs=item_value); body->__data.InlineCCode.code = CORD_all( "if (!has_value) {\n" - " reduction = ", compile(body_scope, item), ";\n" + " reduction = ", compile(body_scope, item_value), ";\n" " has_value = yes;\n" "} else {\n" " reduction = ", compile(body_scope, combination), ";\n", early_out, "}\n"); - code = CORD_all(code, compile_statement(env, loop), "\nhas_value ? ", promote_to_optional(item_t, "reduction"), - " : ", compile_none(item_t), ";})"); + code = CORD_all(code, compile_statement(env, loop), "\nhas_value ? ", promote_to_optional(reduction_type, "reduction"), + " : ", compile_none(reduction_type), ";})"); return code; } } diff --git a/src/parse.c b/src/parse.c index d686bb6d..a11fc971 100644 --- a/src/parse.c +++ b/src/parse.c @@ -840,24 +840,21 @@ PARSER(parse_reduction) { ast_e op = match_binary_operator(&pos); if (op == Unknown) return NULL; - ast_t *key = NULL; - if (op == Min || op == Max) { - key = NewAST(ctx->file, pos, pos, Var, .name="$"); - for (bool progress = true; progress; ) { - ast_t *new_term; - progress = (false - || (new_term=parse_index_suffix(ctx, key)) - || (new_term=parse_method_call_suffix(ctx, key)) - || (new_term=parse_field_suffix(ctx, key)) - || (new_term=parse_fncall_suffix(ctx, key)) - || (new_term=parse_optional_suffix(ctx, key)) - || (new_term=parse_non_optional_suffix(ctx, key)) - ); - if (progress) key = new_term; - } - if (key->tag == Var) key = NULL; - else pos = key->end; + ast_t *key = NewAST(ctx->file, pos, pos, Var, .name="$"); + for (bool progress = true; progress; ) { + ast_t *new_term; + progress = (false + || (new_term=parse_index_suffix(ctx, key)) + || (new_term=parse_method_call_suffix(ctx, key)) + || (new_term=parse_field_suffix(ctx, key)) + || (new_term=parse_fncall_suffix(ctx, key)) + || (new_term=parse_optional_suffix(ctx, key)) + || (new_term=parse_non_optional_suffix(ctx, key)) + ); + if (progress) key = new_term; } + if (key->tag == Var) key = NULL; + else pos = key->end; whitespace(&pos); if (!match(&pos, ":")) return NULL; diff --git a/src/typecheck.c b/src/typecheck.c index 21e02460..1aec4f3b 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1295,6 +1295,11 @@ type_t *get_type(env_t *env, ast_t *ast) type_t *iterated = get_iterated_type(iter_t); if (!iterated) code_err(reduction->iter, "I don't know how to do a reduction over ", type_to_str(iter_t), " values"); + if (reduction->key && !(reduction->op == Min || reduction->op == Max)) { + env_t *item_scope = fresh_scope(env); + set_binding(item_scope, "$", iterated, CORD_EMPTY); + iterated = get_type(item_scope, reduction->key); + } return iterated->tag == OptionalType ? iterated : Type(OptionalType, .type=iterated); } diff --git a/test/reductions.tm b/test/reductions.tm index bd74f306..c56c7d09 100644 --- a/test/reductions.tm +++ b/test/reductions.tm @@ -42,3 +42,13 @@ func main(): >> (<=: [5, 4, 3, 2, 1])! = no + + >> (==: ["x", "y", "z"]) + = no + >> (==.length: ["x", "y", "z"]) + = yes + >> (+.length: ["x", "xy", "xyz"]) + = 6 + + >> (+.abs(): [1, 2, -3]) + = 6 |
