Use optionals for iterators

This commit is contained in:
Bruce Hill 2024-09-11 22:28:43 -04:00
parent f7ff82913f
commit 3443edf760
10 changed files with 44 additions and 204 deletions

View File

@ -30,7 +30,7 @@ CFLAGS_PLACEHOLDER="$$(echo -e '\033[2m<flags...>\033[m')"
LDLIBS=-lgc -lcord -lm -lunistring -lgmp -ldl
BUILTIN_OBJS=builtins/siphash.o builtins/array.o builtins/bool.o builtins/channel.o builtins/nums.o builtins/functions.o builtins/integers.o \
builtins/pointer.o builtins/memory.o builtins/text.o builtins/thread.o builtins/c_string.o builtins/table.o \
builtins/types.o builtins/util.o builtins/files.o builtins/range.o builtins/shell.o builtins/path.o builtins/nextline.o \
builtins/types.o builtins/util.o builtins/files.o builtins/range.o builtins/shell.o builtins/path.o \
builtins/optionals.o
TESTS=$(patsubst %.tm,%.tm.testresult,$(wildcard test/*.tm))

View File

@ -1,108 +0,0 @@
// An enum used for iterating over lines in a file
// Most of the code here was generated by compiling:
// enum NextLine(Done, Next(line:Text))
#include <stdbool.h>
#include <stdint.h>
#include "siphash.h"
#include "datatypes.h"
#include "nextline.h"
#include "text.h"
#include "util.h"
static Text_t NextLine$Next$as_text(NextLine$Next_t *obj, bool use_color)
{
if (!obj)
return Text("Next");
return Text$concat(use_color ? Text("\x1b[0;1mNext\x1b[m(") : Text("Next("), Text("line="),
Text$as_text((Text_t[1]){obj->$line}, use_color, &Text$info), Text(")"));
}
public inline NextLine_t NextLine$tagged$Next(Text_t $line)
{
return (NextLine_t) {
.tag = NextLine$tag$Next,.$Next = { $line }
};
}
static Text_t NextLine$as_text(NextLine_t *obj, bool use_color)
{
if (!obj)
return Text("NextLine");
switch (obj->tag) {
case NextLine$tag$Done:
return use_color ? Text("\x1b[36;1mNextLine.Done\x1b[m") : Text("NextLine.Done");
case NextLine$tag$Next:
return Text$concat(use_color ? Text("\x1b[36;1mNextLine.Next\x1b[m(") :
Text("NextLine.Next("), Text("line="),
Text$as_text((Text_t[1]){obj->$Next.$line}, use_color, &Text$info), Text(")"));
default:
return (Text_t) {
.length = 0};
}
}
static bool NextLine$equal(const NextLine_t *x, const NextLine_t *y,
const TypeInfo *info)
{
(void) info;
if (x->tag != y->tag)
return false;
switch (x->tag) {
case NextLine$tag$Done:
return false;
case NextLine$tag$Next:
return generic_equal(&x->$Next, &y->$Next, (&NextLine$Next));
default:
return 0;
}
}
static int NextLine$compare(const NextLine_t *x, const NextLine_t *y,
const TypeInfo *info)
{
(void) info;
int diff = (int)x->tag - (int)y->tag;
if (diff)
return diff;
switch (x->tag) {
case NextLine$tag$Done:
return 0;
case NextLine$tag$Next:
return generic_compare(&x->$Next, &y->$Next, (&NextLine$Next));
default:
return 0;
}
}
static uint64_t NextLine$hash(const NextLine_t *obj, const TypeInfo *info)
{
(void) info;
uint64_t hashes[2] = { (uint64_t) obj->tag, 0 };
switch (obj->tag) {
case NextLine$tag$Done:
break;
case NextLine$tag$Next:
hashes[1] = generic_hash(&obj->$Next, (&NextLine$Next));
break;
default: break;
}
return siphash24((void *) &hashes, sizeof(hashes));
}
public const TypeInfo NextLine$Done = { 0, 0, {.tag = EmptyStructInfo,.EmptyStructInfo.name =
"NextLine$Done" } };
public const TypeInfo NextLine$Next = { 24, 8, {.tag = CustomInfo,.CustomInfo =
{.as_text =
(void *) NextLine$Next$as_text,.hash =
(void *) Text$hash,.compare =
(void *) Text$compare,.equal =
(void *) Text$equal,} } };
public const TypeInfo NextLine = { 32, 8, {.tag = CustomInfo,.CustomInfo =
{.as_text = (void *) NextLine$as_text,.equal =
(void *) NextLine$equal,.hash =
(void *) NextLine$hash,.compare =
(void *) NextLine$compare} } };
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -1,31 +0,0 @@
#pragma once
#include "datatypes.h"
#include "types.h"
// An enum used for iterating over lines in a file
// Most of the code here was generated by compiling:
// enum NextLine(Done, Next(line:Text))
typedef struct NextLine_s NextLine_t;
typedef struct NextLine$Done_s NextLine$Done_t;
#pragma GCC diagnostic ignored "-Wpedantic"
struct NextLine$Done_s {
};
typedef struct NextLine$Next_s NextLine$Next_t;
struct NextLine$Next_s {
Text_t $line;
};
struct NextLine_s {
enum { NextLine$tag$Done = 0, NextLine$tag$Next = 1 } tag;
union {
NextLine$Done_t $Done;
NextLine$Next_t $Next;
};
};
extern const TypeInfo NextLine;
extern const TypeInfo NextLine$Done;
extern const TypeInfo NextLine$Next;
NextLine_t NextLine$tagged$Next(Text_t $line);
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -16,7 +16,7 @@
#include "files.h"
#include "functions.h"
#include "integers.h"
#include "nextline.h"
#include "optionals.h"
#include "path.h"
#include "text.h"
#include "types.h"
@ -433,16 +433,16 @@ static void _line_reader_cleanup(FILE **f)
}
}
static NextLine_t _next_line(FILE **f)
static Text_t _next_line(FILE **f)
{
if (!f || !*f) return (NextLine_t){NextLine$tag$Done};
if (!f || !*f) return NULL_TEXT;
char *line = NULL;
size_t size = 0;
ssize_t len = getline(&line, &size, *f);
if (len <= 0) {
_line_reader_cleanup(f);
return (NextLine_t){NextLine$tag$Done};
return NULL_TEXT;
}
while (len > 0 && (line[len-1] == '\r' || line[len-1] == '\n'))
@ -453,7 +453,7 @@ static NextLine_t _next_line(FILE **f)
Text_t line_text = Text$format("%.*s", len, line);
free(line);
return NextLine$tagged$Next(line_text);
return line_text;
}
public Closure_t Path$by_line(Path_t path)

View File

@ -17,7 +17,6 @@
#include "datatypes.h"
#include "functions.h"
#include "integers.h"
#include "nextline.h"
#include "macros.h"
#include "memory.h"
#include "nums.h"

View File

@ -318,7 +318,9 @@ static CORD compile_inline_block(env_t *env, ast_t *ast)
static CORD optional_var_into_nonnull(binding_t *b)
{
switch (b->type->tag) {
type_t *t = b->type->tag == OptionalType ? Match(b->type, OptionalType)->type : b->type;
switch (t->tag) {
case OptionalType:
case IntType:
return CORD_all(b->code, ".i");
case StructType:
@ -1212,6 +1214,7 @@ CORD compile_statement(env_t *env, ast_t *ast)
code = CORD_all(code, compile_declaration(iter_t, "next"), " = ", compile(env, for_->iter), ";\n");
auto fn = iter_t->tag == ClosureType ? Match(Match(iter_t, ClosureType)->fn, FunctionType) : Match(iter_t, FunctionType);
assert(fn->ret->tag == OptionalType);
code = CORD_all(code, compile_declaration(fn->ret, "cur"), ";\n"); // Iteration enum
CORD next_fn;
@ -1228,9 +1231,17 @@ CORD compile_statement(env_t *env, ast_t *ast)
next_fn = "next";
}
env_t *enum_env = Match(fn->ret, EnumType)->env;
next_fn = CORD_all("(cur=", next_fn, iter_t->tag == ClosureType ? "(next.userdata)" : "()", ").tag == ",
namespace_prefix(enum_env->libname, enum_env->namespace), "tag$Next");
env_t *tmp_env = fresh_scope(env);
set_binding(tmp_env, "cur", new(binding_t, .type=fn->ret, .code="cur"));
next_fn = CORD_all("(cur=", next_fn, iter_t->tag == ClosureType ? "(next.userdata)" : "()", ", ",
compile_optional_check(tmp_env, FakeAST(Var, "cur")), ")");
if (for_->vars) {
naked_body = CORD_all(
compile_declaration(Match(fn->ret, OptionalType)->type, CORD_all("$", Match(for_->vars->ast, Var)->name)),
" = ", optional_var_into_nonnull(new(binding_t, .type=fn->ret, .code="cur")), ";\n",
naked_body);
}
if (for_->empty) {
code = CORD_all(code, "if (", next_fn, ") {\n"

View File

@ -95,7 +95,7 @@ Returns an iterator that can be used to iterate over a file one line at a time.
**Usage:**
```markdown
by_line(path: Path) -> func()->NextLine
by_line(path: Path) -> func()->Text?
```
**Parameters:**

View File

@ -509,32 +509,14 @@ env_t *for_scope(env_t *env, ast_t *ast)
}
case FunctionType: case ClosureType: {
auto fn = iter_t->tag == ClosureType ? Match(Match(iter_t, ClosureType)->fn, FunctionType) : Match(iter_t, FunctionType);
if (fn->ret->tag != EnumType)
code_err(for_->iter, "Iterator functions must return an enum with a Done and Next field");
auto iter_enum = Match(fn->ret, EnumType);
type_t *next_type = NULL;
for (tag_t *tag = iter_enum->tags; tag; tag = tag->next) {
if (streq(tag->name, "Done")) {
if (Match(tag->type, StructType)->fields)
code_err(for_->iter, "This iterator function returns an enum with a Done field that has values, when none are allowed");
} else if (streq(tag->name, "Next")) {
next_type = tag->type;
} else {
code_err(for_->iter, "This iterator function returns an enum with a value that isn't Done or Next: %s", tag->name);
}
}
if (fn->ret->tag != OptionalType)
code_err(for_->iter, "Iterator functions must return an optional type, not %T", fn->ret);
if (!next_type)
code_err(for_->iter, "This iterator function returns an enum that doesn't have a Next field");
arg_t *iter_field = Match(next_type, StructType)->fields;
for (ast_list_t *var = for_->vars; var; var = var->next) {
if (!iter_field)
code_err(var->ast, "This is one variable too many for this iterator, which returns a %T", fn->ret);
const char *name = Match(var->ast, Var)->name;
type_t *t = get_arg_type(env, iter_field);
set_binding(scope, name, new(binding_t, .type=t, .code=CORD_cat("cur.$Next.$", iter_field->name)));
iter_field = iter_field->next;
if (for_->vars) {
if (for_->vars->next)
code_err(for_->vars->next->ast, "This is too many variables for this loop");
const char *var = Match(for_->vars->ast, Var)->name;
set_binding(scope, var, new(binding_t, .type=Match(fn->ret, OptionalType)->type, .code=CORD_cat("$", var)));
}
return scope;
}

View File

@ -1,33 +1,33 @@
enum PairIteration(Done, Next(x:Text, y:Text))
func pairwise(strs:[Text])->func()->PairIteration:
struct Pair(x:Text, y:Text)
func pairwise(strs:[Text])->func()->Pair?:
i := 1
return func():
if i + 1 > strs.length: return PairIteration.Done
if i + 1 > strs.length: return !Pair
i += 1
return PairIteration.Next(strs[i-1], strs[i])
return Pair(strs[i-1], strs[i])?
enum RangeIteration(Done, Next(i:Int))
func range(first:Int, last:Int)->func()->RangeIteration:
func range(first:Int, last:Int)->func()->Int?:
i := first
return func():
if i > last:
return RangeIteration.Done
return !Int
i += 1
return RangeIteration.Next(i-1)
return (i-1)?
func main():
values := ["A", "B", "C", "D"]
>> ((++) "($(foo)$(baz))" for foo, baz in pairwise(values))
>> ((++) "($(foo.x)$(foo.y))" for foo in pairwise(values))
= "(AB)(BC)(CD)"
>> ["$(foo)$(baz)" for foo, baz in pairwise(values)]
>> ["$(foo.x)$(foo.y)" for foo in pairwise(values)]
= ["AB", "BC", "CD"]
do:
result := [:Text]
for foo, baz in pairwise(values):
result:insert("$(foo)$(baz)")
for foo in pairwise(values):
result:insert("$(foo.x)$(foo.y)")
>> result
= ["AB", "BC", "CD"]

View File

@ -1081,22 +1081,9 @@ type_t *get_type(env_t *env, ast_t *ast)
Match(Match(iter_value_t, ClosureType)->fn, FunctionType) : Match(iter_value_t, FunctionType);
if (fn->args)
code_err(reduction->iter, "I expected this iterator function to not take any arguments, but it's %T", iter_value_t);
if (fn->ret->tag != EnumType)
code_err(reduction->iter, "I expected this iterator function to return an enum, but it's %T", iter_value_t);
value_t = NULL;
for (tag_t *tag = Match(fn->ret, EnumType)->tags; tag; tag = tag->next) {
if (streq(tag->name, "Next")) {
arg_t *fields = Match(tag->type, StructType)->fields;
if (!fields || fields->next)
code_err(reduction->iter,
"I expected this iterator function to return an enum with a Next() that has exactly one value, not %T",
tag->type);
value_t = fields->type;
break;
}
}
if (!value_t)
code_err(reduction->iter, "This iterator function doesn't return an enum with a Next() value");
if (fn->ret->tag != OptionalType)
code_err(reduction->iter, "I expected this iterator function to return an optional value, but it's %T", iter_value_t);
value_t = Match(fn->ret, OptionalType)->type;
break;
}
default: code_err(reduction->iter, "I don't know how to do a reduction over %T values", iter_t);