diff --git a/crates/runtime/include/scuzz_rt.h b/crates/runtime/include/scuzz_rt.h index f9c49ac8..056ba43f 100644 --- a/crates/runtime/include/scuzz_rt.h +++ b/crates/runtime/include/scuzz_rt.h @@ -1244,6 +1244,24 @@ void sz_scenario_run_setup(void); void sz_testrt_fault_hold(void); void sz_testrt_fault_release(void); +/* Fuzz hooks for the evaluator probe (`scuzz eval --probe`). The + * registration nodes register when they are built and return IO[Unit]. + * Sequence them before `sz_fuzz_probe` in one for-comprehension. + * Closures are (fn, env) pairs with the SzCont shape. */ +SzIo *sz_fuzz_setup(SzIo *setup); /* setup IO; its value is Scenario.context */ +/* fn(List[String] tokens, env) gives IO[Unit]. nargs 0: empty list. nargs 1: + * the rest of the line. More: whitespace tokens; a missing token is "". */ +SzIo *sz_fuzz_driver(SzString *name, int64_t nargs, void *fn, void *env); +SzIo *sz_fuzz_verify(SzString *name, void *fn, void *env); /* Timeline to Verdict */ +SzIo *sz_fuzz_verify_rel(SzString *name, void *fn, void *env); /* (Timeline, Timeline) pair to Verdict */ +/* Coverage hit with a key the evaluator interns. */ +void sz_fuzz_hit(SzString *key); +/* One probe: copy SCUZZ_EV_* to SCUZZ_*, install TestRuntime under + * SCUZZ_TESTRT=1, refresh cached env reads, run setup, run the drive script + * or `program`, end the session, flush the dumps. Fails with the runtime + * message when `program` fails. */ +SzIo *sz_fuzz_probe(SzIo *program); + /* Entrypoint helper used by @main codegen */ int sz_runtime_main_args(SzIo *program, int argc, char **argv); diff --git a/crates/runtime/src/runtime.c b/crates/runtime/src/runtime.c index a3075506..e1f9aadf 100644 --- a/crates/runtime/src/runtime.c +++ b/crates/runtime/src/runtime.c @@ -2551,7 +2551,8 @@ typedef enum JoinKind { JOIN_NONE = 0, JOIN_RACE = 1, JOIN_BOTH = 2, - JOIN_TIMEOUT = 3 + JOIN_TIMEOUT = 3, + JOIN_CANCEL_WAIT = 4 /* winner settled; wait for the loser's finalizers */ } JoinKind; typedef struct Fiber { @@ -2570,6 +2571,8 @@ typedef struct Fiber { void *child_val[2]; SzError *child_err[2]; int children_settled; + int win_slot; /* JOIN_CANCEL_WAIT: child_val slot to resume with */ + SzError *wait_err; /* JOIN_CANCEL_WAIT: fail with this instead */ int result_ok; void *result_value; SzError *result_error; @@ -3134,12 +3137,15 @@ static SzError *deferred_copy_error(SzDeferred *d) { : sz_error_new(1, "deferred failed"); } -/* Unique parent: take the slot. Shared parent (loop template): retain. */ +/* Unique parent: take the slot. Shared parent (loop template): retain. + * A raw (non-RC) env is single-use state that its continuation frees. Take + * it even from a shared parent so a later release of the parent does not + * touch freed memory. */ static void *io_slot_child(SzIo *parent, void **slot) { void *e = *slot; if (!e) return NULL; - if (parent && sz_is_rc(parent) && sz_rc_hdr(parent)->rc > 1) { + if (parent && sz_is_rc(parent) && sz_rc_hdr(parent)->rc > 1 && sz_is_rc(e)) { sz_retain(e); return e; } @@ -3236,6 +3242,11 @@ static void fiber_cancel(Sched *s, Fiber *f) { poller_remove(s, f); if (f->state == FIB_FWAIT && f->fwait) fiber_join_waiter_remove(f->fwait, f); + if (f->wait_err) { + sz_error_free(f->wait_err); + f->wait_err = NULL; + } + f->join_kind = JOIN_NONE; if (f->children[0]) fiber_cancel(s, f->children[0]); if (f->children[1]) @@ -3299,7 +3310,21 @@ static void fiber_wake_joiners(Sched *s, Fiber *target, int ok, void *val, } } +static void parent_after_cancel_wait(Sched *s, Fiber *p) { + SzError *err = p->wait_err; + p->join_kind = JOIN_NONE; + p->wait_err = NULL; + if (err) { + fiber_fail(s, p, err); + return; + } + p->state = FIB_READY; + fiber_set_pure_retained(p, p->child_val[p->win_slot]); + ready_enqueue(s, p); +} + static void fiber_settle_cancelled(Sched *s, Fiber *f) { + Fiber *p = f->parent; f->state = FIB_CANCELLED; f->result_ok = 0; if (!f->result_error) @@ -3307,6 +3332,30 @@ static void fiber_settle_cancelled(Sched *s, Fiber *f) { if (f->forked) forked_live_remove(s, f); fiber_wake_joiners(s, f, 0, NULL, f->result_error); + if (p && p->join_kind == JOIN_CANCEL_WAIT && p->state == FIB_JOIN && + p->children[f->child_slot] == f) + parent_after_cancel_wait(s, p); +} + +/* Cancel the loser. The parent resumes after the loser's finalizers run, so + * `race`, `timeout`, and `both` return only when no child is still active. + * Returns 1 when the parent waits. */ +static int cancel_sibling_then(Sched *s, Fiber *p, int slot, SzError *err) { + Fiber *sib = p->children[1 - slot]; + if (sib) + fiber_cancel(s, sib); + if (sib && sib->state == FIB_FINALIZING) { + p->join_kind = JOIN_CANCEL_WAIT; + p->win_slot = slot; + p->wait_err = err; + return 1; + } + p->join_kind = JOIN_NONE; + if (err) { + fiber_fail(s, p, err); + return 1; + } + return 0; } static void join_child_done(Sched *s, Fiber *child, int ok, void *val, @@ -3324,12 +3373,10 @@ static void join_child_done(Sched *s, Fiber *child, int ok, void *val, if (p->join_kind == JOIN_RACE) { if (ok) { - Fiber *sib = p->children[1 - slot]; - if (sib) - fiber_cancel(s, sib); + if (cancel_sibling_then(s, p, slot, NULL)) + return; p->state = FIB_READY; fiber_set_pure_retained(p, val); - p->join_kind = JOIN_NONE; ready_enqueue(s, p); return; } @@ -3347,33 +3394,25 @@ static void join_child_done(Sched *s, Fiber *child, int ok, void *val, } if (p->join_kind == JOIN_TIMEOUT) { - Fiber *sib = p->children[1 - slot]; - if (sib) - fiber_cancel(s, sib); - p->join_kind = JOIN_NONE; - if (slot == 1) { - if (ok) { - p->state = FIB_READY; - fiber_set_pure_retained(p, val); - ready_enqueue(s, p); - } else { - fiber_fail(s, p, err ? error_copy_or_interrupt(err) - : sz_error_new(1, "timeout inner failed")); - } - } else { - fiber_fail(s, p, sz_error_new(1, "timeout")); - } + SzError *out = NULL; + if (slot == 0) + out = sz_error_new(1, "timeout"); + else if (!ok) + out = err ? error_copy_or_interrupt(err) + : sz_error_new(1, "timeout inner failed"); + if (cancel_sibling_then(s, p, slot, out)) + return; + p->state = FIB_READY; + fiber_set_pure_retained(p, val); + ready_enqueue(s, p); return; } if (p->join_kind == JOIN_BOTH) { if (!ok) { - Fiber *sib = p->children[1 - slot]; - if (sib) - fiber_cancel(s, sib); - p->join_kind = JOIN_NONE; - fiber_fail(s, p, err ? error_copy_or_interrupt(err) - : sz_error_new(7, "both failed")); + cancel_sibling_then(s, p, slot, + err ? error_copy_or_interrupt(err) + : sz_error_new(7, "both failed")); return; } if (p->children_settled >= 2) { diff --git a/crates/runtime/src/stream.c b/crates/runtime/src/stream.c index d5a611bf..751f4192 100644 --- a/crates/runtime/src/stream.c +++ b/crates/runtime/src/stream.c @@ -760,7 +760,7 @@ static SzIo *filter_into(SzStream *s, SzList *acc, int64_t remain, st->pred = pred; st->penv = penv; st->stopped = NULL; - return fm_drop((SzIo *)s->left, after_filter_eval, st); + return sz_io_flatmap((SzIo *)s->left, after_filter_eval, st); } case SZ_ST_CONCAT: { StTWConcat *st = (StTWConcat *)sz_alloc(sizeof(StTWConcat)); @@ -876,7 +876,7 @@ static SzIo *dropwhile_into(SzStream *s, SzList *acc, int64_t remain, st->pred = pred; st->penv = penv; st->stopped = NULL; - return fm_drop((SzIo *)s->left, after_dw_eval, st); + return sz_io_flatmap((SzIo *)s->left, after_dw_eval, st); } case SZ_ST_CONCAT: { StTWConcat *st = (StTWConcat *)sz_alloc(sizeof(StTWConcat)); @@ -1006,7 +1006,7 @@ static SzIo *compile_into(SzStream *s, SzList *acc, int64_t remain) { st->tail = (SzStream *)s->right; st->acc = acc; st->remain = remain_dec(remain); - return fm_drop((SzIo *)s->left, after_eval, st); + return sz_io_flatmap((SzIo *)s->left, after_eval, st); } case SZ_ST_CONCAT: { StConcat *st = (StConcat *)sz_alloc(sizeof(StConcat)); @@ -1632,7 +1632,7 @@ static SzIo *stream_step(SzStream *s) { StStep *st = (StStep *)sz_alloc(sizeof(StStep)); sz_retain(s->right); st->pin = (SzStream *)s->right; - return fm_drop((SzIo *)s->left, after_step_eval, st); + return sz_io_flatmap((SzIo *)s->left, after_step_eval, st); } case SZ_ST_EVALMAP: { StStep *st = (StStep *)sz_alloc(sizeof(StStep)); @@ -2150,7 +2150,7 @@ static SzIo *mapconcat_into(SzStream *s, SzList *acc, int64_t remain, st->acc_len = 0; st->next = (SzStream *)s->right; st->acc = acc; - return fm_drop((SzIo *)s->left, after_mc_eval, st); + return sz_io_flatmap((SzIo *)s->left, after_mc_eval, st); } case SZ_ST_CONCAT: { StMc *st = (StMc *)sz_alloc(sizeof(StMc)); @@ -2266,7 +2266,7 @@ static SzIo *changes_into(SzStream *s, SzList *acc, int64_t remain, void *prev, st->acc_len = 0; st->next = (SzStream *)s->right; st->acc = acc; - return fm_drop((SzIo *)s->left, after_ch_eval, st); + return sz_io_flatmap((SzIo *)s->left, after_ch_eval, st); } case SZ_ST_CONCAT: { StCh *st = (StCh *)sz_alloc(sizeof(StCh)); @@ -2402,7 +2402,7 @@ static SzIo *flatmap_into(SzStream *s, SzList *acc, int64_t remain, st->next = (SzStream *)s->right; st->cur = NULL; st->acc = acc; - return fm_drop((SzIo *)s->left, after_fp_eval, st); + return sz_io_flatmap((SzIo *)s->left, after_fp_eval, st); } case SZ_ST_CONCAT: { StFp *st = (StFp *)sz_alloc(sizeof(StFp)); @@ -2524,7 +2524,7 @@ static SzIo *takewhile_into(SzStream *s, SzList *acc, int64_t remain, st->pred = pred; st->penv = penv; st->stopped = stopped; - return fm_drop((SzIo *)s->left, after_tw_eval, st); + return sz_io_flatmap((SzIo *)s->left, after_tw_eval, st); } case SZ_ST_CONCAT: { StTWConcat *st = (StTWConcat *)sz_alloc(sizeof(StTWConcat)); @@ -2705,7 +2705,7 @@ static SzIo *find_into(SzStream *s, SzList *acc, int64_t remain, st->pred = pred; st->penv = penv; st->stopped = found; - return fm_drop((SzIo *)s->left, after_find_eval, st); + return sz_io_flatmap((SzIo *)s->left, after_find_eval, st); } case SZ_ST_CONCAT: { StTWConcat *st = (StTWConcat *)sz_alloc(sizeof(StTWConcat)); @@ -2738,21 +2738,29 @@ static void *st_release_io(void *env) { return NULL; } +/* Build the pull graph at run time. The graph carries single-use state in + * raw envs, so a shared or repeated compile node must build a fresh graph + * on every run. The outer node keeps only the stream (RC). */ +static SzIo *compile_build(void *value, void *env) { + SzStream *s = (SzStream *)env; + SzIo *body = fm_drop(compile_into(s, sz_list_nil(), -1), reverse_acc, NULL); + SzIo *fin = sz_io_delay(st_release_io, s); + SzIo *ens = sz_io_ensure(body, fin); + (void)value; + sz_release(body); + sz_release(fin); + return ens; +} + SzIo *sz_stream_compile_to_list(SzStream *s) { - SzIo *body; + SzIo *io; if (!s) s = sz_stream_nil(); else sz_retain(s); - body = fm_drop(compile_into(s, sz_list_nil(), -1), reverse_acc, NULL); - { - SzIo *fin = sz_io_delay(st_release_io, s); - SzIo *ens = sz_io_ensure(body, fin); - sz_release(body); - sz_release(fin); - sz_release(s); - return ens; - } + io = fm_drop(sz_io_pure(NULL), compile_build, s); + sz_release(s); + return io; } static SzIo *drain_discard(void *list, void *env) { @@ -2807,6 +2815,7 @@ static SzIo *fold_from_list(void *list, void *env) { void *out = sz_list_fold_left(xs, z, (SzListMapFn)fn, st->outer); sz_release(xs); sz_release(z); + sz_release(st->outer); sz_free(st); return pure_drop(out); } @@ -2816,6 +2825,7 @@ SzIo *sz_stream_fold(SzStream *s, void *z, SzStreamMapFn f, void *env) { if (!f) sz_panic("sz_stream_fold(null fn)"); st = (StLift *)sz_alloc(sizeof(StLift)); + sz_retain(env); st->outer = env; st->remain = 0; st->tag = 0; diff --git a/crates/runtime/src/testrt.c b/crates/runtime/src/testrt.c index c4889f5f..5c563e9f 100644 --- a/crates/runtime/src/testrt.c +++ b/crates/runtime/src/testrt.c @@ -1,3 +1,4 @@ +#define _POSIX_C_SOURCE 200809L #include "scuzz_rt.h" #include "scuzz_ui.h" #include "rt_util.h" @@ -232,6 +233,7 @@ static void fault_arm_from_env(void) { static int g_fault_hold; static void *g_scenario_setup; +static SzIo *g_scenario_setup_io; static void *g_scenario_ctx; void sz_testrt_fault_hold(void) { g_fault_hold = 1; } @@ -240,14 +242,26 @@ void sz_testrt_fault_release(void) { g_fault_hold = 0; } void sz_scenario_register_setup(void *fn) { g_scenario_setup = fn; } +SzIo *sz_fuzz_setup(SzIo *setup) { + sz_retain(setup); + sz_release(g_scenario_setup_io); + g_scenario_setup_io = setup; + return sz_io_pure(NULL); +} + void *sz_scenario_context(void) { return g_scenario_ctx; } void sz_scenario_run_setup(void) { SzIo *io; SzIoResult r; - if (!g_scenario_setup) + if (!g_scenario_setup && !g_scenario_setup_io) return; - io = ((SzIo * (*)(void)) g_scenario_setup)(); + if (g_scenario_setup_io) { + io = g_scenario_setup_io; + sz_retain(io); + } else { + io = ((SzIo * (*)(void)) g_scenario_setup)(); + } r = sz_io_unsafe_run(io); if (!r.ok) { fprintf(stderr, "scuzz: scenario setup failed: %s\n", @@ -2992,9 +3006,13 @@ typedef struct { static SzResponseProp g_response[SZ_SESSION_MAX]; static int g_response_n; +/* `fn` is a fn-pointer claim. `cfn` with `env` is a closure claim from the + * evaluator probe. One of them is set. */ typedef struct { char *name; SzVerdict *(*fn)(void *); + SzVerdict *(*cfn)(void *, void *); + void *env; } SzVerifyProp; static SzVerifyProp g_verify[SZ_SESSION_MAX]; @@ -3003,6 +3021,8 @@ static int g_verify_n; typedef struct { char *name; SzVerdict *(*fn)(void *, void *); + SzVerdict *(*cfn)(void *, void *); + void *env; } SzVerifyRel; static SzVerifyRel g_verify_rel[SZ_SESSION_MAX]; @@ -4316,6 +4336,17 @@ void *sz_timeline_load(const char *path) { return t; } +static SzVerdict *verify_rel_call(SzVerifyRel *r, void *a, void *b) { + SzPair *pair; + SzVerdict *v; + if (r->fn) + return r->fn(a, b); + pair = sz_pair_new(a, b); + v = r->cfn(pair, r->env); + sz_release(pair); + return v; +} + int sz_judge_rel_main(const char *spec) { char *copy; char *comma; @@ -4346,9 +4377,9 @@ int sz_judge_rel_main(const char *spec) { for (i = 0; i < g_verify_rel_n; i++) { SzVerdict *v; char msg[256]; - if (!g_verify_rel[i].fn) + if (!g_verify_rel[i].fn && !g_verify_rel[i].cfn) continue; - v = g_verify_rel[i].fn(a, b); + v = verify_rel_call(&g_verify_rel[i], a, b); if (!v || v->valid) continue; verdict_msg(msg, sizeof msg, g_verify_rel[i].name, v); @@ -4501,6 +4532,131 @@ void sz_verify_register_rel(const char *name, SzVerdict *(*fn)(void *, void *)) g_verify_rel_n++; } +SzIo *sz_fuzz_verify(SzString *name, void *fn, void *env) { + const char *s = name ? sz_string_cstr(name) : ""; + if (fn && s[0]) { + if (g_verify_n >= SZ_SESSION_MAX) + sz_panic("sz_property session: too many verify predicates"); + g_verify[g_verify_n].name = dup_cstr(s); + g_verify[g_verify_n].fn = NULL; + g_verify[g_verify_n].cfn = (SzVerdict * (*)(void *, void *)) fn; + sz_retain(env); + g_verify[g_verify_n].env = env; + g_verify_n++; + } + return sz_io_pure(NULL); +} + +SzIo *sz_fuzz_verify_rel(SzString *name, void *fn, void *env) { + const char *s = name ? sz_string_cstr(name) : ""; + if (fn && s[0]) { + if (g_verify_rel_n >= SZ_SESSION_MAX) + sz_panic("sz_property session: too many verify relations"); + g_verify_rel[g_verify_rel_n].name = dup_cstr(s); + g_verify_rel[g_verify_rel_n].fn = NULL; + g_verify_rel[g_verify_rel_n].cfn = (SzVerdict * (*)(void *, void *)) fn; + sz_retain(env); + g_verify_rel[g_verify_rel_n].env = env; + g_verify_rel_n++; + } + return sz_io_pure(NULL); +} + +void sz_fuzz_hit(SzString *key) { + if (key && sz_string_cstr(key)[0]) + sz_coverage_hit(sz_string_cstr(key)); +} + +/* Copy every SCUZZ_EV_= to SCUZZ_. The parent sets probe + * env under the EV prefix so the evaluator process itself runs live. */ +static void fuzz_probe_env(void) { + extern char **environ; + char **e; + size_t n = 0; + char **names = NULL; + size_t i; + for (e = environ; e && *e; e++) + if (strncmp(*e, "SCUZZ_EV_", 9) == 0) + n++; + if (!n) + return; + names = (char **)sz_alloc(n * sizeof(char *)); + i = 0; + for (e = environ; e && *e; e++) + if (strncmp(*e, "SCUZZ_EV_", 9) == 0) + names[i++] = dup_cstr(*e); + for (i = 0; i < n; i++) { + char *eq = strchr(names[i], '='); + if (eq) { + char key[256]; + *eq = 0; + snprintf(key, sizeof key, "SCUZZ_%s", names[i] + 9); + setenv(key, eq + 1, 1); + } + sz_free(names[i]); + } + sz_free(names); +} + +static void *fuzz_probe_thunk(void *env) { + SzIo *program = (SzIo *)env; + const char *tr; + const char *ds; + void *out = NULL; + fuzz_probe_env(); + sz_coverage_env_refresh(); + sz_testrt_oracles_refresh(); + tr = getenv("SCUZZ_TESTRT"); + if (tr && tr[0] == '1') + sz_testrt_install(); + sz_testrt_fault_hold(); + sz_scenario_run_setup(); + sz_testrt_fault_release(); + ds = getenv("SCUZZ_DRIVE_SCRIPT"); + if (ds && ds[0]) { + sz_driver_run_script(ds); + sz_property_session_end(); + } else { + SzIoResult r; + sz_retain(program); + r = sz_io_unsafe_run(program); + if (!r.ok) { + out = sz_string_from_cstr(r.error ? sz_string_cstr(r.error->message) + : "unknown"); + if (r.error) + sz_error_free(r.error); + } else { + sz_release(r.value); + sz_property_session_end(); + } + } + sz_property_sometimes_flush(); + sz_timeline_varied_flush(); + sz_property_classify_flush(); + return out; +} + +static SzIo *fuzz_probe_after(void *value, void *env) { + SzIo *out; + (void)env; + if (!value) + return sz_io_pure(NULL); + out = sz_io_fail_cstr(sz_string_cstr((SzString *)value)); + sz_release(value); + return out; +} + +SzIo *sz_fuzz_probe(SzIo *program) { + SzIo *delay; + SzIo *out; + if (!program) + sz_panic("sz_fuzz_probe(null)"); + delay = sz_io_delay(fuzz_probe_thunk, program); + out = sz_io_flatmap(delay, fuzz_probe_after, NULL); + sz_release(delay); + return out; +} + int sz_property_session_armed(void) { return g_always_n > 0 || g_eventually_n > 0 || g_response_n > 0 || g_verify_n > 0; @@ -4610,9 +4766,10 @@ void sz_property_session_end(void) { frozen.states = g_tl; for (i = 0; i < g_verify_n; i++) { SzVerdict *v; - if (!g_verify[i].fn) + if (!g_verify[i].fn && !g_verify[i].cfn) continue; - v = g_verify[i].fn(&frozen); + v = g_verify[i].fn ? g_verify[i].fn(&frozen) + : g_verify[i].cfn(&frozen, g_verify[i].env); if (!v || v->valid) continue; claim_fail_verdict(g_verify[i].name, v); @@ -4649,15 +4806,21 @@ void sz_property_session_reset(void) { for (i = 0; i < g_verify_n; i++) { if (g_verify[i].name) sz_free(g_verify[i].name); + sz_release(g_verify[i].env); g_verify[i].name = NULL; g_verify[i].fn = NULL; + g_verify[i].cfn = NULL; + g_verify[i].env = NULL; } g_verify_n = 0; for (i = 0; i < g_verify_rel_n; i++) { if (g_verify_rel[i].name) sz_free(g_verify_rel[i].name); + sz_release(g_verify_rel[i].env); g_verify_rel[i].name = NULL; g_verify_rel[i].fn = NULL; + g_verify_rel[i].cfn = NULL; + g_verify_rel[i].env = NULL; } g_verify_rel_n = 0; tl_free_states(); @@ -4672,14 +4835,28 @@ typedef struct { int nargs; int kind; /* 0=Int, 1=String, 2=Bool (i64 0/1) */ void *fn; + int closure; /* 1: fn(List[String], env) from the evaluator probe */ + void *env; } SzDriver; static SzDriver *g_drivers; static size_t g_drivers_n; static size_t g_drivers_cap; +static void driver_add(const char *s, int64_t nargs, int64_t kind, void *fn, + int closure, void *env); + void sz_driver_register(SzString *name, int64_t nargs, int64_t kind, void *fn) { - const char *s = name ? sz_string_cstr(name) : ""; + driver_add(name ? sz_string_cstr(name) : "", nargs, kind, fn, 0, NULL); +} + +SzIo *sz_fuzz_driver(SzString *name, int64_t nargs, void *fn, void *env) { + driver_add(name ? sz_string_cstr(name) : "", nargs, 0, fn, 1, env); + return sz_io_pure(NULL); +} + +static void driver_add(const char *s, int64_t nargs, int64_t kind, void *fn, + int closure, void *env) { size_t n; char *copy; if (!fn || !s[0]) @@ -4704,6 +4881,9 @@ void sz_driver_register(SzString *name, int64_t nargs, int64_t kind, void *fn) { g_drivers[g_drivers_n].nargs = (int)nargs; g_drivers[g_drivers_n].kind = (int)kind; g_drivers[g_drivers_n].fn = fn; + g_drivers[g_drivers_n].closure = closure; + sz_retain(env); + g_drivers[g_drivers_n].env = env; g_drivers_n++; } @@ -4876,6 +5056,32 @@ static int sz_driver_split_rest(const char *rest, char tok[][128], int maxn) { return n; } +static SzList *driver_tokens_cons(SzList *tail, const char *t) { + SzString *s = sz_string_from_cstr(t); + SzList *out = sz_list_cons(s, tail); + sz_release(s); + sz_release(tail); + return out; +} + +/* Closure drivers take the same tokens as the fn-pointer path, as strings. */ +static SzIo *driver_closure_io(SzDriver *d, const char *rest) { + SzList *args = sz_list_nil(); + SzIo *io; + if (d->nargs == 1) + args = driver_tokens_cons(args, rest); + else if (d->nargs > 1) { + char tok[4][128]; + int ntok = sz_driver_split_rest(rest, tok, 4); + int j; + for (j = d->nargs - 1; j >= 0; j--) + args = driver_tokens_cons(args, j < ntok ? tok[j] : ""); + } + io = ((SzIo * (*)(void *, void *)) d->fn)(args, d->env); + sz_release(args); + return io; +} + void sz_driver_run_line(const char *spec) { char name[64]; const char *rest; @@ -4896,7 +5102,9 @@ void sz_driver_run_line(const char *spec) { fprintf(stderr, "scuzz: unknown driver %s\n", name); sz_panic("Ui.run: unknown driver"); } - if (d->nargs <= 0) + if (d->closure) + io = driver_closure_io(d, rest); + else if (d->nargs <= 0) io = ((SzIo * (*)(void)) d->fn)(); else if (d->nargs == 1) { if (d->kind == 1) diff --git a/crates/runtime/tests/test_io.c b/crates/runtime/tests/test_io.c index 6545e08d..bed061c4 100644 --- a/crates/runtime/tests/test_io.c +++ b/crates/runtime/tests/test_io.c @@ -182,6 +182,24 @@ static void *take_hit(void *env) { return sz_string_from_cstr((const char *)env); } +static SzIo *cont_delay_inc(void *value, void *env) { + (void)env; + sz_release(value); + return sz_io_delay(delay_inc, NULL); +} + +static SzIo *cont_delay_calls(void *value, void *env) { + (void)env; + sz_release(value); + return sz_io_pure((void *)(intptr_t)delay_calls); +} + +static SzIo *handle_delay_calls(SzError *err, void *env) { + (void)env; + sz_error_free(err); + return sz_io_pure((void *)(intptr_t)delay_calls); +} + static SzIo *cont_println(void *value, void *env) { (void)value; (void)env; @@ -274,6 +292,12 @@ static SzIo *fm_drop(SzIo *inner, SzCont cont, void *env) { return io; } +/* A finalizer that takes several scheduler steps. */ +static SzIo *slow_inc(void) { + return fm_drop(fm_drop(pure_drop(NULL), cont_pure_unit, NULL), cont_delay_inc, + NULL); +} + static SzIo *pure_drop(void *value) { SzIo *io = sz_io_pure(value); @@ -2232,6 +2256,154 @@ static void test_driver_growth(void) { sz_testrt_reset(); } +static int64_t closure_sum; +static int closure_pair_ok; +static int closure_verify_ran; + +static SzIo *closure_add(void *tokens, void *env) { + SzList *xs = (SzList *)tokens; + assert(sz_unbox_i64(env) == 5); + assert(sz_list_len(xs) == 1); + closure_sum += atoi(sz_string_cstr((SzString *)sz_list_head(xs))); + return sz_io_pure(NULL); +} + +static SzIo *closure_pair(void *tokens, void *env) { + SzList *xs = (SzList *)tokens; + (void)env; + assert(sz_list_len(xs) == 3); + closure_pair_ok = strcmp(sz_string_cstr((SzString *)sz_list_head(xs)), "a") == 0 && + strcmp(sz_string_cstr((SzString *)sz_list_head(sz_list_tail(xs))), "b") == 0 && + sz_string_cstr((SzString *)sz_list_head(sz_list_tail(sz_list_tail(xs))))[0] == 0; + return sz_io_pure(NULL); +} + +static SzVerdict *closure_verify(void *tl, void *env) { + assert(sz_unbox_i64(env) == 9); + closure_verify_ran = 1; + return sz_timeline_len(tl) >= 3 ? sz_verdict_ok() + : sz_verdict_fail(0, "short timeline"); +} + +static SzVerdict *closure_rel(void *pair, void *env) { + SzPair *p = (SzPair *)pair; + (void)env; + return sz_timeline_len(p->left) == sz_timeline_len(p->right) + ? sz_verdict_ok() + : sz_verdict_fail(0, "lengths differ"); +} + +/* Evaluator probe hooks: closure setup, drivers, and claims run one probe + * under SCUZZ_EV_* env, in process. */ +static void test_fuzz_probe_closures(void) { + const char *dump = "/tmp/scuzz_test_fuzz_probe.dump"; + const char *cov = "/tmp/scuzz_test_fuzz_probe.cov"; + const char *script = "/tmp/scuzz_test_fuzz_probe.json"; + void *env5 = sz_box_i64(5); + void *env9 = sz_box_i64(9); + void *ctx = sz_box_i64(7); + SzString *name; + SzString *key; + SzIoResult r; + FILE *f = fopen(script, "w"); + assert(f); + fputs("{\"v\":1,\"kind\":\"inject\",\"events\":[{\"op\":\"drive\",\"name\":\"evAdd\",\"args\":[3]}," + "{\"op\":\"drive\",\"name\":\"evPair\",\"args\":[\"a\",\"b\"]}]}", + f); + fclose(f); + remove(dump); + remove(cov); + sz_testrt_reset(); + sz_property_session_reset(); + setenv("SCUZZ_EV_TESTRT", "1", 1); + setenv("SCUZZ_EV_TIMELINE_DUMP", dump, 1); + setenv("SCUZZ_EV_COVERAGE_DUMP", cov, 1); + setenv("SCUZZ_EV_DRIVE_SCRIPT", script, 1); + unsetenv("SCUZZ_TESTRT"); + { + SzIo *setup = sz_io_pure(ctx); + r = sz_io_unsafe_run(sz_fuzz_setup(setup)); + assert(r.ok); + sz_release(setup); + } + name = sz_string_from_cstr("evAdd"); + r = sz_io_unsafe_run(sz_fuzz_driver(name, 1, (void *)closure_add, env5)); + assert(r.ok); + sz_release(name); + name = sz_string_from_cstr("evPair"); + r = sz_io_unsafe_run(sz_fuzz_driver(name, 3, (void *)closure_pair, NULL)); + assert(r.ok); + sz_release(name); + name = sz_string_from_cstr("evLen"); + r = sz_io_unsafe_run(sz_fuzz_verify(name, (void *)closure_verify, env9)); + assert(r.ok); + sz_release(name); + name = sz_string_from_cstr("evRel"); + r = sz_io_unsafe_run(sz_fuzz_verify_rel(name, (void *)closure_rel, NULL)); + assert(r.ok); + sz_release(name); + sz_release(env5); + sz_release(env9); + { + SzIo *unit = sz_io_pure(NULL); + r = sz_io_unsafe_run(sz_fuzz_probe(unit)); + assert(r.ok); + sz_release(unit); + } + assert(getenv("SCUZZ_TESTRT") && getenv("SCUZZ_TESTRT")[0] == '1'); + assert(sz_unbox_i64(sz_scenario_context()) == 7); + assert(closure_sum == 3); + assert(closure_pair_ok); + assert(closure_verify_ran); + { + void *tl = sz_timeline_load(dump); + assert(tl && sz_timeline_len(tl) >= 3); + sz_timeline_free(tl); + } + { + char spec[256]; + snprintf(spec, sizeof spec, "%s,%s", dump, dump); + assert(sz_judge_rel_main(spec) == 0); + } + key = sz_string_from_cstr("Main:1:2:probe"); + sz_fuzz_hit(key); + sz_release(key); + { + char line[64]; + FILE *c = fopen(cov, "r"); + assert(c && fgets(line, sizeof line, c)); + assert(strncmp(line, "Main:1:2:probe", 14) == 0); + fclose(c); + } + /* No drive script: the probe runs `program` and fails with its message. */ + unsetenv("SCUZZ_EV_DRIVE_SCRIPT"); + unsetenv("SCUZZ_DRIVE_SCRIPT"); + { + SzIo *boom = sz_io_fail_cstr("boom"); + r = sz_io_unsafe_run(sz_fuzz_probe(boom)); + assert(!r.ok); + assert(strcmp(sz_string_cstr(r.error->message), "boom") == 0); + sz_release(r.error); + sz_release(boom); + } + r = sz_io_unsafe_run(sz_fuzz_setup(NULL)); + assert(r.ok); + sz_release(ctx); + unsetenv("SCUZZ_EV_TESTRT"); + unsetenv("SCUZZ_EV_TIMELINE_DUMP"); + unsetenv("SCUZZ_EV_COVERAGE_DUMP"); + unsetenv("SCUZZ_TESTRT"); + unsetenv("SCUZZ_TIMELINE_DUMP"); + unsetenv("SCUZZ_COVERAGE_DUMP"); + sz_testrt_oracles_refresh(); + sz_coverage_env_refresh(); + sz_property_session_reset(); + sz_testrt_reset(); + remove(dump); + remove(cov); + remove(script); +} + static void check_retry_after(const char *text, int64_t now, int64_t want) { SzString *value = sz_string_from_cstr(text); int64_t got = sz_net_retry_after_millis(value, now); @@ -5058,6 +5230,23 @@ int main(void) { r = sz_io_unsafe_run(sz_stream_drain(sz_stream_emit(sz_string_from_cstr("d")))); assert(r.ok); + /* A shared compile node reruns: each run builds a fresh pull graph. */ + xs = sz_list_cons(sz_string_from_cstr("a"), + sz_list_cons(sz_string_from_cstr("b"), sz_list_nil())); + { + SzIo *once = sz_stream_compile_to_list( + sz_stream_evalmap(sz_stream_emits(xs), stream_bang, NULL)); + SzIo *twice = sz_io_repeat_n(2, once); + r = sz_io_unsafe_run(twice); + assert(r.ok); + joined = test_list_join((SzList *)r.value, ","); + assert(strcmp(sz_string_cstr(joined), "a!,b!") == 0); + r = sz_io_unsafe_run(once); + assert(r.ok); + joined = test_list_join((SzList *)r.value, ","); + assert(strcmp(sz_string_cstr(joined), "a!,b!") == 0); + } + xs = sz_list_cons( sz_string_from_cstr("a"), sz_list_cons(sz_string_from_cstr("b"), @@ -6143,6 +6332,20 @@ int main(void) { assert(t1 - t0 < 80); } + /* race and timeout resume after the loser's finalizer runs. */ + delay_calls = 0; + r = sz_io_unsafe_run(fm_drop( + race_drop(ensure_drop(sz_io_sleep_ms(300), slow_inc()), sz_io_sleep_ms(1)), + cont_delay_calls, NULL)); + assert(r.ok); + assert((intptr_t)r.value == 1); + delay_calls = 0; + r = sz_io_unsafe_run(handle_drop( + timeout_drop(1, ensure_drop(sz_io_sleep_ms(300), slow_inc())), + handle_delay_calls, NULL)); + assert(r.ok); + assert((intptr_t)r.value == 1); + /* both */ r = sz_io_unsafe_run( both_drop(pure_drop((void *)(intptr_t)1), pure_drop((void *)(intptr_t)2))); @@ -13635,6 +13838,7 @@ int main(void) { } test_driver_growth(); + test_fuzz_probe_closures(); test_file_timeline(); puts("runtime io tests ok"); return 0; diff --git a/docs/gaps.md b/docs/gaps.md index 10fa416c..41981ae6 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -23,6 +23,12 @@ The local iOS loop targets arm64 simulators on iOS 16 or later. Source edits rel **Proof.** `SCUZZ_SKIA=gpu` renders `examples/counter` with unchanged live structural dumps. `scuzz fuzz --differential --iterations 0` on counter is the host proof. Unused GPU stubs do not close the proof. +### 3. Evaluator parity and speed + +**Unproven.** An evaluator written in Scuzz produces the same observable output as the emitted binary on every example. An evaluator `scuzz fuzz` campaign on an app-sized package finishes in less wall-clock time than the compiled campaign, with identical summaries. Interpreted steps are slower; rebuilds and process spawns are gone. The balance is not measured. + +**Proof.** CI diffs `scuzz eval` against `scuzz run` on `examples/hello`, `examples/kernel`, and `examples/io`. `scuzz fuzz` on `examples/counter`, `examples/webhook`, and `examples/api-report` reports the same kill, coverage, and reach results on both engines and completes faster on the evaluator. Arc and slices: [`vision.md`](vision.md#evaluator-arc). + ## Known gaps Next work improves general language usability. Prioritize compiler correctness, memory ownership, type composition, standard kits, and tooling. Examples prove these capabilities. Locks: [`philosophy.md`](philosophy.md). @@ -47,10 +53,10 @@ Required for CLI, server, and desktop applications. Filesystem symbolic links, extended metadata preservation, and power-loss durability remain open. -`Map` / `Set` keys beyond `Int` or `String`. `scuzz eval`. Time parse and zones. Generators. Drive `==` wrap on UI. OS threads. HTTPS serve with app cert and key files. +`Map` / `Set` keys beyond `Int` or `String`. `scuzz eval` UI kits, the live signal readers (`Property.signal*`, `Property.a11yHas`), and `Fuzz.*`: the prefixes in `Eval.excludedKits()` (evaluator arc, [`vision.md`](vision.md#evaluator-arc)). `scuzz fuzz` on the evaluator: step 2 of the fuzz engine slice. Time parse and zones. Generators. Drive `==` wrap on UI. OS threads. HTTPS serve with app cert and key files. ### Later Do not start FFI, plugins, or a package registry. Other later items stay parked. -Generated setup inputs. Multiple named scenarios and campaign selection. Stable scroll keys. Simulation faults. Semantic mutants. Windows desktop. OS IME candidate windows. macOS release packaging in default CI. Developer ID signing and notarization. Full web accessibility. Real phone and screen-reader checks. Hot reload on web. Multiple UI factories in host hot reload. Oracle mining. Emit scalar fallbacks. Dogfood IDE: native file dialogs, menus, multi-window, multi-cursor, minimap, Git UI, debugger, plugin host, custom canvas kit. +Generated setup inputs. Multiple named scenarios and campaign selection. Stable scroll keys. Simulation faults. Semantic mutants. Schedule replay: `schedule_seed` replays a PRNG walk over fiber creation order and contention steps, not recorded decisions. A code change elsewhere in the program can shift the interleaving under the same seed and turn a pinned concurrency failure green. Fix: record the fiber picked at each contention step in the corpus entry and report drift on replay. Do it in the evaluator scheduler first (evaluator arc). Windows desktop. OS IME candidate windows. macOS release packaging in default CI. Developer ID signing and notarization. Full web accessibility. Real phone and screen-reader checks. Hot reload on web. Multiple UI factories in host hot reload. Oracle mining. Emit scalar fallbacks. Dogfood IDE: native file dialogs, menus, multi-window, multi-cursor, minimap, Git UI, debugger, plugin host, custom canvas kit. diff --git a/docs/philosophy.md b/docs/philosophy.md index 5fb567a4..83fb92a8 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -9,10 +9,10 @@ Edit this file when a decision changes. ## Thesis - **Language**: a purposeful Scala-inspired subset for native CLI, server, desktop, and mobile apps. Effects use built-in `IO` (ZIO-inspired, not a ZIO or cats port). `for` is the primary binder. Dense and token-efficient. Functional by default. Keep it practical. See [Language direction](#language-direction). -- **Runtime**: custom native (LLVM). Native binaries, not a VM. No JVM. No Java interop. No classpath or Maven. GUI apps also target WebAssembly. Scuzz Docs is the first browser app. +- **Runtime**: custom native (LLVM). Native binaries, not a VM. No JVM. No Java interop. No classpath or Maven. GUI apps also target WebAssembly. Scuzz Docs is the first browser app. One evaluator runs checked programs for `scuzz fuzz`, `scuzz eval`, and the browser playground. It is not a deploy target ([Evaluator](#evaluator)). - **UI**: a primary product path, not the only one. Flutter-shaped: GUI is first-class; so are CLI and server. One design language plus Skia, as a `Ui` effect with Headless, Desktop, and Mobile interpreters. Headless is a product runtime for agents and CI. It is not a test-only shim. - **Batteries**: the language and standard kits cover common app cases. No ecosystem library sprawl. No Maven, cats, or ZIO ports. -- **Tooling**: one CLI (`scuzz`). One formatter (`scuzz fmt`). One linter (`scuzz check`; no `lint` subcommand). One testing strategy. Mutation, fuzz, properties, simulation, and determinism are first-class. Compiler and CLI are Scuzz (`examples/compiler`, `examples/cli`). Bootstrap uses the newest GitHub `v*` release ([Self-hosting](#self-hosting)). `scuzz ide` launches the dogfood `[ui]` app. It is not the compiler. +- **Tooling**: one CLI (`scuzz`). One formatter (`scuzz fmt`). One linter (`scuzz check`; no `lint` subcommand). One compiler. One evaluator. One testing strategy. Mutation, fuzz, properties, simulation, and determinism are first-class. Compiler and CLI are Scuzz (`examples/compiler`, `examples/cli`). Bootstrap uses the newest GitHub `v*` release ([Self-hosting](#self-hosting)). `scuzz ide` launches the dogfood `[ui]` app. It is not the compiler. - **Language proof**: examples that exercise the surface (`examples/`). The shipped CLI is Scuzz ([Self-hosting](#self-hosting)). - **AI-Friendly**: Headless, hot reload, and debugging tools aid agents. Headless is a peer runtime. `scuzz watch` only rebuilds. `[ui] run --watch` is hot reload: it stamp-reloads Views. Dump and inject ops: run `scuzz docs commands`. @@ -47,6 +47,7 @@ Upstream Scala Native is a reference, not a dependency. Divergence is intentiona - Not an sbt / Gradle / `pubspec` plugin DSL (`scuzz.toml` is data) - Not Flutter platform channels - Not a dual shipped product CLI. The product CLI is Scuzz. Bootstrap uses the newest GitHub `v*` release. The tagged bootstrap `scuzz` is not a second product CLI. +- Not a VM deploy target. The evaluator serves `fuzz`, `eval`, and the browser playground. `scuzz run` and `scuzz package` stay compiled. - Not a second IDE typer. External editors speak `scuzz lsp`. The dogfood IDE consumes `scuzz check` JSON. It does not grow a parallel analyze frontend. ## Decisions @@ -57,7 +58,7 @@ Brand in prose: **Scuzz Lang** (short form **Scuzz**). CLI `scuzz`; compiler pac ### Tooling -One CLI. One typer. One formatter. One linter. One testing strategy. No second analyze frontend. No `*.g.scuzz` codegen. No `src/test` runner. No bolted-on mutation/fuzz/property ecosystems. +One CLI. One typer. One formatter. One linter. One compiler. One evaluator. One testing strategy. No second analyze frontend. No `*.g.scuzz` codegen. No `src/test` runner. No bolted-on mutation/fuzz/property ecosystems. - **Watch** rebuilds when sources or `scuzz.toml` change. `[ui]` `run --watch` is hot reload. See `scuzz docs gui`. IO-only `run --watch` kills and reruns. - **Static hygiene** is `scuzz check` (the linter). An expression that ends before its required body or operand is a parse error. `scuzz fmt` rewrites. No `lint` subcommand. @@ -74,7 +75,20 @@ One CLI. One typer. One formatter. One linter. One testing strategy. No second a The product CLI is Scuzz (`examples/cli`). `scripts/bootstrap.sh` fetches the newest GitHub `v*` release. It builds a temporary compiler from the checkout. That compiler builds the product CLI with the current emission rules. The script removes the temporary compiler. Do not ship two toolchains. Product version lives in `VERSION`. -`examples/syntax` is the lexer and parser. `examples/compiler` is the checker, emit, and compile pipeline. `examples/fmt`, `examples/tyck`, and `examples/codegen` prove printer, checker, and emitter. Toolchain sources only call builtins that the newest `v*` bootstrap already emits. +`examples/syntax` is the lexer and parser. `examples/compiler` is the checker, evaluator, emit, and compile pipeline. `examples/fmt`, `examples/tyck`, and `examples/codegen` prove printer, checker, evaluator, and emitter. Toolchain sources only call builtins that the newest `v*` bootstrap already emits. + +### Evaluator + +`Eval.scuzz` in `examples/compiler` evaluates a checked program. It is Scuzz. It is one module of the one compiler, not a second toolchain. + +- **Scope.** `scuzz eval` runs a package on the host. `scuzz fuzz` searches, mutates, and measures coverage on the evaluator. The browser playground in Docs runs the evaluator compiled to WebAssembly. `scuzz run` and `scuzz package` stay compiled. +- **Same meaning.** A program has one meaning. A difference between evaluator output and emitted output, other than speed, is a compiler bug or an evaluator bug. `scuzz fuzz` replays the corpus on the compiled binary after an evaluator campaign. A difference fails the campaign. +- **One scheduler.** The evaluator maps `IO` to native `IO`. It does not own a scheduler, fibers, fakes, faults, clocks, or timelines. TestRuntime, hermetic simulation, schedule seeds, and `Timeline` are shared with compiled programs. One probe path: the runtime registers setup, drivers, and claims as fn pointers from emitted code or as closures from the evaluator (`Fuzz.*` kits) and runs both the same way. A probe env reaches the evaluator process under the `SCUZZ_EV_` prefix, so the toolchain itself runs live until `Fuzz.probe` starts. +- **One kit table.** `Kits.scuzz` is the one list of builtins. The evaluator dispatches by kit name. A kit without an evaluator case fails the compiler's own verification. Kits are native runtime calls in both engines. +- **Checked input only.** The evaluator runs after `check` passes. Values carry runtime tags. Generics need no instantiation. Traits dispatch on the receiver tag. +- **Erasure matches live builds.** `.require`, `where`, and `Property.sometimes` erase in `eval` and `run`. They stay active under `fuzz`. +- **Tail calls.** A self tail call runs in constant evaluator stack, as emit does. +- **Fail loud.** An unsupported construct or kit stops evaluation with a Scuzz file and line. The evaluator does not guess. ### GC (v0) @@ -86,7 +100,7 @@ No vendored Skia tree. Thin `sk_capi` (measure + draw). **Default UI backend** i ### IO and impurity -One failure channel: `SzError` on `IO[T]`. Typed `E` on `IO` without environment `R`. Do not add `ZIO[R, E, A]`. Blessed kits only. No app-level `IO.delay`. No user FFI, `extern`, or plugins. Determinism and effect capture are not settled. Cooperative single-threaded fibers are the scheduler. Simulation is hermetic. No live sockets under sim. Persistent HTTP servers wait for new requests until cancellation in live and simulation runtimes. One Net API uses shared request checks, timeline events, and simulation dispatch. CLI and server HTTP use the HTTP/1.0 transport with OpenSSL. iOS and macOS GUI HTTP use URLSession and platform certificate trust. GUI clients verify loopback certificates. Requests park fibers and cancel through IO finalizers. GUI transport preserves status responses and does not follow redirects. A response is `(Int, Map[String, String], String)`. Serve binds `0.0.0.0` and `::`. `Net.serveTls` and `Net.serveOnceTls` terminate TLS with a process cert. The CLI and server loopback `https://` client does not verify that cert. Do not expose POSIX sockets. Do not add an app transport API. `Clock.iso8601` formats UTC from epoch milliseconds. No general time parser. No time zone kit. `Str.matches` is POSIX ERE full-string match on UTF-8 bytes. `Str.capture` returns the first match as a list: the full match, then each group. An empty list means no match or a bad pattern. `Str.replaceMatch` replaces the first match with a literal string. It does not expand backreferences. An empty pattern copies the text. `Hash.sha256` returns lowercase hex of the SHA-256 of UTF-8 bytes. Software SHA-256. No OpenSSL. Hash.hmacSha256 computes HMAC-SHA-256. Hash.constantTimeEqual compares equal-length byte strings without an early exit. Length is public. No other digests. `Hex.encode` returns lowercase hex of UTF-8 bytes. `Hex.decode` reverses that encoding. Odd length or a bad digit yields the empty string. `Base64.encode` returns RFC 4648 of UTF-8 bytes. `Base64.decode` reverses that encoding. Bad length, digit, or pad yields the empty string. No URL-safe alphabet. `Uuid.v4` returns an RFC 4122 version-4 UUID as lowercase hex with hyphens. It uses the blessed Random stream. No parse. No other versions. `Bytes.fromStr` copies UTF-8 bytes. `Bytes.len` is the byte count. No Fs or Net Bytes. Kits: run `scuzz docs kits`. A panic must print a Scuzz file and line. +One failure channel: `SzError` on `IO[T]`. Typed `E` on `IO` without environment `R`. Do not add `ZIO[R, E, A]`. Blessed kits only. No app-level `IO.delay`. No user FFI, `extern`, or plugins. Determinism and effect capture are not settled. Cooperative single-threaded fibers are the scheduler. `IO.race`, `IO.timeout`, and `IO.both` cancel the other child and resume after its finalizers run. Simulation is hermetic. No live sockets under sim. Persistent HTTP servers wait for new requests until cancellation in live and simulation runtimes. One Net API uses shared request checks, timeline events, and simulation dispatch. CLI and server HTTP use the HTTP/1.0 transport with OpenSSL. iOS and macOS GUI HTTP use URLSession and platform certificate trust. GUI clients verify loopback certificates. Requests park fibers and cancel through IO finalizers. GUI transport preserves status responses and does not follow redirects. A response is `(Int, Map[String, String], String)`. Serve binds `0.0.0.0` and `::`. `Net.serveTls` and `Net.serveOnceTls` terminate TLS with a process cert. The CLI and server loopback `https://` client does not verify that cert. Do not expose POSIX sockets. Do not add an app transport API. `Clock.iso8601` formats UTC from epoch milliseconds. No general time parser. No time zone kit. `Str.matches` is POSIX ERE full-string match on UTF-8 bytes. `Str.capture` returns the first match as a list: the full match, then each group. An empty list means no match or a bad pattern. `Str.replaceMatch` replaces the first match with a literal string. It does not expand backreferences. An empty pattern copies the text. `Hash.sha256` returns lowercase hex of the SHA-256 of UTF-8 bytes. Software SHA-256. No OpenSSL. Hash.hmacSha256 computes HMAC-SHA-256. Hash.constantTimeEqual compares equal-length byte strings without an early exit. Length is public. No other digests. `Hex.encode` returns lowercase hex of UTF-8 bytes. `Hex.decode` reverses that encoding. Odd length or a bad digit yields the empty string. `Base64.encode` returns RFC 4648 of UTF-8 bytes. `Base64.decode` reverses that encoding. Bad length, digit, or pad yields the empty string. No URL-safe alphabet. `Uuid.v4` returns an RFC 4122 version-4 UUID as lowercase hex with hyphens. It uses the blessed Random stream. No parse. No other versions. `Bytes.fromStr` copies UTF-8 bytes. `Bytes.len` is the byte count. No Fs or Net Bytes. Kits: run `scuzz docs kits`. A panic must print a Scuzz file and line. `Fs.write` replaces a regular file in one operation. The live runtime writes a temporary file in the same directory, checks write and close errors, then renames it over the destination. An error before replacement preserves the destination. New files use mode 0600. Replacement keeps the existing access permission bits. It does not preserve other inode metadata. The destination cannot be a symbolic link or a special file. This is atomic visibility, not a power-loss durability guarantee. Simulation applies the same complete-content replacement. @@ -150,7 +164,7 @@ App correctness is not classical unit tests. Prefer mutation, fuzzing, propertie - **Drivers** live in one `*.scuzz_scenario`. They are impure, parameterized, and oracle-free. `check` rejects `Property.*` and `.require` in scenario files. - **Simulation is hermetic.** Fuzz, mutation, and TestRuntime keep impurity inside fakes. No live sockets. Scheduler ownership, not address, is the determinism boundary. - **Probe limits.** Each probe has a 20-second deadline. Linux probes also have a 512 MiB virtual memory limit. Darwin has no `RLIMIT_AS`. Simulation stops after 1000000 scheduler steps per IO run. A limit failure fails the probe. Process cancellation kills the shell and its process group. -- **One `scuzz fuzz`.** `--iterations N` allocates five eighths of N to search, rounded down. Mutation uses the remaining allocation, up to the number of sites. Initial probes and corpus replay do not use this allocation. Small packages obey the same limit. `--iterations 0` is corpus-only. Mutation is a phase of that command. Search and corpus failures fail the campaign. Summaries count completed search iterations and keep corpus failures separate. --no-fail-fast cannot turn a corpus failure into a passing campaign. Catalog: run `scuzz docs verify`. +- **One `scuzz fuzz`, two engines.** Search, mutation, and coverage run on the evaluator when the evaluator covers the package. Corpus replay runs the compiled binary. Any difference in observable output between engines fails the campaign. A package the evaluator does not cover runs the compiled path for every phase. `--iterations N` allocates five eighths of N to search, rounded down. Mutation uses the remaining allocation, up to the number of sites. Initial probes and corpus replay do not use this allocation. Small packages obey the same limit. `--iterations 0` is corpus-only. Mutation is a phase of that command. Search and corpus failures fail the campaign. Summaries count completed search iterations and keep corpus failures separate. --no-fail-fast cannot turn a corpus failure into a passing campaign. Catalog: run `scuzz docs verify`. ```text src/ diff --git a/docs/plans.md b/docs/plans.md new file mode 100644 index 00000000..1a1adf7a --- /dev/null +++ b/docs/plans.md @@ -0,0 +1,26 @@ +# Plan: evaluator slice 4 (fuzz engine) + +Arc and slice order: [`vision.md`](vision.md#evaluator-arc). Locks: [`philosophy.md`](philosophy.md#evaluator). Delete this file when the slice is done. + +## Goal + +`scuzz fuzz` runs search probes and mutant probes on the evaluator. Corpus replay, `--relate`, and `--replay` stay compiled. A UI package (`[ui]`) keeps compiled probes until the browser slice lands `View`, `Signal`, and `Ui` cases. + +## Constraint + +Toolchain source (`examples/compiler`, `examples/cli`, `examples/shared`) can only call kits that the newest GitHub `v*` release compiles. The slice lands in two steps with a release between them. + +## Step 1: in the tree + +- `Value` has `VTimeline` and `VVerdict`. `Property.*`, `Timeline.*`, and `Verdict.*` map to native kits. `Property.check` applies a lambda or forces an `IO[Bool]` predicate. `Scenario.context` reads `EvProg.ctx`, a `Ref` the probe entry fills in step 2; outside a probe it fails loud. `Eval.excludedKits()` keeps the UI prefixes, the live signal readers, and `Fuzz.`. +- Runtime hooks in `crates/runtime/src/testrt.c`: `sz_fuzz_setup`, `sz_fuzz_driver`, `sz_fuzz_verify`, `sz_fuzz_verify_rel`, `sz_fuzz_hit`, `sz_fuzz_probe`. Closure drivers get the drive line as `List[String]`. Closure claims get a `Timeline` or a `(Timeline, Timeline)` pair. The probe copies `SCUZZ_EV_*` to `SCUZZ_*`, refreshes cached env reads, installs TestRuntime under `SCUZZ_TESTRT=1`, runs setup, runs the drive script or the program, ends the session, and flushes the dumps. Test: `test_fuzz_probe_closures` in `crates/runtime/tests/test_io.c`. +- `Kits.scuzz` rows `Fuzz.setup`, `Fuzz.driver`, `Fuzz.verify`, `Fuzz.verifyRel`, `Fuzz.hit`, `Fuzz.probe`. `Emit` lowers them. Proof: `examples/codegen` prints `probe-ok` under the env `scripts/ci.sh codegen` sets. + +Cut a release before step 2. + +## Step 2: after the release + +1. **Probe entry.** `Eval.probe(files): IO[Unit]` loads the prepared fuzz files (the list `Drive.fuzzCollect` emits: scenario-applied sources, `rewriteReqFiles`, and the `__verify` wrapper). Register `setup` through `Fuzz.setup` and store its value in `EvProg.ctx`. Register every `_drv_*` def through `Fuzz.driver` with a closure that converts tokens by parameter type (`Int`: `Str.toInt(tok, 0)`; `Bool`: `true` or `1`; `String`: the token) and runs the evaluated IO. Register every `Timeline => Verdict` def through `Fuzz.verify` and every two-`Timeline` def through `Fuzz.verifyRel`. Call `Fuzz.hit(Check.covKeyOf(...))` at def entry and at `if` and `match` arms with the keys `Emit.covHitIf` interns. Run `Fuzz.probe(main)`. +2. **CLI.** `scuzz eval --probe DIR` reads `DIR/*.scuzz` as prepared files, runs `check`, then `Eval.probe`. A check failure exits nonzero with the check message (a mutant that does not compile). +3. **Drive.** `fuzzSearchRun`, `fuzzShrinkTry`, and mutant probes spawn `scuzz eval --probe` with the `fuzzProbeEnv` values under the `SCUZZ_EV_` prefix when `!job.hasUi`. Mutants write `Mutate.applyKept` files to `build/fuzz/mutate//ev/` instead of emit and link. A promoted search failure replays compiled before `fuzzDone`; a difference fails the campaign. Corpus replay, `--replay`, `--relate`, live paint, and the split check stay compiled. +4. **Proof.** `scripts/ci-fuzz.sh` runs `examples/webhook` and `examples/api-report` and compares `build/fuzz/summary.json` against a compiled-probe run (`SCUZZ_FUZZ_ENGINE=compiled`) on `fuzz.search`, `fuzz.search_failures`, `mutate.*`, `coverage`, `sometimes`, and `triggers`. Wall clock prints for both. `examples/counter` stays compiled and is the UI control. diff --git a/docs/vision.md b/docs/vision.md index 6ebb498e..b4b150a5 100644 --- a/docs/vision.md +++ b/docs/vision.md @@ -8,6 +8,21 @@ Edit this file when the next-step order changes. Next: make the language usable for general application development. Prioritize compiler correctness, memory ownership, type composition, standard kits, and tooling. Use examples to prove these capabilities through the built-in verification strategy. Specific application workflows do not define the scope. +### Evaluator arc + +Current arc. Locks: [`philosophy.md`](philosophy.md#evaluator). Current slice: **Fuzz engine** (4). Plan: [`plans.md`](plans.md). + +The evaluator runs checked programs without emit or link. It gives `scuzz fuzz` an in-process engine: no rebuild per mutant, no process spawn per probe, cheap state forks for branching, and coverage with comparison operand feedback. It gives the Docs site a static "try it" playground through the existing WebAssembly target. It gives `scuzz eval` on the host. The compiled binary stays the deploy artifact and the corpus replay engine. + +Slices, in order. Each slice closes with a proof in `examples/`. + +1. **Core.** In the tree. `examples/compiler/src/Eval.scuzz` evaluates expressions, `match`, `for`, closures, records, enums, traits, and module calls. `IO.println`, `IO.pure`, `map`, and `flatMap` map to native `IO`. A self tail call runs in constant stack. `scuzz eval PATH` runs an IO-only package. Proof: `examples/codegen` `ev*` oracles call evaluated defs and print `eval-ok`; `scripts/ci.sh hello` diffs `scuzz eval` against `scuzz run` on `examples/hello`. +2. **Kits.** In the tree. Evaluator cases for `Str`, `List`, `Map`, `Set`, `Json`, `Float`, `Builder`, `Hash`, `Hex`, `Base64`, and `IO.both`, `IO.fail`, `handleErrorWith` with typed errors. Record `copy`, implicit `self` defs, `for` guards, and bare `_` callbacks evaluate. `Eval.excludedKits()` lists the namespace prefixes later slices own. Proof: `examples/codegen` `evKitsCovered` probes every non-excluded row in `Kits.scuzz`; `scripts/ci-kernel.sh` diffs `scuzz eval` against `scuzz run` on `examples/kernel`. +3. **Effects.** In the tree. `IO` combinators, `Fs`, `Sys`, `Clock`, `Random`, `Uuid`, `Bytes`, `Ref`, `Queue`, `Deferred`, `Fiber`, `Resource`, `Stream`, and `Net` map to native `IO` at `Value`. Native `IO[A]` failures lift to `VStr`; typed failures stay `Value`. `Property.sometimes` is a no-op outside `scuzz fuzz`. Proof: `scripts/ci-kernel.sh` diffs `scuzz eval` against `scuzz run` on `examples/io` with clock and random lines removed; `evKitsCovered` probes every row outside `Eval.excludedKits()`. +4. **Fuzz engine.** Current. Two steps with a release between them ([`plans.md`](plans.md)). Step 1, in the tree: `Property`, `Scenario`, `Timeline`, and `Verdict` cases at `Value`. The runtime accepts closure setup, drivers, and claims and runs one probe in process (`sz_fuzz_*`). `Fuzz.*` kits lower to those hooks; a `scuzz fuzz` probe env travels under the `SCUZZ_EV_` prefix. Proof: `examples/codegen` `probe-ok` runs a driver, a claim, and a coverage key through `Fuzz.probe`; `crates/runtime/tests/test_io.c` covers the hooks. Step 2, after the release: `scuzz eval --probe DIR` registers the prepared fuzz files through `Fuzz.*`; `scuzz fuzz` search and mutation spawn it for IO packages. Corpus replay runs compiled. Proof: identical summaries and wall clock on `examples/webhook` and `examples/api-report` against the compiled campaign; `examples/counter` stays compiled. +5. **Branching and coverage.** Snapshot and fork at scheduler steps. Expression and branch coverage from the evaluator. Comparison operand distance feeds search. Proof: a `Property.sometimes` that compiled search does not reach in budget and evaluator search does. +6. **Browser.** `View`, `Signal`, and `Ui` cases. The evaluator compiles to WebAssembly inside Docs. A "try it" page evaluates a source field and mounts the result. Proof: the Docs browser proof runs a counter typed into the page. + ### Session control arc `scuzz run` carries the session control channel on every runtime. The channel is file-based: an inject document drives the session and a debug dump reports it. `scuzz exec` sends ops to a live session. `--exec` plays a finite ops program at boot, then exits. The same op vocabulary serves batch and attached modes. Headless, Desktop, and Mobile share the channel. Web needs a second transport and waits for the web hot-reload work. @@ -53,6 +68,8 @@ Ranked list: [`gaps.md`](gaps.md). | “Almost Scala” confusion | Explicit non-goals. Language direction: [`philosophy.md`](philosophy.md). Run `scuzz docs language`. | | Watch confused with hot reload | `scuzz watch` rebuilds. `[ui]` `run --watch` is hot reload (stamp-reload Views). IO-only `run --watch` kills and reruns | | IDE typer ≠ batch typer | One JSON schema. LSP wraps `scuzz check`. No second typer | +| Evaluator ≠ emitted binary | One meaning. `fuzz` replays the corpus compiled after an evaluator campaign and fails on a difference. CI diffs `eval` against `run` on examples. The toolchain, editor, and Docs run under both engines before end users do | +| Evaluator grows a second runtime | `IO` maps to native `IO`. No evaluator scheduler, fakes, or clock. Kits are native calls through one `Kits.scuzz` table | | Dogfood IDE before editor primitives | `scuzz ide` launches the bundled editor (`examples/editor`). Headless stays a peer. Do not add a `scuzz-ide` binary | | Skia weight | pinned CPU prebuilt default. `sk_sw` opt-out | | Desktop-only features | Headless peer rule. One input alphabet for editor keys, caret, selection, clipboard, compose, and inject | diff --git a/examples/api-report/corpus/requests.toml b/examples/api-report/corpus/requests.toml index a6c4fa67..0d09edc0 100644 --- a/examples/api-report/corpus/requests.toml +++ b/examples/api-report/corpus/requests.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 42 events = ["drive pageQueryForms", "drive pageReplacement 2", "drive queryPages", "drive success 0", "drive rejected 1", "drive malformed", "drive badStatus", "drive timedOut", "drive success 2", "drive noToken", "drive insecureUrl", "drive recovers 0", "drive recovers 1", "drive recovers 2", "drive exhausted", "drive retryDeadline", "drive permanentStatus", "drive rateLimited 0", "drive rateLimited 1", "drive rateDeadline", "drive rateExhausted", "drive badRetryAfter", "drive zeroRetry", "drive paginated", "drive paginatedPlain", "drive pageMalformed", "drive pageCycle", "drive pageTimeout", "drive priorReport -17", "drive rejected 0", "drive pageMalformed", "drive pageCycle", "drive pageTimeout", "drive noToken", "drive dateLimited 0", "drive dateLimited 1", "drive dateLimited 2", "drive dateExpired", "drive dateDeadline", "drive linkPages 0", "drive linkPages 1", "drive linkPages 2", "drive priorReport 99", "drive linkCycle", "drive linkOrigin", "drive linkMalformed", "drive linkTimeout", "drive linkAmbiguous", "drive linkLimit", "drive linkHundred"] diff --git a/examples/api-report/corpus/search-42-2.toml b/examples/api-report/corpus/search-42-2.toml index b2cfe92b..220de554 100644 --- a/examples/api-report/corpus/search-42-2.toml +++ b/examples/api-report/corpus/search-42-2.toml @@ -1,8 +1,5 @@ [fuzz] oracle = "recovers" -seed = 42 schedule_seed = "2" -pct_d = 2 -pct_k = 2 fault_seed = "2" events = ["drive recovers -10"] diff --git a/examples/api-report/corpus/timeout.toml b/examples/api-report/corpus/timeout.toml index 1a59b923..3a34b935 100644 --- a/examples/api-report/corpus/timeout.toml +++ b/examples/api-report/corpus/timeout.toml @@ -1,8 +1,5 @@ [fuzz] oracle = "timedOut" -seed = 42 schedule_seed = "2" -pct_d = 2 -pct_k = 2 fault_seed = "2" events = ["drive timedOut"] diff --git a/examples/api-report/corpus/write-failure.toml b/examples/api-report/corpus/write-failure.toml index c5b4945e..4fbd85fc 100644 --- a/examples/api-report/corpus/write-failure.toml +++ b/examples/api-report/corpus/write-failure.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 42 fault_seed = "1" events = ["drive success 0", "drive paginated", "drive rejected 0"] diff --git a/examples/bad-adt/corpus/209ce82661a8103a.toml b/examples/bad-adt/corpus/209ce82661a8103a.toml index c6d2db2f..2e67764f 100644 --- a/examples/bad-adt/corpus/209ce82661a8103a.toml +++ b/examples/bad-adt/corpus/209ce82661a8103a.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 42 schedule_seed = "42" events = ["drive area Rect(0,1)"] diff --git a/examples/bad-example/corpus/85578a525035a645.toml b/examples/bad-example/corpus/85578a525035a645.toml index d5c73a59..af8abaa5 100644 --- a/examples/bad-example/corpus/85578a525035a645.toml +++ b/examples/bad-example/corpus/85578a525035a645.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 42 schedule_seed = "42" events = ["drive bump 0"] diff --git a/examples/bad-fault/corpus/f83245e1fbf633a5.toml b/examples/bad-fault/corpus/f83245e1fbf633a5.toml index 3fba5877..78935d1c 100644 --- a/examples/bad-fault/corpus/f83245e1fbf633a5.toml +++ b/examples/bad-fault/corpus/f83245e1fbf633a5.toml @@ -1,8 +1,4 @@ [fuzz] -seed = 42 schedule_seed = "42" fault_seed = "1" -fault_kind = "fs" -fault_n = 1 -fault_mode = "fail" events = ["drive checkNote a"] diff --git a/examples/bad-response/corpus/a4650ec913f80b04.toml b/examples/bad-response/corpus/a4650ec913f80b04.toml index 03cc5d9a..84148bda 100644 --- a/examples/bad-response/corpus/a4650ec913f80b04.toml +++ b/examples/bad-response/corpus/a4650ec913f80b04.toml @@ -1,6 +1,3 @@ [fuzz] -seed = 42 schedule_seed = "40" -pct_d = 3 -pct_k = 0 events = ["tap 0"] diff --git a/examples/bad-sched/corpus/d037d00bc981a2fb.toml b/examples/bad-sched/corpus/d037d00bc981a2fb.toml index bb664078..7de5fd34 100644 --- a/examples/bad-sched/corpus/d037d00bc981a2fb.toml +++ b/examples/bad-sched/corpus/d037d00bc981a2fb.toml @@ -1,6 +1,3 @@ [fuzz] -seed = 42 schedule_seed = "1344" -pct_d = 2 -pct_k = 0 events = ["drive checkOrder"] diff --git a/examples/cli/src/Cli.scuzz b/examples/cli/src/Cli.scuzz index 0966f90f..679fe9e9 100644 --- a/examples/cli/src/Cli.scuzz +++ b/examples/cli/src/Cli.scuzz @@ -4,6 +4,7 @@ enum Cmd: case Fail(msg: String) case Fmt(path: String, check: Bool) case Check(path: String, json: Bool) + case Eval(path: String) case Build(path: String, outDir: String, full: Bool, verify: Bool) case Run(path: String, outDir: String, target: String, watch: Bool, exec: String, hasExec: Bool, dump: String, snap: String, device: String) case Devices @@ -35,7 +36,7 @@ def isVer(s: String): Bool = s == "-V" || s == "--version" def isCmd(s: String): Bool = - s == "devices" || s == "build" || s == "run" || s == "watch" || s == "check" || s == "lsp" || s == "fmt" || s == "fuzz" || s == "new" || s == "ide" || s == "exec" || s == "package" || s == "docs" || s == "help" + s == "devices" || s == "build" || s == "run" || s == "watch" || s == "check" || s == "eval" || s == "lsp" || s == "fmt" || s == "fuzz" || s == "new" || s == "ide" || s == "exec" || s == "package" || s == "docs" || s == "help" def unrec(name: String): String = Str.concat("error: unrecognized subcommand '", Str.concat(name, """' @@ -197,7 +198,7 @@ def takeVal3(args: List[String], j: Int): (String, Int) = if (j >= List.len(args)) ("", 0 - 1) else (arg(args, j), j + 1) def helpOf(topic: String): String = - if (topic == "" || topic == "help") Help.helpRoot() else if (topic == "fmt") Help.helpFmt() else if (topic == "check") Help.helpCheck() else if (topic == "build") Help.helpBuild() else if (topic == "run") Help.helpRun() else if (topic == "devices") Help.helpDevices() else if (topic == "fuzz") Help.helpFuzz() else if (topic == "new") Help.helpNew() else if (topic == "lsp") Help.helpLsp() else if (topic == "watch") Help.helpWatch() else if (topic == "ide") Help.helpIde() else if (topic == "exec") Help.helpExec() else if (topic == "package") Help.helpPkg() else if (topic == "docs") Help.helpDocs() else unrec(topic) + if (topic == "" || topic == "help") Help.helpRoot() else if (topic == "fmt") Help.helpFmt() else if (topic == "check") Help.helpCheck() else if (topic == "eval") Help.helpEv() else if (topic == "build") Help.helpBuild() else if (topic == "run") Help.helpRun() else if (topic == "devices") Help.helpDevices() else if (topic == "fuzz") Help.helpFuzz() else if (topic == "new") Help.helpNew() else if (topic == "lsp") Help.helpLsp() else if (topic == "watch") Help.helpWatch() else if (topic == "ide") Help.helpIde() else if (topic == "exec") Help.helpExec() else if (topic == "package") Help.helpPkg() else if (topic == "docs") Help.helpDocs() else unrec(topic) def show(c: Cmd): String = c match { @@ -206,6 +207,7 @@ def show(c: Cmd): String = case Cmd.Fail(m) => Str.concat("fail:", m) case Cmd.Fmt(path, check) => Str.concat("fmt path=", Str.concat(path, Str.concat(" check=", yn(check)))) case Cmd.Check(path, json) => Str.concat("check path=", Str.concat(path, Str.concat(" json=", yn(json)))) + case Cmd.Eval(path) => Str.concat("eval path=", path) case Cmd.Build(path, outDir, full, verify) => Str.concat("build path=", Str.concat(path, Str.concat(" out=", Str.concat(outDir, Str.concat(" full=", Str.concat(yn(full), Str.concat(" verify=", yn(verify)))))))) case Cmd.Run(path, outDir, target, watch, exec, hasExec, dump, snap, device) => Str.concat("run path=", Str.concat(path, Str.concat(" out=", Str.concat(outDir, Str.concat(" target=", Str.concat(target, Str.concat(" watch=", Str.concat(yn(watch), Str.concat(" exec=", Str.concat(exec, Str.concat(" hasExec=", Str.concat(yn(hasExec), Str.concat(" dump=", Str.concat(dump, Str.concat(" snap=", Str.concat(snap, Str.concat(" device=", device))))))))))))))))) case Cmd.Devices => "devices" @@ -261,7 +263,7 @@ def parseHelp(args: List[String], i: Int, json: Bool): Cmd = if (i >= List.len(args)) gateJson(Cmd.Help(""), json) else if (isCmd(arg(args, i)) || arg(args, i) == "") gateJson(Cmd.Help(arg(args, i)), json) else gateJson(Cmd.Fail(unrec(arg(args, i))), json) def parseCmd(args: List[String], cmd: String, i: Int, json: Bool): Cmd = - if (cmd == "devices") parseDevices(args, i, json) else if (cmd == "fmt") parseFmt(args, i, json, "", false) else if (cmd == "check") parseCheck(args, i, json, "") else if (cmd == "build") parseBuild(args, i, json, "", "build", false, false) else if (cmd == "run") parseRun(args, i, json, "", "build", "", false, "", false, "", "", "") else if (cmd == "watch") parseWatch(args, i, json, "", "build") else if (cmd == "lsp") parseLsp(args, i, json, "") else if (cmd == "fuzz") parseFuzz(args, i, json, "", 32, 42, "", false, false, false, false) else if (cmd == "new") parseNew(args, i, json, "", ".", false) else if (cmd == "ide") parseIde(args, i, json, "", "build", "") else if (cmd == "exec") parseExec(args, i, json, "", "build", []) else if (cmd == "docs") parseDocs(args, i, json) else parsePkg(args, i, json, "", "build", "") + if (cmd == "devices") parseDevices(args, i, json) else if (cmd == "fmt") parseFmt(args, i, json, "", false) else if (cmd == "check") parseCheck(args, i, json, "") else if (cmd == "eval") parseEv(args, i, json, "") else if (cmd == "build") parseBuild(args, i, json, "", "build", false, false) else if (cmd == "run") parseRun(args, i, json, "", "build", "", false, "", false, "", "", "") else if (cmd == "watch") parseWatch(args, i, json, "", "build") else if (cmd == "lsp") parseLsp(args, i, json, "") else if (cmd == "fuzz") parseFuzz(args, i, json, "", 32, 42, "", false, false, false, false) else if (cmd == "new") parseNew(args, i, json, "", ".", false) else if (cmd == "ide") parseIde(args, i, json, "", "build", "") else if (cmd == "exec") parseExec(args, i, json, "", "build", []) else if (cmd == "docs") parseDocs(args, i, json) else parsePkg(args, i, json, "", "build", "") def parseDocs(args: List[String], i: Int, json: Bool): Cmd = if (i >= List.len(args)) gateJson(Cmd.Docs("list", ""), json) else parseDocsTok(args, i, json, arg(args, i)) @@ -311,6 +313,17 @@ def parseCheckMsg(p: (String, Int), args: List[String], path: String): Cmd = case (v, n) => if (n < 0) Cmd.Fail(if (n == 0 - 1) needVal("--message-format") else unexp(arg(args, 0), "scuzz check [OPTIONS] [PATH]")) else if (v == "json") parseCheck(args, n, true, path) else if (v == "human") parseCheck(args, n, false, path) else Cmd.Fail(badFmt(v)) } +def parseEv(args: List[String], i: Int, json: Bool, path: String): Cmd = + if (i >= List.len(args)) gateJson(Cmd.Eval(orDot(path)), json) else parseEvTok(args, i, json, path, arg(args, i)) + +def parseEvTok(args: List[String], i: Int, json: Bool, path: String, a: String): Cmd = + if (isHelp(a)) gateJson(Cmd.Help("eval"), json) else if (a == "--message-format" || flagPref(a, "--message-format")) parseEvMsg(takeVal(args, i, "--message-format"), args, path) else if (isFlag(a)) Cmd.Fail(unexp(a, "scuzz eval [OPTIONS] [PATH]")) else if (path != "") Cmd.Fail(extraArg(a, "scuzz eval [OPTIONS] [PATH]")) else parseEv(args, i + 1, json, a) + +def parseEvMsg(p: (String, Int), args: List[String], path: String): Cmd = + p match { + case (v, n) => if (n < 0) Cmd.Fail(if (n == 0 - 1) needVal("--message-format") else unexp(arg(args, 0), "scuzz eval [OPTIONS] [PATH]")) else if (v == "json") parseEv(args, n, true, path) else if (v == "human") parseEv(args, n, false, path) else Cmd.Fail(badFmt(v)) + } + def parseBuild(args: List[String], i: Int, json: Bool, path: String, outDir: String, full: Bool, verify: Bool): Cmd = if (i >= List.len(args)) gateJson(Cmd.Build(orDot(path), outDir, full, verify), json) else parseBuildTok(args, i, json, path, outDir, full, verify, arg(args, i)) @@ -604,6 +617,7 @@ def dispatch(c: Cmd): IO[Unit] = case Cmd.Devices => Ios.devices() case Cmd.Watch(path, outDir) => dispatchWatch(path, outDir) case Cmd.Check(path, json) => Drive.checkDir(path, json) + case Cmd.Eval(path) => Drive.evDir(path) case Cmd.Fmt(path, check) => dispatchFmt(path, check) case Cmd.New(name, path, ui) => dispatchNew(name, path, ui) case Cmd.Package(path, outDir, target) => dispatchPkg(path, outDir, target) diff --git a/examples/cli/src/Help.scuzz b/examples/cli/src/Help.scuzz index ffe77cb1..74e2c52d 100644 --- a/examples/cli/src/Help.scuzz +++ b/examples/cli/src/Help.scuzz @@ -1,5 +1,25 @@ def helpRoot(): String = - "Scuzz Lang CLI\n\nUsage: scuzz [OPTIONS] \n\nCommands:\n build Compile a Scuzz Lang project to a native executable (LLVM)\n run Build and run a Scuzz Lang project\n watch Watch sources and rebuild on change (compile loop, not hot reload)\n check Format-verify src/ + parse + typecheck (the linter; no codegen / link). JSON with --message-format=json\n lsp Language server wrapping `scuzz check` JSON diagnostics (stdin/stdout LSP)\n fmt Format Scuzz Lang sources under src/\n fuzz Search in-source properties, drivers, and [taps] events under TestRuntime; mix coverage-guided search and mutation\n new Create a new Scuzz Lang project\n ide Launch the bundled Scuzz IDE (`[ui]` package in the SDK)\n devices List available iOS simulators\n exec Send inject ops to a live session started by `scuzz run`\n package Package a project for Linux, macOS, Android, iOS, or web\n docs Print the technical manual, or one topic, or a kit row\n help Print this message or the help of the given subcommand(s)\n\nOptions:\n --message-format \n Diagnostic format: human (default) or json (`scuzz check` only) [default: human] [possible values: human, json]\n -h, --help\n Print help\n -V, --version\n Print version\n\nExamples:\n scuzz new myapp --ui\n scuzz check\n scuzz docs verify\n scuzz fuzz --iterations 0\n scuzz run --target headless --exec \"\"\n\nSee: scuzz docs start\n" + "Scuzz Lang CLI\n\nUsage: scuzz [OPTIONS] \n\nCommands:\n build Compile a Scuzz Lang project to a native executable (LLVM)\n run Build and run a Scuzz Lang project\n watch Watch sources and rebuild on change (compile loop, not hot reload)\n check Format-verify src/ + parse + typecheck (the linter; no codegen / link). JSON with --message-format=json\n eval Run an IO-only project on the evaluator (typecheck, then interpret; no codegen / link)\n lsp Language server wrapping `scuzz check` JSON diagnostics (stdin/stdout LSP)\n fmt Format Scuzz Lang sources under src/\n fuzz Search in-source properties, drivers, and [taps] events under TestRuntime; mix coverage-guided search and mutation\n new Create a new Scuzz Lang project\n ide Launch the bundled Scuzz IDE (`[ui]` package in the SDK)\n devices List available iOS simulators\n exec Send inject ops to a live session started by `scuzz run`\n package Package a project for Linux, macOS, Android, iOS, or web\n docs Print the technical manual, or one topic, or a kit row\n help Print this message or the help of the given subcommand(s)\n\nOptions:\n --message-format \n Diagnostic format: human (default) or json (`scuzz check` only) [default: human] [possible values: human, json]\n -h, --help\n Print help\n -V, --version\n Print version\n\nExamples:\n scuzz new myapp --ui\n scuzz check\n scuzz docs verify\n scuzz fuzz --iterations 0\n scuzz run --target headless --exec \"\"\n\nSee: scuzz docs start\n" + +def helpEv(): String = + """Run an IO-only project on the evaluator (typecheck, then interpret; no codegen / link) + +Usage: scuzz eval [OPTIONS] [PATH] + +Arguments: + [PATH] [default: .] + +Options: + --message-format + Diagnostic format: human (default) or json (`scuzz check` only) [default: human] [possible values: human, json] + -h, --help + Print help + +Examples: + scuzz eval examples/hello + +See: scuzz docs commands +""" def helpFmt(): String = """Format Scuzz Lang sources under src/ @@ -106,7 +126,7 @@ Options: --seed Deterministic LCG seed [default: 42] --replay - Replay a repro.toml (events + optional schedule_seed / pct_d / pct_k + optional fault_seed) + Replay a repro.toml (events + optional schedule_seed + optional fault_seed) --oracles Mutate residual Property.check / Property.assert / .require predicates --no-fail-fast diff --git a/examples/cli/src/Main.scuzz b/examples/cli/src/Main.scuzz index a17e3bd1..9c648acc 100644 --- a/examples/cli/src/Main.scuzz +++ b/examples/cli/src/Main.scuzz @@ -316,7 +316,7 @@ def wantOpaqueDrive(): String = "bad.scuzz_verify:1:5: error: drive hold params must be generator-friendly (at most 3; ADT or List only as the sole param)" def srcBumpRepro(): String = - "[fuzz]\nseed = 42\nschedule_seed = \"42\"\nevents = [\"drive bump 0\"]\n" + "[fuzz]\nschedule_seed = \"42\"\nevents = [\"drive bump 0\"]\n" def wantBumpEvents(): String = """drive bump 0 @@ -447,7 +447,7 @@ def mutationSampleOk(): Bool = Str.contains(Verify.jMut(2, 1, 1, 5, 100, false, 1), "\"score\":0.666") && Str.contains(Verify.jMut(2, 1, 1, 5, 100, false, 1), "\"invalid\":1") && !Str.contains(Verify.jMut(0, 0, 1, 2, 100, false, 1), "score") && !Str.contains(Verify.jMut(0, 0, 0, 0, 100, false, 0), "score") def campReproSeeds(): Bool = - Verify.searchN(8) == 5 && Verify.mutateN(8) == 3 && Verify.decodeD(Verify.parseSeed("1344")) == 2 && Verify.decodeK(Verify.parseSeed("1344")) == 0 && Str.contains(Verify.reproText(42, "1344", "", srcQuoteEv()), "pct_d = 2") && Str.contains(Verify.reproText(42, "1344", "", srcQuoteEv()), "pct_k = 0") && Verify.nls(Verify.parseEvents(Verify.reproText(42, "1344", "", srcQuoteEv()))) == wantQuoteEv() + Verify.searchN(8) == 5 && Verify.mutateN(8) == 3 && Verify.decodeD(Verify.parseSeed("1344")) == 2 && Verify.decodeK(Verify.parseSeed("1344")) == 0 && Str.contains(Verify.reproText("1344", "", srcQuoteEv()), "schedule_seed = \"1344\"") && Verify.nls(Verify.parseEvents(Verify.reproText("1344", "", srcQuoteEv()))) == wantQuoteEv() def campaignOk(): Bool = Verify.seeds(srcAddVerify()) == wantAddSeeds() && Verify.drivers(srcAddVerify()) == wantAddDrivers() && Verify.wrapSrc("bump", srcBumpVerify()) == wantWrapBump() && Verify.nls(Verify.parseEvents(srcBumpRepro())) == wantBumpEvents() && Verify.nls(Verify.linesOf(wantBumpEvents())) == wantBumpEvents() && Verify.tomlQuoted(srcBumpRepro(), "schedule_seed") == "42" && campReproOk() && Verify.checkVerify("", "bad-intent/empty.scuzz_verify") == wantEmptyVerify() && Verify.checkVerify(srcAddVerify(), "add.scuzz_verify") == "scuzz check ok" && Verify.checkVerify(srcBadDrive(), "bad.scuzz_verify") == wantBadDrive() && Verify.checkVerify(srcOpaqueDrive(), "bad.scuzz_verify") == wantOpaqueDrive() && Verify.checkVerify("nope", "junk.scuzz_verify") != wantEmptyVerify() && Verify.simLeftoverMsg("src/Shared.scuzz_sim") == "src/Shared.scuzz_sim:1:1: error: leftover *.scuzz_sim; move replacements into *.scuzz_scenario or delete the file" && Verify.drvLeftoverMsg("src/Main.scuzz_drivers") == "src/Main.scuzz_drivers:1:1: error: leftover *.scuzz_drivers; move drivers into *.scuzz_scenario or delete the file" && Verify.manyScenarioMsg() == "scuzz.toml:1:1: error: at most one *.scuzz_scenario file is allowed" && Verify.checkScenario("", "bad.scuzz_scenario", []) == "bad.scuzz_scenario:1:1: error: scenario file needs a setup entrypoint" @@ -585,7 +585,7 @@ def panicOk(): Bool = Str.contains(Drive.compileSrc(srcPanic()).ir, "sz_panic_push_src") && Str.contains(Drive.compileSrc(srcPanic()).ir, "Main.scuzz:") def allOk(): Bool = - cliMoreOk() && panicOk() && lspOk() && cliDiff(Cli.render(Cli.parse(a1("--help"))), Help.helpRoot()) && cliDiff(Cli.render(Cli.parse(a2("fmt", "--help"))), Help.helpFmt()) && cliDiff(Cli.render(Cli.parse(a2("check", "--help"))), Help.helpCheck()) && cliDiff(Cli.render(Cli.parse(a2("build", "--help"))), Help.helpBuild()) && cliDiff(Cli.render(Cli.parse(a2("run", "--help"))), Help.helpRun()) && cliDiff(Cli.render(Cli.parse(a2("fuzz", "--help"))), Help.helpFuzz()) && cliDiff(Cli.render(Cli.parse(a2("new", "--help"))), Help.helpNew()) && cliDiff(Cli.render(Cli.parse(a2("lsp", "--help"))), Help.helpLsp()) && cliDiff(Cli.render(Cli.parse(a1("-V"))), Version.line()) && cliDiff(Cli.show(Cli.parse(a1("fmt"))), "fmt path=. check=false") && cliDiff(Cli.show(Cli.parse(a2("fmt", "--check"))), "fmt path=. check=true") && cliDiff(Cli.show(Cli.parse(a3("fmt", "--check", "examples/hello"))), "fmt path=examples/hello check=true") && cliDiff(Cli.show(Cli.parse(a2("check", "examples/kernel"))), "check path=examples/kernel json=false") && cliDiff(Cli.show(Cli.parse(a2("check", "--message-format=json"))), "check path=. json=true") && cliDiff(Cli.show(Cli.parse(a2("--message-format=json", "check"))), "check path=. json=true") && cliDiff(Cli.show(Cli.parse(a3("build", "--full", "examples/hello"))), "build path=examples/hello out=build full=true verify=false") && cliDiff(Cli.show(Cli.parse(a4("run", "--target", "headless", "examples/studio"))), "run path=examples/studio out=build target=headless watch=false exec= hasExec=false dump= snap= device=") && cliDiff(Cli.show(Cli.parse(a3("fuzz", "--iterations", "16"))), "fuzz path=. iterations=16 seed=42 replay= oracles=false noFailFast=false relate=false differential=false") && cliDiff(Cli.show(Cli.parse(a4("fuzz", "--iterations", "0", "examples/counter"))), "fuzz path=examples/counter iterations=0 seed=42 replay= oracles=false noFailFast=false relate=false differential=false") && cliDiff(Cli.show(Cli.parse(a2("build", "--verify"))), "build path=. out=build full=false verify=true") && cliDiff(Cli.show(Cli.parse(a3("new", "myapp", "--ui"))), "new name=myapp path=. ui=true") && cliDiff(Cli.show(Cli.parse(a2("package", "--target=ios"))), "package path=. out=build target=ios") && cliDiff(Cli.show(Cli.parse(a1("package"))), "package path=. out=build target=") && cliDiff(Cli.render(Cli.parse(a3("package", "--target", "foo"))), Drive.badTarget("foo")) && cliDiff(Cli.show(Cli.parse(a3("ide", "--target", "headless"))), "ide path=. out=build target=headless") && cliDiff(Cli.show(Cli.parse(a4("exec", "examples/counter", "tap", "id:button:+1"))), "exec path=examples/counter out=build ops=tap;id:button:+1") && cliDiff(Cli.show(Cli.parse(a2("exec", "quit"))), "exec path=. out=build ops=quit") && cliDiff(Cli.show(Cli.parse(a1("devices"))), "devices") && cliDiff(Cli.render(Cli.parse(a2("run", "--device=sim"))), "error: --device needs --target ios. List simulators with scuzz devices.") && cliDiff(Cli.render(Cli.parse(a1("nope"))), wantUnrec()) && cliDiff(Cli.render(Cli.parse(a1("test"))), Cli.unrec("test")) && cliDiff(Cli.render(Cli.parse(a2("fmt", "--nope"))), wantUnexp()) && cliDiff(Cli.render(Cli.parse(a2("--message-format=json", "fmt"))), wantJsonOnly()) && cliDiff(Cli.render(Cli.parse(a1("new"))), wantMissName()) && cliDiff(Cli.render(Cli.parse(a2("fuzz", "--iterations"))), wantNeedIter()) && cliDiff(Cli.render(Cli.parse(noArgs())), Help.helpRoot()) && cliDiff(Cli.fmtSrc(srcHi()), wantHi()) && cliIdem(a1("fmt")) && cliIdem(a2("fuzz", "--oracles")) && driveOk() && verifyOk() + cliMoreOk() && panicOk() && lspOk() && cliDiff(Cli.render(Cli.parse(a1("--help"))), Help.helpRoot()) && cliDiff(Cli.render(Cli.parse(a2("fmt", "--help"))), Help.helpFmt()) && cliDiff(Cli.render(Cli.parse(a2("check", "--help"))), Help.helpCheck()) && cliDiff(Cli.render(Cli.parse(a2("eval", "--help"))), Help.helpEv()) && cliDiff(Cli.show(Cli.parse(a2("eval", "examples/hello"))), "eval path=examples/hello") && cliDiff(Cli.show(Cli.parse(a1("eval"))), "eval path=.") && cliDiff(Cli.render(Cli.parse(a2("--message-format=json", "eval"))), wantJsonOnly()) && cliDiff(Cli.render(Cli.parse(a2("build", "--help"))), Help.helpBuild()) && cliDiff(Cli.render(Cli.parse(a2("run", "--help"))), Help.helpRun()) && cliDiff(Cli.render(Cli.parse(a2("fuzz", "--help"))), Help.helpFuzz()) && cliDiff(Cli.render(Cli.parse(a2("new", "--help"))), Help.helpNew()) && cliDiff(Cli.render(Cli.parse(a2("lsp", "--help"))), Help.helpLsp()) && cliDiff(Cli.render(Cli.parse(a1("-V"))), Version.line()) && cliDiff(Cli.show(Cli.parse(a1("fmt"))), "fmt path=. check=false") && cliDiff(Cli.show(Cli.parse(a2("fmt", "--check"))), "fmt path=. check=true") && cliDiff(Cli.show(Cli.parse(a3("fmt", "--check", "examples/hello"))), "fmt path=examples/hello check=true") && cliDiff(Cli.show(Cli.parse(a2("check", "examples/kernel"))), "check path=examples/kernel json=false") && cliDiff(Cli.show(Cli.parse(a2("check", "--message-format=json"))), "check path=. json=true") && cliDiff(Cli.show(Cli.parse(a2("--message-format=json", "check"))), "check path=. json=true") && cliDiff(Cli.show(Cli.parse(a3("build", "--full", "examples/hello"))), "build path=examples/hello out=build full=true verify=false") && cliDiff(Cli.show(Cli.parse(a4("run", "--target", "headless", "examples/studio"))), "run path=examples/studio out=build target=headless watch=false exec= hasExec=false dump= snap= device=") && cliDiff(Cli.show(Cli.parse(a3("fuzz", "--iterations", "16"))), "fuzz path=. iterations=16 seed=42 replay= oracles=false noFailFast=false relate=false differential=false") && cliDiff(Cli.show(Cli.parse(a4("fuzz", "--iterations", "0", "examples/counter"))), "fuzz path=examples/counter iterations=0 seed=42 replay= oracles=false noFailFast=false relate=false differential=false") && cliDiff(Cli.show(Cli.parse(a2("build", "--verify"))), "build path=. out=build full=false verify=true") && cliDiff(Cli.show(Cli.parse(a3("new", "myapp", "--ui"))), "new name=myapp path=. ui=true") && cliDiff(Cli.show(Cli.parse(a2("package", "--target=ios"))), "package path=. out=build target=ios") && cliDiff(Cli.show(Cli.parse(a1("package"))), "package path=. out=build target=") && cliDiff(Cli.render(Cli.parse(a3("package", "--target", "foo"))), Drive.badTarget("foo")) && cliDiff(Cli.show(Cli.parse(a3("ide", "--target", "headless"))), "ide path=. out=build target=headless") && cliDiff(Cli.show(Cli.parse(a4("exec", "examples/counter", "tap", "id:button:+1"))), "exec path=examples/counter out=build ops=tap;id:button:+1") && cliDiff(Cli.show(Cli.parse(a2("exec", "quit"))), "exec path=. out=build ops=quit") && cliDiff(Cli.show(Cli.parse(a1("devices"))), "devices") && cliDiff(Cli.render(Cli.parse(a2("run", "--device=sim"))), "error: --device needs --target ios. List simulators with scuzz devices.") && cliDiff(Cli.render(Cli.parse(a1("nope"))), wantUnrec()) && cliDiff(Cli.render(Cli.parse(a1("test"))), Cli.unrec("test")) && cliDiff(Cli.render(Cli.parse(a2("fmt", "--nope"))), wantUnexp()) && cliDiff(Cli.render(Cli.parse(a2("--message-format=json", "fmt"))), wantJsonOnly()) && cliDiff(Cli.render(Cli.parse(a1("new"))), wantMissName()) && cliDiff(Cli.render(Cli.parse(a2("fuzz", "--iterations"))), wantNeedIter()) && cliDiff(Cli.render(Cli.parse(noArgs())), Help.helpRoot()) && cliDiff(Cli.fmtSrc(srcHi()), wantHi()) && cliIdem(a1("fmt")) && cliIdem(a2("fuzz", "--oracles")) && driveOk() && verifyOk() def go(args: List[String]): IO[Unit] = if (List.isEmpty(args)) IO.println(if (allOk()) "cli-ok" else "cli-bad") else Cli.dispatch(Cli.parse(args)) diff --git a/examples/codegen/codegen.scuzz_verify b/examples/codegen/codegen.scuzz_verify index ae750126..960c2603 100644 --- a/examples/codegen/codegen.scuzz_verify +++ b/examples/codegen/codegen.scuzz_verify @@ -49,3 +49,24 @@ def reloadCaptureType(): Bool = def reloadRecordLayout(): Bool = Main.reloadRecordLayout() +def contInCtor(): Bool = + Main.contInCtor() + +def evAdd(): Bool = + Main.evAdd() + +def evTco(): Bool = + Main.evTco() + +def evTcoMatch(): Bool = + Main.evTcoMatch() + +def evGenerated(n: Int where n >= 0): Bool = + Main.evGenerated(n) + +def evMatch(): Bool = + Main.evMatch() + +def evKitsCovered(): Bool = + Main.evKitsCovered() + diff --git a/examples/codegen/src/Main.scuzz b/examples/codegen/src/Main.scuzz index 49de90a3..1995a71f 100644 --- a/examples/codegen/src/Main.scuzz +++ b/examples/codegen/src/Main.scuzz @@ -679,10 +679,10 @@ def wantLam(): String = "@.str0 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:5:3\\00\", align 1\n@.str1 = private unnamed_addr constant [15 x i8] c\"Main.scuzz:1:5\\00\", align 1\n\ndefine internal ptr @sz_neth_Main_plusOne_body(ptr %.closure.value, ptr %.closure.env) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %n = bitcast ptr %.closure.value to ptr\n %nbody_bl_u = call i64 @sz_unbox_i64(ptr %n)\n %nbody_v = add i64 %nbody_bl_u, 1\n %body_nr = call ptr @sz_box_i64(i64 %nbody_v)\n call void @sz_panic_pop_src()\n ret ptr %body_nr\n}\ndefine internal ptr @sz_user_Main_plusOne() {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str1, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %body_cl0 = call ptr @sz_list_nil()\n %body_cl1 = call ptr @sz_list_cons(ptr null, ptr %body_cl0)\n call void @sz_release(ptr %body_cl0)\n %body_cl2 = call ptr @sz_list_cons(ptr @sz_neth_Main_plusOne_body, ptr %body_cl1)\n call void @sz_release(ptr %body_cl1)\n call void @sz_panic_pop_src()\n ret ptr %body_cl2\n}\n\ndefine i32 @main(i32 %argc, ptr %argv) {\n.entry:\n %szlocg0 = getelementptr inbounds [15 x i8], ptr @.str0, i64 0, i64 0\n call void @sz_panic_push_src(ptr %szlocg0)\n %build_e_arg0_fn_v = call ptr @sz_user_Main_plusOne()\n %build_e_arg0_fnp = call ptr @sz_list_head(ptr %build_e_arg0_fn_v)\n %build_e_arg0_fnt = call ptr @sz_list_tail(ptr %build_e_arg0_fn_v)\n %build_e_arg0_envp = call ptr @sz_list_head(ptr %build_e_arg0_fnt)\n %build_e_arg0_ab = call ptr @sz_box_i64(i64 5)\n %build_e_arg0_v = call ptr %build_e_arg0_fnp(ptr %build_e_arg0_ab, ptr %build_e_arg0_envp)\n call void @sz_release(ptr %build_e_arg0_ab)\n call void @sz_release(ptr %build_e_arg0_fn_v)\n %build_e_arg0_u = call i64 @sz_unbox_i64(ptr %build_e_arg0_v)\n call void @sz_release(ptr %build_e_arg0_v)\n %build_e_v = call ptr @sz_string_from_int(i64 %build_e_arg0_u)\n %build_io = call ptr @sz_io_println(ptr %build_e_v)\n call void @sz_release(ptr %build_e_v)\n %rc = call i32 @sz_runtime_main_args(ptr %build_io, i32 %argc, ptr %argv)\n call void @sz_panic_pop_src()\n ret i32 %rc\n}" def allOk(): Bool = - okHi() && okIf() && okCmp() && okList() && okOpt() && okKw() && okPair() && okMod() && okPtr() && okFs() && irDiff(srcLam(), wantLam()) && irIdem(srcLam()) + okHi() && okIf() && okCmp() && okList() && okOpt() && okKw() && okPair() && okMod() && okPtr() && okFs() && irDiff(srcLam(), wantLam()) && irIdem(srcLam()) && contInCtor() && fmParamBorrowed() def dumpAll(): String = - if (!okHi()) "okHi" else if (!okIf()) "okIf" else if (!okCmp()) "okCmp" else if (!okList()) "okList" else if (!okOpt()) dumpOpt() else if (!okKw()) "okKw" else if (!okPair()) "okPair" else if (!okMod()) "okMod" else if (!okPtr()) dumpPtr() else if (!okFs()) "okFs" else if (!irDiff(srcLam(), wantLam()) || !irIdem(srcLam())) "okLam" else "other" + if (!okHi()) "okHi" else if (!okIf()) "okIf" else if (!okCmp()) "okCmp" else if (!okList()) "okList" else if (!okOpt()) dumpOpt() else if (!okKw()) "okKw" else if (!okPair()) "okPair" else if (!okMod()) "okMod" else if (!okPtr()) dumpPtr() else if (!okFs()) "okFs" else if (!irDiff(srcLam(), wantLam()) || !irIdem(srcLam())) "okLam" else if (!contInCtor()) "contInCtor" else if (!fmParamBorrowed()) "fmParamBorrowed" else "other" def dumpPtr(): String = if (!irDiff(srcIfPtr(), wantIfPtr()) || !irIdem(srcIfPtr())) "srcIfPtr" else if (!irDiff(srcTCons(), wantTCons()) || !irIdem(srcTCons())) "srcTCons" else if (!irDiff(srcTupPtr(), wantTupPtr()) || !irIdem(srcTupPtr())) "srcTupPtr" else if (!irDiff(srcT3(), wantT3()) || !irIdem(srcT3())) "srcT3" else if (!irDiff(srcIfTup(), wantIfTup()) || !irIdem(srcIfTup())) "srcIfTup" else if (!irDiff(srcNestTup(), wantNestTup()) || !irIdem(srcNestTup())) "srcNestTup" else "okPtr-other" @@ -699,5 +699,122 @@ def reloadCaptureType(): Bool = def reloadRecordLayout(): Bool = Emit.captureSchema([Param("s", "Signal[State]", "", "")], [En(true, "State", [], [EnCase("State", [Param("value", "Int", "", "")])])]) != Emit.captureSchema([Param("s", "Signal[State]", "", "")], [En(true, "State", [], [EnCase("State", [Param("value", "String", "", "")])])]) +def srcEvMatch(): String = + "enum Shape:\n case Circle(r: Int)\n case Rect(w: Int, h: Int)\n\nrecord Pt(x: Int, y: Int)\n\ndef pick(xs: List[Int], s: Shape, p: Pt, name: String): Int =\n xs match {\n case [] => 0\n case h :: _ if h > 100 => 1\n case whole @ (h :: t) => h + List.len(t) + area(s) + p.x * p.y + tag(name) + List.len(whole)\n }\n\ndef area(s: Shape): Int =\n s match {\n case Shape.Circle(r) => r * r * 3\n case Shape.Rect(w = ww, h = hh) => ww * hh\n }\n\ndef tag(name: String): Int =\n name match {\n case \"a\" | \"b\" => 10\n case \"c\" => 20\n case _ => 30\n }\n\ndef opt(o: Option[Int]): Int =\n o match {\n case Some(n) => n\n case None => 0 - 1\n }\n\ndef pair(t: (Int, String)): String =\n t match {\n case (n, s) => s\"$s=${n + 1}\"\n }\n\ndef lam(): Int =\n List.len(List.filter(List.map([1, 2, 3], x => x * 2), y => y > 2))\n\ndef divs(): List[Int] =\n [7 / 2, (0 - 7) / 2, 7 % 3, (0 - 7) % 3, 7 % (0 - 3)]\n\n@main def main: IO[Unit] =\n IO.println(Str.fromInt(pick([5, 6, 7], Shape.Rect(2, 3), Pt(2, 5), \"b\")))\n" + +def srcContInCtor(): String = + """enum Box: + case B(io: IO[Int]) + +def mk(): Box = + Box.B(IO.pure(0).flatMap(_ => IO.pure(1))) + +def loop(n: Int, acc: Box): Box = + if (n <= 0) acc else loop(n - 1, Box.B(unbox(acc).flatMap(k => IO.pure(k + 1)))) + +def unbox(b: Box): IO[Int] = + b match { + case Box.B(io) => io + } + +@main def main: IO[Unit] = + unbox(loop(3, mk())).flatMap(n => IO.println(Str.fromInt(n))) +""" + +def contInCtor(): Bool = + contDefined(Emit.emit(srcContInCtor()), "Main_mk_0") && contDefined(Emit.emit(srcContInCtor()), "Main_loop_0") && Check.check(srcContInCtor()) == "[]" + +def contDefined(ir: String, key: String): Bool = + Str.contains(ir, Str.concat("define internal ptr @sz_cont_", Str.concat(key, "("))) && Str.contains(ir, Str.concat("ptr @sz_cont_", Str.concat(key, ","))) + +def srcFmParam(): String = + "def after(io: IO[Unit], s: String): IO[Unit] =\n io.flatMap(_ => IO.println(s))\n\n@main def main: IO[Unit] =\n after(IO.println(\"a\"), \"b\")\n" + +def fmParamBorrowed(): Bool = + Str.contains(Emit.emit(srcFmParam()), "call ptr @sz_io_flatmap(ptr %io, ptr @sz_cont_Main_after_0, ptr %body_cap") && !Str.contains(Emit.emit(srcFmParam()), "call void @sz_release(ptr %io)") && Check.check(srcFmParam()) == "[]" + +def evProg(src: String): EvProg = + Eval.load(("Main", src) :: []) + +def evCall(src: String, name: String, args: List[Value]): String = + Eval.show(Eval.callPure(evProg(src), "Main", name, args)) + +def evAdd(): Bool = + evCall(srcAdd(), "add", [Value.VInt(1), Value.VInt(2)]) == "3" + +def evTco(): Bool = + evCall(srcTco(), "countdown", [Value.VInt(100000)]) == "0" + +def evTcoMatch(): Bool = + evCall(srcTcoMatch(), "countdownMatch", [Value.VInt(100000)]) == "0" + +def evGenerated(n: Int where n >= 0): Bool = + evCall(genProg(n), "f", [Value.VInt(1)]) == Str.fromInt(1 + n % 8) + +def evMatch(): Bool = + evCall(srcEvMatch(), "pick", [Value.VList([Value.VInt(5), Value.VInt(6), Value.VInt(7)]), Value.VCon("Shape", "Rect", [Value.VInt(2), Value.VInt(3)]), Value.VCon("Pt", "Pt", [Value.VInt(2), Value.VInt(5)]), Value.VStr("b")]) == "36" && evCall(srcEvMatch(), "pick", [Value.VList([Value.VInt(500)]), Value.VCon("Shape", "Circle", [Value.VInt(1)]), Value.VCon("Pt", "Pt", [Value.VInt(0), Value.VInt(0)]), Value.VStr("z")]) == "1" && evCall(srcEvMatch(), "pick", [Value.VList([]), Value.VCon("Shape", "Circle", [Value.VInt(1)]), Value.VCon("Pt", "Pt", [Value.VInt(0), Value.VInt(0)]), Value.VStr("z")]) == "0" && evCall(srcEvMatch(), "area", [Value.VCon("Shape", "Circle", [Value.VInt(2)])]) == "12" && evCall(srcEvMatch(), "tag", [Value.VStr("c")]) == "20" && evCall(srcEvMatch(), "tag", [Value.VStr("q")]) == "30" && evCall(srcEvMatch(), "opt", [Value.VCon("Option", "Some", [Value.VInt(9)])]) == "9" && evCall(srcEvMatch(), "opt", [Value.VCon("Option", "None", [])]) == "-1" && evCall(srcEvMatch(), "pair", [Value.VTuple([Value.VInt(4), Value.VStr("k")])]) == "\"k=5\"" && evCall(srcEvMatch(), "lam", []) == "2" && evCall(srcEvMatch(), "divs", []) == "[3, -3, 1, -1, 1]" && Check.check(srcEvMatch()) == "[]" + +def evKitsCovered(): Bool = + List.isEmpty(evKitsMissing()) + +def evKitsMissing(): List[String] = + List.filter(List.filter(Kits.names(), k => !Eval.isExcludedKit(k)), k => Eval.isUnsupported(Eval.kitProbe(k, List.map(Kits.params(k), ty => evSample(ty)), evProg(srcAdd())))) + +def evSample(ty: String): Value = + if (Str.contains(ty, "=>")) Value.VFun("Str.len", "") else if (ty == "Int") Value.VInt(1) else if (ty == "String") Value.VStr("a") else if (ty == "Bool") Value.VBool(true) else if (ty == "Float") Value.VFloat(1.5) else if (Str.startsWith(ty, "List")) Value.VList([]) else if (Str.startsWith(ty, "Map")) Value.VMap([]) else if (Str.startsWith(ty, "Set")) Value.VSet([]) else if (ty == "Json") Value.VCon("Json", "Null", []) else if (ty == "Builder") Value.VBuilder(Builder.empty()) else if (Str.startsWith(ty, "(")) Value.VTuple([Value.VInt(1), Value.VInt(2)]) else Value.VInt(1) + +def evAllOk(): Bool = + evAdd() && evTco() && evTcoMatch() && evGenerated(0) && evGenerated(13) && evMatch() && evKitsCovered() + +def evDump(): String = + if (!evAdd()) evCall(srcAdd(), "add", [Value.VInt(1), Value.VInt(2)]) else if (!evTco()) "evTco" else if (!evTcoMatch()) "evTcoMatch" else if (!evGenerated(0) || !evGenerated(13)) "evGenerated" else if (!evMatch()) evCall(srcEvMatch(), "pick", [Value.VList([Value.VInt(5), Value.VInt(6), Value.VInt(7)]), Value.VCon("Shape", "Rect", [Value.VInt(2), Value.VInt(3)]), Value.VCon("Pt", "Pt", [Value.VInt(2), Value.VInt(5)]), Value.VStr("b")]) else if (!evKitsCovered()) List.join(evKitsMissing(), ",") else "ev-other" + +def probeDriver(r: Ref[Int], toks: List[String]): IO[Unit] = + Ref.update(r, n => n + Str.toInt(List.at(toks, 0), 0)) + +def probeClaim(seen: Ref[Int], t: Timeline): Verdict = + probeClaimAt(Property.force(Ref.update(seen, x => x + Timeline.len(t))), t) + +def probeClaimAt(_u: Unit, t: Timeline): Verdict = + if (Timeline.exists(t, i => Timeline.driveHas(t, i, "evAdd 3"))) Verdict.ok() else Verdict.fail(0, "evAdd 3 is not in the timeline") + +def probeRel(a: Timeline, b: Timeline): Verdict = + if (Timeline.len(a) == Timeline.len(b)) Verdict.ok() else Verdict.fail(0, "lengths differ") + +def probeHit(_u: Unit): IO[Unit] = + IO.pure(()) + +def probeRun(r: Ref[Int], seen: Ref[Int], s: Ref[Int]): IO[Unit] = + for { + _ <- Fuzz.setup(Ref.set(s, 1)) + _ <- Fuzz.driver("evAdd", 1, toks => probeDriver(r, toks)) + _ <- Fuzz.verify("evClaim", t => probeClaim(seen, t)) + _ <- Fuzz.verifyRel("evRel", __tup => __tup match { + case (a, b) => probeRel(a, b) +}) + _ <- Fuzz.probe(IO.pure(())) + _ <- probeHit(Fuzz.hit("codegen:probe")) + } yield () + +def probeReport(r: Ref[Int], seen: Ref[Int], s: Ref[Int]): IO[String] = + for { + a <- Ref.get(r) + b <- Ref.get(seen) + c <- Ref.get(s) + } yield if (a == 7 && b >= 2 && c == 1) "probe-ok" else s"probe-bad drivers=${a} claimLen=${b} setup=${c}" + +def probeGo(): IO[Unit] = + for { + r <- Ref.of(0) + seen <- Ref.of(0) + s <- Ref.of(0) + _ <- probeRun(r, seen, s) + msg <- probeReport(r, seen, s) + _ <- IO.println(msg) + } yield () + +def probeMain(): IO[Unit] = + Sys.getenv("SCUZZ_EV_TESTRT").flatMap(ev => if (ev == "") IO.println("probe-skip") else probeGo()) + @main def main: IO[Unit] = - IO.println(if (allOk()) "ir-ok" else dumpAll()) + IO.println(if (allOk()) "ir-ok" else dumpAll()).flatMap(_ => IO.println(if (evAllOk()) "eval-ok" else evDump())).flatMap(_ => probeMain()) diff --git a/examples/compiler/src/Drive.scuzz b/examples/compiler/src/Drive.scuzz index 02b831e3..62479fdf 100644 --- a/examples/compiler/src/Drive.scuzz +++ b/examples/compiler/src/Drive.scuzz @@ -603,6 +603,21 @@ def checkDir(dir: String, json: Bool): IO[Unit] = def checkDirMan(dir: String, json: Bool, m: Man): IO[Unit] = manNeed(m).flatMap(_ => Fs.list(joinSlash(dir, "src")).flatMap(ents => checkDirListed(dir, json, m.deps, ents))) +def evDir(dir: String): IO[Unit] = + Fs.read(joinSlash(dir, "scuzz.toml")).flatMap(toml => evDirMan(dir, Manifest.parse(toml))) + +def evDirMan(dir: String, m: Man): IO[Unit] = + if (m.hasUi) evFail("eval: [ui] packages are not supported yet") else manNeed(m).flatMap(_ => Fs.list(joinSlash(dir, "src")).flatMap(ents => evDirListed(dir, m.deps, ents))) + +def evDirListed(dir: String, deps: List[(String, String)], ents: List[(String, Bool)]): IO[Unit] = + readSrcFiles(joinSlash(dir, "src"), ents).flatMap(own => collectDeps(dir, deps, own).flatMap(files => evFiles(files, Check.humanFiles(files)))) + +def evFiles(files: List[(String, String)], human: String): IO[Unit] = + if (human != "scuzz check ok") evFail(human) else Eval.runMain(Eval.load(files)) + +def evFail(msg: String): IO[Unit] = + IO.println(msg).flatMap(_ => IO.fail("eval")) + def isVerify(name: String): Bool = Str.len(name) > 13 && Str.slice(name, Str.len(name) - 13, Str.len(name)) == ".scuzz_verify" @@ -1288,7 +1303,7 @@ def fuzzBody3(job: FuzzJob, script: String, runs: List[FuzzRun]): IO[Unit] = fuzzBody4(job, script, fuzzAddSeeds(runs, joinSlash(job.outDir, "seeds.txt"), script, job.hasUi)) def fuzzBody4(job: FuzzJob, script: String, runs: List[FuzzRun]): IO[Unit] = - fuzzUniversals(job).flatMap(_ => fuzzReplayGo(job.exe, job.hasUi, job.outDir, job.seed, runs, FuzzAcc(0, 0, 0, ""), job.uiEnv).flatMap(acc => fuzzAfterReplay(job, script, runs, acc))) + fuzzUniversals(job).flatMap(_ => fuzzReplayGo(job.exe, job.hasUi, job.outDir, runs, FuzzAcc(0, 0, 0, ""), job.uiEnv).flatMap(acc => fuzzAfterReplay(job, script, runs, acc))) def fuzzUniversals(job: FuzzJob): IO[Unit] = fuzzLivePaint(job).flatMap(_ => fuzzSplit(job).flatMap(_ => fuzzDiffFlag(job))) @@ -1423,32 +1438,32 @@ def fuzzHasScript(runs: List[FuzzRun], script: String): Bool = def fuzzHasScriptHd(h: FuzzRun, rest: List[FuzzRun], script: String): Bool = h.script == script || fuzzHasScript(rest, script) -def fuzzReplayGo(exe: String, hasUi: Bool, outDir: String, seed: Int, runs: List[FuzzRun], acc: FuzzAcc, uiEnv: String): IO[FuzzAcc] = - if (List.isEmpty(runs) || acc.corpFail > 0) IO.pure(acc) else fuzzReplayOne(exe, hasUi, outDir, seed, List.at(runs, 0), List.tail(runs), acc, uiEnv) +def fuzzReplayGo(exe: String, hasUi: Bool, outDir: String, runs: List[FuzzRun], acc: FuzzAcc, uiEnv: String): IO[FuzzAcc] = + if (List.isEmpty(runs) || acc.corpFail > 0) IO.pure(acc) else fuzzReplayOne(exe, hasUi, outDir, List.at(runs, 0), List.tail(runs), acc, uiEnv) -def fuzzReplayOne(exe: String, hasUi: Bool, outDir: String, seed: Int, h: FuzzRun, rest: List[FuzzRun], acc: FuzzAcc, uiEnv: String): IO[FuzzAcc] = - fuzzWriteDrive(outDir, h.script).flatMap(_ => fuzzProbeRun(exe, hasUi, outDir, h, uiEnv).flatMap(bad => fuzzReplayNext(exe, hasUi, outDir, seed, rest, acc, h, bad, uiEnv))) +def fuzzReplayOne(exe: String, hasUi: Bool, outDir: String, h: FuzzRun, rest: List[FuzzRun], acc: FuzzAcc, uiEnv: String): IO[FuzzAcc] = + fuzzWriteDrive(outDir, h.script).flatMap(_ => fuzzProbeRun(exe, hasUi, outDir, h, uiEnv).flatMap(bad => fuzzReplayNext(exe, hasUi, outDir, rest, acc, h, bad, uiEnv))) -def fuzzReplayNext(exe: String, hasUi: Bool, outDir: String, seed: Int, rest: List[FuzzRun], acc: FuzzAcc, h: FuzzRun, bad: Int, uiEnv: String): IO[FuzzAcc] = - if (bad == 0) fuzzReplayGo(exe, hasUi, outDir, seed, rest, acc, uiEnv) else fuzzNoteFail(outDir, seed, h, acc).flatMap(next => fuzzReplayGo(exe, hasUi, outDir, seed, rest, next, uiEnv)) +def fuzzReplayNext(exe: String, hasUi: Bool, outDir: String, rest: List[FuzzRun], acc: FuzzAcc, h: FuzzRun, bad: Int, uiEnv: String): IO[FuzzAcc] = + if (bad == 0) fuzzReplayGo(exe, hasUi, outDir, rest, acc, uiEnv) else fuzzNoteFail(outDir, h, acc).flatMap(next => fuzzReplayGo(exe, hasUi, outDir, rest, next, uiEnv)) -def fuzzNoteFail(outDir: String, seed: Int, run: FuzzRun, acc: FuzzAcc): IO[FuzzAcc] = - fuzzSaveRepro(outDir, seed, run).flatMap(path => fuzzNoteFail2(outDir, run, acc, path)) +def fuzzNoteFail(outDir: String, run: FuzzRun, acc: FuzzAcc): IO[FuzzAcc] = + fuzzSaveRepro(outDir, run).flatMap(path => fuzzNoteFail2(outDir, run, acc, path)) def fuzzNoteFail2(_outDir: String, run: FuzzRun, acc: FuzzAcc, path: String): IO[FuzzAcc] = IO.println(Str.concat("fuzz failure on corpus ", Str.concat(run.path, Str.concat(" (", Str.concat(Str.fromInt(List.len(Verify.linesOf(run.script))), " events)"))))).flatMap(_ => IO.println(Str.concat("replay: scuzz fuzz --replay ", path)).flatMap(_ => IO.pure(FuzzAcc(acc.search, acc.searchFail, acc.corpFail + 1, path)))) -def fuzzSaveRepro(outDir: String, seed: Int, run: FuzzRun): IO[String] = - Fs.mkdirs(joinSlash(outDir, "fuzz")).flatMap(_ => fuzzSaveRepro2(outDir, seed, run)) +def fuzzSaveRepro(outDir: String, run: FuzzRun): IO[String] = + Fs.mkdirs(joinSlash(outDir, "fuzz")).flatMap(_ => fuzzSaveRepro2(outDir, run)) -def fuzzSaveRepro2(outDir: String, seed: Int, run: FuzzRun): IO[String] = - fuzzSaveRepro3(joinSlash(joinSlash(outDir, "fuzz"), "repro.toml"), seed, run) +def fuzzSaveRepro2(outDir: String, run: FuzzRun): IO[String] = + fuzzSaveRepro3(joinSlash(joinSlash(outDir, "fuzz"), "repro.toml"), run) -def fuzzSaveRepro3(path: String, seed: Int, run: FuzzRun): IO[String] = - Fs.write(path, fuzzReproBody(seed, run)).flatMap(_ => IO.pure(path)) +def fuzzSaveRepro3(path: String, run: FuzzRun): IO[String] = + Fs.write(path, fuzzReproBody(run)).flatMap(_ => IO.pure(path)) -def fuzzReproBody(seed: Int, run: FuzzRun): String = - Verify.withOracle(if (run.src != "") run.src else Verify.reproText(seed, run.sched, run.fault, Verify.linesOf(run.script)), Verify.linesOf(run.script)) +def fuzzReproBody(run: FuzzRun): String = + Verify.withOracle(if (run.src != "") run.src else Verify.reproText(run.sched, run.fault, Verify.linesOf(run.script)), Verify.linesOf(run.script)) def fuzzDrivePath(outDir: String): String = joinSlash(joinSlash(outDir, "fuzz"), "drive.json") @@ -1511,7 +1526,7 @@ def fuzzSearchRun(job: FuzzJob, script: String, runs: List[FuzzRun], defs: List[ fuzzWriteDrive(job.outDir, run.script).flatMap(_ => fuzzProbeRun(job.exe, job.hasUi, job.outDir, run, job.uiEnv).flatMap(bad => fuzzSearchResult(job, script, runs, defs, acc, run, bad))) def fuzzSearchResult(job: FuzzJob, script: String, runs: List[FuzzRun], defs: List[Fun], acc: FuzzAcc, run: FuzzRun, bad: Int): IO[FuzzAcc] = - if (bad > 0 && acc.searchFail == 0) fuzzShrink(job, run, 0, 32).flatMap(small => fuzzSaveRepro(job.outDir, job.seed, small).flatMap(path => fuzzPromoteFailure(job, path, acc.search).flatMap(_ => fuzzSearch(job, script, runs, defs, FuzzAcc(acc.search + 1, acc.searchFail + bad, acc.corpFail, path))))) else fuzzSearch(job, script, runs, defs, FuzzAcc(acc.search + 1, acc.searchFail + bad, acc.corpFail, acc.repro)) + if (bad > 0 && acc.searchFail == 0) fuzzShrink(job, run, 0, 32).flatMap(small => fuzzSaveRepro(job.outDir, small).flatMap(path => fuzzPromoteFailure(job, path, acc.search).flatMap(_ => fuzzSearch(job, script, runs, defs, FuzzAcc(acc.search + 1, acc.searchFail + bad, acc.corpFail, path))))) else fuzzSearch(job, script, runs, defs, FuzzAcc(acc.search + 1, acc.searchFail + bad, acc.corpFail, acc.repro)) def fuzzShrink(job: FuzzJob, run: FuzzRun, at: Int, budget: Int): IO[FuzzRun] = if (budget <= 0 || at >= List.len(Verify.linesOf(run.script)) || List.len(Verify.linesOf(run.script)) <= 1) IO.pure(run) else fuzzShrinkTry(job, run, at, budget, FuzzRun("", "", Verify.nls(List.concat(List.take(Verify.linesOf(run.script), at), List.drop(Verify.linesOf(run.script), at + 1))), run.sched, run.fault)) diff --git a/examples/compiler/src/Emit.scuzz b/examples/compiler/src/Emit.scuzz index 852ceeee..86d160b5 100644 --- a/examples/compiler/src/Emit.scuzz +++ b/examples/compiler/src/Emit.scuzz @@ -338,7 +338,7 @@ def emitExprGo(e: Expr, prefix: String, strs: List[String], defs: Ftab, ens: Lis case Expr.EBin(op, l, r, _) => emitBin(op, l, r, prefix, strs, defs, ens, ps, loc) case Expr.EUn(op, inner, _) => emitUn(op, inner, prefix, strs, defs, ens, ps, loc) case Expr.ENamed(_, inner) => emitExprFid(inner, prefix, strs, defs, ens, ps, loc, fid) - case Expr.EMethod(recv, name, args, _) => if (isFmName(name)) emitFmRt(fmRt(name), recv, args, prefix, strs, defs, ens, ps, loc, fid) else if (name == "apply") emitApplyRecv(recv, args, prefix, strs, defs, ens, ps, loc, fid) else if (name == "map") emitIoMap(recv, args, prefix, strs, defs, ens, ps, loc, fid) else emitCtorCall(recv, name, args, prefix, strs, defs, ens, ps, loc) + case Expr.EMethod(recv, name, args, _) => if (isFmName(name)) emitFmRt(fmRt(name), recv, args, prefix, strs, defs, ens, ps, loc, fid) else if (name == "apply") emitApplyRecv(recv, args, prefix, strs, defs, ens, ps, loc, fid) else if (name == "map") emitIoMap(recv, args, prefix, strs, defs, ens, ps, loc, fid) else emitCtorCall(recv, name, args, prefix, strs, defs, ens, ps, loc, fid) case Expr.ELam(p, _, body) => emitLam(p, body, prefix, strs, defs, ens, ps, loc) case Expr.EIf(c, t, el, off) => emitIfFid(c, t, el, off, prefix, strs, defs, ens, ps, loc, fid) case Expr.EField(recv, name, _) => emitField(recv, name, prefix, strs, defs, ens, ps, loc) @@ -1011,10 +1011,10 @@ def emitCall3(f: String, code: String, vals: List[String], owns: List[Bool], pre if (f == "IO.fail" && !List.isEmpty(args) && isI64Ty(exprRetTyEns(List.at(args, 0), defs, ens, ps, mod))) emitFailBox(code, arg0(vals), prefix) else if (f == "Net.retryAfterMillis") emitKitPtrI64I64("sz_net_retry_after_millis", code, vals, owns, prefix) else if (isJsonExtra(f)) emitJsonExtra(f, code, vals, owns, prefix) else if (isRtExtra(f)) emitRtExtra(f, code, vals, owns, prefix) else if (isTlKit(f)) emitTlKit(f, code, vals, owns, prefix) else emitCall3b(f, code, vals, owns, prefix, defs, args, ens, ps, mod) def isRtExtra(f: String): Bool = - f == "IO.sleep" || f == "IO.fail" || f == "IO.timeout" || f == "IO.when" || f == "IO.unless" || f == "IO.repeatN" || f == "IO.retryN" || f == "Property.sometimes" || f == "Stream.range" || f == "Deferred.fail" + f == "IO.sleep" || f == "IO.fail" || f == "IO.timeout" || f == "IO.when" || f == "IO.unless" || f == "IO.repeatN" || f == "IO.retryN" || f == "Property.sometimes" || f == "Fuzz.hit" || f == "Stream.range" || f == "Deferred.fail" def emitRtExtra(f: String, code: String, vals: List[String], owns: List[Bool], prefix: String): Slot = - if (f == "IO.sleep") emitSleep(code, vals, prefix) else if (f == "IO.fail") emitFail(code, vals, owns, prefix) else if (f == "Deferred.fail") emitDeferredFail(code, vals, owns, prefix) else if (f == "IO.timeout") emitTimeout("sz_io_timeout", code, vals, owns, prefix) else if (f == "IO.when") emitWhen("sz_io_when", code, vals, owns, prefix) else if (f == "IO.unless") emitWhen("sz_io_unless", code, vals, owns, prefix) else if (f == "IO.repeatN") emitTimeout("sz_io_repeat_n", code, vals, owns, prefix) else if (f == "IO.retryN") emitTimeout("sz_io_retry_n", code, vals, owns, prefix) else if (f == "Stream.range") emitRange(code, vals, prefix) else emitSometimes(code, vals, owns, prefix) + if (f == "IO.sleep") emitSleep(code, vals, prefix) else if (f == "IO.fail") emitFail(code, vals, owns, prefix) else if (f == "Deferred.fail") emitDeferredFail(code, vals, owns, prefix) else if (f == "IO.timeout") emitTimeout("sz_io_timeout", code, vals, owns, prefix) else if (f == "IO.when") emitWhen("sz_io_when", code, vals, owns, prefix) else if (f == "IO.unless") emitWhen("sz_io_unless", code, vals, owns, prefix) else if (f == "IO.repeatN") emitTimeout("sz_io_repeat_n", code, vals, owns, prefix) else if (f == "IO.retryN") emitTimeout("sz_io_retry_n", code, vals, owns, prefix) else if (f == "Stream.range") emitRange(code, vals, prefix) else emitVoidStr(if (f == "Fuzz.hit") "sz_fuzz_hit" else "sz_property_sometimes", code, vals, owns) def emitSleep(code: String, vals: List[String], prefix: String): Slot = Slot(join(code, line(Str.concat(tmp(prefix, "io"), Str.concat("call ptr @sz_io_sleep_ms(i64 ", Str.concat(arg0(vals), ")"))))), pct(prefix, "io"), true) @@ -1097,20 +1097,26 @@ def emitVStr1(rt: String, code: String, vals: List[String], owns: List[Bool], pr def emitVStr2(rt: String, code: String, vals: List[String], owns: List[Bool], prefix: String): Slot = Slot(join(code, join(line(Str.concat(tmp(prefix, "tl"), Str.concat("call ptr @", Str.concat(rt, Str.concat("(ptr ", Str.concat(arg0(vals), Str.concat(", ptr ", Str.concat(arg1(vals), Str.concat(", ptr ", Str.concat(arg2(vals), ")")))))))))), join(if (headOwn(tailOwn(owns))) relPtr(arg1(vals)) else "", if (headOwn(tailOwn(tailOwn(owns)))) relPtr(arg2(vals)) else ""))), pct(prefix, "tl"), true) -def emitOnHit(args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = - emitOnHit2(emitExpr(List.at(args, 0), Str.concat(prefix, "_tl"), strs, defs, ens, ps, loc), emitExpr(List.at(args, 1), Str.concat(prefix, "_h"), strs, defs, ens, ps, loc), wrapPh(List.at(args, 2), ps, defs, modOfLoc(loc)), prefix, strs, defs, ens, ps, loc) +def isLam2Kit(f: String): Bool = + f == "Verdict.onHit" || f == "Fuzz.driver" -def emitOnHit2(tl: Slot, hit: Slot, lam: Expr, prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = - emitOnHit3(tl, hit, mapLamBind(lam), mapLamBody(lam), prefix, strs, defs, ens, ps, loc) +def lam2Rt(f: String): String = + if (f == "Verdict.onHit") "sz_verdict_on_hit" else "sz_fuzz_driver" -def emitOnHit3(tl: Slot, hit: Slot, bind: String, body: Expr, prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = - emitOnHit4(tl, hit, emitNethTy("sz_verdict_on_hit", prefix, bind, body, strs, defs, ens, ps, loc, true, ""), emitPackEnv(ps, Str.concat(prefix, "_e")), prefix) +def lam2Ty(f: String): String = + if (f == "Fuzz.driver") "i64" else "ptr" -def emitOnHit4(tl: Slot, hit: Slot, neth: String, env: Slot, prefix: String): Slot = - tl match { - case Slot(tc, tv, to) => hit match { +def emitKitLam2(f: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = + emitKitLam2b(lam2Rt(f), lam2Ty(f), f == "Verdict.onHit", emitExpr(List.at(args, 0), Str.concat(prefix, "_tl"), strs, defs, ens, ps, loc), emitExpr(List.at(args, 1), Str.concat(prefix, "_h"), strs, defs, ens, ps, loc), wrapPh(List.at(args, 2), ps, defs, modOfLoc(loc)), prefix, strs, defs, ens, ps, loc) + +def emitKitLam2b(rt: String, ty2: String, pred: Bool, a: Slot, b: Slot, lam: Expr, prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = + emitKitLam2c(rt, ty2, a, b, emitNethTy(rt, prefix, mapLamBind(lam), mapLamBody(lam), strs, defs, ens, ps, loc, pred, ""), emitPackEnv(ps, Str.concat(prefix, "_e")), prefix) + +def emitKitLam2c(rt: String, ty2: String, a: Slot, b: Slot, neth: String, env: Slot, prefix: String): Slot = + a match { + case Slot(tc, tv, to) => b match { case Slot(hc, hv, ho) => env match { - case Slot(ec, ev, eo) => Slot(join(neth, join(tc, join(hc, join(ec, join(line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @sz_verdict_on_hit(ptr ", Str.concat(tv, Str.concat(", ptr ", Str.concat(hv, Str.concat(", ptr @sz_neth_", Str.concat(prefix, Str.concat(", ptr ", Str.concat(ev, ")")))))))))), join(if (to) relPtr(tv) else "", join(if (ho) relPtr(hv) else "", emitFmRel(ev, eo)))))))), pct(prefix, "v"), true) + case Slot(ec, ev, eo) => Slot(join(neth, join(tc, join(hc, join(ec, join(line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @", Str.concat(rt, Str.concat("(ptr ", Str.concat(tv, Str.concat(", ", Str.concat(ty2, Str.concat(" ", Str.concat(hv, Str.concat(", ptr @sz_neth_", Str.concat(prefix, Str.concat(", ptr ", Str.concat(ev, ")")))))))))))))), join(if (to) relPtr(tv) else "", join(if (ho) relPtr(hv) else "", emitFmRel(ev, eo)))))))), pct(prefix, "v"), true) } } } @@ -1121,8 +1127,8 @@ def emitWhen(rt: String, code: String, vals: List[String], owns: List[Bool], pre def emitRange(code: String, vals: List[String], prefix: String): Slot = Slot(join(code, line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @sz_stream_range(i64 ", Str.concat(arg0(vals), Str.concat(", i64 ", Str.concat(arg1(vals), ")"))))))), pct(prefix, "v"), true) -def emitSometimes(code: String, vals: List[String], owns: List[Bool], _prefix: String): Slot = - Slot(join(code, join(line(Str.concat("call void @sz_property_sometimes(ptr ", Str.concat(arg0(vals), ")"))), if (headOwn(owns)) relPtr(arg0(vals)) else "")), "null", false) +def emitVoidStr(rt: String, code: String, vals: List[String], owns: List[Bool]): Slot = + Slot(join(code, join(line(Str.concat("call void @", Str.concat(rt, Str.concat("(ptr ", Str.concat(arg0(vals), ")"))))), if (headOwn(owns)) relPtr(arg0(vals)) else "")), "null", false) def emitNetServe(f: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = emitNetServe2(netServeRt(f), emitExpr(List.at(args, 0), Str.concat(prefix, "_p"), strs, defs, ens, ps, loc), wrapPh(if (List.isEmpty(List.tail(args))) Expr.EUnit else List.at(List.tail(args), 0), ps, defs, modOfLoc(loc)), prefix, strs, defs, ens, ps, loc) @@ -1153,13 +1159,13 @@ def isPredI64(f: String): Bool = f == "List.exists" || f == "List.forall" || f == "List.indexWhere" || f == "List.lastIndexWhere" || f == "List.count" || f == "List.prefixLength" || f == "Map.exists" || f == "Map.forall" || f == "Set.exists" || f == "Set.forall" || f == "Timeline.exists" def isMapKit(f: String): Bool = - isPredKit(f) || isPredI64(f) || f == "Ref.update" || f == "Ref.updateAndGet" || f == "IO.foreach" || f == "IO.foreachDiscard" || f == "Stream.map" || f == "Stream.evalMap" || f == "Stream.flatMap" || f == "Stream.evalTap" || f == "Stream.mapConcat" || f == "Stream.unfold" || f == "Resource.use" || f == "Resource.make" || f == "List.map" || f == "List.flatMap" || f == "Map.mapValues" || f == "Set.map" || f == "List.groupBy" || f == "List.distinctBy" || f == "List.sortBy" || f == "List.maxBy" || f == "List.minBy" || f == "List.reduceLeft" || f == "List.reduceRight" + isPredKit(f) || isPredI64(f) || f == "Fuzz.verify" || f == "Fuzz.verifyRel" || f == "Ref.update" || f == "Ref.updateAndGet" || f == "IO.foreach" || f == "IO.foreachDiscard" || f == "Stream.map" || f == "Stream.evalMap" || f == "Stream.flatMap" || f == "Stream.evalTap" || f == "Stream.mapConcat" || f == "Stream.unfold" || f == "Resource.use" || f == "Resource.make" || f == "List.map" || f == "List.flatMap" || f == "Map.mapValues" || f == "Set.map" || f == "List.groupBy" || f == "List.distinctBy" || f == "List.sortBy" || f == "List.maxBy" || f == "List.minBy" || f == "List.reduceLeft" || f == "List.reduceRight" def isFoldKit(f: String): Bool = f == "Stream.fold" || f == "Stream.scan" || f == "Stream.zipWith" || f == "List.foldLeft" || f == "List.foldRight" || f == "List.scanLeft" || f == "List.scanRight" def mapKitRt(f: String): String = - if (f == "Verdict.every") "sz_verdict_every" else if (f == "Verdict.any") "sz_verdict_any" else if (f == "Verdict.stepEvery") "sz_verdict_step_every" else if (f == "Timeline.exists") "sz_timeline_exists" else if (f == "Ref.update") "sz_ref_update" else if (f == "Ref.updateAndGet") "sz_ref_update_and_get" else if (f == "IO.foreach") "sz_io_foreach" else if (f == "IO.foreachDiscard") "sz_io_foreach_discard" else if (f == "Stream.map") "sz_stream_map" else if (f == "Stream.filter") "sz_stream_filter" else if (f == "Stream.evalMap") "sz_stream_evalmap" else if (f == "Stream.takeWhile") "sz_stream_takewhile" else if (f == "Stream.dropWhile") "sz_stream_dropwhile" else if (f == "Stream.find") "sz_stream_find" else if (f == "Stream.exists") "sz_stream_exists" else if (f == "Stream.flatMap") "sz_stream_flatmap" else if (f == "Stream.evalTap") "sz_stream_evaltap" else if (f == "Stream.filterNot") "sz_stream_filter_not" else if (f == "Stream.forall") "sz_stream_forall" else if (f == "Stream.none") "sz_stream_none" else if (f == "Stream.findLast") "sz_stream_find_last" else if (f == "Stream.mapConcat") "sz_stream_map_concat" else if (f == "Stream.unfold") "sz_stream_unfold" else if (f == "Resource.make") "sz_lang_resource_make" else if (f == "List.map") "sz_list_map" else if (f == "List.flatMap") "sz_list_flat_map" else if (f == "Map.mapValues") "sz_map_map_values" else if (f == "Set.map") "sz_set_map" else if (f == "List.exists") "sz_list_exists" else if (f == "List.forall") "sz_list_forall" else if (f == "List.indexWhere") "sz_list_index_where" else if (f == "List.lastIndexWhere") "sz_list_last_index_where" else if (f == "List.count") "sz_list_count" else if (f == "List.prefixLength") "sz_list_prefix_length" else if (f == "Map.exists") "sz_map_exists" else if (f == "Map.forall") "sz_map_forall" else if (f == "Set.exists") "sz_set_exists" else if (f == "Set.forall") "sz_set_forall" else if (f == "List.filter") "sz_list_filter" else if (f == "List.filterNot") "sz_list_filter_not" else if (f == "List.takeWhile") "sz_list_takewhile" else if (f == "List.dropWhile") "sz_list_dropwhile" else if (f == "List.partition") "sz_list_partition" else if (f == "Map.filter") "sz_map_filter" else if (f == "Set.filter") "sz_set_filter" else if (f == "List.find") "sz_list_find" else if (f == "List.findLast") "sz_list_find_last" else if (f == "List.span") "sz_list_span" else if (f == "List.groupBy") "sz_list_group_by" else if (f == "List.distinctBy") "sz_list_distinct_by" else if (f == "List.sortBy") "sz_list_sort_by" else if (f == "List.maxBy") "sz_list_max_by" else if (f == "List.minBy") "sz_list_max_by_min" else if (f == "List.reduceLeft") "sz_list_reduce_left" else if (f == "List.reduceRight") "sz_list_reduce_right" else "sz_lang_resource_use" + if (f == "Fuzz.verify") "sz_fuzz_verify" else if (f == "Fuzz.verifyRel") "sz_fuzz_verify_rel" else if (f == "Verdict.every") "sz_verdict_every" else if (f == "Verdict.any") "sz_verdict_any" else if (f == "Verdict.stepEvery") "sz_verdict_step_every" else if (f == "Timeline.exists") "sz_timeline_exists" else if (f == "Ref.update") "sz_ref_update" else if (f == "Ref.updateAndGet") "sz_ref_update_and_get" else if (f == "IO.foreach") "sz_io_foreach" else if (f == "IO.foreachDiscard") "sz_io_foreach_discard" else if (f == "Stream.map") "sz_stream_map" else if (f == "Stream.filter") "sz_stream_filter" else if (f == "Stream.evalMap") "sz_stream_evalmap" else if (f == "Stream.takeWhile") "sz_stream_takewhile" else if (f == "Stream.dropWhile") "sz_stream_dropwhile" else if (f == "Stream.find") "sz_stream_find" else if (f == "Stream.exists") "sz_stream_exists" else if (f == "Stream.flatMap") "sz_stream_flatmap" else if (f == "Stream.evalTap") "sz_stream_evaltap" else if (f == "Stream.filterNot") "sz_stream_filter_not" else if (f == "Stream.forall") "sz_stream_forall" else if (f == "Stream.none") "sz_stream_none" else if (f == "Stream.findLast") "sz_stream_find_last" else if (f == "Stream.mapConcat") "sz_stream_map_concat" else if (f == "Stream.unfold") "sz_stream_unfold" else if (f == "Resource.make") "sz_lang_resource_make" else if (f == "List.map") "sz_list_map" else if (f == "List.flatMap") "sz_list_flat_map" else if (f == "Map.mapValues") "sz_map_map_values" else if (f == "Set.map") "sz_set_map" else if (f == "List.exists") "sz_list_exists" else if (f == "List.forall") "sz_list_forall" else if (f == "List.indexWhere") "sz_list_index_where" else if (f == "List.lastIndexWhere") "sz_list_last_index_where" else if (f == "List.count") "sz_list_count" else if (f == "List.prefixLength") "sz_list_prefix_length" else if (f == "Map.exists") "sz_map_exists" else if (f == "Map.forall") "sz_map_forall" else if (f == "Set.exists") "sz_set_exists" else if (f == "Set.forall") "sz_set_forall" else if (f == "List.filter") "sz_list_filter" else if (f == "List.filterNot") "sz_list_filter_not" else if (f == "List.takeWhile") "sz_list_takewhile" else if (f == "List.dropWhile") "sz_list_dropwhile" else if (f == "List.partition") "sz_list_partition" else if (f == "Map.filter") "sz_map_filter" else if (f == "Set.filter") "sz_set_filter" else if (f == "List.find") "sz_list_find" else if (f == "List.findLast") "sz_list_find_last" else if (f == "List.span") "sz_list_span" else if (f == "List.groupBy") "sz_list_group_by" else if (f == "List.distinctBy") "sz_list_distinct_by" else if (f == "List.sortBy") "sz_list_sort_by" else if (f == "List.maxBy") "sz_list_max_by" else if (f == "List.minBy") "sz_list_max_by_min" else if (f == "List.reduceLeft") "sz_list_reduce_left" else if (f == "List.reduceRight") "sz_list_reduce_right" else "sz_lang_resource_use" def foldKitRt(f: String): String = if (f == "Stream.fold") "sz_stream_fold" else if (f == "Stream.scan") "sz_stream_scan" else if (f == "List.foldLeft") "sz_list_fold_left" else if (f == "List.foldRight") "sz_list_fold_right" else if (f == "List.scanLeft") "sz_list_scan_left" else if (f == "List.scanRight") "sz_list_scan_right" else "sz_stream_zip_with" @@ -1254,7 +1260,7 @@ def emitNethTy(rt: String, prefix: String, bind: String, body: Expr, strs: List[ emitNethGoTy(prefix, bind, body, dropParam(ps, bind), strs, defs, ens, loc, pred, mapNethBindTy(rt, bind, body, dropParam(ps, bind), defs, elemTy)) def mapNethBindTy(rt: String, bind: String, body: Expr, rest: List[Param], defs: Ftab, elemTy: String): String = - if (nethIdxPred(rt)) "Int" else if (rt == "sz_verdict_step_every") "(Int, Int)" else if (bind == "__tup" || bind == "__p" || bind == "_ph") nethBindTy(bind, body, rest, defs) else if (elemTy != "") elemTy else nethBindTy(bind, body, rest, defs) + if (nethIdxPred(rt)) "Int" else if (rt == "sz_verdict_step_every" || rt == "sz_verdict_on_hit") "(Int, Int)" else if (rt == "sz_fuzz_driver") "List[String]" else if (rt == "sz_fuzz_verify") "Timeline" else if (rt == "sz_fuzz_verify_rel") "(Timeline, Timeline)" else if (bind == "__tup" || bind == "__p" || bind == "_ph") nethBindTy(bind, body, rest, defs) else if (elemTy != "") elemTy else nethBindTy(bind, body, rest, defs) def nethIdxPred(rt: String): Bool = rt == "sz_verdict_every" || rt == "sz_verdict_any" || rt == "sz_timeline_exists" @@ -2225,7 +2231,7 @@ def emitFloatFromInt(code: String, vals: List[String], prefix: String): Slot = Slot(join(code, join(line(Str.concat(tmp(prefix, "ff"), Str.concat("sitofp i64 ", Str.concat(arg0(vals), " to double")))), line(Str.concat(tmp(prefix, "v"), Str.concat("bitcast double ", Str.concat(pct(prefix, "ff"), " to i64")))))), pct(prefix, "v"), false) def emitCall(f: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, fid: Int): Slot = - if (f == "Verdict.onHit") emitOnHit(args, prefix, strs, defs, ens, ps, loc) else if (!List.isEmpty(args) && isApplyName(f, ps, defs)) emitApply(f, args, prefix, strs, defs, ens, ps, loc) else emitCallKit(f, args, prefix, strs, defs, ens, ps, loc, fid) + if (isLam2Kit(f)) emitKitLam2(f, args, prefix, strs, defs, ens, ps, loc) else if (!List.isEmpty(args) && isApplyName(f, ps, defs)) emitApply(f, args, prefix, strs, defs, ens, ps, loc) else emitCallKit(f, args, prefix, strs, defs, ens, ps, loc, fid) def isApplyName(f: String, ps: List[Param], defs: Ftab): Bool = isApplyTy(paramTy(ps, f), f, defs) @@ -2234,7 +2240,7 @@ def isApplyTy(ty: String, f: String, defs: Ftab): Bool = if (isFunTy(ty)) true else if (ty == "" || isI64Ty(ty)) false else List.isEmpty(findDef(defs, f)) def emitCallKit(f: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, fid: Int): Slot = - if (Str.startsWith(f, "Signal.")) emitGenericSignal(f, args, prefix, strs, defs, ens, ps, loc) else if (f == "IO.pure") emitIoPureCall(args, prefix, strs, defs, ens, ps, loc) else if (f == "Net.serveOnce" || f == "Net.serve" || f == "Net.serveOnceTls" || f == "Net.serveTls") emitNetServe(f, args, prefix, strs, defs, ens, ps, loc) else if (f == "Ui.run") emitUiRunCall(args, prefix, strs, defs, ens, ps, loc) else if (f == "Verdict.onHit") emitOnHit(args, prefix, strs, defs, ens, ps, loc) else if (tapViewRt(f) != "") emitTapViewCall(tapViewRt(f), args, prefix, strs, defs, ens, ps, loc) else if (f == "View.each") emitViewEachCall(args, prefix, strs, defs, ens, ps, loc) else if (f == "View.inkWell") emitInkWellCall(args, prefix, strs, defs, ens, ps, loc) else if (isMapKit(f)) emitMapKit(f, args, prefix, strs, defs, ens, ps, loc) else if (isFoldKit(f)) emitFoldKit(f, args, prefix, strs, defs, ens, ps, loc) else if (f == "Stream.iterate") emitIterKit(args, prefix, strs, defs, ens, ps, loc) else if (f == "List.tabulate") emitTabulate(args, prefix, strs, defs, ens, ps, loc) else if (f == "List.segmentLength") emitSegLen(args, prefix, strs, defs, ens, ps, loc) else emitCallKit2(f, args, prefix, strs, defs, ens, ps, loc, fid) + if (Str.startsWith(f, "Signal.")) emitGenericSignal(f, args, prefix, strs, defs, ens, ps, loc) else if (f == "IO.pure") emitIoPureCall(args, prefix, strs, defs, ens, ps, loc) else if (f == "Net.serveOnce" || f == "Net.serve" || f == "Net.serveOnceTls" || f == "Net.serveTls") emitNetServe(f, args, prefix, strs, defs, ens, ps, loc) else if (f == "Ui.run") emitUiRunCall(args, prefix, strs, defs, ens, ps, loc) else if (isLam2Kit(f)) emitKitLam2(f, args, prefix, strs, defs, ens, ps, loc) else if (tapViewRt(f) != "") emitTapViewCall(tapViewRt(f), args, prefix, strs, defs, ens, ps, loc) else if (f == "View.each") emitViewEachCall(args, prefix, strs, defs, ens, ps, loc) else if (f == "View.inkWell") emitInkWellCall(args, prefix, strs, defs, ens, ps, loc) else if (isMapKit(f)) emitMapKit(f, args, prefix, strs, defs, ens, ps, loc) else if (isFoldKit(f)) emitFoldKit(f, args, prefix, strs, defs, ens, ps, loc) else if (f == "Stream.iterate") emitIterKit(args, prefix, strs, defs, ens, ps, loc) else if (f == "List.tabulate") emitTabulate(args, prefix, strs, defs, ens, ps, loc) else if (f == "List.segmentLength") emitSegLen(args, prefix, strs, defs, ens, ps, loc) else emitCallKit2(f, args, prefix, strs, defs, ens, ps, loc, fid) def emitCallKit2(f: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, fid: Int): Slot = if (f == "Property.force") emitPropForce(List.at(args, 0), prefix, strs, defs, ens, ps, loc) else if (f == "Property.check") emitPropCheckCall(args, prefix, strs, defs, ens, ps, loc) else emitCall2(f, emitArgList(alignArgs(f, args, defs, modOfLoc(loc)), prefix, strs, defs, ens, ps, loc, 0, "", noStr(), noBool(), fid), prefix, defs, alignArgs(f, args, defs, modOfLoc(loc)), ens, ps, modOfLoc(loc)) @@ -2672,7 +2678,7 @@ def kitUnaryPtrA(f: String): String = if (f == "List.init") "sz_list_init" else if (f == "List.last") "sz_list_last" else if (f == "List.sort") "sz_list_sort" else if (f == "List.flatten") "sz_list_flatten" else if (f == "List.distinct") "sz_list_distinct" else if (f == "List.indices") "sz_list_indices" else if (f == "List.inits") "sz_list_inits" else if (f == "List.tails") "sz_list_tails" else if (f == "List.transpose") "sz_list_transpose" else if (f == "List.zipWithIndex") "sz_list_zip_with_index" else if (f == "List.toMap") "sz_list_to_map" else if (f == "List.unzip") "sz_list_unzip" else if (f == "Map.keys" || f == "Set.toList") "sz_map_keys" else if (f == "Map.values") "sz_map_values" else if (f == "Map.toList") "sz_map_to_list" else if (f == "Json.keys") "sz_json_keys" else if (f == "Json.parse") "sz_json_parse" else if (f == "Json.stringify") "sz_json_stringify" else if (f == "Json.arr") "sz_json_arr" else if (f == "Json.pairs") "sz_json_pairs" else if (f == "Json.asInt") "sz_json_as_int" else if (f == "Json.asBool") "sz_json_as_bool" else if (f == "Json.asStr") "sz_json_as_str" else if (f == "Json.asFloat") "sz_json_as_float" else if (f == "Str.lines") "sz_string_lines" else if (f == "Str.trim") "sz_string_trim" else if (f == "Str.toLower") "sz_string_to_lower" else if (f == "Str.toUpper") "sz_string_to_upper" else if (f == "Str.capitalize") "sz_string_capitalize" else if (f == "Str.reverse") "sz_string_ureverse" else "" def kitUnaryPtrB(f: String): String = - if (f == "IO.attempt") "sz_io_attempt_as_result" else if (f == "Fs.basename") "sz_fs_basename" else if (f == "Fs.dirname") "sz_fs_dirname" else if (f == "Ref.of") "sz_ref_of" else if (f == "Ref.get") "sz_ref_get" else if (f == "Queue.take") "sz_queue_take" else if (f == "Deferred.get") "sz_deferred_get" else if (f == "Fiber.fork") "sz_fiber_fork" else if (f == "Fiber.join") "sz_fiber_join" else if (f == "Fiber.interrupt") "sz_fiber_interrupt" else if (f == "Hash.sha256") "sz_hash_sha256" else if (f == "Hex.encode") "sz_hex_encode" else if (f == "Hex.decode") "sz_hex_decode" else if (f == "Base64.encode") "sz_base64_encode" else if (f == "Base64.decode") "sz_base64_decode" else if (f == "Bytes.fromStr") "sz_bytes_from_str" else if (f == "Stream.emit") "sz_stream_emit" else if (f == "Stream.emits") "sz_stream_emits" else if (f == "Stream.eval") "sz_stream_eval" else if (f == "Stream.compileToList") "sz_stream_compile_to_list" else if (f == "Stream.drain") "sz_stream_drain" else if (f == "Stream.head") "sz_stream_head" else if (f == "Stream.last") "sz_stream_last" else if (f == "Stream.count") "sz_stream_count" else if (f == "Stream.zipWithIndex") "sz_stream_zip_with_index" else if (f == "Stream.flatten") "sz_stream_flatten" else if (f == "Stream.changes") "sz_stream_changes" else if (f == "IO.forever") "sz_io_forever" else "" + if (f == "IO.attempt") "sz_io_attempt_as_result" else if (f == "Fs.basename") "sz_fs_basename" else if (f == "Fs.dirname") "sz_fs_dirname" else if (f == "Ref.of") "sz_ref_of" else if (f == "Ref.get") "sz_ref_get" else if (f == "Queue.take") "sz_queue_take" else if (f == "Deferred.get") "sz_deferred_get" else if (f == "Fiber.fork") "sz_fiber_fork" else if (f == "Fiber.join") "sz_fiber_join" else if (f == "Fiber.interrupt") "sz_fiber_interrupt" else if (f == "Hash.sha256") "sz_hash_sha256" else if (f == "Hex.encode") "sz_hex_encode" else if (f == "Hex.decode") "sz_hex_decode" else if (f == "Base64.encode") "sz_base64_encode" else if (f == "Base64.decode") "sz_base64_decode" else if (f == "Bytes.fromStr") "sz_bytes_from_str" else if (f == "Stream.emit") "sz_stream_emit" else if (f == "Stream.emits") "sz_stream_emits" else if (f == "Stream.eval") "sz_stream_eval" else if (f == "Stream.compileToList") "sz_stream_compile_to_list" else if (f == "Stream.drain") "sz_stream_drain" else if (f == "Stream.head") "sz_stream_head" else if (f == "Stream.last") "sz_stream_last" else if (f == "Stream.count") "sz_stream_count" else if (f == "Stream.zipWithIndex") "sz_stream_zip_with_index" else if (f == "Stream.flatten") "sz_stream_flatten" else if (f == "Stream.changes") "sz_stream_changes" else if (f == "IO.forever") "sz_io_forever" else if (f == "Fuzz.setup") "sz_fuzz_setup" else if (f == "Fuzz.probe") "sz_fuzz_probe" else "" def netHttpUnaryRt(f: String): String = if (f == "Net.httpDelete") "sz_net_http_delete" else if (f == "Net.httpHead") "sz_net_http_head" else "sz_net_http_get" @@ -2690,7 +2696,7 @@ def kitBinaryPtrB(f: String): String = if (f == "IO.both") "sz_io_both" else if (f == "IO.race") "sz_io_race" else if (f == "IO.ensure") "sz_io_ensure" else if (f == "Ref.set") "sz_ref_set" else if (f == "Queue.offer") "sz_queue_offer" else if (f == "Deferred.complete") "sz_deferred_complete" else if (f == "Deferred.fail") "sz_deferred_fail" else if (f == "Stream.concat") "sz_stream_concat" else if (f == "Stream.zip") "sz_stream_zip" else if (f == "Stream.interleave") "sz_stream_interleave" else if (f == "Stream.orElse") "sz_stream_or_else" else if (f == "Stream.intersperse") "sz_stream_intersperse" else if (f == "List.interleave") "sz_list_interleave" else if (f == "List.append") "sz_list_append" else if (f == "List.zip") "sz_list_zip" else if (f == "List.intersperse") "sz_list_intersperse" else if (f == "List.diff") "sz_list_diff" else if (f == "List.intersect") "sz_list_intersect" else if (f == "Str.split") "sz_string_split" else if (f == "Str.capture") "sz_string_capture" else if (f == "Str.stripPrefix") "sz_string_strip_prefix" else if (f == "Str.stripSuffix") "sz_string_strip_suffix" else if (f == "Fs.join") "sz_fs_join" else "" def kitBinaryI64(f: String): String = - if (f == "Hash.constantTimeEqual") "sz_hash_constant_time_equal" else if (f == "Map.contains" || f == "Set.contains") "sz_map_contains" else if (f == "Json.has") "sz_json_has" else if (f == "Str.contains") "sz_string_contains" else if (f == "Str.matches") "sz_string_matches" else if (f == "Str.endsWith") "sz_string_ends_with" else if (f == "Str.indexOf") "sz_string_uindex_of" else if (f == "Str.lastIndexOf") "sz_string_ulast_index_of" else if (f == "Set.isSubset") "sz_set_is_subset" else if (f == "Set.isDisjoint") "sz_set_is_disjoint" else if (f == "List.contains") "sz_list_contains" else if (f == "List.indexOf") "sz_list_index_of" else if (f == "List.lastIndexOf") "sz_list_last_index_of" else if (f == "List.indexOfSlice") "sz_list_index_of_slice" else if (f == "List.lastIndexOfSlice") "sz_list_last_index_of_slice" else "" + if (f == "Hash.constantTimeEqual") "sz_hash_constant_time_equal" else if (f == "Map.contains" || f == "Set.contains") "sz_map_contains" else if (f == "Json.has") "sz_json_has" else if (f == "Str.contains") "sz_string_contains" else if (f == "Str.matches") "sz_string_matches" else if (f == "Str.endsWith") "sz_string_ends_with" else if (f == "Str.indexOf") "sz_string_uindex_of" else if (f == "Str.lastIndexOf") "sz_string_ulast_index_of" else if (f == "Set.isSubset") "sz_set_is_subset" else if (f == "Set.isDisjoint") "sz_set_is_disjoint" else if (f == "List.contains") "sz_list_contains" else if (f == "List.indexOf") "sz_list_index_of" else if (f == "List.lastIndexOf") "sz_list_last_index_of" else if (f == "List.indexOfSlice") "sz_list_index_of_slice" else if (f == "List.lastIndexOfSlice") "sz_list_last_index_of_slice" else if (f == "List.startsWith") "sz_list_starts_with" else if (f == "List.endsWith") "sz_list_ends_with" else if (f == "List.sameElements") "sz_list_same_elements" else "" def emitKitUnaryPtr(rt: String, code: String, vals: List[String], owns: List[Bool], prefix: String): Slot = emitKitUnaryPtr2(rt, code, arg0(vals), headOwn(owns), prefix) @@ -2699,11 +2705,14 @@ def emitKitUnaryPtr2(rt: String, code: String, v: String, o: Bool, prefix: Strin if (kitBoxVal(v, o)) emitKitUnaryBox(rt, code, v, prefix) else emitKitUnaryPtr3(rt, code, v, o, prefix) def emitKitUnaryPtr3(rt: String, code: String, v: String, o: Bool, prefix: String): Slot = - Slot(join(code, join(kitCallPtr1(rt, v, prefix), kitUnaryRel(rt, v, o))), pct(prefix, "v"), true) + Slot(join(code, join(kitUnaryPre(rt, v, o), join(kitCallPtr1(rt, v, prefix), kitUnaryRel(rt, v, o)))), pct(prefix, "v"), true) def kitKeepUnaryArg(rt: String): Bool = rt == "sz_ref_of" +def kitUnaryPre(rt: String, v: String, o: Bool): String = + if (!o && kitKeepUnaryArg(rt)) retPtr(v) else "" + def kitUnaryRel(rt: String, v: String, o: Bool): String = if (o && !kitKeepUnaryArg(rt)) relPtr(v) else "" @@ -3078,7 +3087,7 @@ def emitFsRead(code: String, vals: List[String], owns: List[Bool], prefix: Strin emitFsRead2(code, arg0(vals), headOwn(owns), prefix) def emitFsRead2(code: String, v: String, o: Bool, prefix: String): Slot = - Slot(join(code, join(line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @sz_fs_read(ptr ", Str.concat(v, ")")))), if (o) relPtr(v) else "")), pct(prefix, "v"), false) + Slot(join(code, join(line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @sz_fs_read(ptr ", Str.concat(v, ")")))), if (o) relPtr(v) else "")), pct(prefix, "v"), true) def emitFsWrite(code: String, vals: List[String], owns: List[Bool], prefix: String): Slot = emitFsWriteRt("sz_fs_write", code, vals, owns, prefix) @@ -3102,7 +3111,7 @@ def emitNetUdpSend2(code: String, sock: String, host: String, port: String, data Slot(join(code, join(line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @sz_net_udp_send(ptr ", Str.concat(sock, Str.concat(", ptr ", Str.concat(host, Str.concat(", i64 ", Str.concat(port, Str.concat(", ptr ", Str.concat(data, ")")))))))))), join(if (os) relPtr(sock) else "", join(if (oh) relPtr(host) else "", if (od) relPtr(data) else "")))), pct(prefix, "v"), true) def emitSysArgs(code: String, prefix: String): Slot = - Slot(join(code, line(Str.concat(tmp(prefix, "v"), "call ptr @sz_sys_args()"))), pct(prefix, "v"), false) + Slot(join(code, line(Str.concat(tmp(prefix, "v"), "call ptr @sz_sys_args()"))), pct(prefix, "v"), true) def emitSysI64(rt: String, code: String, vals: List[String], prefix: String): Slot = Slot(join(code, line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @", Str.concat(rt, Str.concat("(i64 ", Str.concat(arg0(vals), ")"))))))), pct(prefix, "v"), false) @@ -3120,7 +3129,7 @@ def emitFsList(code: String, vals: List[String], owns: List[Bool], prefix: Strin emitFsList2(code, arg0(vals), headOwn(owns), prefix) def emitFsList2(code: String, v: String, o: Bool, prefix: String): Slot = - Slot(join(code, join(line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @sz_fs_list(ptr ", Str.concat(v, ")")))), if (o) relPtr(v) else "")), pct(prefix, "v"), false) + Slot(join(code, join(line(Str.concat(tmp(prefix, "v"), Str.concat("call ptr @sz_fs_list(ptr ", Str.concat(v, ")")))), if (o) relPtr(v) else "")), pct(prefix, "v"), true) def emitIoPure(code: String, vals: List[String], owns: List[Bool], prefix: String, ps: List[Param]): Slot = emitIoPure2(code, arg0(vals), headOwn(owns), prefix, ps) @@ -3421,16 +3430,16 @@ def pairRt(left: Bool): String = def emitPairUnbox(scrut: String, left: Bool, prefix: String): Slot = Slot(join(line(Str.concat(tmp(prefix, "p"), Str.concat("call ptr @", Str.concat(pairRt(left), Str.concat("(ptr ", Str.concat(scrut, ")")))))), line(Str.concat(tmp(prefix, "v"), Str.concat("call i64 @sz_unbox_i64(ptr ", Str.concat(pct(prefix, "p"), ")"))))), pct(prefix, "v"), false) -def emitCtorCall(recv: Expr, name: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = - if (name == "copy" && copyEnAt(recv, ens, defs, ps, loc) != "") emitCopy(recv, args, prefix, strs, defs, ens, ps, loc) else if (name == "require") emitRequire(recv, args, prefix, strs, defs, ens, ps, loc) else emitCtorCall2(recv, name, args, prefix, strs, defs, ens, ps, loc) +def emitCtorCall(recv: Expr, name: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, fid: Int): Slot = + if (name == "copy" && copyEnAt(recv, ens, defs, ps, loc) != "") emitCopy(recv, args, prefix, strs, defs, ens, ps, loc) else if (name == "require") emitRequire(recv, args, prefix, strs, defs, ens, ps, loc) else emitCtorCall2(recv, name, args, prefix, strs, defs, ens, ps, loc, fid) def emitRequire(recv: Expr, _args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = emitExpr(recv, Str.concat(prefix, "_r"), strs, defs, ens, ps, loc) -def emitCtorCall2(recv: Expr, name: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = +def emitCtorCall2(recv: Expr, name: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, fid: Int): Slot = recv match { - case Expr.EVar(en, _) => if (paramTy(ps, en) != "") emitMethodVal(recv, name, args, prefix, recvEn(en, ps, ens, defs, name), strs, defs, ens, ps, loc) else emitCtorOrQual(en, name, args, prefix, strs, defs, ens, ps, loc) - case _ => emitMethodVal(recv, name, args, prefix, copyEnAt(recv, ens, defs, ps, loc), strs, defs, ens, ps, loc) + case Expr.EVar(en, _) => if (paramTy(ps, en) != "") emitMethodVal(recv, name, args, prefix, recvEn(en, ps, ens, defs, name), strs, defs, ens, ps, loc, fid) else emitCtorOrQual(en, name, args, prefix, strs, defs, ens, ps, loc, fid) + case _ => emitMethodVal(recv, name, args, prefix, copyEnAt(recv, ens, defs, ps, loc), strs, defs, ens, ps, loc, fid) } def recvEn(n: String, ps: List[Param], ens: List[En], defs: Ftab, meth: String): String = @@ -3450,8 +3459,8 @@ def methEnOfH(d: Fun, rest: List[Fun], meth: String): String = case Fun(_, name, _, _, _, _, mod, _) => if (name == meth && mod != "") mod else methEnOfL(rest, meth) } -def emitMethodVal(recv: Expr, name: String, args: List[Expr], prefix: String, en: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = - emitMethodVal2(emitExpr(recv, Str.concat(prefix, "_mr"), strs, defs, ens, ps, loc), emitArgList(args, prefix, strs, defs, ens, ps, loc, 0, "", noStr(), noBool(), 0), name, prefix, en, defs) +def emitMethodVal(recv: Expr, name: String, args: List[Expr], prefix: String, en: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, fid: Int): Slot = + emitMethodVal2(emitExprFid(recv, Str.concat(prefix, "_mr"), strs, defs, ens, ps, loc, fid), emitArgList(args, prefix, strs, defs, ens, ps, loc, 0, "", noStr(), noBool(), fid + fmCount(recv)), name, prefix, en, defs) def emitMethodVal2(r: Slot, ap: (String, List[String], List[Bool]), name: String, prefix: String, en: String, defs: Ftab): Slot = r match { @@ -3587,11 +3596,11 @@ def emitCopyArgBox(ec: String, ev: String, code: String, args: List[Expr], prefi def copyArgBox(prefix: String, k: Int, ev: String): String = line(Str.concat(tmp(prefix, Str.concat("ub", Str.fromInt(k))), Str.concat("call ptr @sz_box_i64(i64 ", Str.concat(ev, ")")))) -def emitCtorOrQual(en: String, name: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = - if (isEmitKit(Str.concat(en, Str.concat(".", name)))) emitCall(Str.concat(en, Str.concat(".", name)), args, prefix, strs, defs, ens, ps, loc, 0) else if (hasEn(ens, en)) emitPayloadCtor(en, name, args, prefix, strs, defs, ens, ps, loc) else emitQual(en, name, args, prefix, strs, defs, ens, ps, loc) +def emitCtorOrQual(en: String, name: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, fid: Int): Slot = + if (isEmitKit(Str.concat(en, Str.concat(".", name)))) emitCall(Str.concat(en, Str.concat(".", name)), args, prefix, strs, defs, ens, ps, loc, fid) else if (hasEn(ens, en)) emitPayloadCtor(en, name, args, prefix, strs, defs, ens, ps, loc, fid) else emitQual(en, name, args, prefix, strs, defs, ens, ps, loc, fid) def isEmitKit(f: String): Bool = - isUiKit(f) || f == "List.setAt" || f == "List.join" || kitUnaryPtr(f) != "" || kitUnaryI64(f) != "" || kitPtrI64(f) != "" || kitBinaryPtr(f) != "" || kitBinaryI64(f) != "" || isJsonExtra(f) || isRtExtra(f) || isMapKit(f) || f == "Map.empty" || f == "Set.empty" || f == "List.empty" || f == "Str.replace" || f == "Str.replaceMatch" || f == "Str.padLeft" || f == "Str.padRight" || f == "Map.set" || f == "Set.add" || f == "Map.getOrElse" || f == "Clock.iso8601" || f == "Clock.monotonic" || f == "Clock.realTime" || f == "Fs.mkdirs" || f == "Fs.canonicalize" || f == "Fs.exists" || f == "Fs.walk" || f == "Fs.delete" || f == "List.len" || f == "List.concat" || f == "List.reverse" || f == "List.head" || f == "List.tail" || f == "List.isEmpty" || f == "List.cons" || f == "List.at" || f == "Str.fromInt" || f == "Str.fromBool" || f == "Str.concat" || f == "Str.len" || f == "Str.byteLen" || f == "Str.byteSlice" || f == "Fs.read" || f == "Fs.write" || f == "Fs.list" || f == "IO.pure" || f == "Sys.getenv" || f == "Impurity.runKit" || f == "Net.serveOnce" || f == "Net.serve" || f == "Net.serveOnceTls" || f == "Net.serveTls" || isNetEmit(f) || f == "Queue.unbounded" || f == "Deferred.empty" || f == "Float.toInt" || f == "Float.fromInt" || f == "Map.isEmpty" || f == "Set.isEmpty" || f == "Map.nonEmpty" || f == "Set.nonEmpty" || f == "List.range" || f == "List.fill" || f == "List.getOrElse" || f == "List.padTo" || f == "List.max" || f == "List.min" || f == "List.sort" || f == "List.toSet" || f == "List.slice" || f == "List.isDefinedAt" || f == "List.lengthCompare" || f == "List.tabulate" || f == "List.segmentLength" || isTlKit(f) || f == "Net.retryAfterMillis" || f == "Oracle.sumTo" || f == "Fs.rename" || f == "Random.nextInt" || f == "Uuid.v4" + isUiKit(f) || f == "List.setAt" || f == "List.join" || kitUnaryPtr(f) != "" || kitUnaryI64(f) != "" || kitPtrI64(f) != "" || kitBinaryPtr(f) != "" || kitBinaryI64(f) != "" || isJsonExtra(f) || isRtExtra(f) || isMapKit(f) || f == "Map.empty" || f == "Set.empty" || f == "List.empty" || f == "Str.replace" || f == "Str.replaceMatch" || f == "Str.padLeft" || f == "Str.padRight" || f == "Map.set" || f == "Set.add" || f == "Map.getOrElse" || f == "Clock.iso8601" || f == "Clock.monotonic" || f == "Clock.realTime" || f == "Fs.mkdirs" || f == "Fs.canonicalize" || f == "Fs.exists" || f == "Fs.walk" || f == "Fs.delete" || f == "List.len" || f == "List.concat" || f == "List.reverse" || f == "List.head" || f == "List.tail" || f == "List.isEmpty" || f == "List.cons" || f == "List.at" || f == "Str.fromInt" || f == "Str.fromBool" || f == "Str.concat" || f == "Str.len" || f == "Str.byteLen" || f == "Str.byteSlice" || f == "Fs.read" || f == "Fs.write" || f == "Fs.list" || f == "IO.pure" || f == "Sys.getenv" || f == "Impurity.runKit" || f == "Net.serveOnce" || f == "Net.serve" || f == "Net.serveOnceTls" || f == "Net.serveTls" || isNetEmit(f) || f == "Queue.unbounded" || f == "Deferred.empty" || f == "Float.toInt" || f == "Float.fromInt" || f == "Map.isEmpty" || f == "Set.isEmpty" || f == "Map.nonEmpty" || f == "Set.nonEmpty" || f == "List.range" || f == "List.fill" || f == "List.getOrElse" || f == "List.padTo" || f == "List.max" || f == "List.min" || f == "List.sort" || f == "List.toSet" || f == "List.slice" || f == "List.isDefinedAt" || f == "List.lengthCompare" || f == "List.tabulate" || f == "List.segmentLength" || isTlKit(f) || f == "Net.retryAfterMillis" || f == "Oracle.sumTo" || f == "Fs.rename" || f == "Random.nextInt" || f == "Uuid.v4" || f == "Fuzz.driver" def isNetEmit(f: String): Bool = f == "Net.httpGet" || f == "Net.httpHead" || f == "Net.httpDelete" || f == "Net.httpPost" || f == "Net.httpPut" || f == "Net.httpPatch" || f == "Net.tcpConnect" || f == "Net.tcpListen" || f == "Net.tcpAccept" || f == "Net.tcpRead" || f == "Net.tcpWrite" || f == "Net.tcpClose" || f == "Net.udpBind" || f == "Net.udpSend" || f == "Net.udpRecv" || f == "Net.udpClose" @@ -3599,8 +3608,8 @@ def isNetEmit(f: String): Bool = def isUiKit(f: String): Bool = Str.startsWith(f, "View.") || Str.startsWith(f, "Signal.") || Str.startsWith(f, "Color.") || Str.startsWith(f, "Theme.") || Str.startsWith(f, "Icon.") || Str.startsWith(f, "Property.") || Str.startsWith(f, "Ui.") -def emitQual(en: String, name: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = - emitQual2(en, name, emitArgList(alignFill(funParamsOfHit(findDefMod(defs, en, name)), args), prefix, strs, defs, ens, ps, loc, 0, "", noStr(), noBool(), 0), prefix, defs) +def emitQual(en: String, name: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, fid: Int): Slot = + emitQual2(en, name, emitArgList(alignFill(funParamsOfHit(findDefMod(defs, en, name)), args), prefix, strs, defs, ens, ps, loc, 0, "", noStr(), noBool(), fid), prefix, defs) def emitQual2(en: String, name: String, p: (String, List[String], List[Bool]), prefix: String, defs: Ftab): Slot = p match { @@ -3613,11 +3622,11 @@ def findDefMod(funs: Ftab, mod: String, name: String): List[Fun] = def emitUserMod(en: String, name: String, code: String, vals: List[String], owns: List[Bool], prefix: String, defs: Ftab): Slot = emitUserHit(Str.concat(en, Str.concat("_", name)), code, vals, owns, prefix, findDefMod(defs, en, name)) -def emitPayloadCtor(en: String, name: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = - if (List.isEmpty(args)) emitCtor(en, name, prefix, ens) else emitPayloadCtorRest(en, name, List.at(args, 0), List.tail(args), prefix, strs, defs, ens, ps, loc) +def emitPayloadCtor(en: String, name: String, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, fid: Int): Slot = + if (List.isEmpty(args)) emitCtor(en, name, prefix, ens) else emitPayloadCtorRest(en, name, List.at(args, 0), List.tail(args), prefix, strs, defs, ens, ps, loc, fid) -def emitPayloadCtorRest(en: String, name: String, h: Expr, rest: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): Slot = - if (List.isEmpty(rest)) emitPayloadCtor1(emitExpr(h, Str.concat(prefix, "_ap"), strs, defs, ens, ps, loc), en, name, prefix, ens, exprRetTyEns(h, defs, ens, ps, loc)) else emitPayloadCtorN(en, name, emitArgList(h :: rest, prefix, strs, defs, ens, ps, loc, 0, "", noStr(), noBool(), 0), h :: rest, prefix, ens, ps, defs, modOfLoc(loc)) +def emitPayloadCtorRest(en: String, name: String, h: Expr, rest: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, fid: Int): Slot = + if (List.isEmpty(rest)) emitPayloadCtor1(emitExprFid(h, Str.concat(prefix, "_ap"), strs, defs, ens, ps, loc, fid), en, name, prefix, ens, exprRetTyEns(h, defs, ens, ps, loc)) else emitPayloadCtorN(en, name, emitArgList(h :: rest, prefix, strs, defs, ens, ps, loc, 0, "", noStr(), noBool(), fid), h :: rest, prefix, ens, ps, defs, modOfLoc(loc)) def emitPayloadCtorN(en: String, name: String, p: (String, List[String], List[Bool]), args: List[Expr], prefix: String, ens: List[En], ps: List[Param], defs: Ftab, mod: String): Slot = p match { @@ -4734,7 +4743,13 @@ def isIoTy(ret: String): Bool = Str.len(ret) >= 2 && Str.slice(ret, 0, 2) == "IO" def emitDefByRet(name: String, mod: String, ret: String, ps: List[Param], body: Expr, strs: List[String], defs: Ftab, ens: List[En], loc: String): String = - stampNeth(if (isIoTy(ret)) stampCont(Str.concat(emitConts(body, strs, defs, ens, ps, loc), emitDef3(llvmRet(ret), symOf(mod, name), defParams2(ps), emitExpr(body, "body", strs, defs, ens, ps, loc), loc, strs)), contKey(mod, name)) else if (hasTailIf(body, name)) emitDefTco(name, mod, ret, ps, body, strs, defs, ens, loc) else emitDef3(llvmRet(ret), symOf(mod, name), defParams2(ps), emitExpr(body, "body", strs, defs, ens, ps, loc), loc, strs), contKey(mod, name)) + stampNeth(stampCont(Str.concat(emitDefConts(ret, body, strs, defs, ens, ps, loc), emitDefBody(name, mod, ret, ps, body, strs, defs, ens, loc)), contKey(mod, name)), contKey(mod, name)) + +def emitDefConts(ret: String, body: Expr, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String): String = + if (isIoTy(ret) || fmCount(body) > 0) emitConts(body, strs, defs, ens, ps, loc) else "" + +def emitDefBody(name: String, mod: String, ret: String, ps: List[Param], body: Expr, strs: List[String], defs: Ftab, ens: List[En], loc: String): String = + if (!isIoTy(ret) && hasTailIf(body, name)) emitDefTco(name, mod, ret, ps, body, strs, defs, ens, loc) else emitDef3(llvmRet(ret), symOf(mod, name), defParams2(ps), emitExpr(body, "body", strs, defs, ens, ps, loc), loc, strs) def hasTailIf(e: Expr, name: String): Bool = e match { @@ -5960,7 +5975,7 @@ def fmCountArgsHit(xs: List[Expr]): Int = } def emitIoMap(recv: Expr, args: List[Expr], prefix: String, strs: List[String], defs: Ftab, ens: List[En], ps: List[Param], loc: String, fid: Int): Slot = - if (Check.isIo(exprRetTyEns(recv, defs, ens, ps, loc))) emitFmRt("sz_io_flatmap", recv, wrapIoMapArgs(args, ps, defs, modOfLoc(loc)), prefix, strs, defs, ens, ps, loc, fid) else emitCtorCall(recv, "map", args, prefix, strs, defs, ens, ps, loc) + if (Check.isIo(exprRetTyEns(recv, defs, ens, ps, loc))) emitFmRt("sz_io_flatmap", recv, wrapIoMapArgs(args, ps, defs, modOfLoc(loc)), prefix, strs, defs, ens, ps, loc, fid) else emitCtorCall(recv, "map", args, prefix, strs, defs, ens, ps, loc, fid) def wrapIoMapArgs(args: List[Expr], ps: List[Param], defs: Ftab, mod: String): List[Expr] = if (List.isEmpty(args)) args else wrapIoMapLam(List.at(args, 0), ps, defs, mod) :: List.tail(args) @@ -5989,16 +6004,16 @@ def emitFm2(rt: String, inner: Slot, prefix: String, id: Int, ps: List[Param]): def emitFm3(rt: String, inner: Slot, env: Slot, prefix: String, id: Int): Slot = inner match { - case Slot(ic, iv, _) => emitFm4(rt, ic, iv, env, prefix, id) + case Slot(ic, iv, io) => emitFm4(rt, ic, iv, io, env, prefix, id) } -def emitFm4(rt: String, ic: String, iv: String, env: Slot, prefix: String, id: Int): Slot = +def emitFm4(rt: String, ic: String, iv: String, io: Bool, env: Slot, prefix: String, id: Int): Slot = env match { - case Slot(ec, ev, eo) => emitFm5(rt, join(ic, ec), iv, ev, eo, prefix, id) + case Slot(ec, ev, eo) => emitFm5(rt, join(ic, ec), iv, io, ev, eo, prefix, id) } -def emitFm5(rt: String, code: String, iv: String, ev: String, eo: Bool, prefix: String, id: Int): Slot = - Slot(join(code, join(emitFmCall(rt, prefix, iv, id, ev), join(relPtr(iv), emitFmRel(ev, eo)))), pct(prefix, "fm"), true) +def emitFm5(rt: String, code: String, iv: String, io: Bool, ev: String, eo: Bool, prefix: String, id: Int): Slot = + Slot(join(code, join(emitFmCall(rt, prefix, iv, id, ev), join(if (io) relPtr(iv) else "", emitFmRel(ev, eo)))), pct(prefix, "fm"), true) def emitFmCall(rt: String, prefix: String, iv: String, id: Int, ev: String): String = line(Str.concat(tmp(prefix, "fm"), Str.concat("call ptr @", Str.concat(rt, Str.concat("(ptr ", Str.concat(iv, Str.concat(", ptr @sz_cont_", Str.concat(Str.fromInt(id), Str.concat(", ptr ", Str.concat(ev, ")")))))))))) @@ -6468,7 +6483,7 @@ def declsI(): String = join(decl("ptr @sz_json_arr(ptr)"), join(decl("ptr @sz_json_at(ptr, i64)"), join(decl("i64 @sz_json_get_int(ptr, ptr, i64)"), join(decl("ptr @sz_json_get_str(ptr, ptr, ptr)"), join(decl("i64 @sz_json_get_bool(ptr, ptr, i64)"), join(decl("i64 @sz_json_is_null(ptr)"), join(decl("i64 @sz_json_is_obj(ptr)"), join(decl("i64 @sz_json_is_arr(ptr)"), join(decl("i64 @sz_json_int_or(ptr, i64)"), join(decl("ptr @sz_json_pairs(ptr)"), join(decl("ptr @sz_json_set(ptr, ptr, ptr)"), join(decl("ptr @sz_json_remove(ptr, ptr)"), join(decl("ptr @sz_json_append(ptr, ptr)"), join(decl("ptr @sz_json_prepend(ptr, ptr)"), join(decl("ptr @sz_json_set_at(ptr, i64, ptr)"), join(decl("ptr @sz_json_drop_at(ptr, i64)"), join(decl("ptr @sz_json_merge(ptr, ptr)"), join(decl("ptr @sz_sys_getenv(ptr)"), join(decl("ptr @sz_impurity_run_kit()"), join(decl("ptr @sz_json_as_int(ptr)"), join(decl("ptr @sz_json_as_bool(ptr)"), join(decl("ptr @sz_json_as_str(ptr)"), join(decl("ptr @sz_json_as_float(ptr)"), decl("double @sz_json_float_or(ptr, double)")))))))))))))))))))))))) def declsJ(): String = - join(decl("ptr @sz_io_both(ptr, ptr)"), join(decl("ptr @sz_io_race(ptr, ptr)"), join(decl("ptr @sz_io_ensure(ptr, ptr)"), join(decl("ptr @sz_io_sleep_ms(i64)"), join(decl("ptr @sz_io_fail(ptr)"), join(decl("ptr @sz_io_timeout(i64, ptr)"), join(decl("ptr @sz_io_when(i64, ptr)"), join(decl("ptr @sz_io_unless(i64, ptr)"), join(decl("ptr @sz_io_repeat_n(i64, ptr)"), join(decl("ptr @sz_io_retry_n(i64, ptr)"), join(decl("ptr @sz_io_forever(ptr)"), join(decl("ptr @sz_io_handle_error_with(ptr, ptr, ptr)"), join(decl("ptr @sz_error_new(i32, ptr)"), decl("void @sz_property_sometimes(ptr)")))))))))))))) + join(decl("ptr @sz_io_both(ptr, ptr)"), join(decl("ptr @sz_io_race(ptr, ptr)"), join(decl("ptr @sz_io_ensure(ptr, ptr)"), join(decl("ptr @sz_io_sleep_ms(i64)"), join(decl("ptr @sz_io_fail(ptr)"), join(decl("ptr @sz_io_timeout(i64, ptr)"), join(decl("ptr @sz_io_when(i64, ptr)"), join(decl("ptr @sz_io_unless(i64, ptr)"), join(decl("ptr @sz_io_repeat_n(i64, ptr)"), join(decl("ptr @sz_io_retry_n(i64, ptr)"), join(decl("ptr @sz_io_forever(ptr)"), join(fuzzDecls(), join(decl("ptr @sz_io_handle_error_with(ptr, ptr, ptr)"), join(decl("ptr @sz_error_new(i32, ptr)"), decl("void @sz_property_sometimes(ptr)"))))))))))))))) def declsK(): String = join(decl("ptr @sz_net_http_get(ptr, ptr)"), join(decl("ptr @sz_net_serve_once(i64, ptr, ptr)"), join(decl("ptr @sz_net_serve(i64, ptr, ptr)"), join(decl("ptr @sz_net_serve_once_tls(i64, ptr, ptr)"), join(decl("ptr @sz_net_serve_tls(i64, ptr, ptr)"), join(decl("ptr @sz_ref_of(ptr)"), join(decl("ptr @sz_ref_get(ptr)"), join(decl("ptr @sz_ref_set(ptr, ptr)"), join(decl("ptr @sz_queue_unbounded()"), join(decl("ptr @sz_queue_offer(ptr, ptr)"), join(decl("ptr @sz_queue_take(ptr)"), join(decl("ptr @sz_deferred_empty()"), join(decl("ptr @sz_deferred_complete(ptr, ptr)"), join(decl("ptr @sz_deferred_fail(ptr, ptr)"), join(decl("ptr @sz_deferred_get(ptr)"), join(decl("ptr @sz_fiber_fork(ptr)"), join(decl("ptr @sz_fiber_join(ptr)"), decl("ptr @sz_fiber_interrupt(ptr)")))))))))))))))))) @@ -6557,3 +6572,6 @@ def emitViewSection(code: String, vals: List[String], owns: List[Bool], prefix: def emitHttpBody(rt: String, code: String, vals: List[String], owns: List[Bool], prefix: String): Slot = Slot(join(code, join(viewPtr3Call(rt, vals, prefix), join(if (headOwn(owns)) relPtr(arg0(vals)) else "", join(if (headOwn(tailOwn(owns))) relPtr(arg1(vals)) else "", if (headOwn(tailOwn(tailOwn(owns)))) relPtr(arg2(vals)) else "")))), pct(prefix, "v"), true) +def fuzzDecls(): String = + join(decl("ptr @sz_fuzz_setup(ptr)"), join(decl("ptr @sz_fuzz_driver(ptr, i64, ptr, ptr)"), join(decl("ptr @sz_fuzz_verify(ptr, ptr, ptr)"), join(decl("ptr @sz_fuzz_verify_rel(ptr, ptr, ptr)"), join(decl("void @sz_fuzz_hit(ptr)"), decl("ptr @sz_fuzz_probe(ptr)")))))) + diff --git a/examples/compiler/src/Eval.scuzz b/examples/compiler/src/Eval.scuzz new file mode 100644 index 00000000..a83a999a --- /dev/null +++ b/examples/compiler/src/Eval.scuzz @@ -0,0 +1,1534 @@ +enum Value: + case VUnit + case VInt(n: Int) + case VBool(b: Bool) + case VStr(s: String) + case VList(xs: List[Value]) + case VTuple(xs: List[Value]) + case VCon(en: String, tag: String, fields: List[Value]) + case VClo(param: String, body: Expr, env: EvEnv) + case VFun(name: String, mod: String) + case VIo(io: IO[Value, Value]) + case VErr(msg: String) + case VFloat(f: Float) + case VMap(kvs: List[(Value, Value)]) + case VSet(xs: List[Value]) + case VBuilder(b: Builder) + case VBytes(b: Bytes) + case VRef(r: Ref[Value]) + case VQueue(q: Queue[Value]) + case VDeferred(d: Deferred[Value]) + case VFiber(f: Fiber[Value]) + case VStream(s: Stream[Value]) + case VResource(r: Resource[Value]) + case VTcp(t: Tcp) + case VUdp(u: Udp) + case VTimeline(t: Timeline) + case VVerdict(v: Verdict) + +record EvEnv(vars: List[(String, Value)], mod: String) + +record EvStep(e: Expr, env: EvEnv) + +record EvProg(funs: Ftab, ens: List[En], main: Expr, mainMod: String, files: List[(String, String)], ctx: List[Ref[Value]]) + +import Parse.Expr +import Parse.Fun +import Parse.Prog +import Parse.En +import Parse.Arm +import Parse.Param +import Parse.Bind +import Check.Ftab + +def noVars(): List[(String, Value)] = + [] + +def noVals(): List[Value] = + [] + +def noNames(): List[String] = + [] + +def noPairs(): List[(Value, Value)] = + [] + +def load(files: List[(String, String)]): EvProg = + loadGo(files, Parse.emptyProg(), "Main", files) + +def loadGo(rest: List[(String, String)], acc: Prog, mainMod: String, files: List[(String, String)]): EvProg = + if (List.isEmpty(rest)) loadProg(acc, mainMod, files) else loadFile(List.at(rest, 0), List.tail(rest), acc, mainMod, files) + +def loadFile(f: (String, String), rest: List[(String, String)], acc: Prog, mainMod: String, files: List[(String, String)]): EvProg = + f match { + case (stem, src) => loadParsed(stem, Parse.parseFile(stem, src), rest, acc, mainMod, files) + } + +def loadParsed(stem: String, p: Prog, rest: List[(String, String)], acc: Prog, mainMod: String, files: List[(String, String)]): EvProg = + loadGo(rest, Parse.merge(acc, p), if (p.main == "") mainMod else stem, files) + +def noRefs(): List[Ref[Value]] = + [] + +def loadProg(p: Prog, mainMod: String, files: List[(String, String)]): EvProg = + p match { + case Prog(_, enums, aliases, _, impls, imps, defs, _, body) => EvProg(Check.ftabOfList(Emit.withSelfFuns(Check.expandDefs(Check.withImportFuns(imps, Check.implFuns(impls, defs)), aliases))), Parse.builtins(enums), body, mainMod, files, noRefs()) + } + +def callPure(p: EvProg, mod: String, name: String, args: List[Value]): Value = + callPureHit(Check.ftabGetMod(p.funs.tab, mod, name), mod, name, args, p) + +def callPureHit(hit: List[Fun], mod: String, name: String, args: List[Value], p: EvProg): Value = + if (List.isEmpty(hit)) Value.VErr(Str.concat("eval: unknown function ", Str.concat(mod, Str.concat(".", name)))) else step(defStepVals(List.at(hit, 0), args, p), p) + +def runMain(p: EvProg): IO[Unit] = + mainIo(step(EvStep(p.main, EvEnv(noVars(), p.mainMod)), p)) + +def mainIo(v: Value): IO[Unit] = + v match { + case Value.VIo(io) => io.handleErrorWith(e => mainFail(e)).flatMap(_ => IO.pure(())) + case Value.VErr(msg) => IO.println(msg).flatMap(_ => IO.fail("eval")) + case _ => IO.println("eval: @main did not produce IO").flatMap(_ => IO.fail("eval")) + } + +def mainFail(e: Value): IO[Value] = + e match { + case Value.VStr(s) => IO.fail(s) + case Value.VErr(msg) => IO.println(msg).flatMap(_ => IO.fail("eval")) + case _ => IO.fail("typed failure") + } + +def excludedKits(): List[String] = + "Signal." :: "Ui." :: "View." :: "Icon." :: "Color." :: "Theme." :: "Property.signal" :: "Property.a11yHas" :: "Fuzz." :: noNames() + +def isExcludedKit(f: String): Bool = + List.exists(excludedKits(), pre => Str.startsWith(f, pre)) + +def kitProbe(f: String, vals: List[Value], p: EvProg): Value = + kitCall(f, vals, EvEnv(noVars(), p.mainMod), p, 0) + +def isUnsupported(v: Value): Bool = + v match { + case Value.VErr(msg) => Str.startsWith(msg, "eval: unsupported kit") + case _ => false + } + +def show(v: Value): String = + v match { + case Value.VUnit => "()" + case Value.VInt(n) => Str.fromInt(n) + case Value.VBool(b) => if (b) "true" else "false" + case Value.VStr(s) => Check.jsonStr(s) + case Value.VList(xs) => Str.concat("[", Str.concat(showAll(xs), "]")) + case Value.VTuple(xs) => Str.concat("(", Str.concat(showAll(xs), ")")) + case Value.VCon(_, tag, fields) => if (List.isEmpty(fields)) tag else Str.concat(tag, Str.concat("(", Str.concat(showAll(fields), ")"))) + case Value.VClo(_, _, _) => "" + case Value.VFun(name, _) => Str.concat("")) + case Value.VIo(_) => "" + case Value.VErr(msg) => msg + case Value.VFloat(f) => fmtFloat(f) + case Value.VMap(kvs) => Str.concat("Map(", Str.concat(showAll(pairsToTuples(kvs)), ")")) + case Value.VSet(xs) => Str.concat("Set(", Str.concat(showAll(xs), ")")) + case Value.VBuilder(_) => "" + case Value.VBytes(_) => "" + case Value.VRef(_) => "" + case Value.VQueue(_) => "" + case Value.VDeferred(_) => "" + case Value.VFiber(_) => "" + case Value.VStream(_) => "" + case Value.VResource(_) => "" + case Value.VTcp(_) => "" + case Value.VUdp(_) => "" + case Value.VTimeline(_) => "" + case Value.VVerdict(_) => "" + } + +def fmtFloat(f: Float): String = + s"${f}" + +def showAll(xs: List[Value]): String = + List.join(List.map(xs, x => show(x)), ", ") + +def step(s: EvStep, p: EvProg): Value = + s.e match { + case Expr.EInt(n, _) => Value.VInt(n) + case Expr.EBool(b) => Value.VBool(b) + case Expr.EStr(str, _) => Value.VStr(str) + case Expr.EUnit => Value.VUnit + case Expr.EVar(name, off) => varValue(name, s.env, p, off) + case Expr.EPrint(inner, off) => printValue(step(EvStep(inner, s.env), p), s.env, p, off) + case Expr.ECall(f, args, off) => step(callStep(s.e, f, args, s.env, p, off), p) + case Expr.EMethod(recv, name, args, off) => step(methodStep(s.e, recv, name, args, s.env, p, off), p) + case Expr.EField(recv, name, off) => fieldValue(s.e, recv, name, s.env, p, off) + case Expr.EBin(op, l, r, off) => binValue(s.e, op, l, r, s.env, p, off) + case Expr.EUn(op, inner, off) => unValue(s.e, op, inner, s.env, p, off) + case Expr.ELam(param, _, body) => Value.VClo(param, body, s.env) + case Expr.EIf(c, t, el, off) => step(ifStep(step(EvStep(c, s.env), p), t, el, s.env, p, off), p) + case Expr.EMatch(scr, arms, off) => step(matchStep(step(EvStep(scr, s.env), p), arms, s.env, p, off), p) + case Expr.EFor(bs, body, off) => forValue(bs, body, s.env, p, off, false) + case Expr.EList(xs) => listValue(stepAll(xs, s.env, p)) + case Expr.ETuple(xs) => tupleValue(stepAll(xs, s.env, p)) + case Expr.ENamed(_, inner) => step(EvStep(inner, s.env), p) + case Expr.EAscribe(inner, _, _) => step(EvStep(inner, s.env), p) + case Expr.EInterp(str) => interpAcc(Emit.interpParts(str, 0, Builder.empty(), []), s.env, p, Builder.empty()) + case Expr.EHole => Value.VClo("_ph", Expr.EVar("_ph", 0), s.env) + case Expr.EFloat(str) => Value.VFloat(floatLit(Str.replace(str, "_", ""))) + } + +def floatLit(str: String): Float = + floatLitExp(str, expAt(str, 0)) + +def expAt(str: String, i: Int): Int = + if (i >= Str.len(str)) 0 - 1 else if (Str.charAt(str, i) == 101 || Str.charAt(str, i) == 69) i else expAt(str, i + 1) + +def floatLitExp(str: String, e: Int): Float = + if (e < 0) floatMant(str, 0) else floatMant(Str.slice(str, 0, e), Str.toInt(Str.stripPrefix(Str.slice(str, e + 1, Str.len(str)), "+"), 0)) + +def floatMant(m: String, e: Int): Float = + floatMantDot(m, Str.indexOf(m, "."), e) + +def floatMantDot(m: String, d: Int, e: Int): Float = + if (d < 0) floatScale(Float.fromInt(Str.toInt(m, 0)), e) else floatScale(Float.fromInt(Str.toInt(Str.concat(Str.slice(m, 0, d), Str.slice(m, d + 1, Str.len(m))), 0)), e - (Str.len(m) - d - 1)) + +def floatScale(x: Float, e: Int): Float = + if (e == 0) x else if (e > 0) x * Float.fromInt(pow10(e)) else x / Float.fromInt(pow10(0 - e)) + +def pow10(k: Int): Int = + if (k <= 0) 1 else 10 * pow10(k - 1) + +def stepAll(xs: List[Expr], env: EvEnv, p: EvProg): List[Value] = + if (List.isEmpty(xs)) noVals() else step(EvStep(List.at(xs, 0), env), p) :: stepAll(List.tail(xs), env, p) + +def valueStep(v: Value): EvStep = + EvStep(Expr.EVar("#v", 0), EvEnv(("#v", v) :: noVars(), "")) + +def holeStep(e: Expr, env: EvEnv): EvStep = + valueStep(Value.VClo("_ph", Check.rewriteHole(e, "_ph"), env)) + +def isErr(v: Value): Bool = + v match { + case Value.VErr(_) => true + case _ => false + } + +def hasErr(vals: List[Value]): Bool = + List.exists(vals, v => isErr(v)) + +def firstErr(vals: List[Value]): Value = + if (List.isEmpty(vals)) Value.VErr("eval: internal error") else if (isErr(List.at(vals, 0))) List.at(vals, 0) else firstErr(List.tail(vals)) + +def errAt(msg: String, env: EvEnv, p: EvProg, off: Int): Value = + Value.VErr(Str.concat("eval: ", Str.concat(msg, Str.concat(" at ", locStr(env.mod, p, off))))) + +def unsupported(what: String, env: EvEnv, p: EvProg, off: Int): Value = + errAt(Str.concat("unsupported ", what), env, p, off) + +def locStr(mod: String, p: EvProg, off: Int): String = + locSrc(Check.fileOf(mod), Check.srcOfStem(p.files, mod), off) + +def locSrc(file: String, src: String, off: Int): String = + if (src == "" || off < 0) file else locLine(file, Check.locOf(src, off)) + +def locLine(file: String, loc: (Int, Int, Int, Int)): String = + loc match { + case (line, col, _, _) => Str.concat(file, Str.concat(":", Str.concat(Str.fromInt(line), Str.concat(":", Str.fromInt(col))))) + } + +def lookupVars(vars: List[(String, Value)], name: String): Option[Value] = + if (List.isEmpty(vars)) None else lookupVars1(List.at(vars, 0), List.tail(vars), name) + +def lookupVars1(h: (String, Value), rest: List[(String, Value)], name: String): Option[Value] = + h match { + case (k, v) => if (k == name) Some(v) else lookupVars(rest, name) + } + +def isBound(env: EvEnv, name: String): Bool = + lookupVars(env.vars, name) match { + case Some(_) => true + case None => false + } + +def varValue(name: String, env: EvEnv, p: EvProg, off: Int): Value = + lookupVars(env.vars, name) match { + case Some(v) => v + case None => varGlobal(Check.findFunPrefer2(p.funs, name, env.mod), name, env, p, off) + } + +def varGlobal(hit: List[Fun], name: String, env: EvEnv, p: EvProg, off: Int): Value = + if (!List.isEmpty(hit)) Value.VFun(name, List.at(hit, 0).mod) else if (Check.enOfCase(p.ens, name) != "") Value.VCon(Check.enOfCase(p.ens, name), name, noVals()) else errAt(Str.concat("unbound variable ", name), env, p, off) + +def printValue(v: Value, env: EvEnv, p: EvProg, off: Int): Value = + v match { + case Value.VStr(s) => Value.VIo(printlnIo(s)) + case Value.VErr(_) => v + case _ => errAt("IO.println needs String", env, p, off) + } + +def liftIo(io: IO[Value]): IO[Value, Value] = + io.handleErrorWith(e => IO.fail(Value.VStr(e))) + +def printlnIo(s: String): IO[Value, Value] = + liftIo(IO.println(s).flatMap(_ => IO.pure(Value.VUnit))) + +def pureIo(v: Value): IO[Value, Value] = + liftIo(IO.pure(v)) + +def failIo(msg: String): IO[Value, Value] = + IO.fail(Value.VErr(msg)) + +def failWith(e: Value): IO[Value, Value] = + IO.fail(e) + +def ioOf(v: Value): IO[Value, Value] = + v match { + case Value.VIo(io) => io + case Value.VErr(msg) => failIo(msg) + case Value.VUnit => pureIo(v) + case _ => failIo("eval: continuation did not produce IO") + } + +def ioOfPure(v: Value): IO[Value, Value] = + v match { + case Value.VErr(msg) => failIo(msg) + case _ => pureIo(v) + } + +def handleIo(io: IO[Value, Value], f: Value, env: EvEnv, p: EvProg, off: Int): IO[Value, Value] = + io.handleErrorWith(e => handleErr(e, f, env, p, off)) + +def handleErr(e: Value, f: Value, env: EvEnv, p: EvProg, off: Int): IO[Value, Value] = + e match { + case Value.VErr(_) => failWith(e) + case _ => ioOf(applyValue(f, e :: noVals(), env, p, off)) + } + +def ifStep(cv: Value, t: Expr, el: Expr, env: EvEnv, p: EvProg, off: Int): EvStep = + cv match { + case Value.VBool(b) => if (b) EvStep(t, env) else EvStep(el, env) + case Value.VErr(_) => valueStep(cv) + case _ => valueStep(errAt("if condition is not Bool", env, p, off)) + } + +def callStep(e: Expr, f: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + if (Check.needsPh(e)) holeStep(e, env) else if (Check.isKit(f)) kitStep(f, args, env, p, off) else if (Check.hasEn(p.ens, f)) ctorStep(f, f, args, env, p, off) else if (Check.enOfCase(p.ens, f) != "") ctorStep(Check.enOfCase(p.ens, f), f, args, env, p, off) else callLocal(lookupVars(env.vars, f), f, args, env, p, off) + +def callLocal(bound: Option[Value], f: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + bound match { + case Some(fv) => applyStep(fv, stepAll(args, env, p), env, p, off) + case None => callDef(Check.findFunPrefer2(p.funs, f, env.mod), f, args, env, p, off) + } + +def callDef(hit: List[Fun], f: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + if (List.isEmpty(hit)) valueStep(errAt(Str.concat("unknown function ", f), env, p, off)) else defStep(List.at(hit, 0), args, env, p, off) + +def defStep(d: Fun, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + defStepAligned(d, Check.alignCall(d.params, args), env, p, off) + +def defStepAligned(d: Fun, al: (List[Expr], String), env: EvEnv, p: EvProg, off: Int): EvStep = + al match { + case (xs, err) => if (err != "") valueStep(errAt(err, env, p, off)) else defStepVals(d, stepAll(xs, env, p), p) + } + +def defStepVals(d: Fun, vals: List[Value], p: EvProg): EvStep = + if (hasErr(vals)) valueStep(firstErr(vals)) else EvStep(d.body, EvEnv(bindParams(d.params, vals, noVars()), d.mod)) + +def bindParams(ps: List[Param], vals: List[Value], acc: List[(String, Value)]): List[(String, Value)] = + if (List.isEmpty(ps) || List.isEmpty(vals)) acc else bindParams(List.tail(ps), List.tail(vals), (List.at(ps, 0).name, List.at(vals, 0)) :: acc) + +def kitStep(f: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + if (List.isEmpty(args) && !List.isEmpty(Kits.params(f))) valueStep(Value.VFun(f, "")) else valueStep(kitCall(f, stepAll(args, env, p), env, p, off)) + +def ctorStep(en: String, tag: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + ctorAligned(en, tag, Check.alignCall(Check.constructorFields(p.ens, qualTag(en, tag)), args), env, p, off) + +def qualTag(en: String, tag: String): String = + if (en == tag) en else Str.concat(en, Str.concat(".", tag)) + +def ctorAligned(en: String, tag: String, al: (List[Expr], String), env: EvEnv, p: EvProg, off: Int): EvStep = + al match { + case (xs, err) => if (err != "") valueStep(errAt(err, env, p, off)) else valueStep(conValue(en, tag, stepAll(xs, env, p))) + } + +def conValue(en: String, tag: String, vals: List[Value]): Value = + if (hasErr(vals)) firstErr(vals) else Value.VCon(en, tag, vals) + +def applyStep(fv: Value, vals: List[Value], env: EvEnv, p: EvProg, off: Int): EvStep = + if (hasErr(vals)) valueStep(firstErr(vals)) else applyStep2(fv, vals, env, p, off) + +def applyStep2(fv: Value, vals: List[Value], env: EvEnv, p: EvProg, off: Int): EvStep = + fv match { + case Value.VClo(param, body, cenv) => bindArg(param, applyArg(vals), cenv, body, env, p, off) + case Value.VFun(name, mod) => applyFun(name, mod, vals, env, p, off) + case Value.VErr(_) => valueStep(fv) + case _ => valueStep(errAt("apply needs a function", env, p, off)) + } + +def applyArg(vals: List[Value]): Value = + if (List.len(vals) == 1) List.at(vals, 0) else Value.VTuple(vals) + +def bindArg(param: String, arg: Value, cenv: EvEnv, body: Expr, env: EvEnv, p: EvProg, off: Int): EvStep = + if (param == "" || param == "()") EvStep(body, cenv) else bindArgPat(tryPat(param, arg, p.ens), cenv, body, env, p, off) + +def bindArgPat(bound: Option[List[(String, Value)]], cenv: EvEnv, body: Expr, env: EvEnv, p: EvProg, off: Int): EvStep = + bound match { + case Some(bs) => EvStep(body, EvEnv(List.concat(bs, cenv.vars), cenv.mod)) + case None => valueStep(errAt("lambda argument does not match its parameter", env, p, off)) + } + +def applyFun(name: String, mod: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): EvStep = + if (Check.isKit(name)) valueStep(kitCall(name, vals, env, p, off)) else applyFunHit(Check.ftabGetMod(p.funs.tab, mod, name), name, vals, env, p, off) + +def applyFunHit(hit: List[Fun], name: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): EvStep = + if (List.isEmpty(hit)) valueStep(errAt(Str.concat("unknown function ", name), env, p, off)) else defStepVals(List.at(hit, 0), spreadArgs(List.len(List.at(hit, 0).params), vals), p) + +def spreadArgs(arity: Int, vals: List[Value]): List[Value] = + if (arity > 1 && List.len(vals) == 1) spreadTuple(arity, List.at(vals, 0), vals) else vals + +def spreadTuple(arity: Int, v: Value, vals: List[Value]): List[Value] = + v match { + case Value.VTuple(xs) => if (List.len(xs) == arity) xs else vals + case _ => vals + } + +def applyValue(fv: Value, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + step(applyStep(fv, vals, env, p, off), p) + +def methodStep(e: Expr, recv: Expr, name: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + if (Check.needsPh(e)) holeStep(e, env) else if (name == "apply") applyStep(step(EvStep(recv, env), p), stepAll(args, env, p), env, p, off) else methodRecv(recv, name, args, env, p, off) + +def methodRecv(recv: Expr, name: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + recv match { + case Expr.EVar(en, _) => if (isBound(env, en)) methodValue(varValue(en, env, p, off), name, args, env, p, off) else methodQual(en, name, args, env, p, off) + case _ => methodValue(step(EvStep(recv, env), p), name, args, env, p, off) + } + +def methodQual(en: String, name: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + if (Check.isKit(Str.concat(en, Str.concat(".", name)))) kitStep(Str.concat(en, Str.concat(".", name)), args, env, p, off) else if (Check.hasEn(p.ens, en)) ctorStep(en, name, args, env, p, off) else methodQualDef(Check.ftabGetMod(p.funs.tab, en, name), en, name, args, env, p, off) + +def methodQualDef(hit: List[Fun], en: String, name: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + if (List.isEmpty(hit)) methodValue(varValue(en, env, p, off), name, args, env, p, off) else defStep(List.at(hit, 0), args, env, p, off) + +def methodValue(rv: Value, name: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + if (isErr(rv)) valueStep(rv) else if (name == "require") valueStep(rv) else if (name == "copy" && isCon(rv)) valueStep(copyValue(rv, args, env, p, off)) else if (name == "handleErrorWith" && isIo(rv)) valueStep(ioHandle(rv, stepAll(args, env, p), env, p, off)) else if (name == "flatMap" && isIo(rv)) valueStep(ioBind(rv, stepAll(args, env, p), env, p, off, true)) else if (name == "map" && isIo(rv)) valueStep(ioBind(rv, stepAll(args, env, p), env, p, off, false)) else methodUser(rv, Check.ftabGetMod(p.funs.tab, tyHeadOf(rv), name), name, args, env, p, off) + +def isCon(v: Value): Bool = + v match { + case Value.VCon(_, _, _) => true + case _ => false + } + +def copyValue(rv: Value, args: List[Expr], env: EvEnv, p: EvProg, off: Int): Value = + rv match { + case Value.VCon(en, tag, fields) => conValue(en, tag, copyFields(Check.constructorFields(p.ens, qualTag(en, tag)), fields, args, 0, env, p)) + case _ => rv + } + +def copyFields(ps: List[Param], fields: List[Value], args: List[Expr], k: Int, env: EvEnv, p: EvProg): List[Value] = + if (List.isEmpty(ps) || List.isEmpty(fields)) noVals() else copyField(Emit.copyOverride(args, k, List.at(ps, 0).name), List.at(fields, 0), env, p) :: copyFields(List.tail(ps), List.tail(fields), args, k + 1, env, p) + +def copyField(hit: List[Expr], old: Value, env: EvEnv, p: EvProg): Value = + if (List.isEmpty(hit)) old else step(EvStep(List.at(hit, 0), env), p) + +def isIo(v: Value): Bool = + v match { + case Value.VIo(_) => true + case _ => false + } + +def ioHandle(rv: Value, fs: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (hasErr(fs)) firstErr(fs) else if (List.len(fs) != 1) errAt("handleErrorWith expects 1 argument", env, p, off) else Value.VIo(handleIo(ioOf(rv), List.at(fs, 0), env, p, off)) + +def ioBind(rv: Value, fs: List[Value], env: EvEnv, p: EvProg, off: Int, flat: Bool): Value = + if (hasErr(fs)) firstErr(fs) else if (List.len(fs) != 1) errAt("flatMap expects 1 argument", env, p, off) else Value.VIo(ioBindGo(ioOf(rv), List.at(fs, 0), env, p, off, flat)) + +def ioBindGo(io: IO[Value, Value], f: Value, env: EvEnv, p: EvProg, off: Int, flat: Bool): IO[Value, Value] = + io.flatMap(x => ioCont(applyValue(f, x :: noVals(), env, p, off), flat)) + +def ioCont(v: Value, flat: Bool): IO[Value, Value] = + if (flat) ioOf(v) else ioOfPure(v) + +def tyHeadOf(v: Value): String = + v match { + case Value.VCon(en, _, _) => en + case Value.VStr(_) => "String" + case Value.VInt(_) => "Int" + case Value.VBool(_) => "Bool" + case Value.VList(_) => "List" + case Value.VFloat(_) => "Float" + case Value.VMap(_) => "Map" + case Value.VSet(_) => "Set" + case Value.VBuilder(_) => "Builder" + case _ => "" + } + +def kitNs(v: Value): String = + v match { + case Value.VStr(_) => "Str" + case Value.VList(_) => "List" + case Value.VIo(_) => "IO" + case _ => tyHeadOf(v) + } + +def methodUser(rv: Value, hit: List[Fun], name: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + if (!List.isEmpty(hit)) methodDef(List.at(hit, 0), rv, args, env, p, off) else methodKit(rv, Str.concat(kitNs(rv), Str.concat(".", name)), name, args, env, p, off) + +def methodDef(d: Fun, rv: Value, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + methodDefAligned(d, rv, Check.alignCall(List.tail(d.params), args), env, p, off) + +def methodDefAligned(d: Fun, rv: Value, al: (List[Expr], String), env: EvEnv, p: EvProg, off: Int): EvStep = + al match { + case (xs, err) => if (err != "") valueStep(errAt(err, env, p, off)) else defStepVals(d, rv :: stepAll(xs, env, p), p) + } + +def methodKit(rv: Value, q: String, name: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + if (Check.isKit(q)) valueStep(kitCall(q, rv :: stepAll(args, env, p), env, p, off)) else methodAny(rv, Check.ftabGet(p.funs.tab, name), name, args, env, p, off) + +def methodAny(rv: Value, hit: List[Fun], name: String, args: List[Expr], env: EvEnv, p: EvProg, off: Int): EvStep = + if (List.isEmpty(hit)) valueStep(errAt(Str.concat("unknown method ", name), env, p, off)) else methodDef(List.at(hit, 0), rv, args, env, p, off) + +def fieldValue(e: Expr, recv: Expr, name: String, env: EvEnv, p: EvProg, off: Int): Value = + if (Check.needsPh(e)) Value.VClo("_ph", Check.rewriteHole(e, "_ph"), env) else fieldRecv(recv, name, env, p, off) + +def fieldRecv(recv: Expr, name: String, env: EvEnv, p: EvProg, off: Int): Value = + recv match { + case Expr.EVar(en, _) => if (!isBound(env, en) && Check.hasEn(p.ens, en)) Value.VCon(en, name, noVals()) else fieldOf(step(EvStep(recv, env), p), name, env, p, off) + case _ => fieldOf(step(EvStep(recv, env), p), name, env, p, off) + } + +def fieldOf(rv: Value, name: String, env: EvEnv, p: EvProg, off: Int): Value = + rv match { + case Value.VCon(en, tag, fields) => fieldAt(fields, fieldIndex(Check.constructorFields(p.ens, qualTag(en, tag)), name, 0), name, env, p, off) + case Value.VTuple(xs) => fieldAt(xs, Check.tupIdx(name), name, env, p, off) + case Value.VErr(_) => rv + case _ => errAt(Str.concat("no field ", name), env, p, off) + } + +def fieldIndex(ps: List[Param], name: String, i: Int): Int = + if (List.isEmpty(ps)) 0 - 1 else if (List.at(ps, 0).name == name) i else fieldIndex(List.tail(ps), name, i + 1) + +def fieldAt(xs: List[Value], i: Int, name: String, env: EvEnv, p: EvProg, off: Int): Value = + if (i < 0 || i >= List.len(xs)) errAt(Str.concat("no field ", name), env, p, off) else List.at(xs, i) + +def binValue(e: Expr, op: String, l: Expr, r: Expr, env: EvEnv, p: EvProg, off: Int): Value = + if (Check.needsPh(e)) Value.VClo("_ph", Check.rewriteHole(e, "_ph"), env) else if (op == "&&") andValue(step(EvStep(l, env), p), r, env, p, off) else if (op == "||") orValue(step(EvStep(l, env), p), r, env, p, off) else binVals(op, step(EvStep(l, env), p), step(EvStep(r, env), p), env, p, off) + +def andValue(lv: Value, r: Expr, env: EvEnv, p: EvProg, off: Int): Value = + lv match { + case Value.VBool(b) => if (b) step(EvStep(r, env), p) else lv + case Value.VErr(_) => lv + case _ => errAt("&& needs Bool", env, p, off) + } + +def orValue(lv: Value, r: Expr, env: EvEnv, p: EvProg, off: Int): Value = + lv match { + case Value.VBool(b) => if (b) lv else step(EvStep(r, env), p) + case Value.VErr(_) => lv + case _ => errAt("|| needs Bool", env, p, off) + } + +def binVals(op: String, lv: Value, rv: Value, env: EvEnv, p: EvProg, off: Int): Value = + if (isErr(lv)) lv else if (isErr(rv)) rv else if (op == "::") consValue(lv, rv, env, p, off) else if (op == "==") Value.VBool(valEq(lv, rv)) else if (op == "!=") Value.VBool(!valEq(lv, rv)) else if (isFloat(lv)) binFloats(op, floatOf(lv), floatOf(rv), env, p, off) else binInt(op, lv, rv, env, p, off) + +def binFloats(op: String, a: Float, b: Float, env: EvEnv, p: EvProg, off: Int): Value = + if (op == "+") Value.VFloat(a + b) else if (op == "-") Value.VFloat(a - b) else if (op == "*") Value.VFloat(a * b) else if (op == "/") Value.VFloat(a / b) else if (op == "<") Value.VBool(a < b) else if (op == "<=") Value.VBool(a <= b) else if (op == ">") Value.VBool(a > b) else if (op == ">=") Value.VBool(a >= b) else unsupported(Str.concat("Float operator ", op), env, p, off) + +def consValue(lv: Value, rv: Value, env: EvEnv, p: EvProg, off: Int): Value = + rv match { + case Value.VList(xs) => Value.VList(lv :: xs) + case _ => errAt(":: needs a List", env, p, off) + } + +def binInt(op: String, lv: Value, rv: Value, env: EvEnv, p: EvProg, off: Int): Value = + lv match { + case Value.VInt(a) => binInt2(op, a, rv, env, p, off) + case _ => errAt(Str.concat("operator ", Str.concat(op, " needs Int")), env, p, off) + } + +def binInt2(op: String, a: Int, rv: Value, env: EvEnv, p: EvProg, off: Int): Value = + rv match { + case Value.VInt(b) => binInts(op, a, b, env, p, off) + case _ => errAt(Str.concat("operator ", Str.concat(op, " needs Int")), env, p, off) + } + +def binInts(op: String, a: Int, b: Int, env: EvEnv, p: EvProg, off: Int): Value = + if (op == "+") Value.VInt(a + b) else if (op == "-") Value.VInt(a - b) else if (op == "*") Value.VInt(a * b) else if (op == "/") divInts(a, b, false, env, p, off) else if (op == "%") divInts(a, b, true, env, p, off) else if (op == "<") Value.VBool(a < b) else if (op == "<=") Value.VBool(a <= b) else if (op == ">") Value.VBool(a > b) else if (op == ">=") Value.VBool(a >= b) else bitInts(op, a, b, env, p, off) + +def bitInts(op: String, a: Int, b: Int, env: EvEnv, p: EvProg, off: Int): Value = + if (op == "&") Value.VInt(a & b) else if (op == "|") Value.VInt(a | b) else if (op == "^") Value.VInt(a ^ b) else if (op == "<<") Value.VInt(a << b) else if (op == ">>") Value.VInt(a >> b) else unsupported(Str.concat("operator ", op), env, p, off) + +def divInts(a: Int, b: Int, rem: Bool, env: EvEnv, p: EvProg, off: Int): Value = + if (b == 0) errAt("division by zero", env, p, off) else if (rem) Value.VInt(a % b) else Value.VInt(a / b) + +def unValue(e: Expr, op: String, inner: Expr, env: EvEnv, p: EvProg, off: Int): Value = + if (Check.needsPh(e)) Value.VClo("_ph", Check.rewriteHole(e, "_ph"), env) else unVal(op, step(EvStep(inner, env), p), env, p, off) + +def unVal(op: String, v: Value, env: EvEnv, p: EvProg, off: Int): Value = + v match { + case Value.VBool(b) => if (op == "!") Value.VBool(!b) else unsupported(Str.concat("operator ", op), env, p, off) + case Value.VInt(n) => if (op == "-") Value.VInt(0 - n) else if (op == "~") Value.VInt(~n) else unsupported(Str.concat("operator ", op), env, p, off) + case Value.VFloat(f) => if (op == "-") Value.VFloat(0.0 - f) else unsupported(Str.concat("operator ", op), env, p, off) + case Value.VErr(_) => v + case _ => unsupported(Str.concat("operator ", op), env, p, off) + } + +def valEq(a: Value, b: Value): Bool = + a match { + case Value.VUnit => isUnit(b) + case Value.VInt(x) => eqInt(x, b) + case Value.VBool(x) => eqBool(x, b) + case Value.VStr(x) => eqStr(x, b) + case Value.VList(xs) => eqList(xs, b) + case Value.VTuple(xs) => eqTuple(xs, b) + case Value.VCon(_, tag, fields) => eqCon(tag, fields, b) + case Value.VFloat(x) => eqFloat(x, b) + case Value.VMap(kvs) => eqList(pairsToTuples(kvs), mapTuples(b)) + case Value.VSet(xs) => eqList(xs, setTuples(b)) + case _ => false + } + +def eqFloat(x: Float, b: Value): Bool = + b match { + case Value.VFloat(y) => x == y + case _ => false + } + +def mapTuples(b: Value): Value = + b match { + case Value.VMap(kvs) => Value.VList(pairsToTuples(kvs)) + case _ => Value.VUnit + } + +def setTuples(b: Value): Value = + b match { + case Value.VSet(xs) => Value.VList(xs) + case _ => Value.VUnit + } + +def isUnit(b: Value): Bool = + b match { + case Value.VUnit => true + case _ => false + } + +def eqInt(x: Int, b: Value): Bool = + b match { + case Value.VInt(y) => x == y + case _ => false + } + +def eqBool(x: Bool, b: Value): Bool = + b match { + case Value.VBool(y) => x == y + case _ => false + } + +def eqStr(x: String, b: Value): Bool = + b match { + case Value.VStr(y) => x == y + case _ => false + } + +def eqList(xs: List[Value], b: Value): Bool = + b match { + case Value.VList(ys) => eqAll(xs, ys) + case _ => false + } + +def eqTuple(xs: List[Value], b: Value): Bool = + b match { + case Value.VTuple(ys) => eqAll(xs, ys) + case _ => false + } + +def eqCon(tag: String, fields: List[Value], b: Value): Bool = + b match { + case Value.VCon(_, tag2, fields2) => tag == tag2 && eqAll(fields, fields2) + case _ => false + } + +def eqAll(xs: List[Value], ys: List[Value]): Bool = + if (List.len(xs) != List.len(ys)) false else eqAllGo(xs, ys) + +def eqAllGo(xs: List[Value], ys: List[Value]): Bool = + if (List.isEmpty(xs)) true else valEq(List.at(xs, 0), List.at(ys, 0)) && eqAllGo(List.tail(xs), List.tail(ys)) + +def matchStep(v: Value, arms: List[Arm], env: EvEnv, p: EvProg, off: Int): EvStep = + if (isErr(v)) valueStep(v) else if (List.isEmpty(arms)) valueStep(errAt("no match arm matched", env, p, off)) else matchArm(v, List.at(arms, 0), List.tail(arms), env, p, off) + +def matchArm(v: Value, arm: Arm, rest: List[Arm], env: EvEnv, p: EvProg, off: Int): EvStep = + arm match { + case Arm(pat, g, body) => matchBound(v, tryPat(pat, v, p.ens), g, body, rest, env, p, off) + } + +def matchBound(v: Value, bound: Option[List[(String, Value)]], g: String, body: Expr, rest: List[Arm], env: EvEnv, p: EvProg, off: Int): EvStep = + bound match { + case Some(bs) => matchGuard(v, EvEnv(List.concat(bs, env.vars), env.mod), g, body, rest, env, p, off) + case None => matchStep(v, rest, env, p, off) + } + +def matchGuard(v: Value, benv: EvEnv, g: String, body: Expr, rest: List[Arm], env: EvEnv, p: EvProg, off: Int): EvStep = + if (g == "") EvStep(body, benv) else matchGuardVal(v, step(EvStep(Emit.parseGuard(g), benv), p), benv, body, rest, env, p, off) + +def matchGuardVal(v: Value, gv: Value, benv: EvEnv, body: Expr, rest: List[Arm], env: EvEnv, p: EvProg, off: Int): EvStep = + gv match { + case Value.VBool(b) => if (b) EvStep(body, benv) else matchStep(v, rest, env, p, off) + case Value.VErr(_) => valueStep(gv) + case _ => valueStep(errAt("guard is not Bool", env, p, off)) + } + +def tryPat(pat: String, v: Value, ens: List[En]): Option[List[(String, Value)]] = + if (Parse.patternAlternativeAt(pat, 0, 0) >= 0) tryOr(pat, Parse.patternAlternativeAt(pat, 0, 0), v, ens) else if (Check.asAt(pat, 0) >= 0) tryAs(pat, Check.asAt(pat, 0), v, ens) else if (Check.consAt(pat, 0) >= 0) tryCons(pat, Check.consAt(pat, 0), v, ens) else if (pat == "_") Some(noVars()) else if (pat == "[]") tryNil(v) else if (Emit.isStrPat(pat)) tryLit(valEq(v, Value.VStr(Emit.unquote(pat)))) else if (Emit.isIntPat(pat)) tryLit(valEq(v, Value.VInt(Str.toInt(pat, 0)))) else if (Emit.isBoolPat(pat)) tryLit(valEq(v, Value.VBool(pat == "true"))) else if (Check.isTuplePat(pat)) tryTuple(Check.splitComma(Str.slice(pat, 1, Str.len(pat) - 1)), v, ens) else if (Check.patBind(pat) == "") tryBare(pat, v, ens) else tryTag(Check.patCore(pat), v, ens, Check.splitComma(Check.patBind(pat))) + +def tryLit(ok: Bool): Option[List[(String, Value)]] = + if (ok) Some(noVars()) else None + +def tryNil(v: Value): Option[List[(String, Value)]] = + v match { + case Value.VList(xs) => tryLit(List.isEmpty(xs)) + case _ => None + } + +def tryOr(pat: String, i: Int, v: Value, ens: List[En]): Option[List[(String, Value)]] = + tryOrLeft(tryPat(Str.slice(pat, 0, i), v, ens), Str.slice(pat, i + 3, Str.len(pat)), v, ens) + +def tryOrLeft(l: Option[List[(String, Value)]], right: String, v: Value, ens: List[En]): Option[List[(String, Value)]] = + l match { + case Some(_) => l + case None => tryPat(right, v, ens) + } + +def tryAs(pat: String, i: Int, v: Value, ens: List[En]): Option[List[(String, Value)]] = + tryAsInner(Str.slice(pat, 0, i), tryPat(Str.slice(pat, i + 3, Str.len(pat)), v, ens), v) + +def tryAsInner(name: String, inner: Option[List[(String, Value)]], v: Value): Option[List[(String, Value)]] = + inner match { + case Some(bs) => Some((name, v) :: bs) + case None => None + } + +def tryCons(pat: String, i: Int, v: Value, ens: List[En]): Option[List[(String, Value)]] = + v match { + case Value.VList(xs) => if (List.isEmpty(xs)) None else tryBoth(tryPat(Str.slice(pat, 0, i), List.at(xs, 0), ens), Str.slice(pat, i + 4, Str.len(pat)), Value.VList(List.tail(xs)), ens) + case _ => None + } + +def tryBoth(l: Option[List[(String, Value)]], pat2: String, v2: Value, ens: List[En]): Option[List[(String, Value)]] = + l match { + case Some(bs) => tryJoin(bs, tryPat(pat2, v2, ens)) + case None => None + } + +def tryJoin(bs: List[(String, Value)], r: Option[List[(String, Value)]]): Option[List[(String, Value)]] = + r match { + case Some(bs2) => Some(List.concat(bs, bs2)) + case None => None + } + +def tryTuple(comps: List[String], v: Value, ens: List[En]): Option[List[(String, Value)]] = + if (List.len(comps) == 1) tryPat(List.at(comps, 0), v, ens) else tryTupleVals(comps, v, ens) + +def tryTupleVals(comps: List[String], v: Value, ens: List[En]): Option[List[(String, Value)]] = + v match { + case Value.VTuple(xs) => tryAll(comps, xs, ens) + case _ => None + } + +def tryAll(pats: List[String], vals: List[Value], ens: List[En]): Option[List[(String, Value)]] = + if (List.len(pats) != List.len(vals)) None else if (List.isEmpty(pats)) Some(noVars()) else tryAllHead(tryPat(List.at(pats, 0), List.at(vals, 0), ens), List.tail(pats), List.tail(vals), ens) + +def tryAllHead(l: Option[List[(String, Value)]], pats: List[String], vals: List[Value], ens: List[En]): Option[List[(String, Value)]] = + l match { + case Some(bs) => tryJoin(bs, tryAll(pats, vals, ens)) + case None => None + } + +def tryBare(pat: String, v: Value, ens: List[En]): Option[List[(String, Value)]] = + if (Check.dotAt(pat, 0) >= 0 || Check.enOfCase(ens, pat) != "") tryTag(pat, v, ens, noNames()) else Some((pat, v) :: noVars()) + +def tagOf(core: String): String = + if (Check.dotAt(core, 0) < 0) core else Str.slice(core, Check.dotAt(core, 0) + 1, Str.len(core)) + +def enOf(core: String): String = + if (Check.dotAt(core, 0) < 0) "" else Str.slice(core, 0, Check.dotAt(core, 0)) + +def tryTag(core: String, v: Value, ens: List[En], subs: List[String]): Option[List[(String, Value)]] = + v match { + case Value.VCon(en, tag, fields) => if (tagOf(core) == tag && (enOf(core) == "" || enOf(core) == en)) tryFields(subs, fields, Check.constructorFields(ens, core), ens, 0) else None + case _ => None + } + +def tryFields(subs: List[String], fields: List[Value], params: List[Param], ens: List[En], i: Int): Option[List[(String, Value)]] = + if (List.isEmpty(subs)) Some(noVars()) else tryField(List.at(subs, 0), List.tail(subs), fields, params, ens, i) + +def tryField(sub: String, rest: List[String], fields: List[Value], params: List[Param], ens: List[En], i: Int): Option[List[(String, Value)]] = + if (Check.eqAt(sub, 0) >= 0) tryFieldAt(Str.drop(sub, Check.eqAt(sub, 0) + 3), fieldIndex(params, Str.take(sub, Check.eqAt(sub, 0)), 0), rest, fields, params, ens, i) else tryFieldAt(sub, i, rest, fields, params, ens, i) + +def tryFieldAt(sub: String, k: Int, rest: List[String], fields: List[Value], params: List[Param], ens: List[En], i: Int): Option[List[(String, Value)]] = + if (k < 0 || k >= List.len(fields)) None else tryFieldNext(tryPat(sub, List.at(fields, k), ens), rest, fields, params, ens, i + 1) + +def tryFieldNext(l: Option[List[(String, Value)]], rest: List[String], fields: List[Value], params: List[Param], ens: List[En], i: Int): Option[List[(String, Value)]] = + l match { + case Some(bs) => tryJoin(bs, tryFields(rest, fields, params, ens, i)) + case None => None + } + +def forValue(bs: List[Bind], body: Expr, env: EvEnv, p: EvProg, off: Int, drew: Bool): Value = + if (List.isEmpty(bs)) forBody(step(EvStep(body, env), p), drew) else forBind(List.at(bs, 0), List.tail(bs), body, env, p, off, drew) + +def forBody(v: Value, drew: Bool): Value = + if (drew) Value.VIo(ioOfPure(v)) else v + +def forBind(b: Bind, rest: List[Bind], body: Expr, env: EvEnv, p: EvProg, off: Int, drew: Bool): Value = + b match { + case Bind(draw, name, value) => forBound(draw, name, step(EvStep(value, env), p), rest, body, env, p, off, drew) + } + +def forBound(draw: Bool, name: String, v: Value, rest: List[Bind], body: Expr, env: EvEnv, p: EvProg, off: Int, drew: Bool): Value = + if (isErr(v)) v else if (name == "__guard") forGuard(v, rest, body, env, p, off, drew) else if (!draw) forPure(name, v, rest, body, env, p, off, drew) else forDraw(name, v, rest, body, env, p, off) + +def forGuard(v: Value, rest: List[Bind], body: Expr, env: EvEnv, p: EvProg, off: Int, drew: Bool): Value = + v match { + case Value.VBool(b) => if (b) forValue(rest, body, env, p, off, drew) else Value.VIo(failWith(Value.VStr("guard"))) + case _ => errAt("guard is not Bool", env, p, off) + } + +def forPure(name: String, v: Value, rest: List[Bind], body: Expr, env: EvEnv, p: EvProg, off: Int, drew: Bool): Value = + tryPat(name, v, p.ens) match { + case Some(vars) => forValue(rest, body, EvEnv(List.concat(vars, env.vars), env.mod), p, off, drew) + case None => errAt("for binding does not match", env, p, off) + } + +def forDraw(name: String, v: Value, rest: List[Bind], body: Expr, env: EvEnv, p: EvProg, off: Int): Value = + v match { + case Value.VIo(io) => Value.VIo(forDrawIo(io, name, rest, body, env, p, off)) + case Value.VUnit => Value.VIo(forDrawIo(pureIo(v), name, rest, body, env, p, off)) + case _ => errAt("<- needs IO", env, p, off) + } + +def forDrawIo(io: IO[Value, Value], name: String, rest: List[Bind], body: Expr, env: EvEnv, p: EvProg, off: Int): IO[Value, Value] = + io.flatMap(x => ioOf(forPure(name, x, rest, body, env, p, off, true))) + +def listValue(vals: List[Value]): Value = + if (hasErr(vals)) firstErr(vals) else Value.VList(vals) + +def tupleValue(vals: List[Value]): Value = + if (hasErr(vals)) firstErr(vals) else if (List.isEmpty(vals)) Value.VUnit else Value.VTuple(vals) + +def interpAcc(parts: List[Expr], env: EvEnv, p: EvProg, b: Builder): Value = + if (List.isEmpty(parts)) Value.VStr(Builder.result(b)) else interpPart(step(EvStep(List.at(parts, 0), env), p), List.tail(parts), env, p, b) + +def interpPart(v: Value, rest: List[Expr], env: EvEnv, p: EvProg, b: Builder): Value = + v match { + case Value.VStr(s) => interpAcc(rest, env, p, Builder.append(b, s)) + case Value.VInt(n) => interpAcc(rest, env, p, Builder.append(b, Str.fromInt(n))) + case Value.VBool(bv) => interpAcc(rest, env, p, Builder.append(b, if (bv) "1" else "0")) + case Value.VFloat(f) => interpAcc(rest, env, p, Builder.append(b, fmtFloat(f))) + case Value.VErr(_) => v + case _ => unsupported(Str.concat("interpolation of a non-scalar value ", show(v)), env, p, 0) + } + +def intOf(v: Value): Int = + v match { + case Value.VInt(n) => n + case _ => 0 + } + +def strOf(v: Value): String = + v match { + case Value.VStr(s) => s + case _ => "" + } + +def boolOf(v: Value): Bool = + v match { + case Value.VBool(b) => b + case _ => false + } + +def floatOf(v: Value): Float = + v match { + case Value.VFloat(f) => f + case _ => 0.0 + } + +def listOf(v: Value): List[Value] = + v match { + case Value.VList(xs) => xs + case _ => noVals() + } + +def mapOf(v: Value): List[(Value, Value)] = + v match { + case Value.VMap(kvs) => kvs + case _ => noPairs() + } + +def setOf(v: Value): List[Value] = + v match { + case Value.VSet(xs) => xs + case _ => noVals() + } + +def builderOf(v: Value): Builder = + v match { + case Value.VBuilder(b) => b + case _ => Builder.empty() + } + +def isInt(v: Value): Bool = + v match { + case Value.VInt(_) => true + case _ => false + } + +def isStr(v: Value): Bool = + v match { + case Value.VStr(_) => true + case _ => false + } + +def isFloat(v: Value): Bool = + v match { + case Value.VFloat(_) => true + case _ => false + } + +def intAt(vals: List[Value], i: Int): Int = + intOf(List.at(vals, i)) + +def strAt(vals: List[Value], i: Int): String = + strOf(List.at(vals, i)) + +def listAt(vals: List[Value], i: Int): List[Value] = + listOf(List.at(vals, i)) + +def vStrs(xs: List[String]): Value = + Value.VList(List.map(xs, s => Value.VStr(s))) + +def vInts(xs: List[Int]): Value = + Value.VList(List.map(xs, n => Value.VInt(n))) + +def vLists(xss: List[List[Value]]): Value = + Value.VList(List.map(xss, ys => Value.VList(ys))) + +def pairFst(pr: (Value, Value)): Value = + pr match { + case (a, _) => a + } + +def pairSnd(pr: (Value, Value)): Value = + pr match { + case (_, b) => b + } + +def pairsToTuples(ps: List[(Value, Value)]): List[Value] = + List.map(ps, pr => Value.VTuple(pairFst(pr) :: pairSnd(pr) :: noVals())) + +def tupleToPair(t: Value): (Value, Value) = + t match { + case Value.VTuple(ys) => (List.at(ys, 0), List.at(ys, 1)) + case _ => (t, Value.VUnit) + } + +def tuplesToPairs(xs: List[Value]): List[(Value, Value)] = + List.map(xs, t => tupleToPair(t)) + +def someV(v: Value): Value = + Value.VCon("Option", "Some", v :: noVals()) + +def noneV(): Value = + Value.VCon("Option", "None", noVals()) + +def optValue(o: Option[Value]): Value = + o match { + case Some(v) => someV(v) + case None => noneV() + } + +def optOr(o: Option[Value], d: Value): Value = + o match { + case Some(v) => v + case None => d + } + +def isSome(o: Option[Value]): Bool = + o match { + case Some(_) => true + case None => false + } + +def okV(v: Value): Value = + Value.VCon("Result", "Ok", v :: noVals()) + +def errV(v: Value): Value = + Value.VCon("Result", "Err", v :: noVals()) + +def cmpInt(x: Int, y: Int): Int = + if (x < y) 0 - 1 else if (x > y) 1 else 0 + +def cmpStr(a: String, b: String, i: Int): Int = + if (i >= Str.len(a) && i >= Str.len(b)) 0 else if (i >= Str.len(a)) 0 - 1 else if (i >= Str.len(b)) 1 else if (Str.charAt(a, i) != Str.charAt(b, i)) cmpInt(Str.charAt(a, i), Str.charAt(b, i)) else cmpStr(a, b, i + 1) + +def cmpValue(a: Value, b: Value): Int = + a match { + case Value.VInt(x) => cmpInt(x, intOf(b)) + case Value.VStr(x) => cmpStr(x, strOf(b), 0) + case _ => 0 + } + +def keyOk(k: Value): Bool = + isInt(k) || isStr(k) + +def keysOk(ks: List[Value]): Bool = + List.forall(ks, k => keyOk(k)) + +def mapPut(kvs: List[(Value, Value)], k: Value, v: Value): List[(Value, Value)] = + if (List.isEmpty(kvs)) (k, v) :: noPairs() else mapPutHd(List.at(kvs, 0), List.tail(kvs), k, v) + +def mapPutHd(h: (Value, Value), rest: List[(Value, Value)], k: Value, v: Value): List[(Value, Value)] = + if (cmpValue(k, pairFst(h)) < 0) (k, v) :: h :: rest else if (cmpValue(k, pairFst(h)) == 0) (k, v) :: rest else h :: mapPut(rest, k, v) + +def mapPutAll(kvs: List[(Value, Value)], more: List[(Value, Value)]): List[(Value, Value)] = + if (List.isEmpty(more)) kvs else mapPutAll(mapPut(kvs, pairFst(List.at(more, 0)), pairSnd(List.at(more, 0))), List.tail(more)) + +def mapFind(kvs: List[(Value, Value)], k: Value): Option[Value] = + if (List.isEmpty(kvs)) None else mapFindHd(List.at(kvs, 0), List.tail(kvs), k) + +def mapFindHd(h: (Value, Value), rest: List[(Value, Value)], k: Value): Option[Value] = + if (cmpValue(k, pairFst(h)) == 0) Some(pairSnd(h)) else if (cmpValue(k, pairFst(h)) < 0) None else mapFind(rest, k) + +def mapHas(kvs: List[(Value, Value)], k: Value): Bool = + isSome(mapFind(kvs, k)) + +def mapDel(kvs: List[(Value, Value)], k: Value): List[(Value, Value)] = + List.filter(kvs, pr => cmpValue(pairFst(pr), k) != 0) + +def pairKeys(kvs: List[(Value, Value)]): List[Value] = + List.map(kvs, pr => pairFst(pr)) + +def pairVals(kvs: List[(Value, Value)]): List[Value] = + List.map(kvs, pr => pairSnd(pr)) + +def setPut(xs: List[Value], k: Value): List[Value] = + if (List.isEmpty(xs)) k :: noVals() else if (cmpValue(k, List.at(xs, 0)) < 0) k :: xs else if (cmpValue(k, List.at(xs, 0)) == 0) xs else List.at(xs, 0) :: setPut(List.tail(xs), k) + +def setPutAll(xs: List[Value], more: List[Value]): List[Value] = + if (List.isEmpty(more)) xs else setPutAll(setPut(xs, List.at(more, 0)), List.tail(more)) + +def setHas(xs: List[Value], k: Value): Bool = + List.exists(xs, x => cmpValue(x, k) == 0) + +def setDel(xs: List[Value], k: Value): List[Value] = + List.filter(xs, x => cmpValue(x, k) != 0) + +def apply1(f: Value, x: Value, env: EvEnv, p: EvProg, off: Int): Value = + applyValue(f, x :: noVals(), env, p, off) + +def apply2(f: Value, a: Value, b: Value, env: EvEnv, p: EvProg, off: Int): Value = + applyValue(f, a :: b :: noVals(), env, p, off) + +def mapVals(xs: List[Value], f: Value, env: EvEnv, p: EvProg, off: Int): List[Value] = + if (List.isEmpty(xs)) noVals() else apply1(f, List.at(xs, 0), env, p, off) :: mapVals(List.tail(xs), f, env, p, off) + +def mapValsThen(xs: List[Value], f: Value, env: EvEnv, p: EvProg, off: Int, k: List[Value] => Value): Value = + mapValsGot(mapVals(xs, f, env, p, off), k) + +def mapValsGot(rs: List[Value], k: List[Value] => Value): Value = + if (hasErr(rs)) firstErr(rs) else k(rs) + +def keepWhere(xs: List[Value], rs: List[Value], want: Bool): List[Value] = + List.map(List.filter(List.zip(xs, rs), pr => boolOf(pairSnd(pr)) == want), pr => pairFst(pr)) + +def scanPred(xs: List[Value], f: Value, want: Bool, i: Int, env: EvEnv, p: EvProg, off: Int): Value = + if (List.isEmpty(xs)) Value.VInt(0 - 1) else scanPredHit(apply1(f, List.at(xs, 0), env, p, off), List.tail(xs), f, want, i, env, p, off) + +def scanPredHit(r: Value, rest: List[Value], f: Value, want: Bool, i: Int, env: EvEnv, p: EvProg, off: Int): Value = + r match { + case Value.VBool(b) => if (b == want) Value.VInt(i) else scanPred(rest, f, want, i + 1, env, p, off) + case Value.VErr(_) => r + case _ => errAt("predicate is not Bool", env, p, off) + } + +def withIdx(r: Value, k: Value => Value): Value = + if (isErr(r)) r else k(r) + +def lastTrue(rs: List[Value], i: Int, hit: Int): Int = + if (List.isEmpty(rs)) hit else lastTrue(List.tail(rs), i + 1, if (boolOf(List.at(rs, 0))) i else hit) + +def kitCall(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (hasErr(vals)) firstErr(vals) else if (Str.startsWith(f, "Str.")) strKit(f, vals, env, p, off) else if (Str.startsWith(f, "List.")) listKit(f, vals, env, p, off) else if (Str.startsWith(f, "Map.")) mapKit(f, vals, env, p, off) else if (Str.startsWith(f, "Set.")) setKit(f, vals, env, p, off) else if (Str.startsWith(f, "Json.")) jsonKit(f, vals, env, p, off) else if (Str.startsWith(f, "IO.")) ioKit(f, vals, env, p, off) else if (Str.startsWith(f, "Stream.")) streamKit(f, vals, env, p, off) else if (Str.startsWith(f, "Fs.") || Str.startsWith(f, "Sys.")) fsKit(f, vals, env, p, off) else if (Str.startsWith(f, "Net.")) netKit(f, vals, env, p, off) else if (isHandleKit(f)) handleKit(f, vals, env, p, off) else if (Str.startsWith(f, "Property.")) propKit(f, vals, env, p, off) else if (Str.startsWith(f, "Timeline.")) tlKit(f, vals, env, p, off) else if (Str.startsWith(f, "Verdict.")) verdictKit(f, vals, env, p, off) else miscKit(f, vals, env, p, off) + +def strKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Str.fromInt") Value.VStr(Str.fromInt(intAt(vals, 0))) else if (f == "Str.fromBool") Value.VStr(Str.fromBool(boolOf(List.at(vals, 0)))) else if (f == "Str.concat") Value.VStr(Str.concat(strAt(vals, 0), strAt(vals, 1))) else if (f == "Str.len") Value.VInt(Str.len(strAt(vals, 0))) else if (f == "Str.byteLen") Value.VInt(Str.byteLen(strAt(vals, 0))) else if (f == "Str.eq") Value.VBool(strAt(vals, 0) == strAt(vals, 1)) else if (f == "Str.slice") Value.VStr(Str.slice(strAt(vals, 0), intAt(vals, 1), intAt(vals, 2))) else if (f == "Str.byteSlice") Value.VStr(Str.byteSlice(strAt(vals, 0), intAt(vals, 1), intAt(vals, 2))) else if (f == "Str.charAt") Value.VInt(Str.charAt(strAt(vals, 0), intAt(vals, 1))) else if (f == "Str.toInt") Value.VInt(Str.toInt(strAt(vals, 0), intAt(vals, 1))) else if (f == "Str.repeat") Value.VStr(Str.repeat(strAt(vals, 0), intAt(vals, 1))) else if (f == "Str.startsWith") Value.VBool(Str.startsWith(strAt(vals, 0), strAt(vals, 1))) else if (f == "Str.endsWith") Value.VBool(Str.endsWith(strAt(vals, 0), strAt(vals, 1))) else if (f == "Str.contains") Value.VBool(Str.contains(strAt(vals, 0), strAt(vals, 1))) else strKit2(f, vals, env, p, off) + +def strKit2(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Str.matches") Value.VBool(Str.matches(strAt(vals, 0), strAt(vals, 1))) else if (f == "Str.capture") vStrs(Str.capture(strAt(vals, 0), strAt(vals, 1))) else if (f == "Str.replaceMatch") Value.VStr(Str.replaceMatch(strAt(vals, 0), strAt(vals, 1), strAt(vals, 2))) else if (f == "Str.indexOf") Value.VInt(Str.indexOf(strAt(vals, 0), strAt(vals, 1))) else if (f == "Str.lastIndexOf") Value.VInt(Str.lastIndexOf(strAt(vals, 0), strAt(vals, 1))) else if (f == "Str.isEmpty") Value.VBool(Str.isEmpty(strAt(vals, 0))) else if (f == "Str.nonEmpty") Value.VBool(Str.nonEmpty(strAt(vals, 0))) else if (f == "Str.isBlank") Value.VBool(Str.isBlank(strAt(vals, 0))) else if (f == "Str.lines") vStrs(Str.lines(strAt(vals, 0))) else if (f == "Str.trim") Value.VStr(Str.trim(strAt(vals, 0))) else if (f == "Str.reverse") Value.VStr(Str.reverse(strAt(vals, 0))) else if (f == "Str.toLower") Value.VStr(Str.toLower(strAt(vals, 0))) else if (f == "Str.toUpper") Value.VStr(Str.toUpper(strAt(vals, 0))) else if (f == "Str.capitalize") Value.VStr(Str.capitalize(strAt(vals, 0))) else strKit3(f, vals, env, p, off) + +def strKit3(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Str.take") Value.VStr(Str.take(strAt(vals, 0), intAt(vals, 1))) else if (f == "Str.drop") Value.VStr(Str.drop(strAt(vals, 0), intAt(vals, 1))) else if (f == "Str.takeRight") Value.VStr(Str.takeRight(strAt(vals, 0), intAt(vals, 1))) else if (f == "Str.dropRight") Value.VStr(Str.dropRight(strAt(vals, 0), intAt(vals, 1))) else if (f == "Str.padLeft") Value.VStr(Str.padLeft(strAt(vals, 0), intAt(vals, 1), strAt(vals, 2))) else if (f == "Str.padRight") Value.VStr(Str.padRight(strAt(vals, 0), intAt(vals, 1), strAt(vals, 2))) else if (f == "Str.replace") Value.VStr(Str.replace(strAt(vals, 0), strAt(vals, 1), strAt(vals, 2))) else if (f == "Str.stripPrefix") Value.VStr(Str.stripPrefix(strAt(vals, 0), strAt(vals, 1))) else if (f == "Str.stripSuffix") Value.VStr(Str.stripSuffix(strAt(vals, 0), strAt(vals, 1))) else if (f == "Str.split") vStrs(Str.split(strAt(vals, 0), strAt(vals, 1))) else unsupported(Str.concat("kit ", f), env, p, off) + +def listKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "List.len") Value.VInt(List.len(listAt(vals, 0))) else if (f == "List.at") listAtKit(listAt(vals, 0), intAt(vals, 1), env, p, off) else if (f == "List.tail") listTailKit(listAt(vals, 0), env, p, off) else if (f == "List.isEmpty") Value.VBool(List.isEmpty(listAt(vals, 0))) else if (f == "List.nonEmpty") Value.VBool(List.nonEmpty(listAt(vals, 0))) else if (f == "List.concat") Value.VList(List.concat(listAt(vals, 0), listAt(vals, 1))) else if (f == "List.reverse") Value.VList(List.reverse(listAt(vals, 0))) else if (f == "List.join") Value.VStr(List.join(List.map(listAt(vals, 0), v => strOf(v)), strAt(vals, 1))) else if (f == "List.head") headKit(listAt(vals, 0)) else if (f == "List.cons") Value.VList(List.at(vals, 0) :: listAt(vals, 1)) else if (f == "List.empty") Value.VList(noVals()) else if (f == "List.take") Value.VList(List.take(listAt(vals, 0), intAt(vals, 1))) else if (f == "List.drop") Value.VList(List.drop(listAt(vals, 0), intAt(vals, 1))) else if (f == "List.takeRight") Value.VList(List.takeRight(listAt(vals, 0), intAt(vals, 1))) else if (f == "List.dropRight") Value.VList(List.dropRight(listAt(vals, 0), intAt(vals, 1))) else if (f == "List.init") Value.VList(List.init(listAt(vals, 0))) else if (f == "List.last") Value.VList(List.last(listAt(vals, 0))) else if (f == "List.flatten") Value.VList(List.flatten(List.map(listAt(vals, 0), x => listOf(x)))) else listKit2(f, vals, env, p, off) + +def listKit2(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "List.setAt") Value.VList(List.setAt(listAt(vals, 0), intAt(vals, 1), List.at(vals, 2))) else if (f == "List.getOrElse") List.getOrElse(listAt(vals, 0), intAt(vals, 1), List.at(vals, 2)) else if (f == "List.fill") Value.VList(List.fill(intAt(vals, 0), List.at(vals, 1))) else if (f == "List.range") vInts(List.range(intAt(vals, 0), intAt(vals, 1))) else if (f == "List.padTo") Value.VList(List.padTo(listAt(vals, 0), intAt(vals, 1), List.at(vals, 2))) else if (f == "List.append") Value.VList(List.append(listAt(vals, 0), List.at(vals, 1))) else if (f == "List.contains") Value.VBool(List.contains(listAt(vals, 0), List.at(vals, 1))) else if (f == "List.indexOf") Value.VInt(List.indexOf(listAt(vals, 0), List.at(vals, 1))) else if (f == "List.lastIndexOf") Value.VInt(List.lastIndexOf(listAt(vals, 0), List.at(vals, 1))) else if (f == "List.distinct") Value.VList(List.distinct(listAt(vals, 0))) else if (f == "List.indices") vInts(List.indices(listAt(vals, 0))) else if (f == "List.inits") vLists(List.inits(listAt(vals, 0))) else if (f == "List.tails") vLists(List.tails(listAt(vals, 0))) else if (f == "List.transpose") vLists(List.transpose(List.map(listAt(vals, 0), x => listOf(x)))) else if (f == "List.zipWithIndex") Value.VList(List.map(List.zipWithIndex(listAt(vals, 0)), __tup => __tup match { + case (i, x) => Value.VTuple(Value.VInt(i) :: x :: noVals()) +})) else if (f == "List.zip") Value.VList(pairsToTuples(List.zip(listAt(vals, 0), listAt(vals, 1)))) else if (f == "List.zipAll") Value.VList(pairsToTuples(List.zipAll(listAt(vals, 0), listAt(vals, 1), List.at(vals, 2), List.at(vals, 3)))) else if (f == "List.unzip") unzipKit(List.unzip(tuplesToPairs(listAt(vals, 0)))) else listKit3(f, vals, env, p, off) + +def unzipKit(pr: (List[Value], List[Value])): Value = + pr match { + case (a, b) => Value.VTuple(Value.VList(a) :: Value.VList(b) :: noVals()) + } + +def listKit3(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "List.interleave") Value.VList(List.interleave(listAt(vals, 0), listAt(vals, 1))) else if (f == "List.intersperse") Value.VList(List.intersperse(listAt(vals, 0), List.at(vals, 1))) else if (f == "List.diff") Value.VList(List.diff(listAt(vals, 0), listAt(vals, 1))) else if (f == "List.intersect") Value.VList(List.intersect(listAt(vals, 0), listAt(vals, 1))) else if (f == "List.startsWith") Value.VBool(eqAll(List.take(listAt(vals, 0), List.len(listAt(vals, 1))), listAt(vals, 1))) else if (f == "List.endsWith") Value.VBool(eqAll(List.takeRight(listAt(vals, 0), List.len(listAt(vals, 1))), listAt(vals, 1))) else if (f == "List.sameElements") Value.VBool(eqAll(listAt(vals, 0), listAt(vals, 1))) else if (f == "List.patch") patchKit(listAt(vals, 0), clampInt(intAt(vals, 1), 0, List.len(listAt(vals, 0))), listAt(vals, 2), intAt(vals, 3)) else if (f == "List.splitAt") vLists(List.splitAt(listAt(vals, 0), intAt(vals, 1))) else if (f == "List.grouped") vLists(List.grouped(listAt(vals, 0), intAt(vals, 1))) else if (f == "List.sliding") vLists(List.sliding(listAt(vals, 0), intAt(vals, 1))) else if (f == "List.slice") Value.VList(List.slice(listAt(vals, 0), intAt(vals, 1), intAt(vals, 2))) else if (f == "List.isDefinedAt") Value.VBool(List.isDefinedAt(listAt(vals, 0), intAt(vals, 1))) else if (f == "List.lengthCompare") Value.VInt(List.lengthCompare(listAt(vals, 0), intAt(vals, 1))) else if (f == "List.indexOfSlice") Value.VInt(List.indexOfSlice(listAt(vals, 0), listAt(vals, 1))) else if (f == "List.lastIndexOfSlice") Value.VInt(List.lastIndexOfSlice(listAt(vals, 0), listAt(vals, 1))) else listKit4(f, vals, env, p, off) + +def clampInt(n: Int, lo: Int, hi: Int): Int = + if (n < lo) lo else if (n > hi) hi else n + +def patchKit(xs: List[Value], f: Int, other: List[Value], replaced: Int): Value = + Value.VList(List.concat(List.take(xs, f), List.concat(other, List.drop(xs, f + clampInt(replaced, 0, List.len(xs) - f))))) + +def listKit4(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "List.sort") sortKit(listAt(vals, 0), env, p, off) else if (f == "List.max" || f == "List.min") extremeKit(f, listAt(vals, 0), env, p, off) else if (f == "List.sum") Value.VInt(List.sum(List.map(listAt(vals, 0), x => intOf(x)))) else if (f == "List.product") Value.VInt(List.product(List.map(listAt(vals, 0), x => intOf(x)))) else if (f == "List.toSet") toSetKit(listAt(vals, 0), env, p, off) else if (f == "List.toMap") toMapKit(tuplesToPairs(listAt(vals, 0)), env, p, off) else if (f == "List.tabulate") mapValsThen(List.tabulate(intAt(vals, 0), i => Value.VInt(i)), List.at(vals, 1), env, p, off, rs => Value.VList(rs)) else listFnKit(f, listAt(vals, 0), List.at(vals, 1), vals, env, p, off) + +def listAtKit(xs: List[Value], i: Int, env: EvEnv, p: EvProg, off: Int): Value = + if (i < 0 || i >= List.len(xs)) errAt("List.at out of bounds", env, p, off) else List.at(xs, i) + +def listTailKit(xs: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (List.isEmpty(xs)) errAt("List.tail on empty", env, p, off) else Value.VList(List.tail(xs)) + +def headKit(xs: List[Value]): Value = + if (List.isEmpty(xs)) noneV() else someV(List.at(xs, 0)) + +def sortKit(xs: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (List.forall(xs, x => isInt(x))) vInts(List.sort(List.map(xs, x => intOf(x)))) else if (List.forall(xs, x => isStr(x))) vStrs(List.sort(List.map(xs, x => strOf(x)))) else errAt("List.sort: not Int or String", env, p, off) + +def extremeKit(f: String, xs: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (List.isEmpty(xs)) errAt(Str.concat(f, " on empty"), env, p, off) else if (List.forall(xs, x => isInt(x))) Value.VInt(extremeInt(f, List.map(xs, x => intOf(x)))) else if (List.forall(xs, x => isStr(x))) Value.VStr(extremeStr(f, List.map(xs, x => strOf(x)))) else errAt(Str.concat(f, ": not Int or String"), env, p, off) + +def extremeInt(f: String, ns: List[Int]): Int = + if (f == "List.max") List.max(ns) else List.min(ns) + +def extremeStr(f: String, ss: List[String]): String = + if (f == "List.max") List.max(ss) else List.min(ss) + +def toSetKit(xs: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (keysOk(xs)) Value.VSet(setPutAll(noVals(), xs)) else errAt("Set element must be Int or String", env, p, off) + +def toMapKit(ps: List[(Value, Value)], env: EvEnv, p: EvProg, off: Int): Value = + if (keysOk(pairKeys(ps))) Value.VMap(mapPutAll(noPairs(), ps)) else errAt("Map key must be Int or String", env, p, off) + +def listFnKit(f: String, xs: List[Value], fn: Value, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "List.map") mapValsThen(xs, fn, env, p, off, rs => Value.VList(rs)) else if (f == "List.flatMap") mapValsThen(xs, fn, env, p, off, rs => Value.VList(List.flatten(List.map(rs, r => listOf(r))))) else if (f == "List.filter") mapValsThen(xs, fn, env, p, off, rs => Value.VList(keepWhere(xs, rs, true))) else if (f == "List.filterNot") mapValsThen(xs, fn, env, p, off, rs => Value.VList(keepWhere(xs, rs, false))) else if (f == "List.count") mapValsThen(xs, fn, env, p, off, rs => Value.VInt(List.count(rs, r => boolOf(r)))) else if (f == "List.partition") mapValsThen(xs, fn, env, p, off, rs => Value.VList(Value.VList(keepWhere(xs, rs, true)) :: Value.VList(keepWhere(xs, rs, false)) :: noVals())) else if (f == "List.findLast") mapValsThen(xs, fn, env, p, off, rs => lastHit(xs, lastTrue(rs, 0, 0 - 1))) else if (f == "List.lastIndexWhere") mapValsThen(xs, fn, env, p, off, rs => Value.VInt(lastTrue(rs, 0, 0 - 1))) else if (f == "List.sortBy") mapValsThen(xs, fn, env, p, off, rs => Value.VList(List.map(List.sortBy(List.zip(xs, rs), pr => intOf(pairSnd(pr))), pr => pairFst(pr)))) else if ((f == "List.maxBy" || f == "List.minBy") && List.isEmpty(xs)) errAt(Str.concat(f, " on empty"), env, p, off) else if (f == "List.maxBy") mapValsThen(xs, fn, env, p, off, rs => pairFst(List.maxBy(List.zip(xs, rs), pr => intOf(pairSnd(pr))))) else if (f == "List.minBy") mapValsThen(xs, fn, env, p, off, rs => pairFst(List.minBy(List.zip(xs, rs), pr => intOf(pairSnd(pr))))) else if (f == "List.groupBy") mapValsThen(xs, fn, env, p, off, rs => groupByKit(List.zip(xs, rs), env, p, off)) else if (f == "List.distinctBy") mapValsThen(xs, fn, env, p, off, rs => distinctByKit(List.zip(xs, rs), noVals(), noVals(), env, p, off)) else listScanKit(f, xs, fn, vals, env, p, off) + +def lastHit(xs: List[Value], i: Int): Value = + if (i < 0) Value.VList(noVals()) else Value.VList(List.at(xs, i) :: noVals()) + +def groupByKit(prs: List[(Value, Value)], env: EvEnv, p: EvProg, off: Int): Value = + if (keysOk(pairVals(prs))) Value.VMap(groupInto(prs, noPairs())) else errAt("Map key must be Int or String", env, p, off) + +def groupInto(prs: List[(Value, Value)], acc: List[(Value, Value)]): List[(Value, Value)] = + if (List.isEmpty(prs)) acc else groupInto(List.tail(prs), mapPut(acc, pairSnd(List.at(prs, 0)), Value.VList(List.append(listOf(optOr(mapFind(acc, pairSnd(List.at(prs, 0))), Value.VList(noVals()))), pairFst(List.at(prs, 0)))))) + +def distinctByKit(prs: List[(Value, Value)], seen: List[Value], acc: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (List.isEmpty(prs)) Value.VList(List.reverse(acc)) else if (!keyOk(pairSnd(List.at(prs, 0)))) errAt("Map key must be Int or String", env, p, off) else if (setHas(seen, pairSnd(List.at(prs, 0)))) distinctByKit(List.tail(prs), seen, acc, env, p, off) else distinctByKit(List.tail(prs), setPut(seen, pairSnd(List.at(prs, 0))), pairFst(List.at(prs, 0)) :: acc, env, p, off) + +def listScanKit(f: String, xs: List[Value], fn: Value, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "List.find") withIdx(scanPred(xs, fn, true, 0, env, p, off), i => lastHit(xs, intOf(i))) else if (f == "List.exists") withIdx(scanPred(xs, fn, true, 0, env, p, off), i => Value.VBool(intOf(i) >= 0)) else if (f == "List.forall") withIdx(scanPred(xs, fn, false, 0, env, p, off), i => Value.VBool(intOf(i) < 0)) else if (f == "List.indexWhere") scanPred(xs, fn, true, 0, env, p, off) else if (f == "List.takeWhile") withIdx(scanPred(xs, fn, false, 0, env, p, off), i => Value.VList(if (intOf(i) < 0) xs else List.take(xs, intOf(i)))) else if (f == "List.dropWhile") withIdx(scanPred(xs, fn, false, 0, env, p, off), i => Value.VList(if (intOf(i) < 0) noVals() else List.drop(xs, intOf(i)))) else if (f == "List.span") withIdx(scanPred(xs, fn, false, 0, env, p, off), i => spanAt(xs, if (intOf(i) < 0) List.len(xs) else intOf(i))) else if (f == "List.prefixLength") withIdx(scanPred(xs, fn, false, 0, env, p, off), i => Value.VInt(if (intOf(i) < 0) List.len(xs) else intOf(i))) else if (f == "List.segmentLength") withIdx(scanPred(List.drop(xs, intAt(vals, 2)), fn, false, 0, env, p, off), i => Value.VInt(if (intOf(i) < 0) List.len(List.drop(xs, intAt(vals, 2))) else intOf(i))) else listFoldKit(f, xs, fn, vals, env, p, off) + +def spanAt(xs: List[Value], i: Int): Value = + Value.VList(Value.VList(List.take(xs, i)) :: Value.VList(List.drop(xs, i)) :: noVals()) + +def listFoldKit(f: String, xs: List[Value], fn: Value, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "List.foldLeft") foldKit(xs, List.at(vals, 1), List.at(vals, 2), env, p, off) else if (f == "List.foldRight") foldRightKit(List.reverse(xs), List.at(vals, 1), List.at(vals, 2), env, p, off) else if (f == "List.scanLeft") scanLeftKit(xs, List.at(vals, 1), List.at(vals, 2), noVals(), env, p, off) else if (f == "List.scanRight") scanRightKit(List.reverse(xs), List.at(vals, 1), List.at(vals, 2), List.at(vals, 1) :: noVals(), env, p, off) else if (f == "List.reduceLeft") reduceKit(xs, fn, false, env, p, off) else if (f == "List.reduceRight") reduceKit(List.reverse(xs), fn, true, env, p, off) else unsupported(Str.concat("kit ", f), env, p, off) + +def foldKit(xs: List[Value], acc: Value, fn: Value, env: EvEnv, p: EvProg, off: Int): Value = + if (isErr(acc) || List.isEmpty(xs)) acc else foldKit(List.tail(xs), apply2(fn, acc, List.at(xs, 0), env, p, off), fn, env, p, off) + +def foldRightKit(rev: List[Value], acc: Value, fn: Value, env: EvEnv, p: EvProg, off: Int): Value = + if (isErr(acc) || List.isEmpty(rev)) acc else foldRightKit(List.tail(rev), apply2(fn, List.at(rev, 0), acc, env, p, off), fn, env, p, off) + +def scanLeftKit(xs: List[Value], acc: Value, fn: Value, out: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (isErr(acc)) acc else if (List.isEmpty(xs)) Value.VList(List.reverse(acc :: out)) else scanLeftKit(List.tail(xs), apply2(fn, acc, List.at(xs, 0), env, p, off), fn, acc :: out, env, p, off) + +def scanRightKit(rev: List[Value], acc: Value, fn: Value, out: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (isErr(acc)) acc else if (List.isEmpty(rev)) Value.VList(out) else scanRightNext(rev, apply2(fn, List.at(rev, 0), acc, env, p, off), fn, out, env, p, off) + +def scanRightNext(rev: List[Value], acc: Value, fn: Value, out: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + scanRightKit(List.tail(rev), acc, fn, acc :: out, env, p, off) + +def reduceKit(xs: List[Value], fn: Value, right: Bool, env: EvEnv, p: EvProg, off: Int): Value = + if (List.isEmpty(xs)) errAt(if (right) "List.reduceRight on empty" else "List.reduceLeft on empty", env, p, off) else if (right) foldRightKit(List.tail(xs), List.at(xs, 0), fn, env, p, off) else foldKit(List.tail(xs), List.at(xs, 0), fn, env, p, off) + +def mapKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Map.empty") Value.VMap(noPairs()) else if (f == "Map.set") mapSetKit(mapOf(List.at(vals, 0)), List.at(vals, 1), List.at(vals, 2), env, p, off) else if (f == "Map.keys") Value.VList(pairKeys(mapOf(List.at(vals, 0)))) else if (f == "Map.values") Value.VList(pairVals(mapOf(List.at(vals, 0)))) else if (f == "Map.size") Value.VInt(List.len(mapOf(List.at(vals, 0)))) else if (f == "Map.contains") Value.VBool(mapHas(mapOf(List.at(vals, 0)), List.at(vals, 1))) else if (f == "Map.get") optValue(mapFind(mapOf(List.at(vals, 0)), List.at(vals, 1))) else if (f == "Map.getOrElse") optOr(mapFind(mapOf(List.at(vals, 0)), List.at(vals, 1)), List.at(vals, 2)) else if (f == "Map.remove") Value.VMap(mapDel(mapOf(List.at(vals, 0)), List.at(vals, 1))) else if (f == "Map.toList") Value.VList(pairsToTuples(mapOf(List.at(vals, 0)))) else if (f == "Map.isEmpty") Value.VBool(List.isEmpty(mapOf(List.at(vals, 0)))) else if (f == "Map.nonEmpty") Value.VBool(List.nonEmpty(mapOf(List.at(vals, 0)))) else mapKit2(f, mapOf(List.at(vals, 0)), List.at(vals, 1), env, p, off) + +def mapSetKit(kvs: List[(Value, Value)], k: Value, v: Value, env: EvEnv, p: EvProg, off: Int): Value = + if (keyOk(k)) Value.VMap(mapPut(kvs, k, v)) else errAt("Map key must be Int or String", env, p, off) + +def mapKit2(f: String, kvs: List[(Value, Value)], arg: Value, env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Map.union") Value.VMap(mapPutAll(kvs, mapOf(arg))) else if (f == "Map.intersect") Value.VMap(List.filter(kvs, pr => mapHas(mapOf(arg), pairFst(pr)))) else if (f == "Map.diff") Value.VMap(List.filter(kvs, pr => !mapHas(mapOf(arg), pairFst(pr)))) else if (f == "Map.filter") mapValsThen(pairVals(kvs), arg, env, p, off, rs => Value.VMap(List.map(List.filter(List.zip(kvs, rs), zr => boolOf(pairSndZ(zr))), zr => pairFstZ(zr)))) else if (f == "Map.mapValues") mapValsThen(pairVals(kvs), arg, env, p, off, rs => Value.VMap(List.zip(pairKeys(kvs), rs))) else if (f == "Map.exists") withIdx(scanPred(pairVals(kvs), arg, true, 0, env, p, off), i => Value.VBool(intOf(i) >= 0)) else if (f == "Map.forall") withIdx(scanPred(pairVals(kvs), arg, false, 0, env, p, off), i => Value.VBool(intOf(i) < 0)) else unsupported(Str.concat("kit ", f), env, p, off) + +def pairFstZ(zr: ((Value, Value), Value)): (Value, Value) = + zr match { + case (a, _) => a + } + +def pairSndZ(zr: ((Value, Value), Value)): Value = + zr match { + case (_, b) => b + } + +def setKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Set.empty") Value.VSet(noVals()) else if (f == "Set.add") setAddKit(setOf(List.at(vals, 0)), List.at(vals, 1), env, p, off) else if (f == "Set.toList") Value.VList(setOf(List.at(vals, 0))) else if (f == "Set.size") Value.VInt(List.len(setOf(List.at(vals, 0)))) else if (f == "Set.contains") Value.VBool(setHas(setOf(List.at(vals, 0)), List.at(vals, 1))) else if (f == "Set.remove") Value.VSet(setDel(setOf(List.at(vals, 0)), List.at(vals, 1))) else if (f == "Set.isEmpty") Value.VBool(List.isEmpty(setOf(List.at(vals, 0)))) else if (f == "Set.nonEmpty") Value.VBool(List.nonEmpty(setOf(List.at(vals, 0)))) else setKit2(f, setOf(List.at(vals, 0)), List.at(vals, 1), env, p, off) + +def setAddKit(xs: List[Value], k: Value, env: EvEnv, p: EvProg, off: Int): Value = + if (keyOk(k)) Value.VSet(setPut(xs, k)) else errAt("Set element must be Int or String", env, p, off) + +def setKit2(f: String, xs: List[Value], arg: Value, env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Set.union") Value.VSet(setPutAll(xs, setOf(arg))) else if (f == "Set.intersect") Value.VSet(List.filter(xs, x => setHas(setOf(arg), x))) else if (f == "Set.diff") Value.VSet(List.filter(xs, x => !setHas(setOf(arg), x))) else if (f == "Set.isSubset") Value.VBool(List.forall(xs, x => setHas(setOf(arg), x))) else if (f == "Set.isDisjoint") Value.VBool(!List.exists(xs, x => setHas(setOf(arg), x))) else if (f == "Set.filter") mapValsThen(xs, arg, env, p, off, rs => Value.VSet(keepWhere(xs, rs, true))) else if (f == "Set.map") mapValsThen(xs, arg, env, p, off, rs => toSetKit(rs, env, p, off)) else if (f == "Set.exists") withIdx(scanPred(xs, arg, true, 0, env, p, off), i => Value.VBool(intOf(i) >= 0)) else if (f == "Set.forall") withIdx(scanPred(xs, arg, false, 0, env, p, off), i => Value.VBool(intOf(i) < 0)) else unsupported(Str.concat("kit ", f), env, p, off) + +def jcon(tag: String, payload: List[Value]): Value = + Value.VCon("Json", tag, payload) + +def toJson(v: Value): Json = + v match { + case Value.VCon(_, tag, fs) => toJsonTag(tag, fs) + case _ => Json.Null() + } + +def toJsonTag(tag: String, fs: List[Value]): Json = + if (tag == "Bool") Json.Bool(boolOf(List.at(fs, 0))) else if (tag == "Int") Json.Int(intAt(fs, 0)) else if (tag == "Float") Json.Float(floatOf(List.at(fs, 0))) else if (tag == "Str") Json.Str(strAt(fs, 0)) else if (tag == "Arr") Json.Arr(List.map(listAt(fs, 0), x => toJson(x))) else if (tag == "Obj") Json.Obj(List.map(listAt(fs, 0), pr => toJsonPair(tupleToPair(pr)))) else Json.Null() + +def toJsonPair(pr: (Value, Value)): (String, Json) = + (strOf(pairFst(pr)), toJson(pairSnd(pr))) + +def fromJson(j: Json): Value = + j match { + case Json.Bool(b) => jcon("Bool", Value.VBool(b) :: noVals()) + case Json.Int(n) => jcon("Int", Value.VInt(n) :: noVals()) + case Json.Float(f) => jcon("Float", Value.VFloat(f) :: noVals()) + case Json.Str(s) => jcon("Str", Value.VStr(s) :: noVals()) + case Json.Arr(xs) => jcon("Arr", Value.VList(List.map(xs, x => fromJson(x))) :: noVals()) + case Json.Obj(ps) => jcon("Obj", Value.VList(List.map(ps, __tup => __tup match { + case (k, v) => Value.VTuple(Value.VStr(k) :: fromJson(v) :: noVals()) +})) :: noVals()) + case _ => jcon("Null", noVals()) + } + +def vJsons(xs: List[Json]): Value = + Value.VList(List.map(xs, x => fromJson(x))) + +def jsonKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Json.Null") jcon("Null", noVals()) else if (f == "Json.Bool" || f == "Json.Int" || f == "Json.Float" || f == "Json.Str" || f == "Json.Arr" || f == "Json.Obj") jcon(Str.drop(f, 5), List.at(vals, 0) :: noVals()) else if (f == "Json.parse") jsonParseKit(Json.parse(strAt(vals, 0))) else if (f == "Json.stringify") jsonStringifyKit(Json.stringify(toJson(List.at(vals, 0)))) else jsonKit2(f, toJson(List.at(vals, 0)), vals, env, p, off) + +def jsonParseKit(r: Result[String, Json]): Value = + r match { + case Result.Ok(j) => okV(fromJson(j)) + case Result.Err(e) => errV(Value.VStr(e)) + } + +def jsonStringifyKit(r: Result[String, String]): Value = + r match { + case Result.Ok(s) => okV(Value.VStr(s)) + case Result.Err(e) => errV(Value.VStr(e)) + } + +def jsonKit2(f: String, j: Json, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Json.keys") vStrs(Json.keys(j)) else if (f == "Json.get") vJsons(Json.get(j, strAt(vals, 1))) else if (f == "Json.has") Value.VBool(Json.has(j, strAt(vals, 1))) else if (f == "Json.getStr") Value.VStr(Json.getStr(j, strAt(vals, 1), strAt(vals, 2))) else if (f == "Json.getBool") Value.VBool(Json.getBool(j, strAt(vals, 1), boolOf(List.at(vals, 2)))) else if (f == "Json.getInt") Value.VInt(Json.getInt(j, strAt(vals, 1), intAt(vals, 2))) else if (f == "Json.getFloat") Value.VFloat(Json.getFloat(j, strAt(vals, 1), floatOf(List.at(vals, 2)))) else if (f == "Json.intOr") Value.VInt(Json.intOr(j, intAt(vals, 1))) else if (f == "Json.boolOr") Value.VBool(Json.boolOr(j, boolOf(List.at(vals, 1)))) else if (f == "Json.strOr") Value.VStr(Json.strOr(j, strAt(vals, 1))) else if (f == "Json.floatOr") Value.VFloat(Json.floatOr(j, floatOf(List.at(vals, 1)))) else if (f == "Json.arr") vJsons(Json.arr(j)) else if (f == "Json.at") vJsons(Json.at(j, intAt(vals, 1))) else if (f == "Json.pairs") Value.VList(List.map(Json.pairs(j), __tup => __tup match { + case (k, v) => Value.VTuple(Value.VStr(k) :: fromJson(v) :: noVals()) +})) else jsonKit3(f, j, vals, env, p, off) + +def jsonKit3(f: String, j: Json, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Json.isNull") Value.VBool(Json.isNull(j)) else if (f == "Json.isObj") Value.VBool(Json.isObj(j)) else if (f == "Json.isArr") Value.VBool(Json.isArr(j)) else if (f == "Json.isBool") Value.VBool(Json.isBool(j)) else if (f == "Json.isInt") Value.VBool(Json.isInt(j)) else if (f == "Json.isStr") Value.VBool(Json.isStr(j)) else if (f == "Json.isFloat") Value.VBool(Json.isFloat(j)) else if (f == "Json.asInt") vInts(Json.asInt(j)) else if (f == "Json.asBool") Value.VList(List.map(Json.asBool(j), b => Value.VBool(b))) else if (f == "Json.asStr") vStrs(Json.asStr(j)) else if (f == "Json.asFloat") Value.VList(List.map(Json.asFloat(j), x => Value.VFloat(x))) else if (f == "Json.set") fromJson(Json.set(j, strAt(vals, 1), toJson(List.at(vals, 2)))) else if (f == "Json.remove") fromJson(Json.remove(j, strAt(vals, 1))) else if (f == "Json.append") fromJson(Json.append(j, toJson(List.at(vals, 1)))) else if (f == "Json.prepend") fromJson(Json.prepend(j, toJson(List.at(vals, 1)))) else if (f == "Json.setAt") fromJson(Json.setAt(j, intAt(vals, 1), toJson(List.at(vals, 2)))) else if (f == "Json.dropAt") fromJson(Json.dropAt(j, intAt(vals, 1))) else if (f == "Json.merge") fromJson(Json.merge(j, toJson(List.at(vals, 1)))) else unsupported(Str.concat("kit ", f), env, p, off) + +def ioKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "IO.println") Value.VIo(printlnIo(strAt(vals, 0))) else if (f == "IO.pure") Value.VIo(pureIo(List.at(vals, 0))) else if (f == "IO.both") Value.VIo(bothIo(ioAt(vals, 0), ioAt(vals, 1))) else if (f == "IO.fail") Value.VIo(failWith(List.at(vals, 0))) else if (f == "IO.attempt") Value.VIo(attemptIo(ioAt(vals, 0))) else if (f == "IO.sleep") Value.VIo(unitIo(IO.sleep(intAt(vals, 0)))) else if (f == "IO.race") Value.VIo(IO.race(ioAt(vals, 0), ioAt(vals, 1))) else if (f == "IO.ensure") Value.VIo(IO.ensure(ioAt(vals, 0), unitOf(ioAt(vals, 1)))) else if (f == "IO.timeout") Value.VIo(IO.timeout(intAt(vals, 0), ioAt(vals, 1))) else if (f == "IO.forever") Value.VIo(IO.forever(ioAt(vals, 0))) else if (f == "IO.repeatN") Value.VIo(IO.repeatN(intAt(vals, 0), ioAt(vals, 1))) else if (f == "IO.retryN") Value.VIo(IO.retryN(intAt(vals, 0), ioAt(vals, 1))) else if (f == "IO.foreach") Value.VIo(foreachIo(listAt(vals, 0), List.at(vals, 1), env, p, off)) else if (f == "IO.foreachDiscard") Value.VIo(foreachIo(listAt(vals, 0), List.at(vals, 1), env, p, off).map(_ => Value.VUnit)) else if (f == "IO.when") Value.VIo(if (boolOf(List.at(vals, 0))) ioAt(vals, 1) else pureIo(Value.VUnit)) else if (f == "IO.unless") Value.VIo(if (boolOf(List.at(vals, 0))) pureIo(Value.VUnit) else ioAt(vals, 1)) else unsupported(Str.concat("kit ", f), env, p, off) + +def ioAt(vals: List[Value], i: Int): IO[Value, Value] = + ioOf(List.at(vals, i)) + +def attemptIo(io: IO[Value, Value]): IO[Value, Value] = + liftIo(IO.attempt(io).map(r => attemptVal(r))) + +def attemptVal(r: Result[Value, Value]): Value = + r match { + case Result.Ok(v) => okV(v) + case Result.Err(e) => errV(e) + } + +def foreachIo(xs: List[Value], fn: Value, env: EvEnv, p: EvProg, off: Int): IO[Value, Value] = + IO.foreach(xs, x => ioOf(applyValue(fn, x :: noVals(), env, p, off))).map(ys => Value.VList(ys)) + +def bothIo(a: IO[Value, Value], b: IO[Value, Value]): IO[Value, Value] = + IO.both(a, b).map(pr => Value.VTuple(pairFst(pr) :: pairSnd(pr) :: noVals())) + +def miscKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Builder.empty") Value.VBuilder(Builder.empty()) else if (f == "Builder.append") Value.VBuilder(Builder.append(builderOf(List.at(vals, 0)), strAt(vals, 1))) else if (f == "Builder.result") Value.VStr(Builder.result(builderOf(List.at(vals, 0)))) else if (f == "Hash.hmacSha256") Value.VStr(Hash.hmacSha256(strAt(vals, 0), strAt(vals, 1))) else if (f == "Hash.constantTimeEqual") Value.VBool(Hash.constantTimeEqual(strAt(vals, 0), strAt(vals, 1))) else if (f == "Hash.sha256") Value.VStr(Hash.sha256(strAt(vals, 0))) else if (f == "Hex.encode") Value.VStr(Hex.encode(strAt(vals, 0))) else if (f == "Hex.decode") Value.VStr(Hex.decode(strAt(vals, 0))) else if (f == "Base64.encode") Value.VStr(Base64.encode(strAt(vals, 0))) else if (f == "Base64.decode") Value.VStr(Base64.decode(strAt(vals, 0))) else if (f == "Float.fromInt") Value.VFloat(Float.fromInt(intAt(vals, 0))) else if (f == "Float.toInt") Value.VInt(Float.toInt(floatOf(List.at(vals, 0)))) else if (f == "Oracle.sumTo") Value.VInt(Oracle.sumTo(intAt(vals, 0))) else if (f == "Clock.monotonic") Value.VIo(intIo(Clock.monotonic())) else if (f == "Clock.realTime") Value.VIo(intIo(Clock.realTime())) else if (f == "Clock.iso8601") Value.VStr(Clock.iso8601(intAt(vals, 0))) else if (f == "Random.nextInt") Value.VIo(intIo(Random.nextInt(intAt(vals, 0)))) else if (f == "Uuid.v4") Value.VIo(strIo(Uuid.v4())) else if (f == "Bytes.fromStr") Value.VBytes(Bytes.fromStr(strAt(vals, 0))) else if (f == "Bytes.len") Value.VInt(Bytes.len(bytesOf(List.at(vals, 0)))) else if (f == "Impurity.runKit") Value.VIo(unitIo(Impurity.runKit())) else if (f == "Scenario.context") scenarioCtx(p, env, off) else unsupported(Str.concat("kit ", f), env, p, off) + +def bytesOf(v: Value): Bytes = + v match { + case Value.VBytes(b) => b + case _ => Bytes.fromStr("") + } + +def unitOf(io: IO[Value, Value]): IO[Unit] = + io.map(_ => ()).handleErrorWith(e => IO.fail(show(e))) + +def unitIo(io: IO[Unit]): IO[Value, Value] = + liftIo(io.map(_ => Value.VUnit)) + +def intIo(io: IO[Int]): IO[Value, Value] = + liftIo(io.map(n => Value.VInt(n))) + +def strIo(io: IO[String]): IO[Value, Value] = + liftIo(io.map(s => Value.VStr(s))) + +def boolIo(io: IO[Bool]): IO[Value, Value] = + liftIo(io.map(b => Value.VBool(b))) + +def listIo(io: IO[List[Value]]): IO[Value, Value] = + liftIo(io.map(xs => Value.VList(xs))) + +def isHandleKit(f: String): Bool = + Str.startsWith(f, "Ref.") || Str.startsWith(f, "Queue.") || Str.startsWith(f, "Deferred.") || Str.startsWith(f, "Fiber.") || Str.startsWith(f, "Resource.") + +def refOf(v: Value): IO[Value, Ref[Value]] = + v match { + case Value.VRef(r) => IO.pure(r).handleErrorWith(e => IO.fail(Value.VStr(e))) + case _ => IO.fail(Value.VErr("eval: Ref expected")) + } + +def queueOf(v: Value): IO[Value, Queue[Value]] = + v match { + case Value.VQueue(q) => IO.pure(q).handleErrorWith(e => IO.fail(Value.VStr(e))) + case _ => IO.fail(Value.VErr("eval: Queue expected")) + } + +def deferredOf(v: Value): IO[Value, Deferred[Value]] = + v match { + case Value.VDeferred(d) => IO.pure(d).handleErrorWith(e => IO.fail(Value.VStr(e))) + case _ => IO.fail(Value.VErr("eval: Deferred expected")) + } + +def fiberOf(v: Value): IO[Value, Fiber[Value]] = + v match { + case Value.VFiber(f) => IO.pure(f).handleErrorWith(e => IO.fail(Value.VStr(e))) + case _ => IO.fail(Value.VErr("eval: Fiber expected")) + } + +def resourceOf(v: Value): Resource[Value] = + v match { + case Value.VResource(r) => r + case _ => Resource.make(IO.pure(Value.VUnit), _ => IO.pure(())) + } + +def handleKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Ref.of") Value.VIo(liftIo(Ref.of(List.at(vals, 0)).map(r => Value.VRef(r)))) else if (f == "Ref.get") Value.VIo(refOf(List.at(vals, 0)).flatMap(r => liftIo(Ref.get(r)))) else if (f == "Ref.set") Value.VIo(refOf(List.at(vals, 0)).flatMap(r => unitIo(Ref.set(r, List.at(vals, 1))))) else if (f == "Ref.update") Value.VIo(refOf(List.at(vals, 0)).flatMap(r => unitIo(Ref.update(r, x => applyValue(List.at(vals, 1), x :: noVals(), env, p, off))))) else if (f == "Ref.updateAndGet") Value.VIo(refOf(List.at(vals, 0)).flatMap(r => liftIo(Ref.updateAndGet(r, x => applyValue(List.at(vals, 1), x :: noVals(), env, p, off))))) else if (f == "Queue.unbounded") Value.VIo(liftIo(Queue.unbounded().map(q => Value.VQueue(q)))) else if (f == "Queue.offer") Value.VIo(queueOf(List.at(vals, 0)).flatMap(q => unitIo(Queue.offer(q, List.at(vals, 1))))) else if (f == "Queue.take") Value.VIo(queueOf(List.at(vals, 0)).flatMap(q => liftIo(Queue.take(q)))) else if (f == "Deferred.empty") Value.VIo(liftIo(Deferred.empty().map(d => Value.VDeferred(d)))) else if (f == "Deferred.get") Value.VIo(deferredOf(List.at(vals, 0)).flatMap(d => liftIo(Deferred.get(d)))) else if (f == "Deferred.complete") Value.VIo(deferredOf(List.at(vals, 0)).flatMap(d => unitIo(Deferred.complete(d, List.at(vals, 1))))) else if (f == "Deferred.fail") Value.VIo(deferredOf(List.at(vals, 0)).flatMap(d => unitIo(Deferred.fail(d, strAt(vals, 1))))) else if (f == "Fiber.fork") Value.VIo(liftIo(Fiber.fork(ioAt(vals, 0)).map(fb => Value.VFiber(fb)))) else if (f == "Fiber.join") Value.VIo(fiberOf(List.at(vals, 0)).flatMap(fb => liftIo(Fiber.join(fb)))) else if (f == "Fiber.interrupt") Value.VIo(fiberOf(List.at(vals, 0)).flatMap(fb => unitIo(Fiber.interrupt(fb)))) else if (f == "Resource.make") Value.VResource(Resource.make(ioAt(vals, 0), a => unitOf(ioOf(applyValue(List.at(vals, 1), a :: noVals(), env, p, off))))) else if (f == "Resource.use") Value.VIo(Resource.use(resourceOf(List.at(vals, 0)), a => ioOf(applyValue(List.at(vals, 1), a :: noVals(), env, p, off)))) else unsupported(Str.concat("kit ", f), env, p, off) + +def streamOf(v: Value): Stream[Value] = + v match { + case Value.VStream(s) => s + case _ => Stream.emits(noVals()) + } + +def streamAt(vals: List[Value], i: Int): Stream[Value] = + streamOf(List.at(vals, i)) + +def seedAt(vals: List[Value], i: Int): Value = + List.at(vals, i) + +def vStream(s: Stream[Value]): Value = + Value.VStream(s) + +def tupleOfPair(pr: (Value, Value)): Value = + Value.VTuple(pairFst(pr) :: pairSnd(pr) :: noVals()) + +def tupleOfIdx(pr: (Int, Value)): Value = + pr match { + case (i, v) => Value.VTuple(Value.VInt(i) :: v :: noVals()) + } + +def pairOfTuple(v: Value): List[(Value, Value)] = + v match { + case Value.VTuple(xs) => if (List.len(xs) == 2) (List.at(xs, 0), List.at(xs, 1)) :: noPairs() else noPairs() + case _ => noPairs() + } + +def unfoldStep(fn: Value, st: Value, env: EvEnv, p: EvProg, off: Int): List[(Value, Value)] = + List.flatMap(listOf(applyValue(fn, st :: noVals(), env, p, off)), t => pairOfTuple(t)) + +def streamKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Stream.emit") vStream(Stream.emit(List.at(vals, 0))) else if (f == "Stream.emits") vStream(Stream.emits(listAt(vals, 0))) else if (f == "Stream.eval") vStream(Stream.eval(ioAt(vals, 0))) else if (f == "Stream.concat") vStream(Stream.concat(streamAt(vals, 0), streamAt(vals, 1))) else if (f == "Stream.range") vStream(Stream.map(Stream.range(intAt(vals, 0), intAt(vals, 1)), n => Value.VInt(n))) else if (f == "Stream.repeatN") vStream(Stream.repeatN(streamAt(vals, 0), intAt(vals, 1))) else if (f == "Stream.zip") vStream(Stream.map(Stream.zip(streamAt(vals, 0), streamAt(vals, 1)), pr => tupleOfPair(pr))) else if (f == "Stream.zipWith") vStream(Stream.map(Stream.zip(streamAt(vals, 0), streamAt(vals, 1)), pr => apply2(List.at(vals, 2), pairFst(pr), pairSnd(pr), env, p, off))) else if (f == "Stream.zipAll") vStream(Stream.map(Stream.zipAll(streamAt(vals, 0), streamAt(vals, 1), List.at(vals, 2), List.at(vals, 3)), pr => tupleOfPair(pr))) else if (f == "Stream.zipWithIndex") vStream(Stream.map(Stream.zipWithIndex(streamAt(vals, 0)), pr => tupleOfIdx(pr))) else if (f == "Stream.interleave") vStream(Stream.interleave(streamAt(vals, 0), streamAt(vals, 1))) else if (f == "Stream.intersperse") vStream(Stream.intersperse(streamAt(vals, 0), List.at(vals, 1))) else if (f == "Stream.grouped") vStream(Stream.map(Stream.grouped(streamAt(vals, 0), intAt(vals, 1)), g => Value.VList(g))) else if (f == "Stream.sliding") vStream(Stream.map(Stream.sliding(streamAt(vals, 0), intAt(vals, 1)), g => Value.VList(g))) else if (f == "Stream.take") vStream(Stream.take(streamAt(vals, 0), intAt(vals, 1))) else if (f == "Stream.drop") vStream(Stream.drop(streamAt(vals, 0), intAt(vals, 1))) else if (f == "Stream.takeRight") vStream(Stream.takeRight(streamAt(vals, 0), intAt(vals, 1))) else if (f == "Stream.dropRight") vStream(Stream.dropRight(streamAt(vals, 0), intAt(vals, 1))) else if (f == "Stream.flatten") vStream(Stream.flatten(Stream.map(streamAt(vals, 0), x => listOf(x)))) else if (f == "Stream.changes") vStream(Stream.changes(streamAt(vals, 0))) else if (f == "Stream.orElse") vStream(Stream.orElse(streamAt(vals, 0), streamAt(vals, 1))) else if (f == "Stream.iterate") vStream(Stream.iterate(List.at(vals, 0), intAt(vals, 1), x => applyValue(List.at(vals, 2), x :: noVals(), env, p, off))) else if (f == "Stream.unfold") vStream(Stream.unfold(List.at(vals, 0), st => unfoldStep(List.at(vals, 1), st, env, p, off))) else streamFnKit(f, vals, env, p, off) + +def streamFnKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Stream.map") vStream(Stream.map(streamAt(vals, 0), x => applyValue(List.at(vals, 1), x :: noVals(), env, p, off))) else if (f == "Stream.evalMap") vStream(Stream.evalMap(streamAt(vals, 0), x => ioOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off)))) else if (f == "Stream.evalTap") vStream(Stream.evalTap(streamAt(vals, 0), x => ioOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off)))) else if (f == "Stream.filter") vStream(Stream.filter(streamAt(vals, 0), x => boolOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off)))) else if (f == "Stream.filterNot") vStream(Stream.filterNot(streamAt(vals, 0), x => boolOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off)))) else if (f == "Stream.takeWhile") vStream(Stream.takeWhile(streamAt(vals, 0), x => boolOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off)))) else if (f == "Stream.dropWhile") vStream(Stream.dropWhile(streamAt(vals, 0), x => boolOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off)))) else if (f == "Stream.find") vStream(Stream.find(streamAt(vals, 0), x => boolOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off)))) else if (f == "Stream.findLast") vStream(Stream.findLast(streamAt(vals, 0), x => boolOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off)))) else if (f == "Stream.flatMap") vStream(Stream.flatMap(streamAt(vals, 0), x => streamOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off)))) else if (f == "Stream.mapConcat") vStream(Stream.mapConcat(streamAt(vals, 0), x => listOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off)))) else if (f == "Stream.scan") vStream(Stream.scan(streamAt(vals, 0), seedAt(vals, 1), __tup => __tup match { + case (acc, x) => apply2(List.at(vals, 2), acc, x, env, p, off) +})) else streamRunKit(f, vals, env, p, off) + +def streamRunKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Stream.exists") Value.VIo(boolIo(Stream.exists(streamAt(vals, 0), x => boolOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off))))) else if (f == "Stream.forall") Value.VIo(boolIo(Stream.forall(streamAt(vals, 0), x => boolOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off))))) else if (f == "Stream.none") Value.VIo(boolIo(Stream.none(streamAt(vals, 0), x => boolOf(applyValue(List.at(vals, 1), x :: noVals(), env, p, off))))) else if (f == "Stream.fold") Value.VIo(liftIo(Stream.fold(streamAt(vals, 0), seedAt(vals, 1), __tup => __tup match { + case (acc, x) => apply2(List.at(vals, 2), acc, x, env, p, off) +}))) else if (f == "Stream.head") Value.VIo(liftIo(Stream.head(streamAt(vals, 0)))) else if (f == "Stream.last") Value.VIo(liftIo(Stream.last(streamAt(vals, 0)))) else if (f == "Stream.count") Value.VIo(intIo(Stream.count(streamAt(vals, 0)))) else if (f == "Stream.compileToList") Value.VIo(listIo(Stream.compileToList(streamAt(vals, 0)))) else if (f == "Stream.drain") Value.VIo(unitIo(Stream.drain(streamAt(vals, 0)))) else unsupported(Str.concat("kit ", f), env, p, off) + +def entryVal(e: (String, Bool)): Value = + e match { + case (name, dir) => Value.VTuple(Value.VStr(name) :: Value.VBool(dir) :: noVals()) + } + +def entriesIo(io: IO[List[(String, Bool)]]): IO[Value, Value] = + liftIo(io.map(xs => Value.VList(List.map(xs, e => entryVal(e))))) + +def execVal(t: (Int, String, String)): Value = + t match { + case (code, out, err) => Value.VTuple(Value.VInt(code) :: Value.VStr(out) :: Value.VStr(err) :: noVals()) + } + +def fsKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Fs.read") Value.VIo(strIo(Fs.read(strAt(vals, 0)))) else if (f == "Fs.write") Value.VIo(unitIo(Fs.write(strAt(vals, 0), strAt(vals, 1)))) else if (f == "Fs.list") Value.VIo(entriesIo(Fs.list(strAt(vals, 0)))) else if (f == "Fs.mkdirs") Value.VIo(unitIo(Fs.mkdirs(strAt(vals, 0)))) else if (f == "Fs.exists") Value.VIo(intIo(Fs.exists(strAt(vals, 0)))) else if (f == "Fs.delete") Value.VIo(unitIo(Fs.delete(strAt(vals, 0)))) else if (f == "Fs.walk") Value.VIo(entriesIo(Fs.walk(strAt(vals, 0)))) else if (f == "Fs.canonicalize") Value.VIo(strIo(Fs.canonicalize(strAt(vals, 0)))) else if (f == "Fs.rename") Value.VIo(unitIo(Fs.rename(strAt(vals, 0), strAt(vals, 1)))) else if (f == "Fs.join") Value.VStr(Fs.join(strAt(vals, 0), strAt(vals, 1))) else if (f == "Fs.dirname") Value.VStr(Fs.dirname(strAt(vals, 0))) else if (f == "Fs.basename") Value.VStr(Fs.basename(strAt(vals, 0))) else sysKit(f, vals, env, p, off) + +def sysKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Sys.args") Value.VIo(liftIo(Sys.args().map(xs => vStrs(xs)))) else if (f == "Sys.getenv") Value.VIo(strIo(Sys.getenv(strAt(vals, 0)))) else if (f == "Sys.write") Value.VIo(unitIo(Sys.write(strAt(vals, 0)))) else if (f == "Sys.read") Value.VIo(strIo(Sys.read(intAt(vals, 0)))) else if (f == "Sys.readLine") Value.VIo(strIo(Sys.readLine())) else if (f == "Sys.spawn") Value.VIo(intIo(Sys.spawn(strAt(vals, 0)))) else if (f == "Sys.exec") Value.VIo(liftIo(Sys.exec(strAt(vals, 0)).map(t => execVal(t)))) else if (f == "Sys.alive") Value.VIo(intIo(Sys.alive(intAt(vals, 0)))) else if (f == "Sys.kill") Value.VIo(unitIo(Sys.kill(intAt(vals, 0)))) else if (f == "Sys.childWrite") Value.VIo(unitIo(Sys.childWrite(intAt(vals, 0), strAt(vals, 1)))) else if (f == "Sys.childRead") Value.VIo(strIo(Sys.childRead(intAt(vals, 0), intAt(vals, 1)))) else if (f == "Sys.childClose") Value.VIo(unitIo(Sys.childClose(intAt(vals, 0)))) else unsupported(Str.concat("kit ", f), env, p, off) + +def hdrPair(kv: (String, String)): (Value, Value) = + kv match { + case (k, v) => (Value.VStr(k), Value.VStr(v)) + } + +def vMapOf(m: Map[String, String]): Value = + Value.VMap(List.map(Map.toList(m), kv => hdrPair(kv))) + +def natMapGo(kvs: List[(Value, Value)], acc: Map[String, String]): Map[String, String] = + if (List.isEmpty(kvs)) acc else natMapGo(List.tail(kvs), Map.set(acc, strOf(pairFst(List.at(kvs, 0))), strOf(pairSnd(List.at(kvs, 0))))) + +def natMap(v: Value): Map[String, String] = + natMapGo(mapOf(v), Map.empty()) + +def respVal(t: (Int, Map[String, String], String)): Value = + t match { + case (status, hs, body) => Value.VTuple(Value.VInt(status) :: vMapOf(hs) :: Value.VStr(body) :: noVals()) + } + +def respIo(io: IO[(Int, Map[String, String], String)]): IO[Value, Value] = + liftIo(io.map(t => respVal(t))) + +def natResp(v: Value): (Int, Map[String, String], String) = + v match { + case Value.VTuple(xs) => if (List.len(xs) == 3) (intOf(List.at(xs, 0)), natMap(List.at(xs, 1)), strOf(List.at(xs, 2))) else (500, Map.empty(), "eval: response tuple expected") + case _ => (500, Map.empty(), "eval: response tuple expected") + } + +def reqVal(req: (String, String, Map[String, String], String)): Value = + req match { + case (a, b, hs, d) => Value.VTuple(Value.VStr(a) :: Value.VStr(b) :: vMapOf(hs) :: Value.VStr(d) :: noVals()) + } + +def serveHandler(fn: Value, req: (String, String, Map[String, String], String), env: EvEnv, p: EvProg, off: Int): IO[(Int, Map[String, String], String)] = + ioOf(applyValue(fn, reqVal(req) :: noVals(), env, p, off)).map(v => natResp(v)).handleErrorWith(e => IO.fail(show(e))) + +def resultVal(r: Result[String, String]): Value = + r match { + case Result.Ok(s) => okV(Value.VStr(s)) + case Result.Err(e) => errV(Value.VStr(e)) + } + +def tcpOf(v: Value): IO[Value, Tcp] = + v match { + case Value.VTcp(t) => IO.pure(t).handleErrorWith(e => IO.fail(Value.VStr(e))) + case _ => IO.fail(Value.VErr("eval: Tcp expected")) + } + +def udpOf(v: Value): IO[Value, Udp] = + v match { + case Value.VUdp(u) => IO.pure(u).handleErrorWith(e => IO.fail(Value.VStr(e))) + case _ => IO.fail(Value.VErr("eval: Udp expected")) + } + +def udpVal(t: (String, Int, String)): Value = + t match { + case (host, port, data) => Value.VTuple(Value.VStr(host) :: Value.VInt(port) :: Value.VStr(data) :: noVals()) + } + +def netKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Net.nextLink") resultVal(Net.nextLink(strAt(vals, 0), strAt(vals, 1))) else if (f == "Net.retryAfterMillis") Value.VInt(Net.retryAfterMillis(strAt(vals, 0), intAt(vals, 1))) else if (f == "Net.httpGet") Value.VIo(respIo(Net.httpGet(strAt(vals, 0), natMap(List.at(vals, 1))))) else if (f == "Net.httpDelete") Value.VIo(respIo(Net.httpDelete(strAt(vals, 0), natMap(List.at(vals, 1))))) else if (f == "Net.httpHead") Value.VIo(respIo(Net.httpHead(strAt(vals, 0), natMap(List.at(vals, 1))))) else if (f == "Net.httpPost") Value.VIo(respIo(Net.httpPost(strAt(vals, 0), natMap(List.at(vals, 1)), strAt(vals, 2)))) else if (f == "Net.httpPut") Value.VIo(respIo(Net.httpPut(strAt(vals, 0), natMap(List.at(vals, 1)), strAt(vals, 2)))) else if (f == "Net.httpPatch") Value.VIo(respIo(Net.httpPatch(strAt(vals, 0), natMap(List.at(vals, 1)), strAt(vals, 2)))) else if (f == "Net.serve") Value.VIo(unitIo(Net.serve(intAt(vals, 0), req => serveHandler(List.at(vals, 1), req, env, p, off)))) else if (f == "Net.serveOnce") Value.VIo(unitIo(Net.serveOnce(intAt(vals, 0), req => serveHandler(List.at(vals, 1), req, env, p, off)))) else if (f == "Net.serveTls") Value.VIo(unitIo(Net.serveTls(intAt(vals, 0), req => serveHandler(List.at(vals, 1), req, env, p, off)))) else if (f == "Net.serveOnceTls") Value.VIo(unitIo(Net.serveOnceTls(intAt(vals, 0), req => serveHandler(List.at(vals, 1), req, env, p, off)))) else sockKit(f, vals, env, p, off) + +def sockKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Net.tcpConnect") Value.VIo(liftIo(Net.tcpConnect(strAt(vals, 0), intAt(vals, 1)).map(t => Value.VTcp(t)))) else if (f == "Net.tcpListen") Value.VIo(liftIo(Net.tcpListen(intAt(vals, 0)).map(t => Value.VTcp(t)))) else if (f == "Net.tcpAccept") Value.VIo(tcpOf(List.at(vals, 0)).flatMap(t => tcpAcceptIo(t))) else if (f == "Net.tcpRead") Value.VIo(tcpOf(List.at(vals, 0)).flatMap(t => strIo(Net.tcpRead(t, intAt(vals, 1))))) else if (f == "Net.tcpWrite") Value.VIo(tcpOf(List.at(vals, 0)).flatMap(t => unitIo(Net.tcpWrite(t, strAt(vals, 1))))) else if (f == "Net.tcpClose") Value.VIo(tcpOf(List.at(vals, 0)).flatMap(t => unitIo(Net.tcpClose(t)))) else if (f == "Net.udpBind") Value.VIo(liftIo(Net.udpBind(intAt(vals, 0)).map(u => Value.VUdp(u)))) else if (f == "Net.udpSend") Value.VIo(udpOf(List.at(vals, 0)).flatMap(u => unitIo(Net.udpSend(u, strAt(vals, 1), intAt(vals, 2), strAt(vals, 3))))) else if (f == "Net.udpRecv") Value.VIo(udpOf(List.at(vals, 0)).flatMap(u => udpRecvIo(u, intAt(vals, 1)))) else if (f == "Net.udpClose") Value.VIo(udpOf(List.at(vals, 0)).flatMap(u => unitIo(Net.udpClose(u)))) else unsupported(Str.concat("kit ", f), env, p, off) + +def tcpAcceptIo(t: Tcp): IO[Value, Value] = + liftIo(Net.tcpAccept(t).map(c => Value.VTcp(c))) + +def udpRecvIo(u: Udp, n: Int): IO[Value, Value] = + liftIo(Net.udpRecv(u, n).map(t => udpVal(t))) + +def seqUnit(_u: Unit): Value = + Value.VUnit + +def propKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Property.sometimes") seqUnit(Property.sometimes(strAt(vals, 0))) else if (f == "Property.check") propCheck(strAt(vals, 0), List.at(vals, 1), List.at(vals, 2), env, p, off) else if (f == "Property.assert") Value.VIo(unitIo(Property.assert(strAt(vals, 0), boolOf(List.at(vals, 1))))) else if (f == "Property.classify") Value.VBool(Property.classify(strAt(vals, 0), boolOf(List.at(vals, 1)))) else if (f == "Property.force") propForce(List.at(vals, 0), env, p, off) else unsupported(Str.concat("kit ", f), env, p, off) + +def propForce(v: Value, env: EvEnv, p: EvProg, off: Int): Value = + v match { + case Value.VIo(io) => Property.force(io.handleErrorWith(e => IO.fail(show(e)))) + case Value.VErr(_) => v + case _ => errAt("Property.force needs IO", env, p, off) + } + +def propCheck(name: String, pred: Value, v: Value, env: EvEnv, p: EvProg, off: Int): Value = + propCheckOk(name, propPred(pred, v, env, p, off), v, env, p, off) + +def propPred(pred: Value, v: Value, env: EvEnv, p: EvProg, off: Int): Value = + pred match { + case Value.VBool(_) => pred + case Value.VClo(_, _, _) => propPred(apply1(pred, v, env, p, off), v, env, p, off) + case Value.VFun(_, _) => propPred(apply1(pred, v, env, p, off), v, env, p, off) + case Value.VIo(_) => propForce(pred, env, p, off) + case Value.VErr(_) => pred + case _ => errAt("Property.check predicate is not Bool", env, p, off) + } + +def propCheckOk(name: String, ok: Value, v: Value, env: EvEnv, p: EvProg, off: Int): Value = + ok match { + case Value.VBool(b) => Property.check(name, b, v) + case Value.VErr(_) => ok + case _ => errAt("Property.check predicate is not Bool", env, p, off) + } + +def tlKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + List.at(vals, 0) match { + case Value.VTimeline(t) => tlKitAt(f, t, vals, env, p, off) + case _ => errAt(Str.concat(f, " needs Timeline"), env, p, off) + } + +def tlKitAt(f: String, t: Timeline, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Timeline.len") Value.VInt(Timeline.len(t)) else if (f == "Timeline.signalInt") Value.VInt(Timeline.signalInt(t, intAt(vals, 1), strAt(vals, 2))) else if (f == "Timeline.signalListLen") Value.VInt(Timeline.signalListLen(t, intAt(vals, 1), strAt(vals, 2))) else if (f == "Timeline.fileSame") Value.VBool(Timeline.fileSame(t, intAt(vals, 1), intAt(vals, 2), strAt(vals, 3))) else if (f == "Timeline.fileTextIs") Value.VBool(Timeline.fileTextIs(t, intAt(vals, 1), strAt(vals, 2), strAt(vals, 3))) else if (f == "Timeline.signalStrHas") Value.VBool(Timeline.signalStrHas(t, intAt(vals, 1), strAt(vals, 2), strAt(vals, 3))) else if (f == "Timeline.a11yHas") Value.VBool(Timeline.a11yHas(t, intAt(vals, 1), strAt(vals, 2))) else if (f == "Timeline.lastHitHas") Value.VBool(Timeline.lastHitHas(t, intAt(vals, 1), strAt(vals, 2))) else if (f == "Timeline.driveHas") Value.VBool(Timeline.driveHas(t, intAt(vals, 1), strAt(vals, 2))) else if (f == "Timeline.effectHas") Value.VBool(Timeline.effectHas(t, intAt(vals, 1), strAt(vals, 2))) else if (f == "Timeline.faultKindHas") Value.VBool(Timeline.faultKindHas(t, intAt(vals, 1), strAt(vals, 2))) else if (f == "Timeline.exists") Value.VBool(Timeline.exists(t, i => boolOf(apply1(List.at(vals, 1), Value.VInt(i), env, p, off)))) else tlIntKit(f, t, intAt(vals, 1), env, p, off) + +def tlIntKit(f: String, t: Timeline, i: Int, env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Timeline.effectCount") Value.VInt(Timeline.effectCount(t, i)) else if (f == "Timeline.fiberLive") Value.VInt(Timeline.fiberLive(t, i)) else if (f == "Timeline.fiberReady") Value.VInt(Timeline.fiberReady(t, i)) else if (f == "Timeline.fiberParked") Value.VInt(Timeline.fiberParked(t, i)) else if (f == "Timeline.fiberDone") Value.VInt(Timeline.fiberDone(t, i)) else if (f == "Timeline.faultN") Value.VInt(Timeline.faultN(t, i)) else if (f == "Timeline.checkpoint") Value.VInt(Timeline.checkpoint(t, i)) else if (f == "Timeline.nearestCheckpoint") Value.VInt(Timeline.nearestCheckpoint(t, i)) else unsupported(Str.concat("kit ", f), env, p, off) + +def verdictOf(v: Value, env: EvEnv, p: EvProg, off: Int): Verdict = + v match { + case Value.VVerdict(x) => x + case _ => Verdict.fail(0, Str.concat("eval: Verdict expected at ", locStr(env.mod, p, off))) + } + +def pairPred(f: Value, a: Int, b: Int, env: EvEnv, p: EvProg, off: Int): Bool = + boolOf(apply2(f, Value.VInt(a), Value.VInt(b), env, p, off)) + +def verdictKit(f: String, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Verdict.ok") Value.VVerdict(Verdict.ok()) else if (f == "Verdict.fail") Value.VVerdict(Verdict.fail(intAt(vals, 0), strAt(vals, 1))) else if (f == "Verdict.and") Value.VVerdict(Verdict.and(verdictOf(List.at(vals, 0), env, p, off), verdictOf(List.at(vals, 1), env, p, off))) else if (f == "Verdict.or") Value.VVerdict(Verdict.or(verdictOf(List.at(vals, 0), env, p, off), verdictOf(List.at(vals, 1), env, p, off))) else List.at(vals, 0) match { + case Value.VTimeline(t) => verdictTlKit(f, t, vals, env, p, off) + case _ => errAt(Str.concat(f, " needs Timeline"), env, p, off) +} + +def verdictTlKit(f: String, t: Timeline, vals: List[Value], env: EvEnv, p: EvProg, off: Int): Value = + if (f == "Verdict.alwaysHas") Value.VVerdict(Verdict.alwaysHas(t, strAt(vals, 1))) else if (f == "Verdict.afterHit") Value.VVerdict(Verdict.afterHit(t, strAt(vals, 1), strAt(vals, 2))) else if (f == "Verdict.onHit") Value.VVerdict(Verdict.onHit(t, strAt(vals, 1), __tup => __tup match { + case (a, b) => pairPred(List.at(vals, 2), a, b, env, p, off) +})) else if (f == "Verdict.stepEvery") Value.VVerdict(Verdict.stepEvery(t, __tup => __tup match { + case (a, b) => pairPred(List.at(vals, 1), a, b, env, p, off) +})) else if (f == "Verdict.every") Value.VVerdict(Verdict.every(t, i => boolOf(apply1(List.at(vals, 1), Value.VInt(i), env, p, off)))) else if (f == "Verdict.any") Value.VVerdict(Verdict.any(t, i => boolOf(apply1(List.at(vals, 1), Value.VInt(i), env, p, off)))) else unsupported(Str.concat("kit ", f), env, p, off) + +def scenarioCtx(p: EvProg, env: EvEnv, off: Int): Value = + if (List.isEmpty(p.ctx)) errAt("Scenario.context outside scuzz fuzz", env, p, off) else Property.force(Ref.get(List.at(p.ctx, 0))) + diff --git a/examples/compiler/src/Kits.scuzz b/examples/compiler/src/Kits.scuzz index 228be48b..cd0275db 100644 --- a/examples/compiler/src/Kits.scuzz +++ b/examples/compiler/src/Kits.scuzz @@ -57,7 +57,7 @@ def viewKits2(): List[Kit] = k("View.textField", "Signal[String]" :: "String" :: ns(), "View") :: k("View.checkbox", "Signal[Int]" :: "String" :: ns(), "View") :: k("View.switch", "Signal[Int]" :: "String" :: ns(), "View") :: k("View.chip", "Signal[Int]" :: "String" :: ns(), "View") :: k("View.filterChip", "Signal[Int]" :: "String" :: ns(), "View") :: k("View.inputChip", "Signal[Int]" :: "String" :: ns(), "View") :: k("View.checkboxListTile", "Signal[Int]" :: "String" :: ns(), "View") :: k("View.switchListTile", "Signal[Int]" :: "String" :: ns(), "View") :: k("View.semantics", "String" :: "View" :: ns(), "View") :: k("View.mergeSemantics", "String" :: "View" :: ns(), "View") :: k("View.tooltip", "String" :: "View" :: ns(), "View") :: k("View.badge", "Signal[Int]" :: "View" :: ns(), "View") :: k("View.visibility", "Signal[Int]" :: "View" :: ns(), "View") :: k("View.offstage", "Signal[Int]" :: "View" :: ns(), "View") :: k("View.overlay", "Signal[Int]" :: "View" :: ns(), "View") :: k("View.radio", "Signal[Int]" :: "Int" :: "String" :: ns(), "View") :: k("View.choiceChip", "Signal[Int]" :: "Int" :: "String" :: ns(), "View") :: k("View.radioListTile", "Signal[Int]" :: "Int" :: "String" :: ns(), "View") :: k("View.showWhen", "Signal[Int]" :: "Int" :: "View" :: ns(), "View") :: k("View.split", "Signal[Int]" :: "View" :: "View" :: ns(), "View") :: k("View.segmented", "Signal[Int]" :: "String" :: "String" :: ns(), "View") :: k("View.expansionTile", "Signal[Int]" :: "String" :: "View" :: ns(), "View") :: k("View.inkWell", "String" :: "A" :: "View" :: ns(), "View") :: ko("View.listTile", "String" :: "View" :: ns(), "View", 1) :: k("View.image", "Int" :: "Int" :: "Int" :: "String" :: ns(), "View") :: k("View.icon", "Int" :: "Int" :: ns(), "View") :: k("View.link", "String" :: "String" :: ns(), "View") :: k("View.navTile", "Int" :: "String" :: "String" :: ns(), "View") :: ko("View.each", "Signal[List[A]]" :: "A => View" :: ns(), "View", 1) :: k("View.stretch", "View" :: ns(), "View") :: k("View.clip", "View" :: ns(), "View") :: k("View.opacity", "Int" :: "View" :: ns(), "View") :: k("View.maxLines", "Int" :: "View" :: ns(), "View") :: k("View.ellipsis", "View" :: ns(), "View") :: k("View.gap", "Int" :: "View" :: ns(), "View") :: k("View.border", "Int" :: "Int" :: "View" :: ns(), "View") :: k("View.radius", "Int" :: "View" :: ns(), "View") :: k("View.ignorePointer", "View" :: ns(), "View") :: k("View.absorbPointer", "View" :: ns(), "View") :: k("View.excludeSemantics", "View" :: ns(), "View") :: k("View.list", ns(), "View") :: nsKit() def extraKits(): List[Kit] = - k("Timeline.len", "Timeline" :: ns(), "Int") :: k("Timeline.signalInt", "Timeline" :: "Int" :: "String" :: ns(), "Int") :: k("Timeline.signalListLen", "Timeline" :: "Int" :: "String" :: ns(), "Int") :: k("Timeline.fileSame", "Timeline" :: "Int" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.fileTextIs", "Timeline" :: "Int" :: "String" :: "String" :: ns(), "Bool") :: k("Timeline.signalStrHas", "Timeline" :: "Int" :: "String" :: "String" :: ns(), "Bool") :: k("Timeline.a11yHas", "Timeline" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.lastHitHas", "Timeline" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.driveHas", "Timeline" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.effectHas", "Timeline" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.faultKindHas", "Timeline" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.effectCount", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.fiberLive", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.fiberReady", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.fiberParked", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.fiberDone", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.faultN", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.checkpoint", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.nearestCheckpoint", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.exists", "Timeline" :: "Int => Bool" :: ns(), "Bool") :: k("Property.signalInt", "String" :: ns(), "Int") :: k("Property.signalStr", "String" :: ns(), "String") :: k("Property.signalListLen", "String" :: ns(), "Int") :: k("Property.signalListAt", "String" :: "Int" :: ns(), "String") :: k("Property.sometimes", "String" :: ns(), "Unit") :: k("Property.check", "String" :: "Bool" :: "A" :: ns(), "A") :: k("Property.assert", "String" :: "Bool" :: ns(), "IO[Unit]") :: k("Property.classify", "String" :: "Bool" :: ns(), "Bool") :: k("Property.a11yHas", "String" :: ns(), "Bool") :: k("Property.force", "IO[A]" :: ns(), "A") :: k("Verdict.ok", ns(), "Verdict") :: k("Verdict.fail", "Int" :: "String" :: ns(), "Verdict") :: k("Verdict.and", "Verdict" :: "Verdict" :: ns(), "Verdict") :: k("Verdict.or", "Verdict" :: "Verdict" :: ns(), "Verdict") :: k("Verdict.alwaysHas", "Timeline" :: "String" :: ns(), "Verdict") :: k("Verdict.afterHit", "Timeline" :: "String" :: "String" :: ns(), "Verdict") :: k("Verdict.onHit", "Timeline" :: "String" :: "(Int, Int) => Bool" :: ns(), "Verdict") :: k("Verdict.stepEvery", "Timeline" :: "(Int, Int) => Bool" :: ns(), "Verdict") :: k("Verdict.every", "Timeline" :: "Int => Bool" :: ns(), "Verdict") :: k("Verdict.any", "Timeline" :: "Int => Bool" :: ns(), "Verdict") :: k("Scenario.context", ns(), "A") :: nsKit() + k("Timeline.len", "Timeline" :: ns(), "Int") :: k("Timeline.signalInt", "Timeline" :: "Int" :: "String" :: ns(), "Int") :: k("Timeline.signalListLen", "Timeline" :: "Int" :: "String" :: ns(), "Int") :: k("Timeline.fileSame", "Timeline" :: "Int" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.fileTextIs", "Timeline" :: "Int" :: "String" :: "String" :: ns(), "Bool") :: k("Timeline.signalStrHas", "Timeline" :: "Int" :: "String" :: "String" :: ns(), "Bool") :: k("Timeline.a11yHas", "Timeline" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.lastHitHas", "Timeline" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.driveHas", "Timeline" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.effectHas", "Timeline" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.faultKindHas", "Timeline" :: "Int" :: "String" :: ns(), "Bool") :: k("Timeline.effectCount", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.fiberLive", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.fiberReady", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.fiberParked", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.fiberDone", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.faultN", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.checkpoint", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.nearestCheckpoint", "Timeline" :: "Int" :: ns(), "Int") :: k("Timeline.exists", "Timeline" :: "Int => Bool" :: ns(), "Bool") :: k("Property.signalInt", "String" :: ns(), "Int") :: k("Property.signalStr", "String" :: ns(), "String") :: k("Property.signalListLen", "String" :: ns(), "Int") :: k("Property.signalListAt", "String" :: "Int" :: ns(), "String") :: k("Property.sometimes", "String" :: ns(), "Unit") :: k("Property.check", "String" :: "Bool" :: "A" :: ns(), "A") :: k("Property.assert", "String" :: "Bool" :: ns(), "IO[Unit]") :: k("Property.classify", "String" :: "Bool" :: ns(), "Bool") :: k("Property.a11yHas", "String" :: ns(), "Bool") :: k("Property.force", "IO[A]" :: ns(), "A") :: k("Verdict.ok", ns(), "Verdict") :: k("Verdict.fail", "Int" :: "String" :: ns(), "Verdict") :: k("Verdict.and", "Verdict" :: "Verdict" :: ns(), "Verdict") :: k("Verdict.or", "Verdict" :: "Verdict" :: ns(), "Verdict") :: k("Verdict.alwaysHas", "Timeline" :: "String" :: ns(), "Verdict") :: k("Verdict.afterHit", "Timeline" :: "String" :: "String" :: ns(), "Verdict") :: k("Verdict.onHit", "Timeline" :: "String" :: "(Int, Int) => Bool" :: ns(), "Verdict") :: k("Verdict.stepEvery", "Timeline" :: "(Int, Int) => Bool" :: ns(), "Verdict") :: k("Verdict.every", "Timeline" :: "Int => Bool" :: ns(), "Verdict") :: k("Verdict.any", "Timeline" :: "Int => Bool" :: ns(), "Verdict") :: k("Scenario.context", ns(), "A") :: k("Fuzz.setup", "IO[E, A]" :: ns(), "IO[Unit]") :: k("Fuzz.driver", "String" :: "Int" :: "List[String] => IO[Unit]" :: ns(), "IO[Unit]") :: k("Fuzz.verify", "String" :: "Timeline => Verdict" :: ns(), "IO[Unit]") :: k("Fuzz.verifyRel", "String" :: "(Timeline, Timeline) => Verdict" :: ns(), "IO[Unit]") :: k("Fuzz.hit", "String" :: ns(), "Unit") :: k("Fuzz.probe", "IO[Unit]" :: ns(), "IO[Unit]") :: nsKit() def find(f: String): Kit = findGo(lookupKits(f), f) @@ -66,7 +66,7 @@ def lookupKits(f: String): List[Kit] = if (Str.startsWith(f, "Str.")) strKits() else if (Str.startsWith(f, "List.")) listKits() else if (Str.startsWith(f, "Map.") || Str.startsWith(f, "Set.")) mapSetKits() else if (Str.startsWith(f, "Fs.") || Str.startsWith(f, "Sys.") || Str.startsWith(f, "Impurity.")) fsSysKits() else if (Str.startsWith(f, "Json.") || Str.startsWith(f, "Net.")) jsonNetKits() else lookupEffectKits(f) def lookupEffectKits(f: String): List[Kit] = - if (Str.startsWith(f, "Signal.") || Str.startsWith(f, "Ui.") || Str.startsWith(f, "Color.") || Str.startsWith(f, "Theme.") || Str.startsWith(f, "Icon.")) uiKits() else if (Str.startsWith(f, "View.")) viewKits() else if (Str.startsWith(f, "Property.") || Str.startsWith(f, "Timeline.") || Str.startsWith(f, "Verdict.") || Str.startsWith(f, "Scenario.")) extraKits() else if (Str.startsWith(f, "Stream.")) streamKits() else if (Str.startsWith(f, "IO.") || Str.startsWith(f, "Builder.") || Str.startsWith(f, "Clock.") || Str.startsWith(f, "Hash.") || Str.startsWith(f, "Hex.") || Str.startsWith(f, "Base64.") || Str.startsWith(f, "Bytes.") || Str.startsWith(f, "Deferred.") || Str.startsWith(f, "Fiber.") || Str.startsWith(f, "Float.") || Str.startsWith(f, "Oracle.") || Str.startsWith(f, "Queue.") || Str.startsWith(f, "Random.") || Str.startsWith(f, "Uuid.") || Str.startsWith(f, "Ref.") || Str.startsWith(f, "Resource.")) ioKits() else nsKit() + if (Str.startsWith(f, "Signal.") || Str.startsWith(f, "Ui.") || Str.startsWith(f, "Color.") || Str.startsWith(f, "Theme.") || Str.startsWith(f, "Icon.")) uiKits() else if (Str.startsWith(f, "View.")) viewKits() else if (Str.startsWith(f, "Property.") || Str.startsWith(f, "Timeline.") || Str.startsWith(f, "Verdict.") || Str.startsWith(f, "Scenario.") || Str.startsWith(f, "Fuzz.")) extraKits() else if (Str.startsWith(f, "Stream.")) streamKits() else if (Str.startsWith(f, "IO.") || Str.startsWith(f, "Builder.") || Str.startsWith(f, "Clock.") || Str.startsWith(f, "Hash.") || Str.startsWith(f, "Hex.") || Str.startsWith(f, "Base64.") || Str.startsWith(f, "Bytes.") || Str.startsWith(f, "Deferred.") || Str.startsWith(f, "Fiber.") || Str.startsWith(f, "Float.") || Str.startsWith(f, "Oracle.") || Str.startsWith(f, "Queue.") || Str.startsWith(f, "Random.") || Str.startsWith(f, "Uuid.") || Str.startsWith(f, "Ref.") || Str.startsWith(f, "Resource.")) ioKits() else nsKit() def findGo(xs: List[Kit], f: String): Kit = if (List.isEmpty(xs)) miss() else findHd(List.at(xs, 0), List.tail(xs), f) diff --git a/examples/compiler/src/Verify.scuzz b/examples/compiler/src/Verify.scuzz index 0390012a..02f36b7b 100644 --- a/examples/compiler/src/Verify.scuzz +++ b/examples/compiler/src/Verify.scuzz @@ -281,10 +281,10 @@ def escEv(s: String, i: Int, b: Builder): String = def escEvCh(c: Int, s: String, i: Int): String = if (c == 92) "\\\\" else if (c == 34) "\\\"" else Str.slice(s, i, i + 1) -def reproText(seed: Int, sched: String, fault: String, events: List[String]): String = +def reproText(sched: String, fault: String, events: List[String]): String = Str.concat("""[fuzz] -""", Str.concat(reproOracle(events), Str.concat("seed = ", Str.concat(Str.fromInt(seed), Str.concat(reproSched(sched), Str.concat(reproFault(fault), Str.concat("events = [", Str.concat(quoteEvents(events), """] -""")))))))) +""", Str.concat(reproOracle(events), Str.concat(reproSched(sched), Str.concat(reproFault(fault), Str.concat("events = [", Str.concat(quoteEvents(events), """] +""")))))) def reproOracle(events: List[String]): String = if (oracleName(events) == "") "" else Str.concat("oracle = \"", Str.concat(oracleName(events), "\"\n")) @@ -387,12 +387,7 @@ def insertOracleAt(src: String, name: String, i: Int): String = if (i < 0) Str.concat("oracle = \"", Str.concat(name, Str.concat("\"\n", src))) else Str.concat(Str.slice(src, 0, i + 7), Str.concat("oracle = \"", Str.concat(name, Str.concat("\"\n", Str.slice(src, i + 7, Str.len(src)))))) def reproSched(sched: String): String = - if (sched == "") nl() else Str.concat("\nschedule_seed = \"", Str.concat(sched, Str.concat("\"\n", pctLines(sched)))) - -def pctLines(sched: String): String = - Str.concat("pct_d = ", Str.concat(Str.fromInt(decodeD(parseSeed(sched))), Str.concat(""" -pct_k = """, Str.concat(Str.fromInt(decodeK(parseSeed(sched))), """ -""")))) + if (sched == "") "" else Str.concat("schedule_seed = \"", Str.concat(sched, "\"\n")) def reproFault(fault: String): String = if (fault == "") "" else Str.concat("fault_seed = \"", Str.concat(fault, "\"\n")) diff --git a/examples/counter/corpus/5e4dd5a610aba46a.toml b/examples/counter/corpus/5e4dd5a610aba46a.toml index ccd82095..f9be6745 100644 --- a/examples/counter/corpus/5e4dd5a610aba46a.toml +++ b/examples/counter/corpus/5e4dd5a610aba46a.toml @@ -1,6 +1,3 @@ [fuzz] -seed = 42 schedule_seed = "42" -pct_d = 3 -pct_k = 2 events = ["tap button:+1"] diff --git a/examples/counter/corpus/632cecf9331a5d5a.toml b/examples/counter/corpus/632cecf9331a5d5a.toml index 6d6d776f..1e8e5e7b 100644 --- a/examples/counter/corpus/632cecf9331a5d5a.toml +++ b/examples/counter/corpus/632cecf9331a5d5a.toml @@ -1,6 +1,3 @@ [fuzz] -seed = 43 schedule_seed = "43" -pct_d = 3 -pct_k = 3 events = ["drive plusN 0"] diff --git a/examples/docs/corpus/ios_navigation.toml b/examples/docs/corpus/ios_navigation.toml index 54c6043c..269a5325 100644 --- a/examples/docs/corpus/ios_navigation.toml +++ b/examples/docs/corpus/ios_navigation.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 4 schedule_seed = "4" events = ["tap choicechip:GUI", "tap link:Open the iOS topic", "tap link:Next: Web", "tap link:Next: IDE", "tap link:Back: Web"] diff --git a/examples/docs/corpus/open_gui.toml b/examples/docs/corpus/open_gui.toml index b96390a0..bdf881dc 100644 --- a/examples/docs/corpus/open_gui.toml +++ b/examples/docs/corpus/open_gui.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 1 schedule_seed = "1" events = ["tap 17", "tap link:Docs"] diff --git a/examples/docs/corpus/open_verify.toml b/examples/docs/corpus/open_verify.toml index 95be5ee6..8c26775f 100644 --- a/examples/docs/corpus/open_verify.toml +++ b/examples/docs/corpus/open_verify.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 3 schedule_seed = "3" events = ["tap navtile:Build a GUI", "tap link:Docs", "tap choicechip:Verify"] diff --git a/examples/docs/corpus/tap_add.toml b/examples/docs/corpus/tap_add.toml index 1da2655e..2da133e4 100644 --- a/examples/docs/corpus/tap_add.toml +++ b/examples/docs/corpus/tap_add.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 2 schedule_seed = "2" events = ["tap choicechip:Signals", "tap button:Add one"] diff --git a/examples/editor/corpus/c_caret.toml b/examples/editor/corpus/c_caret.toml index e343e282..b5ebb509 100644 --- a/examples/editor/corpus/c_caret.toml +++ b/examples/editor/corpus/c_caret.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 16 schedule_seed = "16" events = ["caret 0"] diff --git a/examples/editor/corpus/c_check.toml b/examples/editor/corpus/c_check.toml index ee6796c0..555c3180 100644 --- a/examples/editor/corpus/c_check.toml +++ b/examples/editor/corpus/c_check.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 1 schedule_seed = "1" events = ["tap outlined:Check"] diff --git a/examples/editor/corpus/c_find.toml b/examples/editor/corpus/c_find.toml index 9b99ff74..ebca09bb 100644 --- a/examples/editor/corpus/c_find.toml +++ b/examples/editor/corpus/c_find.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 5 schedule_seed = "5" events = ["tap outlined:Find"] diff --git a/examples/editor/corpus/c_fuzz.toml b/examples/editor/corpus/c_fuzz.toml index 09c28268..216f860f 100644 --- a/examples/editor/corpus/c_fuzz.toml +++ b/examples/editor/corpus/c_fuzz.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 13 schedule_seed = "13" events = ["tap choicechip:Session", "tap outlined:Fuzz"] diff --git a/examples/editor/corpus/c_insert.toml b/examples/editor/corpus/c_insert.toml index 4508cff3..586d2089 100644 --- a/examples/editor/corpus/c_insert.toml +++ b/examples/editor/corpus/c_insert.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 17 schedule_seed = "17" events = ["caret 0", "key z z"] diff --git a/examples/editor/corpus/c_live.toml b/examples/editor/corpus/c_live.toml index 9b8cda8a..ee75b663 100644 --- a/examples/editor/corpus/c_live.toml +++ b/examples/editor/corpus/c_live.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 7 schedule_seed = "7" events = ["tap choicechip:Live"] diff --git a/examples/editor/corpus/c_poll.toml b/examples/editor/corpus/c_poll.toml index d8a7e850..cb02311b 100644 --- a/examples/editor/corpus/c_poll.toml +++ b/examples/editor/corpus/c_poll.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 20 schedule_seed = "20" events = ["pump 1"] diff --git a/examples/editor/corpus/c_run.toml b/examples/editor/corpus/c_run.toml index 7b31254d..7b9fa1a4 100644 --- a/examples/editor/corpus/c_run.toml +++ b/examples/editor/corpus/c_run.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 12 schedule_seed = "12" events = ["tap choicechip:Session", "tap button:Run"] diff --git a/examples/editor/corpus/c_save.toml b/examples/editor/corpus/c_save.toml index dfaab29c..1c4344ff 100644 --- a/examples/editor/corpus/c_save.toml +++ b/examples/editor/corpus/c_save.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 4 schedule_seed = "4" events = ["tap button:Save"] diff --git a/examples/editor/corpus/c_session.toml b/examples/editor/corpus/c_session.toml index 82480045..ec81c96f 100644 --- a/examples/editor/corpus/c_session.toml +++ b/examples/editor/corpus/c_session.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 3 schedule_seed = "3" events = ["tap choicechip:Session"] diff --git a/examples/editor/corpus/c_verify.toml b/examples/editor/corpus/c_verify.toml index ba13205a..5c4267ea 100644 --- a/examples/editor/corpus/c_verify.toml +++ b/examples/editor/corpus/c_verify.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 2 schedule_seed = "2" events = ["tap choicechip:Verify", "tap textbutton:greetFact"] diff --git a/examples/io/corpus/composition.toml b/examples/io/corpus/composition.toml index 0fdf8ae7..15994810 100644 --- a/examples/io/corpus/composition.toml +++ b/examples/io/corpus/composition.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 42 events = ["drive composePayloads 0", "drive composePayloads -17", "drive composePayloads 9999"] diff --git a/examples/io/src/Main.scuzz b/examples/io/src/Main.scuzz index 4e987c51..426884ee 100644 --- a/examples/io/src/Main.scuzz +++ b/examples/io/src/Main.scuzz @@ -2,7 +2,7 @@ def liveClock(): IO[Unit] = Clock.realTime().flatMap(t => IO.println(s"real:$t").flatMap(_ => Clock.monotonic().flatMap(m => IO.println(s"mono:$m").flatMap(_ => IO.println(s"iso:${Clock.iso8601(0)}").flatMap(_ => IO.println(s"leap:${Clock.iso8601(1582934400000)}")))))) def liveFs(): IO[Unit] = - Fs.mkdirs("examples/io/build").flatMap(_ => Fs.write("examples/io/build/note.txt", "fs-note").flatMap(_ => Fs.rename("examples/io/build/note.txt", "examples/io/build/renamed.txt").flatMap(_ => Fs.read("examples/io/build/renamed.txt").flatMap(s => IO.println(s"fs:$s"))))) + Fs.mkdirs("examples/io/build").flatMap(_ => Fs.write("examples/io/build/note.txt", "fs-note").flatMap(_ => Fs.rename("examples/io/build/note.txt", "examples/io/build/renamed.txt").flatMap(_ => Fs.read("examples/io/build/renamed.txt").flatMap(s => IO.println(s"fs:$s").flatMap(_ => Fs.delete("examples/io/build/renamed.txt")))))) def liveRand(): IO[Unit] = Random.nextInt(10).flatMap(n => IO.println(if (n >= 0 && n < 10) "rand:ok" else "rand:bad")) diff --git a/examples/kernel/corpus/alternatives.toml b/examples/kernel/corpus/alternatives.toml index bfa3e0e7..bf86dcb4 100644 --- a/examples/kernel/corpus/alternatives.toml +++ b/examples/kernel/corpus/alternatives.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 42 events = ["drive constructorAlternativeNumbers 19", "drive constructorAlternativeNumbers 0", "drive constructorAlternativeStrings done", "drive constructorAlternativeStrings other", "drive constructorAlternativeFields 7 true", "drive constructorAlternativeFields 7 false", "drive constructorAlternativeValues 31", "drive constructorAlternativeTexts payload", "drive constructorAlternativeGuards 31", "drive constructorAlternativeGuards -1", "drive constructorAlternativesIO 31", "drive constructorAlternativesIO -1", "drive alternativeInt -7", "drive alternativeInt 2", "drive alternativeInt 19", "drive alternativeInt 0", "drive alternativeBool true", "drive alternativeBool false", "drive alternativeString a", "drive alternativeString b", "drive alternativeString c", "drive alternativeString other", "drive alternativeGuard -7", "drive alternativeGuard 2", "drive alternativeGuard 19"] diff --git a/examples/kernel/corpus/closure-names.toml b/examples/kernel/corpus/closure-names.toml index d18ab50b..20651a6f 100644 --- a/examples/kernel/corpus/closure-names.toml +++ b/examples/kernel/corpus/closure-names.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 42 events = ["drive capturedString retain", "drive capturedDirect 37", "drive capturedNames 17", "drive capturedNames -3", "drive capturedIO -9", "drive capturedIO 0", "drive capturedIO 31"] diff --git a/examples/kernel/corpus/interpolation.toml b/examples/kernel/corpus/interpolation.toml index 7523f244..884379f8 100644 --- a/examples/kernel/corpus/interpolation.toml +++ b/examples/kernel/corpus/interpolation.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 42 events = ["drive interpolatedHole payload", "drive interpolatedJson 7", "drive interpolatedJson -19", "drive verificationInterpolation 7", "drive verificationInterpolation -19", "drive interpolatedEscapes payload"] diff --git a/examples/kernel/corpus/literal-patterns.toml b/examples/kernel/corpus/literal-patterns.toml index c1c76b38..6f98603b 100644 --- a/examples/kernel/corpus/literal-patterns.toml +++ b/examples/kernel/corpus/literal-patterns.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 42 events = ["drive constructorStrings ready", "drive constructorStrings other", "drive constructorNumbers -7", "drive constructorNumbers 7", "drive constructorFlags true", "drive constructorFlags false", "drive constructorFields 29", "drive constructorFields -8", "drive constructorNamed 29", "drive constructorNamed -8", "drive constructorEscaped", "drive constructorDelimiters", "drive constructorMatches"] diff --git a/examples/kernel/corpus/temporary-matches.toml b/examples/kernel/corpus/temporary-matches.toml index ce5d671e..039895a7 100644 --- a/examples/kernel/corpus/temporary-matches.toml +++ b/examples/kernel/corpus/temporary-matches.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 42 events = ["drive nestedMatch 7", "drive nestedMatch -1", "drive matchedFields payload 17", "drive matchedInt 7", "drive matchedInt -1", "drive matchedString payload", "drive temporaryRecord payload", "drive temporaryTailSize payload 31", "drive temporaryTailSize payload 0", "drive temporaryTailSize payload -1", "drive temporaryTailValue payload 31", "drive temporaryTailValue payload 0", "drive temporaryTailValue payload -1"] diff --git a/examples/manual/src/Topics.scuzz b/examples/manual/src/Topics.scuzz index 45e8403d..0e87d830 100644 --- a/examples/manual/src/Topics.scuzz +++ b/examples/manual/src/Topics.scuzz @@ -50,7 +50,7 @@ def verify(): Topic = """) :: p("A def with one Timeline parameter is a session claim and returns Verdict. A def with two Timeline parameters is a relation claim. Private functions can share predicates between claims. They do not become drivers or registered claims. Other public defs return Bool and become drive oracles. Drive oracles take at most three generator-friendly params.") :: p("Verdict.alwaysHas(t, needle) requires the needle in a11y at every state that has a view tree. Verdict.afterHit(t, hit, needle) requires the needle after a hit. Verdict.onHit(t, hit, (before, after) => Bool) checks an edge-triggered consecutive-state relation. Verdict.stepEvery(t, (before, after) => Bool) checks every consecutive pair. Do not add a temporal calculus.") :: cmd("scuzz fuzz --iterations 16") :: cmd("scuzz fuzz --iterations 0") :: cmd("scuzz fuzz --differential --iterations 0") :: p("Zero iterations replays corpus and seeds, then stops. A search failure fails the campaign and writes build/fuzz/repro.toml. The campaign writes build/fuzz/summary.json. The document is the typed session schema (v=1) with kind \"fuzz\": fuzz, corpus, classify, mutate, coverage, sometimes, triggers, and breadth sections. sometimes and triggers list declared, reached, and never. breadth lists varied State buckets, Timeline readers that claims call, and varied buckets with no reader. Varied but unclaimed prints as info. It does not fail the campaign. The campaign fails when a declared Property.sometimes name or afterHit / onHit trigger never fires. Zero iterations reports never-reached names and does not fail. A missing hit still leaves that per-timeline fold valid. Mutation survivors stay advisory unless the package sets a score floor. A score below that floor sets fuzz.ok to false. Universal oracles always probe the live graph with no scenario wrap: IO must exit 0; UI must paint a view tree. UI campaigns also probe the verify graph. Live and verify dumps match only when the package has no scenario file. --differential compares live dumps across Skia backends. check reports unclaimed defs, signals, and controls as info. They do not fail check.") :: p("A scenario file must define setup with zero generated params. Qualified defs replace live IO targets by name. Drivers take the setup context as the first param when setup returns a named type. Simulation is hermetic. TestRuntime does not open live sockets. Sys.exec and Sys.spawn fail. Sys.getenv is sealed except SCUZZ_SERVE and SCUZZ_KIT.") :: []) def commands(): Topic = - Topic("commands", "Commands", p("scuzz --help and scuzz --help list flags and examples. Long prose lives in these topics.") :: p("scuzz check format-verifies src/, *.scuzz_scenario, and *.scuzz_verify. It typechecks live sources. JSON with --message-format=json is the editor protocol. scuzz fmt rewrites live sources, scenarios, and claims. It includes nested scenario and claim files. It skips build, corpus, goldens, and hidden directories. Use scuzz fmt --check to find formatting changes without writing files. scuzz lsp wraps check JSON over stdin/stdout.") :: p("scuzz run builds and runs. --target selects the platform: linux, macos, headless, android, or ios. It overrides [ui].default_runtime. linux and macos must match the host. A package without [ui] accepts only the host platform. [ui] run --watch is hot reload. IO-only run --watch kills and reruns. --exec OPS plays a finite ops program after the first pump, then quiesces and exits. Inline JSON starts with { or [; anything else is an inject document path. Without --exec a [ui] run stays live. Every run watches build/inject.json and rewrites build/debug.json. scuzz exec PATH OP [ARGS...] writes ops to a live session. Desktop and Mobile also record live input to build/record.json. scuzz package artifacts strip the channel.") :: p("Timeline.fileTextIs(t, i, path, text) compares file contents at a recorded state. It uses the hermetic filesystem. It returns false for a missing file or a directory. It compares the full contents, including empty text and NUL bytes. Paths use the filesystem path rules. Timeline.fileSame(t, a, b, path) compares the full file contents at two recorded states. It returns false if either state is invalid or the path is missing or is a directory at either state. The comparison includes empty text and NUL bytes. It does not check intermediate states. Use it with Verdict.stepEvery to check that a failed operation preserves a file. No host file is read by a claim. scuzz fuzz --iterations N probes the live graph, replays corpus and seeds, then searches, then mutates. Search uses five eighths of N, rounded down. Mutation uses the remaining allocation, up to the number of sites. The initial probes and corpus replay do not use this allocation. N is not a time limit. The terminal and build/fuzz/summary.json use the same results. Search counts include only completed search probes. Corpus failures have a separate count and always fail the campaign. --no-fail-fast continues search after a corpus failure. Coverage shows reached functions and branch arms as reached/total. Sometimes and triggers show reached/declared counts. A zero denominator means that the group has no entries. --iterations 0 is corpus-only. --replay restores a repro.toml. --relate judges relation claims. --differential compares live dumps across Skia backends. --no-fail-fast finishes search and still mutates.") :: p("scuzz new NAME creates an IO hello. scuzz new NAME --ui creates a Counter GUI, a verify file, and a corpus tap. scuzz devices lists iOS simulators. scuzz run --target ios builds and launches on a simulator. --device selects a name or ID. --watch reloads Views after source changes. Manifest changes restart the app. scuzz package --target linux|macos|android|ios|web|all writes artifacts under build/package/. scuzz ide launches the bundled editor.") :: p("Live inject ops: tap, xy, resize, lifecycle, keyboard, text, type, key, compose, commit, caret, select, copy, cut, paste, drag, hover, secondary, pump, scroll, backspace, dump, snapshot, reload, quit, resetpeak. Dump path: build/debug.json. A dump writes the typed session schema (v=2) at any path. It covers: signals, views, a11y, taps, fields, editors, splits, overlays, scrolls, hits, session, heap, and live. The a11y section is a typed tree in the same preorder as the views lines. Toggle kinds carry on. Numeric kinds carry value. Children nest under children. Signal value and non-string list payloads encode typed: strings, numbers, arrays, and ADTs as tag plus payload. A script, record, or inject path uses the same schema with kind \"inject\" (v=1). The path must end in .json. One object per event: {\"op\":\"tap\",\"i\":0} or {\"op\":\"tap\",\"id\":\"button:+1\"}. A resize takes width, height, and optional scale. A lifecycle event takes phase: pause, resume, or stop. A keyboard event takes visible: true or false. Index keys are i. A named tap uses id with the same role:label key as last_hit. Payload strings are value. Points are x and y. Key events take key, text, mods, and repeat. A record rewrites the whole document on each live event. A watch inject plays the whole document on change.") :: cmd("scuzz check") :: cmd("scuzz fuzz --iterations 0") :: cmd("scuzz docs verify") :: []) + Topic("commands", "Commands", p("scuzz --help and scuzz --help list flags and examples. Long prose lives in these topics.") :: p("scuzz check format-verifies src/, *.scuzz_scenario, and *.scuzz_verify. It typechecks live sources. JSON with --message-format=json is the editor protocol. scuzz fmt rewrites live sources, scenarios, and claims. It includes nested scenario and claim files. It skips build, corpus, goldens, and hidden directories. Use scuzz fmt --check to find formatting changes without writing files. scuzz lsp wraps check JSON over stdin/stdout. scuzz eval runs an IO-only package on the evaluator without emit or link.") :: p("scuzz run builds and runs. --target selects the platform: linux, macos, headless, android, or ios. It overrides [ui].default_runtime. linux and macos must match the host. A package without [ui] accepts only the host platform. [ui] run --watch is hot reload. IO-only run --watch kills and reruns. --exec OPS plays a finite ops program after the first pump, then quiesces and exits. Inline JSON starts with { or [; anything else is an inject document path. Without --exec a [ui] run stays live. Every run watches build/inject.json and rewrites build/debug.json. scuzz exec PATH OP [ARGS...] writes ops to a live session. Desktop and Mobile also record live input to build/record.json. scuzz package artifacts strip the channel.") :: p("Timeline.fileTextIs(t, i, path, text) compares file contents at a recorded state. It uses the hermetic filesystem. It returns false for a missing file or a directory. It compares the full contents, including empty text and NUL bytes. Paths use the filesystem path rules. Timeline.fileSame(t, a, b, path) compares the full file contents at two recorded states. It returns false if either state is invalid or the path is missing or is a directory at either state. The comparison includes empty text and NUL bytes. It does not check intermediate states. Use it with Verdict.stepEvery to check that a failed operation preserves a file. No host file is read by a claim. scuzz fuzz --iterations N probes the live graph, replays corpus and seeds, then searches, then mutates. Search uses five eighths of N, rounded down. Mutation uses the remaining allocation, up to the number of sites. The initial probes and corpus replay do not use this allocation. N is not a time limit. The terminal and build/fuzz/summary.json use the same results. Search counts include only completed search probes. Corpus failures have a separate count and always fail the campaign. --no-fail-fast continues search after a corpus failure. Coverage shows reached functions and branch arms as reached/total. Sometimes and triggers show reached/declared counts. A zero denominator means that the group has no entries. --iterations 0 is corpus-only. --replay restores a repro.toml. --relate judges relation claims. --differential compares live dumps across Skia backends. --no-fail-fast finishes search and still mutates.") :: p("scuzz new NAME creates an IO hello. scuzz new NAME --ui creates a Counter GUI, a verify file, and a corpus tap. scuzz devices lists iOS simulators. scuzz run --target ios builds and launches on a simulator. --device selects a name or ID. --watch reloads Views after source changes. Manifest changes restart the app. scuzz package --target linux|macos|android|ios|web|all writes artifacts under build/package/. scuzz ide launches the bundled editor.") :: p("Live inject ops: tap, xy, resize, lifecycle, keyboard, text, type, key, compose, commit, caret, select, copy, cut, paste, drag, hover, secondary, pump, scroll, backspace, dump, snapshot, reload, quit, resetpeak. Dump path: build/debug.json. A dump writes the typed session schema (v=2) at any path. It covers: signals, views, a11y, taps, fields, editors, splits, overlays, scrolls, hits, session, heap, and live. The a11y section is a typed tree in the same preorder as the views lines. Toggle kinds carry on. Numeric kinds carry value. Children nest under children. Signal value and non-string list payloads encode typed: strings, numbers, arrays, and ADTs as tag plus payload. A script, record, or inject path uses the same schema with kind \"inject\" (v=1). The path must end in .json. One object per event: {\"op\":\"tap\",\"i\":0} or {\"op\":\"tap\",\"id\":\"button:+1\"}. A resize takes width, height, and optional scale. A lifecycle event takes phase: pause, resume, or stop. A keyboard event takes visible: true or false. Index keys are i. A named tap uses id with the same role:label key as last_hit. Payload strings are value. Points are x and y. Key events take key, text, mods, and repeat. A record rewrites the whole document on each live event. A watch inject plays the whole document on change.") :: cmd("scuzz check") :: cmd("scuzz fuzz --iterations 0") :: cmd("scuzz docs verify") :: []) def manifest(): Topic = Topic("manifest", "Manifest", p("scuzz.toml is data. It is not a plugin DSL. Unknown keys fail load. Do not add [plugins] or a build.scuzz hook.") :: code("[package]\nname = \"hello\"\nversion = \"0.1.0\"\ndescription = \"Scuzz Lang hello world\"\n") :: p("[package] requires name. version is semver-ish. description is optional prose. The toolchain ignores description.") :: p("[dependencies] holds named local path dependencies. Each entry is an inline table with exactly path. Paths resolve relative to the declaring scuzz.toml. The complete graph typechecks as one program. An executable root needs exactly one @main.") :: p("[ui] is optional. default_runtime is headless, desktop, or mobile. scuzz run exports SCUZZ_UI_RUNTIME from that key. --target on run and ide overrides it. headless_size is [w, h] logical pixels. headless_scale is a positive float. bundle_id is the Android package and the Apple CFBundleIdentifier. tap_button is an optional 0-based [taps] index.") :: p("[fuzz] is optional. score_floor is a 0.000 to 1.000 mutation score. scuzz fuzz fails when the score drops below that floor. summary.json then records fuzz.ok as false. Omit the table when the package has no floor.") :: p("Sources live in src/*.scuzz. At most one *.scuzz_scenario file may sit anywhere in the package. *.scuzz_verify files may sit anywhere in the package. Leftover *.scuzz_sim and *.scuzz_drivers files fail check.") :: []) diff --git a/examples/network-ui/corpus/load.toml b/examples/network-ui/corpus/load.toml index 53a4c554..ab3dd3d3 100644 --- a/examples/network-ui/corpus/load.toml +++ b/examples/network-ui/corpus/load.toml @@ -1,4 +1,3 @@ [fuzz] -seed = 42 schedule_seed = "42" events = ["tap button:Load", "tap button:Tap"] diff --git a/examples/network-ui/corpus/network-fault.toml b/examples/network-ui/corpus/network-fault.toml index d3e76fae..b39318d0 100644 --- a/examples/network-ui/corpus/network-fault.toml +++ b/examples/network-ui/corpus/network-fault.toml @@ -1,7 +1,4 @@ [fuzz] -seed = 42 schedule_seed = "2" -pct_d = 2 -pct_k = 2 fault_seed = "2" events = ["tap button:Load"] diff --git a/examples/studio/corpus/256abc47dd89ccf5.toml b/examples/studio/corpus/256abc47dd89ccf5.toml index 9611f442..0e415304 100644 --- a/examples/studio/corpus/256abc47dd89ccf5.toml +++ b/examples/studio/corpus/256abc47dd89ccf5.toml @@ -1,10 +1,4 @@ [fuzz] -seed = 64 schedule_seed = "64" -pct_d = 2 -pct_k = 0 fault_seed = "52" -fault_kind = "fs" -fault_n = 6 -fault_mode = "corrupt" events = ["tap choicechip:Tasks", "tap button:Save"] diff --git a/examples/studio/corpus/2c3133d37911236d.toml b/examples/studio/corpus/2c3133d37911236d.toml index 499e7c0e..84961888 100644 --- a/examples/studio/corpus/2c3133d37911236d.toml +++ b/examples/studio/corpus/2c3133d37911236d.toml @@ -1,6 +1,3 @@ [fuzz] -seed = 259 schedule_seed = "256" -pct_d = 2 -pct_k = 0 events = ["tap choicechip:Tasks", "tap button:+1"] diff --git a/examples/studio/corpus/3be0538523504bb3.toml b/examples/studio/corpus/3be0538523504bb3.toml index 3403bc70..f1b82c8d 100644 --- a/examples/studio/corpus/3be0538523504bb3.toml +++ b/examples/studio/corpus/3be0538523504bb3.toml @@ -1,6 +1,3 @@ [fuzz] -seed = 43 schedule_seed = "43" -pct_d = 3 -pct_k = 3 events = ["drive addItem u", "tap choicechip:Tasks", "tap button:Save"] diff --git a/examples/studio/corpus/90ee75d54ab7f812.toml b/examples/studio/corpus/90ee75d54ab7f812.toml index 01309c55..41f56986 100644 --- a/examples/studio/corpus/90ee75d54ab7f812.toml +++ b/examples/studio/corpus/90ee75d54ab7f812.toml @@ -1,6 +1,3 @@ [fuzz] -seed = 67 schedule_seed = "67" -pct_d = 2 -pct_k = 3 events = ["tap choicechip:Tasks", "tap button:Clear"] diff --git a/examples/studio/corpus/a46b0ec913fc76b2.toml b/examples/studio/corpus/a46b0ec913fc76b2.toml index 2c8088bb..00a0187b 100644 --- a/examples/studio/corpus/a46b0ec913fc76b2.toml +++ b/examples/studio/corpus/a46b0ec913fc76b2.toml @@ -1,6 +1,3 @@ [fuzz] -seed = 44 schedule_seed = "40" -pct_d = 3 -pct_k = 0 events = ["tap choicechip:Home"] diff --git a/examples/studio/corpus/a4710ec91400e260.toml b/examples/studio/corpus/a4710ec91400e260.toml index 0dd4f5c4..e56ab6e5 100644 --- a/examples/studio/corpus/a4710ec91400e260.toml +++ b/examples/studio/corpus/a4710ec91400e260.toml @@ -1,6 +1,3 @@ [fuzz] -seed = 46 schedule_seed = "40" -pct_d = 3 -pct_k = 0 events = ["tap choicechip:Preferences"] diff --git a/examples/studio/corpus/drove_clear.toml b/examples/studio/corpus/drove_clear.toml index 587c48dc..ecad20b6 100644 --- a/examples/studio/corpus/drove_clear.toml +++ b/examples/studio/corpus/drove_clear.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 46 events = ["drive clearItems"] diff --git a/examples/studio/corpus/drove_del.toml b/examples/studio/corpus/drove_del.toml index b1bcf11d..09cc60d7 100644 --- a/examples/studio/corpus/drove_del.toml +++ b/examples/studio/corpus/drove_del.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 44 events = ["drive deleteItem x"] diff --git a/examples/studio/corpus/drove_flip.toml b/examples/studio/corpus/drove_flip.toml index e1dd4b6c..3da05cba 100644 --- a/examples/studio/corpus/drove_flip.toml +++ b/examples/studio/corpus/drove_flip.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 45 events = ["drive flipItem x"] diff --git a/examples/studio/corpus/drove_rename.toml b/examples/studio/corpus/drove_rename.toml index eba279b1..7c6c6a3e 100644 --- a/examples/studio/corpus/drove_rename.toml +++ b/examples/studio/corpus/drove_rename.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 43 events = ["drive renameItem x"] diff --git a/examples/studio/corpus/index_book.toml b/examples/studio/corpus/index_book.toml index 60b4015f..05312c39 100644 --- a/examples/studio/corpus/index_book.toml +++ b/examples/studio/corpus/index_book.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 42 events = ["tap button:+1", "tap tab:Status", "key Home", "key Enter", "tap choicechip:Preferences", "tap choicechip:Tasks", "tap choicechip:Home", "tap outlined:Reset counter"] diff --git a/examples/studio/corpus/reach_tasks.toml b/examples/studio/corpus/reach_tasks.toml index 556faa96..4c16867e 100644 --- a/examples/studio/corpus/reach_tasks.toml +++ b/examples/studio/corpus/reach_tasks.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 47 events = ["tap choicechip:Tasks", "tap button:Rename", "tap button:Done", "type 0 x", "tap button:Add", "tap button:Del", "tap button:Clear"] diff --git a/examples/studio/corpus/typed_add.toml b/examples/studio/corpus/typed_add.toml index c8c907f3..821ed575 100644 --- a/examples/studio/corpus/typed_add.toml +++ b/examples/studio/corpus/typed_add.toml @@ -1,6 +1,3 @@ [fuzz] -seed = 46 schedule_seed = "40" -pct_d = 3 -pct_k = 0 events = ["tap textbutton:Open tasks", "type 0 d", "tap button:Add"] diff --git a/examples/tyck/corpus/search-42-0.toml b/examples/tyck/corpus/search-42-0.toml index 9d8fca05..93d799ff 100644 --- a/examples/tyck/corpus/search-42-0.toml +++ b/examples/tyck/corpus/search-42-0.toml @@ -1,7 +1,4 @@ [fuzz] oracle = "tyckFail" -seed = 42 schedule_seed = "0" -pct_d = 2 -pct_k = 0 events = ["drive tyckFail", "drive tyckRes"] diff --git a/examples/webhook/corpus/deliveries.toml b/examples/webhook/corpus/deliveries.toml index 7659400f..b9ddf3c8 100644 --- a/examples/webhook/corpus/deliveries.toml +++ b/examples/webhook/corpus/deliveries.toml @@ -1,3 +1,2 @@ [fuzz] -seed = 42 events = ["drive delivery", "drive prior 17", "drive tampered 0", "drive tampered -8", "drive wrongKey", "drive missingSignature", "drive emptySecret", "drive badJson", "drive arrayJson", "drive wrongPath", "drive wrongMethod", "drive storageFailure", "drive delivery", "drive concurrent", "drive prior -23", "drive continued", "drive concurrent"] diff --git a/examples/webhook/corpus/search-42-94.toml b/examples/webhook/corpus/search-42-94.toml index 01867a41..d8b6dedb 100644 --- a/examples/webhook/corpus/search-42-94.toml +++ b/examples/webhook/corpus/search-42-94.toml @@ -1,8 +1,5 @@ [fuzz] oracle = "prior" -seed = 42 schedule_seed = "94" -pct_d = 5 -pct_k = 6 fault_seed = "94" events = ["drive prior 17", "drive tampered 0", "drive tampered -8", "drive wrongKey", "drive missingSignature", "drive emptySecret", "drive badJson", "drive arrayJson", "drive wrongPath", "drive wrongMethod", "drive storageFailure"] diff --git a/scripts/ci-fuzz.sh b/scripts/ci-fuzz.sh index d3740a18..5aa47e0d 100755 --- a/scripts/ci-fuzz.sh +++ b/scripts/ci-fuzz.sh @@ -64,7 +64,6 @@ def check(n: Int): Bool = CLAIMS cat > "$search_counts_dir/corpus/rejected.toml" <<'CORPUS' [fuzz] -seed = 42 events = ["drive check 37"] CORPUS if fuzz --iterations 8 "$search_counts_dir" > "$search_counts_dir/corpus.log" 2>&1; then @@ -123,13 +122,19 @@ fuzz --relate examples/counter if fuzz --relate examples/bad-sched; then echo "relate should have caught the schedule divergence" && exit 1 fi -if fuzz --no-fail-fast --iterations 8 examples/bad-example; then +# A failing search promotes its repro into /corpus/. Run the campaign that +# must fail on a copy so the tracked corpus stays what a human committed. +rm -rf /tmp/bad-example +cp -R examples/bad-example /tmp/bad-example +rm -rf /tmp/bad-example/build +if fuzz --no-fail-fast --iterations 8 /tmp/bad-example; then echo "fuzz should have found the property failure" && exit 1 fi -test -f examples/bad-example/build/fuzz/repro.toml +test -f /tmp/bad-example/build/fuzz/repro.toml +test -f /tmp/bad-example/corpus/search-42-0.toml python3 - <<'PY' import json -with open("examples/bad-example/build/fuzz/summary.json") as f: +with open("/tmp/bad-example/build/fuzz/summary.json") as f: d = json.load(f) assert d["v"] == 1 and d["kind"] == "fuzz" assert d["fuzz"]["ok"] is False @@ -139,7 +144,7 @@ assert d["corpus"]["failures"] >= 1 assert d["mutate"]["ran"] >= 1 assert d["mutate"]["inert"] >= 1 PY -if fuzz --replay examples/bad-example/build/fuzz/repro.toml examples/bad-example; then +if fuzz --replay /tmp/bad-example/build/fuzz/repro.toml /tmp/bad-example; then echo "replay should have reproduced the property failure" && exit 1 fi if fuzz --iterations 0 examples/bad-example; then @@ -207,21 +212,19 @@ if fuzz --replay examples/bad-adt/corpus/209ce82661a8103a.toml examples/bad-adt; fi test -f examples/bad-fault/build/fuzz/repro.toml grep -q 'fault_seed' examples/bad-fault/build/fuzz/repro.toml -grep -q 'fault_kind = "fs"' examples/bad-fault/build/fuzz/repro.toml if fuzz --replay examples/bad-fault/corpus/f83245e1fbf633a5.toml examples/bad-fault; then echo "fault replay should have reproduced the failure" && exit 1 fi -grep -v -e fault_seed -e fault_kind -e fault_n -e fault_mode examples/bad-fault/corpus/f83245e1fbf633a5.toml > /tmp/bad-fault-nofault.toml +grep -v fault_seed examples/bad-fault/corpus/f83245e1fbf633a5.toml > /tmp/bad-fault-nofault.toml if ! fuzz --replay /tmp/bad-fault-nofault.toml examples/bad-fault; then echo "replay without fault_seed should pass" && exit 1 fi -grep -q 'pct_d = 2' examples/bad-sched/corpus/d037d00bc981a2fb.toml -grep -q 'pct_k = 0' examples/bad-sched/corpus/d037d00bc981a2fb.toml +grep -q 'schedule_seed = "' examples/bad-sched/corpus/d037d00bc981a2fb.toml grep -q 'drive checkOrder' examples/bad-sched/corpus/d037d00bc981a2fb.toml if fuzz --replay examples/bad-sched/corpus/d037d00bc981a2fb.toml examples/bad-sched; then echo "schedule replay should have reproduced the failure" && exit 1 fi -grep -v -e schedule_seed -e pct_d -e pct_k examples/bad-sched/corpus/d037d00bc981a2fb.toml > /tmp/bad-sched-fifo.toml +grep -v schedule_seed examples/bad-sched/corpus/d037d00bc981a2fb.toml > /tmp/bad-sched-fifo.toml if ! fuzz --replay /tmp/bad-sched-fifo.toml examples/bad-sched; then echo "FIFO replay (no schedule_seed) should pass" && exit 1 fi @@ -497,7 +500,6 @@ def run(n: Int): IO[Int] = Main.checked(n) SCENARIO cat > "$match_require_dir/corpus/payload.toml" <<'CORPUS' [fuzz] -seed = 42 events = ["drive run 7", "drive run -1"] CORPUS check_match_require() { @@ -599,13 +601,11 @@ def preserved(t: Timeline): Verdict = CLAIMS cat > "$file_compare_dir/corpus/files.toml" <<'CORPUS' [fuzz] -seed = 42 events = ["drive keep"] CORPUS fuzz --iterations 0 "$file_compare_dir" cat > "$file_compare_dir/corpus/files.toml" <<'CORPUS' [fuzz] -seed = 42 events = ["drive change"] CORPUS if fuzz --iterations 0 "$file_compare_dir" > /tmp/scuzz-file-compare.log 2>&1; then diff --git a/scripts/ci-kernel.sh b/scripts/ci-kernel.sh index a94d123b..87c70291 100755 --- a/scripts/ci-kernel.sh +++ b/scripts/ci-kernel.sh @@ -11,6 +11,9 @@ if [ "${PIPESTATUS[0]}" -ne 0 ]; then gdb -batch -ex run -ex bt ./examples/kernel/build/kernel 2>&1 | tail -30 || true exit 1 fi +# The evaluator is a reference semantics: same stdout as the compiled kernel. +"$SCUZZ" eval examples/kernel | tee /tmp/kernel-eval.out +diff <(grep -vx ok /tmp/kernel.out) /tmp/kernel-eval.out "$SCUZZ" run examples/scale | tee /tmp/scale.out grep -q "mapn:3048" /tmp/scale.out grep -q "maps:2098176" /tmp/scale.out @@ -70,6 +73,9 @@ grep -q "uuid:ok" /tmp/io.out grep -q "bytes:2" /tmp/io.out grep -q "kit:skip" /tmp/io.out grep -q "fs:" /tmp/io.out +# The evaluator runs the same effects. Clock and random draws differ per run. +"$SCUZZ" eval examples/io | tee /tmp/io-eval.out +diff <(grep -vx ok /tmp/io.out | grep -Ev '^(real|mono|nethN):') <(grep -Ev '^(real|mono|nethN):' /tmp/io-eval.out) "$SCUZZ" fuzz --iterations 0 examples/io | tee /tmp/io-test.out grep -q "served:POST:/ping:hi" /tmp/io-test.out grep -q "ping:200:ok:ok:/ping" /tmp/io-test.out diff --git a/scripts/ci.sh b/scripts/ci.sh index e7db0b7d..c7d040cc 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -276,6 +276,11 @@ slice_hello() { "$SCUZZ" run examples/hello | tee /tmp/hello.out grep -q "Hello, Scuzz!" /tmp/hello.out grep -q "ready." /tmp/hello.out + # Evaluator parity: eval stdout must equal the compiled program stdout. + # The driver prints one "ok" line when it emits fresh IR; that line is not program output. + "$SCUZZ" run examples/hello | grep -v '^ok$' > /tmp/hello.run.out + "$SCUZZ" eval examples/hello | tee /tmp/hello.eval.out + diff /tmp/hello.run.out /tmp/hello.eval.out "$SCUZZ" run examples/fmt | tee /tmp/fmt.out grep -q "fmt-ok" /tmp/fmt.out } @@ -297,8 +302,16 @@ slice_codegen() { echo "codegen emit start" "$SCUZZ" build examples/codegen echo "codegen emit done" - "$SCUZZ" run examples/codegen | tee /tmp/codegen.out + # The probe oracle runs last under SCUZZ_EV_*: two evAdd drive lines, one + # claim, one coverage key. + printf '{"v":1,"kind":"inject","events":[{"op":"drive","name":"evAdd","args":[3]},{"op":"drive","name":"evAdd","args":[4]}]}' > /tmp/codegen-probe.json + rm -f /tmp/codegen-probe.cov + SCUZZ_EV_TESTRT=1 SCUZZ_EV_DRIVE_SCRIPT=/tmp/codegen-probe.json SCUZZ_EV_COVERAGE_DUMP=/tmp/codegen-probe.cov \ + "$SCUZZ" run examples/codegen | tee /tmp/codegen.out grep -q "ir-ok" /tmp/codegen.out + grep -q "eval-ok" /tmp/codegen.out + grep -q "probe-ok" /tmp/codegen.out + grep -qx "codegen:probe" /tmp/codegen-probe.cov local memory_dir memory_dir="$(mktemp -d "${TMPDIR:-/tmp}/scuzz-match-memory.XXXXXX")" mkdir -p "$memory_dir/src"