From 881a08617d5ae8bf1ebd860712af5c795d726f0f Mon Sep 17 00:00:00 2001 From: disconcision Date: Tue, 19 Nov 2024 19:18:40 -0500 Subject: [PATCH 01/23] projectors live init: an attempt at extracing vals for projectors: in maketerm, wrap projector syntax in Parens variant, which is then retained through elaboration and its evaluation is tracked like Test --- src/haz3lcore/dynamics/EvalCtx.re | 4 ++++ src/haz3lcore/dynamics/EvaluatorStep.re | 3 +++ src/haz3lcore/dynamics/FilterMatcher.re | 4 ++-- src/haz3lcore/dynamics/Substitution.re | 4 ++-- src/haz3lcore/dynamics/Transition.re | 19 ++++++++++++++++++- src/haz3lcore/dynamics/TypeAssignment.re | 2 +- src/haz3lcore/pretty/ExpToSegment.re | 13 +++++++------ src/haz3lcore/statics/Elaborator.re | 7 ++++++- src/haz3lcore/statics/MakeTerm.re | 11 +++++++++-- src/haz3lcore/statics/Statics.re | 2 +- src/haz3lcore/statics/Term.re | 8 ++++---- src/haz3lcore/statics/TermBase.re | 15 ++++++++++----- src/haz3lweb/app/explainthis/Example.re | 2 ++ src/haz3lweb/app/explainthis/ExplainThis.re | 4 ++-- src/haz3lweb/exercises/SyntaxTest.re | 12 ++++++------ test/Test_Elaboration.re | 2 +- test/Test_MakeTerm.re | 2 +- 17 files changed, 79 insertions(+), 35 deletions(-) diff --git a/src/haz3lcore/dynamics/EvalCtx.re b/src/haz3lcore/dynamics/EvalCtx.re index bed931c8d3..3245e55082 100644 --- a/src/haz3lcore/dynamics/EvalCtx.re +++ b/src/haz3lcore/dynamics/EvalCtx.re @@ -23,6 +23,7 @@ type term = | BinOp2(Operators.op_bin, DHExp.t, t) | Tuple(t, (list(DHExp.t), list(DHExp.t))) | Test(t) + | Parens(t, TermBase.paren_tag) | ListLit(t, (list(DHExp.t), list(DHExp.t))) | MultiHole(t, (list(Any.t), list(Any.t))) | Cons1(t, DHExp.t) @@ -89,6 +90,9 @@ let rec compose = (ctx: t, d: DHExp.t): DHExp.t => { | Test(ctx) => let d1 = compose(ctx, d); Test(d1) |> wrap; + | Parens(ctx, tag) => + let d1 = compose(ctx, d); + Parens(d1, tag) |> wrap; | UnOp(op, ctx) => let d1 = compose(ctx, d); UnOp(op, d1) |> wrap; diff --git a/src/haz3lcore/dynamics/EvaluatorStep.re b/src/haz3lcore/dynamics/EvaluatorStep.re index 3416a46742..cf288a3fc1 100644 --- a/src/haz3lcore/dynamics/EvaluatorStep.re +++ b/src/haz3lcore/dynamics/EvaluatorStep.re @@ -140,6 +140,9 @@ let rec matches = | Test(ctx) => let+ ctx = matches(env, flt, ctx, exp, act, idx); Test(ctx) |> rewrap; + | Parens(ctx, tag) => + let+ ctx = matches(env, flt, ctx, exp, act, idx); + Parens(ctx, tag) |> rewrap; | ListLit(ctx, ds) => let+ ctx = matches(env, flt, ctx, exp, act, idx); ListLit(ctx, ds) |> rewrap; diff --git a/src/haz3lcore/dynamics/FilterMatcher.re b/src/haz3lcore/dynamics/FilterMatcher.re index d6d0bcc543..e05f7cdb0c 100644 --- a/src/haz3lcore/dynamics/FilterMatcher.re +++ b/src/haz3lcore/dynamics/FilterMatcher.re @@ -104,8 +104,8 @@ let rec matches_exp = true; } else { switch (d |> DHExp.term_of, f |> DHExp.term_of) { - | (Parens(d), _) => matches_exp(d, f) - | (_, Parens(f)) => matches_exp(d, f) + | (Parens(d, _), _) => matches_exp(d, f) + | (_, Parens(f, _)) => matches_exp(d, f) | (Constructor("$e", _), _) => failwith("$e in matched expression") | (Constructor("$v", _), _) => failwith("$v in matched expression") diff --git a/src/haz3lcore/dynamics/Substitution.re b/src/haz3lcore/dynamics/Substitution.re index 5d918e520b..350bfc450d 100644 --- a/src/haz3lcore/dynamics/Substitution.re +++ b/src/haz3lcore/dynamics/Substitution.re @@ -115,9 +115,9 @@ let rec subst_var = (m, d1: DHExp.t, x: Var.t, d2: DHExp.t): DHExp.t => { | TyAlias(tp, ut, d4) => let d4' = subst_var(m, d1, x, d4); TyAlias(tp, ut, d4') |> rewrap; - | Parens(d4) => + | Parens(d4, tag) => let d4' = subst_var(m, d1, x, d4); - Parens(d4') |> rewrap; + Parens(d4', tag) |> rewrap; | Deferral(_) => d2 | DeferredAp(d3, d4s) => let d3 = subst_var(m, d1, x, d3); diff --git a/src/haz3lcore/dynamics/Transition.re b/src/haz3lcore/dynamics/Transition.re index c178a49dfa..8be074f305 100644 --- a/src/haz3lcore/dynamics/Transition.re +++ b/src/haz3lcore/dynamics/Transition.re @@ -770,7 +770,24 @@ module Transition = (EV: EV_MODE) => { | Undefined => let. _ = otherwise(env, d); Indet; - | Parens(d) => + | Parens(d'', Probe) => + //TODO(andrew): cleanup + //print_endline("Probe in Transition"); + let. _ = otherwise(env, ((d, _)) => Parens(d, Probe) |> rewrap) + and. (d', _is_value) = + req_final_or_value( + req(state, env), + d => Parens(d, Probe) |> wrap_ctx, + d'', + ); + Step({ + expr: d', + state_update: () => + update_test(state, DHExp.rep_id(d), (d', Indet)), + kind: RemoveParens, + is_value: true, + }); + | Parens(d, Paren) => let. _ = otherwise(env, d); Step({expr: d, state_update, kind: RemoveParens, is_value: false}); | TyAlias(_, _, d) => diff --git a/src/haz3lcore/dynamics/TypeAssignment.re b/src/haz3lcore/dynamics/TypeAssignment.re index f4979d94bf..1ef46091c1 100644 --- a/src/haz3lcore/dynamics/TypeAssignment.re +++ b/src/haz3lcore/dynamics/TypeAssignment.re @@ -344,7 +344,7 @@ and typ_of_dhexp = (ctx: Ctx.t, m: Statics.Map.t, dh: DHExp.t): option(Typ.t) => None; }; | TyAlias(_, _, d) => typ_of_dhexp(ctx, m, d) - | Parens(d) => typ_of_dhexp(ctx, m, d) + | Parens(d, _) => typ_of_dhexp(ctx, m, d) }; }; diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index 1a048d12a6..50f08696fe 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -214,7 +214,7 @@ let rec exp_to_pretty = (~settings: Settings.t, exp: Exp.t): pretty => { let id = exp |> Exp.rep_id; let+ es = es |> List.map(any_to_pretty(~settings)) |> all; ListUtil.flat_intersperse(Grout({id, shape: Concave}), es); - | Parens({term: Fun(p, e, _, _), _}) + | Parens({term: Fun(p, e, _, _), _}, _) | Fun(p, e, _, _) => // TODO: Add optional newlines let id = exp |> Exp.rep_id; @@ -355,8 +355,9 @@ let rec exp_to_pretty = (~settings: Settings.t, exp: Exp.t): pretty => { let id = exp |> Exp.rep_id; let+ e = go(e); [mk_form("test", id, [e])]; - | Parens(e) => + | Parens(e, _) => // TODO: Add optional newlines + let id = exp |> Exp.rep_id; let+ e = go(e); [mk_form("parens_exp", id, [e])]; @@ -743,11 +744,11 @@ let external_precedence_typ = (tp: Typ.t) => let paren_at = (internal_precedence: Precedence.t, exp: Exp.t): Exp.t => external_precedence(exp) >= internal_precedence - ? Exp.fresh(Parens(exp)) : exp; + ? Exp.fresh(Parens(exp, Paren)) : exp; let paren_assoc_at = (internal_precedence: Precedence.t, exp: Exp.t): Exp.t => external_precedence(exp) > internal_precedence - ? Exp.fresh(Parens(exp)) : exp; + ? Exp.fresh(Parens(exp, Paren)) : exp; let paren_pat_at = (internal_precedence: Precedence.t, pat: Pat.t): Pat.t => external_precedence_pat(pat) >= internal_precedence @@ -894,8 +895,8 @@ let rec parenthesize = (exp: Exp.t): Exp.t => { // parenthesize(e) |> paren_at(Precedence.min), // ) // |> rewrap - | Parens(e) => - Parens(parenthesize(e) |> paren_at(Precedence.min)) |> rewrap + | Parens(e, tag) => + Parens(parenthesize(e) |> paren_at(Precedence.min), tag) |> rewrap | Cons(e1, e2) => Cons( parenthesize(e1) |> paren_at(Precedence.cons), diff --git a/src/haz3lcore/statics/Elaborator.re b/src/haz3lcore/statics/Elaborator.re index f00b021130..502b642f17 100644 --- a/src/haz3lcore/statics/Elaborator.re +++ b/src/haz3lcore/statics/Elaborator.re @@ -236,7 +236,12 @@ let rec elaborate = (m: Statics.Map.t, uexp: UExp.t): (DHExp.t, Typ.t) => { |> cast_from(Typ.temp(Unknown(Internal))); | Cast(e, _, _) // We remove these casts because they should be re-inserted in the recursive call | FailedCast(e, _, _) - | Parens(e) => + | Parens(e, Probe) => + //TODO(andrew): am i casting/rewrapping correctly? + let (e', ty) = elaborate(m, e); + Parens(e' |> cast_from(ty), Probe) |> rewrap; + | Parens(e, _) => + //TODO(andrew): dont get rid of these? let (e', ty) = elaborate(m, e); e' |> cast_from(ty); | Deferral(_) => uexp diff --git a/src/haz3lcore/statics/MakeTerm.re b/src/haz3lcore/statics/MakeTerm.re index 71c8043f99..d262dc9609 100644 --- a/src/haz3lcore/statics/MakeTerm.re +++ b/src/haz3lcore/statics/MakeTerm.re @@ -112,13 +112,19 @@ let return = (wrap, ids, tm) => { /* Map to collect projector ids */ let projectors: ref(Id.Map.t(Piece.projector)) = ref(Id.Map.empty); +let mk_probe = (e: list(Piece.t)) => + Piece.mk_tile( + Form.mk(Form.ii, ["@@", "@@"], Mold.mk_op(Exp, [Exp])), + [e], + ); + /* Strip a projector from a segment and log it in the map */ let rm_and_log_projectors = (seg: Segment.t): Segment.t => List.map( fun | Piece.Projector(pr) => { projectors := Id.Map.add(pr.id, pr, projectors^); - pr.syntax; + mk_probe([pr.syntax]); } | x => x, seg, @@ -191,7 +197,8 @@ and exp_term: unsorted => (UExp.term, list(Id.t)) = { | ([t], []) when Form.is_var(t) => ret(Var(t)) | ([t], []) when Form.is_ctr(t) => ret(Constructor(t, Unknown(Internal) |> Typ.temp)) - | (["(", ")"], [Exp(body)]) => ret(Parens(body)) + | (["(", ")"], [Exp(body)]) => ret(Parens(body, Paren)) + | (["@@", "@@"], [Exp(body)]) => ret(Parens(body, Probe)) | (["[", "]"], [Exp(body)]) => switch (body) { | {ids, copied: false, term: Tuple(es)} => (ListLit(es), ids) diff --git a/src/haz3lcore/statics/Statics.re b/src/haz3lcore/statics/Statics.re index fc5011b567..f91208a7e1 100644 --- a/src/haz3lcore/statics/Statics.re +++ b/src/haz3lcore/statics/Statics.re @@ -269,7 +269,7 @@ and uexp_to_info_map = m, ) | DynamicErrorHole(e, _) - | Parens(e) => + | Parens(e, _) => let (e, m) = go(~mode, e, m); add(~self=Just(e.ty), ~co_ctx=e.co_ctx, m); | UnOp(Meta(Unquote), e) when is_in_filter => diff --git a/src/haz3lcore/statics/Term.re b/src/haz3lcore/statics/Term.re index 4a98d237ca..4c604691c0 100644 --- a/src/haz3lcore/statics/Term.re +++ b/src/haz3lcore/statics/Term.re @@ -434,7 +434,7 @@ module Exp = { // determine when to allow for recursive definitions in a let binding. let rec is_fun = (e: t) => { switch (e.term) { - | Parens(e) => is_fun(e) + | Parens(e, _) => is_fun(e) | Cast(e, _, _) => is_fun(e) | TypFun(_) | Fun(_) @@ -478,7 +478,7 @@ module Exp = { || ( switch (e.term) { | Cast(e, _, _) - | Parens(e) => is_tuple_of_functions(e) + | Parens(e, _) => is_tuple_of_functions(e) | Tuple(es) => es |> List.for_all(is_fun) | Invalid(_) | EmptyHole @@ -534,7 +534,7 @@ module Exp = { Some(1); } else { switch (e.term) { - | Parens(e) => get_num_of_functions(e) + | Parens(e, _) => get_num_of_functions(e) | Tuple(es) => is_tuple_of_functions(e) ? Some(List.length(es)) : None | Invalid(_) | EmptyHole @@ -747,7 +747,7 @@ module Exp = { switch (e.term) { | Fun(_, _, _, n) => n | FixF(_, e, _) => get_fn_name(e) - | Parens(e) => get_fn_name(e) + | Parens(e, _) => get_fn_name(e) | TypFun(_, _, n) => n | _ => None }; diff --git a/src/haz3lcore/statics/TermBase.re b/src/haz3lcore/statics/TermBase.re index 432209da0b..3ce99a3259 100644 --- a/src/haz3lcore/statics/TermBase.re +++ b/src/haz3lcore/statics/TermBase.re @@ -42,6 +42,11 @@ let stop = (_, x) => x; the id of the closure. */ +[@deriving (show({with_path: false}), sexp, yojson)] +type paren_tag = + | Paren + | Probe; + module rec Any: { [@deriving (show({with_path: false}), sexp, yojson)] type t = @@ -166,7 +171,7 @@ and Exp: { | Test(t) | Filter(StepperFilterKind.t, t) | Closure([@show.opaque] ClosureEnvironment.t, t) - | Parens(t) // ( + | Parens(t, paren_tag) // ( | Cons(t, t) | ListConcat(t, t) | UnOp(Operators.op_un, t) @@ -233,7 +238,7 @@ and Exp: { | Test(t) | Filter(StepperFilterKind.t, t) | Closure([@show.opaque] ClosureEnvironment.t, t) - | Parens(t) + | Parens(t, paren_tag) | Cons(t, t) | ListConcat(t, t) | UnOp(Operators.op_un, t) @@ -309,7 +314,7 @@ and Exp: { | Test(e) => Test(exp_map_term(e)) | Filter(f, e) => Filter(flt_map_term(f), exp_map_term(e)) | Closure(env, e) => Closure(env, exp_map_term(e)) - | Parens(e) => Parens(exp_map_term(e)) + | Parens(e, tag) => Parens(exp_map_term(e), tag) | Cons(e1, e2) => Cons(exp_map_term(e1), exp_map_term(e2)) | ListConcat(e1, e2) => ListConcat(exp_map_term(e1), exp_map_term(e2)) @@ -335,9 +340,9 @@ and Exp: { let rec fast_equal = (e1, e2) => switch (e1 |> IdTagged.term_of, e2 |> IdTagged.term_of) { | (DynamicErrorHole(x, _), _) - | (Parens(x), _) => fast_equal(x, e2) + | (Parens(x, _), _) => fast_equal(x, e2) | (_, DynamicErrorHole(x, _)) - | (_, Parens(x)) => fast_equal(e1, x) + | (_, Parens(x, _)) => fast_equal(e1, x) | (EmptyHole, EmptyHole) => true | (Undefined, Undefined) => true | (Invalid(s1), Invalid(s2)) => s1 == s2 diff --git a/src/haz3lweb/app/explainthis/Example.re b/src/haz3lweb/app/explainthis/Example.re index 9408167cf6..3941063a23 100644 --- a/src/haz3lweb/app/explainthis/Example.re +++ b/src/haz3lweb/app/explainthis/Example.re @@ -22,6 +22,8 @@ let int = n => mk_monotile(Form.mk_atomic(Exp, n)); let exp = v => mk_monotile(Form.mk_atomic(Exp, v)); let pat = v => mk_monotile(Form.mk_atomic(Pat, v)); let mk_parens_exp = mk_tile(Form.get("parens_exp")); +let mk_probe = (e: list(Piece.t)) => + mk_tile(Form.mk(Form.ii, ["@@", "@@"], Mold.mk_op(Exp, [Exp])), [e]); let mk_fun = mk_tile(Form.get("fun_")); let mk_fun_ancestor = mk_ancestor(Form.get("fun_")); let mk_parens_ancestor = mk_ancestor(Form.get("parens_exp")); diff --git a/src/haz3lweb/app/explainthis/ExplainThis.re b/src/haz3lweb/app/explainthis/ExplainThis.re index b155eece52..6d9422bef0 100644 --- a/src/haz3lweb/app/explainthis/ExplainThis.re +++ b/src/haz3lweb/app/explainthis/ExplainThis.re @@ -411,7 +411,7 @@ let rec bypass_parens_pat = (pat: Pat.t) => { let rec bypass_parens_exp = (exp: Exp.t) => { switch (exp.term) { - | Parens(e) => bypass_parens_exp(e) + | Parens(e, _) => bypass_parens_exp(e) | _ => exp }; }; @@ -1761,7 +1761,7 @@ let get_doc = ), TestExp.tests, ); - | Parens(term) => get_message_exp(term.term) // No Special message? + | Parens(term, _) => get_message_exp(term.term) // No Special message? | Cons(hd, tl) => let hd_id = List.nth(hd.ids, 0); let tl_id = List.nth(tl.ids, 0); diff --git a/src/haz3lweb/exercises/SyntaxTest.re b/src/haz3lweb/exercises/SyntaxTest.re index 23dff72251..bd8622fb09 100644 --- a/src/haz3lweb/exercises/SyntaxTest.re +++ b/src/haz3lweb/exercises/SyntaxTest.re @@ -47,9 +47,9 @@ let rec find_in_let = (name: string, upat: UPat.t, def: UExp.t, l: list(UExp.t)) : list(UExp.t) => { switch (upat.term, def.term) { - | (Parens(up), Parens(ue)) => find_in_let(name, up, ue, l) + | (Parens(up), Parens(ue, _)) => find_in_let(name, up, ue, l) | (Parens(up), _) => find_in_let(name, up, def, l) - | (_, Parens(ue)) => find_in_let(name, upat, ue, l) + | (_, Parens(ue, _)) => find_in_let(name, upat, ue, l) | (Cast(up, _, _), _) => find_in_let(name, up, def, l) | (Var(x), Fun(_)) => x == name ? [def, ...l] : l | (Tuple(pl), Tuple(ul)) => @@ -92,7 +92,7 @@ let rec find_fn = | FixF(_, body, _) | Fun(_, body, _, _) => l |> find_fn(name, body) | TypAp(u1, _) - | Parens(u1) + | Parens(u1, _) | Cast(u1, _, _) | UnOp(_, u1) | TyAlias(_, _, u1) @@ -191,7 +191,7 @@ let rec var_mention = (name: string, uexp: Exp.t): bool => { | TypFun(_, u, _) | TypAp(u, _) | Test(u) - | Parens(u) + | Parens(u, _) | UnOp(_, u) | TyAlias(_, _, u) | Filter(_, u) => var_mention(name, u) @@ -252,7 +252,7 @@ let rec var_applied = (name: string, uexp: Exp.t): bool => { ? false : var_applied(name, def) || var_applied(name, body) | TypFun(_, u, _) | Test(u) - | Parens(u) + | Parens(u, _) | UnOp(_, u) | TyAlias(_, _, u) | Filter(_, u) => var_applied(name, u) @@ -350,7 +350,7 @@ let rec tail_check = (name: string, uexp: Exp.t): bool => { | Closure(_, u) | TypFun(_, u, _) | TypAp(u, _) - | Parens(u) => tail_check(name, u) + | Parens(u, _) => tail_check(name, u) | UnOp(_, u) => !var_mention(name, u) | Ap(_, u1, u2) => var_mention(name, u2) ? false : tail_check(name, u1) | DeferredAp(fn, args) => diff --git a/test/Test_Elaboration.re b/test/Test_Elaboration.re index c515487535..c317ce3cb8 100644 --- a/test/Test_Elaboration.re +++ b/test/Test_Elaboration.re @@ -20,7 +20,7 @@ let empty_hole = () => alco_check("Empty hole", u2, dhexp_of_uexp(u2)); let u3: Exp.t = { ids: [id_at(0)], - term: Parens({ids: [id_at(1)], term: Var("y"), copied: false}), + term: Parens({ids: [id_at(1)], term: Var("y"), copied: false}, Paren), copied: false, }; diff --git a/test/Test_MakeTerm.re b/test/Test_MakeTerm.re index 46818664a9..84bd02ab6c 100644 --- a/test/Test_MakeTerm.re +++ b/test/Test_MakeTerm.re @@ -23,7 +23,7 @@ let tests = [ exp_check(Var("x") |> Exp.fresh, "x") }), test_case("Parenthesized Expression", `Quick, () => { - exp_check(Parens(Int(0) |> Exp.fresh) |> Exp.fresh, "(0)") + exp_check(Parens(Int(0) |> Exp.fresh, Paren) |> Exp.fresh, "(0)") }), test_case("Let Expression", `Quick, () => { exp_check( From 1d07174a2576a22ade8719bbc39cd2600a0458f1 Mon Sep 17 00:00:00 2001 From: disconcision Date: Tue, 19 Nov 2024 19:29:17 -0500 Subject: [PATCH 02/23] clarify live projector statics --- src/haz3lcore/statics/MakeTerm.re | 11 +++++++---- src/haz3lcore/statics/Statics.re | 8 +++++++- src/haz3lcore/tiles/Piece.re | 10 ++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/haz3lcore/statics/MakeTerm.re b/src/haz3lcore/statics/MakeTerm.re index d262dc9609..1883e5623f 100644 --- a/src/haz3lcore/statics/MakeTerm.re +++ b/src/haz3lcore/statics/MakeTerm.re @@ -112,8 +112,9 @@ let return = (wrap, ids, tm) => { /* Map to collect projector ids */ let projectors: ref(Id.Map.t(Piece.projector)) = ref(Id.Map.empty); -let mk_probe = (e: list(Piece.t)) => - Piece.mk_tile( +let mk_probe = (id, e: list(Piece.t)) => + Piece.mk_tile_id( + id, Form.mk(Form.ii, ["@@", "@@"], Mold.mk_op(Exp, [Exp])), [e], ); @@ -124,7 +125,7 @@ let rm_and_log_projectors = (seg: Segment.t): Segment.t => fun | Piece.Projector(pr) => { projectors := Id.Map.add(pr.id, pr, projectors^); - mk_probe([pr.syntax]); + mk_probe(pr.id, [pr.syntax]); } | x => x, seg, @@ -198,7 +199,9 @@ and exp_term: unsorted => (UExp.term, list(Id.t)) = { | ([t], []) when Form.is_ctr(t) => ret(Constructor(t, Unknown(Internal) |> Typ.temp)) | (["(", ")"], [Exp(body)]) => ret(Parens(body, Paren)) - | (["@@", "@@"], [Exp(body)]) => ret(Parens(body, Probe)) + | (["@@", "@@"], [Exp(body)]) => + // TODO(andrew): apologize for this + ret(Parens(body, Probe)) | (["[", "]"], [Exp(body)]) => switch (body) { | {ids, copied: false, term: Tuple(es)} => (ListLit(es), ids) diff --git a/src/haz3lcore/statics/Statics.re b/src/haz3lcore/statics/Statics.re index f91208a7e1..865f866b9a 100644 --- a/src/haz3lcore/statics/Statics.re +++ b/src/haz3lcore/statics/Statics.re @@ -269,9 +269,15 @@ and uexp_to_info_map = m, ) | DynamicErrorHole(e, _) - | Parens(e, _) => + | Parens(e, Paren) => let (e, m) = go(~mode, e, m); add(~self=Just(e.ty), ~co_ctx=e.co_ctx, m); + | Parens(e, Probe) => + /* Currently doing this as otherwise it clobbers the statics + * for the contained expression as i'm just reusing the same id + * in order to associate it through dynamics */ + go(~mode, e, m) + //TODO(andrew): ponder this | UnOp(Meta(Unquote), e) when is_in_filter => let e: UExp.t = { ids: e.ids, diff --git a/src/haz3lcore/tiles/Piece.re b/src/haz3lcore/tiles/Piece.re index fc6207a979..96b02e1004 100644 --- a/src/haz3lcore/tiles/Piece.re +++ b/src/haz3lcore/tiles/Piece.re @@ -186,3 +186,13 @@ let is_term = (p: t) => | Secondary(_) => false // debatable | _ => false }; + +let mk_tile_id: (Id.t, Form.t, list(list(t))) => t = + (id, form, children) => + Tile({ + id, + label: form.label, + mold: form.mold, + shards: List.mapi((i, _) => i, form.label), + children, + }); From 08c21d1de7f22a5b5fe5507dab25b8ed159e3c45 Mon Sep 17 00:00:00 2001 From: disconcision Date: Tue, 19 Nov 2024 23:02:16 -0500 Subject: [PATCH 03/23] live projectors view layer. minimal poc complete --- src/haz3lcore/Measured.re | 8 +++-- src/haz3lcore/zipper/Editor.re | 14 +++++---- src/haz3lcore/zipper/Printer.re | 2 +- src/haz3lcore/zipper/Projector.re | 4 +-- src/haz3lcore/zipper/ProjectorBase.re | 1 + src/haz3lcore/zipper/projectors/FoldProj.re | 18 ++++++++++-- src/haz3lweb/app/common/ProjectorView.re | 6 +++- src/haz3lweb/app/editors/cell/CellEditor.re | 28 +++++++++++++++++- src/haz3lweb/app/editors/code/Code.re | 22 +++++++++++--- src/haz3lweb/app/editors/code/CodeEditable.re | 5 +++- src/haz3lweb/app/editors/code/CodeViewable.re | 29 +++++++++++++++---- .../app/editors/code/CodeWithStatics.re | 26 +++++++++++++++-- .../app/editors/decoration/BackpackView.re | 4 ++- src/haz3lweb/app/editors/decoration/Deco.re | 6 +++- src/haz3lweb/app/editors/mode/ExerciseMode.re | 4 ++- src/haz3lweb/app/editors/result/EvalResult.re | 8 +++++ src/haz3lweb/app/editors/result/Stepper.re | 3 ++ .../app/editors/result/StepperEditor.re | 12 +++++++- src/haz3lweb/app/explainthis/ExplainThis.re | 5 ++++ 19 files changed, 171 insertions(+), 34 deletions(-) diff --git a/src/haz3lcore/Measured.re b/src/haz3lcore/Measured.re index 61dad8a831..d5b1305e4b 100644 --- a/src/haz3lcore/Measured.re +++ b/src/haz3lcore/Measured.re @@ -282,7 +282,8 @@ let last_of_token = (token: string, origin: Point.t): Point.t => row: origin.row + StringUtil.num_linebreaks(token), }; -let of_segment = (seg: Segment.t, info_map: Statics.Map.t): t => { +let of_segment = + (seg: Segment.t, info_map: Statics.Map.t, dyn_map: TestMap.t): t => { let is_indented = is_indented_map(seg); // recursive across seg's bidelimited containers @@ -353,8 +354,9 @@ let of_segment = (seg: Segment.t, info_map: Statics.Map.t): t => { let map = map |> add_g(g, {origin, last}); (contained_indent, last, map); | Projector(p) => - let token = - Projector.placeholder(p, Id.Map.find_opt(p.id, info_map)); + let ci = Id.Map.find_opt(p.id, info_map); + let dyn = TestMap.lookup(p.id, dyn_map); + let token = Projector.placeholder(p, ci, dyn); let last = last_of_token(token, origin); let map = extra_rows(token, origin, map); let map = add_pr(p, {origin, last}, map); diff --git a/src/haz3lcore/zipper/Editor.re b/src/haz3lcore/zipper/Editor.re index 819ae28bf3..baa8e45621 100644 --- a/src/haz3lcore/zipper/Editor.re +++ b/src/haz3lcore/zipper/Editor.re @@ -33,7 +33,7 @@ module CachedSyntax = { let yojson_of_t = _ => failwith("Editor.Meta.yojson_of_t"); let t_of_yojson = _ => failwith("Editor.Meta.t_of_yojson"); - let init = (z, info_map): t => { + let init = (z, info_map, dyn_map): t => { let segment = Zipper.unselect_and_zip(z); let MakeTerm.{term, terms, projectors} = MakeTerm.go(segment); { @@ -42,7 +42,7 @@ module CachedSyntax = { term_ranges: TermRanges.mk(segment), tiles: TileMap.mk(segment), holes: Segment.holes(segment), - measured: Measured.of_segment(segment, info_map), + measured: Measured.of_segment(segment, info_map, dyn_map), selection_ids: Selection.selection_ids(z.selection), term, terms, @@ -52,9 +52,9 @@ module CachedSyntax = { let mark_old: t => t = old => {...old, old: true}; - let calculate = (z: Zipper.t, info_map, old: t) => + let calculate = (z: Zipper.t, info_map, dyn_map, old: t) => old.old - ? init(z, info_map) + ? init(z, info_map, dyn_map) : {...old, selection_ids: Selection.selection_ids(z.selection)}; }; @@ -97,7 +97,7 @@ module Model = { col_target: None, }, history: History.empty, - syntax: CachedSyntax.init(zipper, Id.Map.empty), + syntax: CachedSyntax.init(zipper, Id.Map.empty, TestMap.empty), }; type persistent = PersistentZipper.t; @@ -241,6 +241,7 @@ module Update = { ~settings: CoreSettings.t, ~is_edited, new_statics, + dyn_map, {syntax, state, history}: Model.t, ) => { // 1. Recalculate the autocomplete buffer if necessary @@ -264,7 +265,8 @@ module Update = { // 2. Recalculate syntax cache let syntax = is_edited ? CachedSyntax.mark_old(syntax) : syntax; - let syntax = CachedSyntax.calculate(zipper, new_statics.info_map, syntax); + let syntax = + CachedSyntax.calculate(zipper, new_statics.info_map, dyn_map, syntax); // Recombine Model.{ diff --git a/src/haz3lcore/zipper/Printer.re b/src/haz3lcore/zipper/Printer.re index 1ab6b176f5..0c7c140f4d 100644 --- a/src/haz3lcore/zipper/Printer.re +++ b/src/haz3lcore/zipper/Printer.re @@ -58,7 +58,7 @@ let measured = z => z |> ProjectorPerform.Update.remove_all |> Zipper.seg_without_buffer - |> Measured.of_segment(_, Id.Map.empty); + |> Measured.of_segment(_, Id.Map.empty, TestMap.empty); let pretty_print = (~holes: option(string)=Some(""), z: Zipper.t): string => to_rows( diff --git a/src/haz3lcore/zipper/Projector.re b/src/haz3lcore/zipper/Projector.re index 82b036b821..f06b7e879a 100644 --- a/src/haz3lcore/zipper/Projector.re +++ b/src/haz3lcore/zipper/Projector.re @@ -25,8 +25,8 @@ let shape = (p: Base.projector, info: info): shape => { * in the zipper; a tile consisting of any number of whitespaces * is considered a placeholder. This could be made more principled. * Note that a placeholder retains the UUID of the underlying. */ -let placeholder = (p: Base.projector, ci: option(Info.t)): string => - switch (shape(p, {id: p.id, syntax: p.syntax, ci})) { +let placeholder = (p: Base.projector, ci: option(Info.t), dyn): string => + switch (shape(p, {id: p.id, syntax: p.syntax, ci, dyn})) { | Inline(width) => String.make(width, ' ') | Block({row, col}) => String.make(row - 1, '\n') ++ String.make(col, ' ') }; diff --git a/src/haz3lcore/zipper/ProjectorBase.re b/src/haz3lcore/zipper/ProjectorBase.re index 5abe811d76..0bba32d9fd 100644 --- a/src/haz3lcore/zipper/ProjectorBase.re +++ b/src/haz3lcore/zipper/ProjectorBase.re @@ -33,6 +33,7 @@ type info = { id: Id.t, syntax, ci: option(Info.t), + dyn: option(list(TestMap.instance_report)), }; /* To add a new projector: diff --git a/src/haz3lcore/zipper/projectors/FoldProj.re b/src/haz3lcore/zipper/projectors/FoldProj.re index 8f9665dea6..308714e69c 100644 --- a/src/haz3lcore/zipper/projectors/FoldProj.re +++ b/src/haz3lcore/zipper/projectors/FoldProj.re @@ -9,6 +9,17 @@ type t = { text: string, }; +/* Proof of concept value exposure. This isn't getting set right + after actions, only initially */ +let get_first_val = (rs: option(list(TestMap.instance_report))) => { + switch (rs) { + | Some(rs) => + List.map(((d: DHExp.t, _)) => d.term |> TermBase.Exp.show_term, rs) + |> String.concat(", ") + | _ => "Nein" + }; +}; + module M: Projector = { [@deriving (show({with_path: false}), sexp, yojson)] type model = t; @@ -20,9 +31,12 @@ module M: Projector = { let placeholder = (m, _) => Inline(m.text == "⋱" ? 2 : m.text |> String.length); let update = (m, _) => m; - let view = (m: model, ~info as _, ~local as _, ~parent) => + let view = (m: model, ~info, ~local as _, ~parent) => div( - ~attrs=[Attr.on_double_click(_ => parent(Remove))], + ~attrs=[ + Attr.on_double_click(_ => parent(Remove)), + Attr.title(get_first_val(info.dyn)), + ], [text(m.text)], ); let focus = _ => (); diff --git a/src/haz3lweb/app/common/ProjectorView.re b/src/haz3lweb/app/common/ProjectorView.re index 387ccc5cd7..3e3369f892 100644 --- a/src/haz3lweb/app/common/ProjectorView.re +++ b/src/haz3lweb/app/common/ProjectorView.re @@ -118,6 +118,7 @@ let setup_view = id: Id.t, ~cached_statics: CachedStatics.t, ~cached_syntax: Editor.CachedSyntax.t, + ~dyn_map, ~inject: Action.t => Ui_effect.t(unit), ~font_metrics, ~indication: option(Direction.t), @@ -126,7 +127,8 @@ let setup_view = let* p = Id.Map.find_opt(id, cached_syntax.projectors); let* syntax = Some(p.syntax); let ci = Id.Map.find_opt(id, cached_statics.info_map); - let info = {id, ci, syntax}; + let dyn = TestMap.lookup(id, dyn_map); + let info = {id, ci, dyn, syntax}; let+ measurement = Measured.find_pr_opt(p, cached_syntax.measured); let (module P) = to_module(p.kind); let parent = a => inject(Project(handle(id, a))); @@ -156,6 +158,7 @@ let all = z, ~cached_statics: CachedStatics.t, ~cached_syntax: Editor.CachedSyntax.t, + ~dyn_map, ~inject, ~font_metrics, ) => { @@ -172,6 +175,7 @@ let all = id, ~cached_statics, ~cached_syntax, + ~dyn_map, ~inject, ~font_metrics, ~indication, diff --git a/src/haz3lweb/app/editors/cell/CellEditor.re b/src/haz3lweb/app/editors/cell/CellEditor.re index f1fe88bc4b..a33753642b 100644 --- a/src/haz3lweb/app/editors/cell/CellEditor.re +++ b/src/haz3lweb/app/editors/cell/CellEditor.re @@ -56,7 +56,13 @@ module Update = { ) : Model.t => { let editor = - CodeEditable.Update.calculate(~settings, ~is_edited, ~stitch, editor); + CodeEditable.Update.calculate( + ~settings, + ~is_edited, + ~stitch, + ~dyn_map=TestMap.empty, //TODO(andrew): see below + editor, + ); let result = EvalResult.Update.calculate( ~settings, @@ -65,6 +71,20 @@ module Update = { editor |> CodeEditable.Model.get_statics, result, ); + //TODO(andrew): double-calcing editor... + let dyn_map = + switch (EvalResult.Model.make_test_report(result)) { + | Some(res) => res.test_map + | None => TestMap.empty + }; + let editor = + CodeEditable.Update.calculate( + ~settings, + ~is_edited, + ~stitch, + ~dyn_map, + editor, + ); {editor, result}; }; }; @@ -149,6 +169,11 @@ module View = { ~locked, model.result, ); + let dyn_map = + switch (EvalResult.Model.make_test_report(model.result)) { + | Some(res) => res.test_map + | None => TestMap.empty + }; div( ~attrs=[Attr.classes(["cell", locked ? "locked" : "unlocked"])], Option.to_list(caption) @@ -166,6 +191,7 @@ module View = { : (action => inject(MainEditor(action))), ~selected=selected == Some(MainEditor), ~overlays=overlays(model.editor.editor), + ~dyn_map, ~sort?, model.editor, ), diff --git a/src/haz3lweb/app/editors/code/Code.re b/src/haz3lweb/app/editors/code/Code.re index ee0cd2b564..10ba2801df 100644 --- a/src/haz3lweb/app/editors/code/Code.re +++ b/src/haz3lweb/app/editors/code/Code.re @@ -71,9 +71,15 @@ let of_secondary = } ); -let of_projector = (p, expected_sort, indent, info_map) => +let of_projector = (p, expected_sort, indent, info_map, dyn_map) => of_delim'(( - [Projector.placeholder(p, Id.Map.find_opt(p.id, info_map))], + [ + Projector.placeholder( + p, + Id.Map.find_opt(p.id, info_map), + TestMap.lookup(p.id, dyn_map), + ), + ], false, expected_sort, true, @@ -88,6 +94,7 @@ module Text = let map: Measured.t; let settings: Settings.Model.t; let info_map: Statics.Map.t; + let dyn_map: TestMap.t; }, ) => { let m = p => Measured.find_p(~msg="Text", p, M.map); @@ -118,7 +125,13 @@ module Text = | Secondary({content, _}) => of_secondary((content, M.settings.secondary_icons, m(p).last.col)) | Projector(p) => - of_projector(p, expected_sort, m(Projector(p)).origin.col, M.info_map) + of_projector( + p, + expected_sort, + m(Projector(p)).origin.col, + M.info_map, + M.dyn_map, + ) }; } and of_tile = (buffer_ids, expected_sort: Sort.t, t: Tile.t): list(Node.t) => { @@ -160,12 +173,13 @@ let rec holes = ); let simple_view = (~font_metrics, ~segment, ~settings: Settings.t): Node.t => { - let map = Measured.of_segment(segment, Id.Map.empty); + let map = Measured.of_segment(segment, Id.Map.empty, TestMap.empty); module Text = Text({ let map = map; let settings = settings; let info_map = Id.Map.empty; /* Assume this doesn't contain projectors */ + let dyn_map = TestMap.empty; /* Assume this doesn't contain projectors */ }); let holes = holes(~map, ~font_metrics, segment); div( diff --git a/src/haz3lweb/app/editors/code/CodeEditable.re b/src/haz3lweb/app/editors/code/CodeEditable.re index 9745fb3809..d2b7a10d1b 100644 --- a/src/haz3lweb/app/editors/code/CodeEditable.re +++ b/src/haz3lweb/app/editors/code/CodeEditable.re @@ -242,6 +242,7 @@ module View = { ~selected: bool, ~overlays: list(Node.t)=[], ~sort=?, + ~dyn_map, model: Model.t, ) => { let edit_decos = { @@ -250,6 +251,7 @@ module View = { let editor = model.editor; let globals = globals; let statics = model.statics; + let dynamics = dyn_map; }); Deco.editor(model.editor.state.zipper, selected); }; @@ -260,10 +262,11 @@ module View = { ~cached_syntax=model.editor.syntax, ~inject=x => inject(Perform(x)), ~font_metrics=globals.font_metrics, + ~dyn_map, ); let overlays = edit_decos @ overlays @ [projectors]; let code_view = - CodeWithStatics.View.view(~globals, ~overlays, ~sort?, model); + CodeWithStatics.View.view(~globals, ~overlays, ~dyn_map, ~sort?, model); let mousedown_overlay = selected && globals.mousedown ? [mousedown_overlay(~globals, ~inject=x => inject(Perform(x)))] diff --git a/src/haz3lweb/app/editors/code/CodeViewable.re b/src/haz3lweb/app/editors/code/CodeViewable.re index ab789bdce0..701a3026f1 100644 --- a/src/haz3lweb/app/editors/code/CodeViewable.re +++ b/src/haz3lweb/app/editors/code/CodeViewable.re @@ -13,6 +13,7 @@ let view = ~segment, ~holes, ~info_map, + ~dyn_map, ) : Node.t => { module Text = @@ -20,6 +21,7 @@ let view = let map = measured; let settings = globals.settings; let info_map = info_map; + let dyn_map = dyn_map; }); let code = Text.of_segment(buffer_ids, false, sort, segment); let holes = List.map(Code.of_hole(~measured, ~globals), holes); @@ -51,21 +53,36 @@ let view = // }; let view_segment = - (~globals: Globals.t, ~sort: Sort.t, ~info_map, segment: Segment.t) => { - let measured = Measured.of_segment(segment, info_map); + ( + ~globals: Globals.t, + ~sort: Sort.t, + ~info_map, + ~dyn_map, + segment: Segment.t, + ) => { + let measured = Measured.of_segment(segment, info_map, dyn_map); let buffer_ids = []; let holes = Segment.holes(segment); - view(~globals, ~sort, ~measured, ~buffer_ids, ~holes, ~segment, ~info_map); + view( + ~globals, + ~sort, + ~measured, + ~buffer_ids, + ~holes, + ~segment, + ~dyn_map, + ~info_map, + ); }; -let view_exp = (~globals: Globals.t, ~settings, exp: Exp.t) => { +let view_exp = (~dyn_map, ~globals: Globals.t, ~settings, exp: Exp.t) => { exp |> ExpToSegment.exp_to_segment(~settings) - |> view_segment(~globals, ~sort=Exp); + |> view_segment(~dyn_map, ~globals, ~sort=Exp); }; let view_typ = (~globals: Globals.t, ~settings, typ: Typ.t) => { typ |> ExpToSegment.typ_to_segment(~settings) - |> view_segment(~globals, ~sort=Typ); + |> view_segment(~dyn_map=TestMap.empty, ~globals, ~sort=Typ); }; diff --git a/src/haz3lweb/app/editors/code/CodeWithStatics.re b/src/haz3lweb/app/editors/code/CodeWithStatics.re index b183e3c861..b8ccb8e18b 100644 --- a/src/haz3lweb/app/editors/code/CodeWithStatics.re +++ b/src/haz3lweb/app/editors/code/CodeWithStatics.re @@ -52,11 +52,23 @@ module Update = { /* Calculates the statics for the editor. */ let calculate = - (~settings, ~is_edited, ~stitch, {editor, statics: _}: Model.t) + ( + ~settings, + ~is_edited, + ~stitch, + ~dyn_map, + {editor, statics: _}: Model.t, + ) : Model.t => { let statics = CachedStatics.init(~settings, ~stitch, editor.state.zipper); let editor = - Editor.Update.calculate(~settings, ~is_edited, statics, editor); + Editor.Update.calculate( + ~settings, + ~is_edited, + statics, + dyn_map, + editor, + ); {editor, statics}; }; }; @@ -66,7 +78,13 @@ module View = { type event; let view = - (~globals, ~overlays: list(Node.t)=[], ~sort=Sort.root, model: Model.t) => { + ( + ~globals, + ~overlays: list(Node.t)=[], + ~sort=Sort.root, + ~dyn_map, + model: Model.t, + ) => { let { statics: {info_map, _}, editor: @@ -86,6 +104,7 @@ module View = { ~segment, ~holes, ~info_map, + ~dyn_map, ); let statics_decos = { module Deco = @@ -93,6 +112,7 @@ module View = { let globals = globals; let editor = model.editor; let statics = model.statics; + let dynamics = dyn_map; }); Deco.statics(); }; diff --git a/src/haz3lweb/app/editors/decoration/BackpackView.re b/src/haz3lweb/app/editors/decoration/BackpackView.re index 1e8c93985e..d0f0ed3bca 100644 --- a/src/haz3lweb/app/editors/decoration/BackpackView.re +++ b/src/haz3lweb/app/editors/decoration/BackpackView.re @@ -4,7 +4,8 @@ open Haz3lcore; open Util; /* Assume this doesn't contain projectors */ -let measured_of = seg => Measured.of_segment(seg, Id.Map.empty); +let measured_of = seg => + Measured.of_segment(seg, Id.Map.empty, TestMap.empty); let text_view = (seg: Segment.t): list(Node.t) => { module Text = @@ -12,6 +13,7 @@ let text_view = (seg: Segment.t): list(Node.t) => { let map = measured_of(seg); let settings = Settings.Model.init; let info_map = Id.Map.empty; /* Assume this doesn't contain projectors */ + let dyn_map = TestMap.empty; /* Assume this doesn't contain projectors */ }); Text.of_segment([], true, Any, seg); }; diff --git a/src/haz3lweb/app/editors/decoration/Deco.re b/src/haz3lweb/app/editors/decoration/Deco.re index 56b019b6f0..d0a19f4e20 100644 --- a/src/haz3lweb/app/editors/decoration/Deco.re +++ b/src/haz3lweb/app/editors/decoration/Deco.re @@ -65,6 +65,7 @@ module HighlightSegment = M: { let measured: Measured.t; let info_map: Statics.Map.t; + let dyn_map: TestMap.t; let font_metrics: FontMetrics.t; }, ) => { @@ -122,7 +123,8 @@ module HighlightSegment = | None => failwith("Deco.of_projector: missing measurement") | Some(_m) => let ci = Id.Map.find_opt(p.id, M.info_map); - let token = Projector.placeholder(p, ci); + let dyn = TestMap.lookup(p.id, M.dyn_map); + let token = Projector.placeholder(p, ci, dyn); /* Handling this internal to ProjectorsView at the moment because the * commented-out strategy doesn't work well, since the inserted str8- * edged lines vertical edge placement doesn't account for whether @@ -182,6 +184,7 @@ module Deco = let globals: Globals.t; let editor: Editor.t; let statics: CachedStatics.t; + let dynamics: TestMap.t; }, ) => { let font_metrics = M.globals.font_metrics; @@ -218,6 +221,7 @@ module Deco = HighlightSegment({ let measured = M.editor.syntax.measured; let info_map = M.statics.info_map; + let dyn_map = M.dynamics; let font_metrics = font_metrics; }); diff --git a/src/haz3lweb/app/editors/mode/ExerciseMode.re b/src/haz3lweb/app/editors/mode/ExerciseMode.re index 0db58233ff..213f135bbd 100644 --- a/src/haz3lweb/app/editors/mode/ExerciseMode.re +++ b/src/haz3lweb/app/editors/mode/ExerciseMode.re @@ -191,7 +191,9 @@ module Update = { one of the editors is shown in two cells, so we arbitrarily choose which statics to take */ let editors: Exercise.p('a) = { - let calculate = Editor.Update.calculate(~settings, ~is_edited); + let dynamics = TestMap.empty; //TODO(andrew): dynamics for projs in exercise mode + let calculate = statics => + Editor.Update.calculate(~settings, statics, dynamics, ~is_edited); { title: model.editors.title, version: model.editors.version, diff --git a/src/haz3lweb/app/editors/result/EvalResult.re b/src/haz3lweb/app/editors/result/EvalResult.re index c8b59e2f3a..916bf92653 100644 --- a/src/haz3lweb/app/editors/result/EvalResult.re +++ b/src/haz3lweb/app/editors/result/EvalResult.re @@ -260,6 +260,7 @@ module Update = { ~settings, ~stitch=_ => exp, ~is_edited, + ~dyn_map=Haz3lcore.TestMap.empty, //TODO(andrew) editor, ) |> (x => (exp, x)) @@ -373,6 +374,7 @@ module View = { ~globals, ~selected, ~sort=Haz3lcore.Sort.root, + ~dyn_map=Haz3lcore.TestMap.empty, //TODO(andrew): this is just the result display, right? editor, ); let exn_view = @@ -528,6 +530,11 @@ module View = { // Just showing elaboration because evaluation is off: | EvalResults when globals.settings.core.elaborate => + let dyn_map = + switch (Model.test_results(model)) { + | Some(result) => result.test_map + | None => Haz3lcore.TestMap.empty + }; let result = [ text("Evaluation disabled, showing elaboration:"), switch (Model.get_elaboration(model)) { @@ -543,6 +550,7 @@ module View = { ~globals, ~sort=Exp, ~info_map=Haz3lcore.Id.Map.empty, + ~dyn_map, ) | None => text("No elaboration found") }, diff --git a/src/haz3lweb/app/editors/result/Stepper.re b/src/haz3lweb/app/editors/result/Stepper.re index e2abfc8af5..4a6fdee536 100644 --- a/src/haz3lweb/app/editors/result/Stepper.re +++ b/src/haz3lweb/app/editors/result/Stepper.re @@ -255,6 +255,7 @@ module Update = { CodeSelectable.Update.calculate( ~settings=settings |> Calc.get_value, ~is_edited=false, + ~dyn_map=TestMap.empty, ~stitch=x => x ), @@ -425,6 +426,7 @@ module View = { ~globals, ~overlays=[], ~selected=selection == Some(A(i + 1, ())), + ~dyn_map=TestMap.empty, ~inject= (x: StepperEditor.Update.t) => inject(StepperEditor(i + 1, x)), @@ -475,6 +477,7 @@ module View = { ~inject= (x: StepperEditor.Update.t) => inject(StepperEditor(current_n, x)), + ~dyn_map=TestMap.empty, ~signal= fun | TakeStep(x) => inject(Update.StepForward(x)) diff --git a/src/haz3lweb/app/editors/result/StepperEditor.re b/src/haz3lweb/app/editors/result/StepperEditor.re index b42b7bbc79..9e61b066eb 100644 --- a/src/haz3lweb/app/editors/result/StepperEditor.re +++ b/src/haz3lweb/app/editors/result/StepperEditor.re @@ -33,11 +33,18 @@ module Update = { ~settings, ~is_edited, ~stitch, + ~dyn_map, {editor, taken_steps, next_steps}: Model.t, ) : Model.t => { let editor = - CodeSelectable.Update.calculate(~settings, ~is_edited, ~stitch, editor); + CodeSelectable.Update.calculate( + ~settings, + ~is_edited, + ~stitch, + ~dyn_map, + editor, + ); {editor, taken_steps, next_steps}; }; }; @@ -62,6 +69,7 @@ module View = { ~signal: event => 'a, ~overlays=[], ~selected, + ~dyn_map, model: Model.t, ) => { let overlays = { @@ -70,6 +78,7 @@ module View = { let editor = model.editor.editor; let globals = globals; let statics = model.editor.statics; + let dynamics = dyn_map; }); overlays @ Deco.taken_steps(model.taken_steps) @@ -82,6 +91,7 @@ module View = { ~selected, ~globals, ~overlays, + ~dyn_map, model.editor, ); }; diff --git a/src/haz3lweb/app/explainthis/ExplainThis.re b/src/haz3lweb/app/explainthis/ExplainThis.re index 6d9422bef0..4823d1e430 100644 --- a/src/haz3lweb/app/explainthis/ExplainThis.re +++ b/src/haz3lweb/app/explainthis/ExplainThis.re @@ -236,6 +236,7 @@ let expander_deco = let editor = editor; let globals = globals; let statics = CachedStatics.empty; + let dynamics = TestMap.empty; }); switch (doc.expandable_id, List.length(options)) { | (None, _) @@ -278,6 +279,7 @@ let expander_deco = ~globals, ~sort=Exp, ~info_map=Id.Map.empty, + ~dyn_map=TestMap.empty, segment, ); let classes = @@ -496,12 +498,14 @@ let get_doc = editor, ); let statics = CachedStatics.empty; + let dynamics = TestMap.empty; let highlight_deco = { module Deco = Deco.Deco({ let editor = editor; let globals = {...globals, color_highlights: highlights}; let statics = statics; + let dynamics = dynamics; }); [Deco.color_highlights()]; }; @@ -509,6 +513,7 @@ let get_doc = CodeWithStatics.View.view( ~globals, ~overlays=highlight_deco @ [expander_deco], + ~dyn_map=dynamics, ~sort, {editor, statics}, ); From dfbd0344fc5ba47a5f1a43fb155a753a57863546 Mon Sep 17 00:00:00 2001 From: disconcision Date: Mon, 25 Nov 2024 16:34:26 -0500 Subject: [PATCH 04/23] rename Dynamics --- src/haz3lcore/Measured.re | 8 +++---- src/haz3lcore/dynamics/EvaluatorState.re | 7 +++++- src/haz3lcore/prog/CachedStatics.re | 6 ++++- src/haz3lcore/prog/Dynamics.re | 22 +++++++++++++++++++ src/haz3lcore/statics/Statics.re | 3 +++ src/haz3lcore/zipper/Editor.re | 2 +- src/haz3lcore/zipper/Printer.re | 2 +- src/haz3lcore/zipper/Projector.re | 10 +++++++-- src/haz3lcore/zipper/ProjectorBase.re | 4 ++-- src/haz3lcore/zipper/projectors/FoldProj.re | 10 ++++----- src/haz3lcore/zipper/projectors/InfoProj.re | 6 ++--- src/haz3lweb/app/common/ProjectorView.re | 12 +++++----- src/haz3lweb/app/editors/cell/CellEditor.re | 17 ++++---------- src/haz3lweb/app/editors/code/Code.re | 18 ++++++++------- src/haz3lweb/app/editors/code/CodeEditable.re | 14 ++++++++---- src/haz3lweb/app/editors/code/CodeViewable.re | 16 +++++++------- .../app/editors/code/CodeWithStatics.re | 10 ++++----- .../app/editors/decoration/BackpackView.re | 8 +++---- src/haz3lweb/app/editors/decoration/Deco.re | 12 +++++----- src/haz3lweb/app/editors/result/EvalResult.re | 20 +++++++++-------- .../app/editors/result/StepperEditor.re | 10 ++++----- src/haz3lweb/app/explainthis/ExplainThis.re | 10 ++++----- src/haz3lweb/view/ExerciseMode.re | 2 +- src/haz3lweb/view/StepperView.re | 6 ++--- 24 files changed, 138 insertions(+), 97 deletions(-) create mode 100644 src/haz3lcore/prog/Dynamics.re diff --git a/src/haz3lcore/Measured.re b/src/haz3lcore/Measured.re index d5b1305e4b..a9c58a202d 100644 --- a/src/haz3lcore/Measured.re +++ b/src/haz3lcore/Measured.re @@ -283,7 +283,7 @@ let last_of_token = (token: string, origin: Point.t): Point.t => }; let of_segment = - (seg: Segment.t, info_map: Statics.Map.t, dyn_map: TestMap.t): t => { + (seg: Segment.t, statics: Statics.Map.t, dynamics: Dynamics.Map.t): t => { let is_indented = is_indented_map(seg); // recursive across seg's bidelimited containers @@ -354,9 +354,9 @@ let of_segment = let map = map |> add_g(g, {origin, last}); (contained_indent, last, map); | Projector(p) => - let ci = Id.Map.find_opt(p.id, info_map); - let dyn = TestMap.lookup(p.id, dyn_map); - let token = Projector.placeholder(p, ci, dyn); + let si = Id.Map.find_opt(p.id, statics); + let di = Dynamics.Map.lookup(p.id, dynamics); + let token = Projector.placeholder(p, si, di); let last = last_of_token(token, origin); let map = extra_rows(token, origin, map); let map = add_pr(p, {origin, last}, map); diff --git a/src/haz3lcore/dynamics/EvaluatorState.re b/src/haz3lcore/dynamics/EvaluatorState.re index bb85612d05..2d761f894c 100644 --- a/src/haz3lcore/dynamics/EvaluatorState.re +++ b/src/haz3lcore/dynamics/EvaluatorState.re @@ -2,9 +2,14 @@ type t = { stats: EvaluatorStats.t, tests: TestMap.t, + probes: Dynamics.Map.t, }; -let init = {stats: EvaluatorStats.initial, tests: TestMap.empty}; +let init = { + stats: EvaluatorStats.initial, + tests: TestMap.empty, + probes: Dynamics.Map.empty, +}; let take_step = ({stats, _} as es) => { ...es, diff --git a/src/haz3lcore/prog/CachedStatics.re b/src/haz3lcore/prog/CachedStatics.re index f6d90076ab..106ce24ab0 100644 --- a/src/haz3lcore/prog/CachedStatics.re +++ b/src/haz3lcore/prog/CachedStatics.re @@ -14,7 +14,11 @@ let empty: t = { copied: false, term: Tuple([]), }, - elaborated: UExp.{ids: [Id.invalid], copied: false, term: Tuple([])}, + elaborated: { + ids: [Id.invalid], + copied: false, + term: Tuple([]), + }, info_map: Id.Map.empty, error_ids: [], }; diff --git a/src/haz3lcore/prog/Dynamics.re b/src/haz3lcore/prog/Dynamics.re new file mode 100644 index 0000000000..2651a38efe --- /dev/null +++ b/src/haz3lcore/prog/Dynamics.re @@ -0,0 +1,22 @@ +open Util; + +module Info = { + [@deriving (show({with_path: false}), sexp, yojson)] + type t = {vals: list(DHExp.t)}; + + let first_val = (di: t): option(DHExp.t) => ListUtil.hd_opt(di.vals); +}; + +module Map = { + [@deriving (show({with_path: false}), sexp, yojson)] + type t = {test_map: TestMap.t}; + let empty: t = {test_map: TestMap.empty}; + + let mk = (test_map: TestMap.t): t => {test_map: test_map}; + + let lookup = (id: Id.t, dm: t): option(Info.t) => + switch (TestMap.lookup(id, dm.test_map)) { + | None => None + | Some(vals) => Some({vals: List.map(fst, vals)}) + }; +}; diff --git a/src/haz3lcore/statics/Statics.re b/src/haz3lcore/statics/Statics.re index 90fc5fa74b..bb6775fdc3 100644 --- a/src/haz3lcore/statics/Statics.re +++ b/src/haz3lcore/statics/Statics.re @@ -34,6 +34,9 @@ module Map = { [@deriving (show({with_path: false}), sexp, yojson)] type t = Id.Map.t(Info.t); + let empty = Id.Map.empty; + let lookup = Id.Map.find_opt; + let error_ids = (info_map: t): list(Id.t) => Id.Map.fold( (id, info, acc) => diff --git a/src/haz3lcore/zipper/Editor.re b/src/haz3lcore/zipper/Editor.re index baa8e45621..2ab9c920b0 100644 --- a/src/haz3lcore/zipper/Editor.re +++ b/src/haz3lcore/zipper/Editor.re @@ -97,7 +97,7 @@ module Model = { col_target: None, }, history: History.empty, - syntax: CachedSyntax.init(zipper, Id.Map.empty, TestMap.empty), + syntax: CachedSyntax.init(zipper, Statics.Map.empty, Dynamics.Map.empty), }; type persistent = PersistentZipper.t; diff --git a/src/haz3lcore/zipper/Printer.re b/src/haz3lcore/zipper/Printer.re index 0c7c140f4d..9e55e4d22e 100644 --- a/src/haz3lcore/zipper/Printer.re +++ b/src/haz3lcore/zipper/Printer.re @@ -58,7 +58,7 @@ let measured = z => z |> ProjectorPerform.Update.remove_all |> Zipper.seg_without_buffer - |> Measured.of_segment(_, Id.Map.empty, TestMap.empty); + |> Measured.of_segment(_, Statics.Map.empty, Dynamics.Map.empty); let pretty_print = (~holes: option(string)=Some(""), z: Zipper.t): string => to_rows( diff --git a/src/haz3lcore/zipper/Projector.re b/src/haz3lcore/zipper/Projector.re index f06b7e879a..3476d63d4d 100644 --- a/src/haz3lcore/zipper/Projector.re +++ b/src/haz3lcore/zipper/Projector.re @@ -25,8 +25,14 @@ let shape = (p: Base.projector, info: info): shape => { * in the zipper; a tile consisting of any number of whitespaces * is considered a placeholder. This could be made more principled. * Note that a placeholder retains the UUID of the underlying. */ -let placeholder = (p: Base.projector, ci: option(Info.t), dyn): string => - switch (shape(p, {id: p.id, syntax: p.syntax, ci, dyn})) { +let placeholder = + ( + p: Base.projector, + statics: option(Statics.Info.t), + dynamics: option(Dynamics.Info.t), + ) + : string => + switch (shape(p, {id: p.id, syntax: p.syntax, statics, dynamics})) { | Inline(width) => String.make(width, ' ') | Block({row, col}) => String.make(row - 1, '\n') ++ String.make(col, ' ') }; diff --git a/src/haz3lcore/zipper/ProjectorBase.re b/src/haz3lcore/zipper/ProjectorBase.re index 0bba32d9fd..0adc255776 100644 --- a/src/haz3lcore/zipper/ProjectorBase.re +++ b/src/haz3lcore/zipper/ProjectorBase.re @@ -32,8 +32,8 @@ type external_action = type info = { id: Id.t, syntax, - ci: option(Info.t), - dyn: option(list(TestMap.instance_report)), + statics: option(Statics.Info.t), + dynamics: option(Dynamics.Info.t), }; /* To add a new projector: diff --git a/src/haz3lcore/zipper/projectors/FoldProj.re b/src/haz3lcore/zipper/projectors/FoldProj.re index 308714e69c..2d695c30f4 100644 --- a/src/haz3lcore/zipper/projectors/FoldProj.re +++ b/src/haz3lcore/zipper/projectors/FoldProj.re @@ -11,10 +11,10 @@ type t = { /* Proof of concept value exposure. This isn't getting set right after actions, only initially */ -let get_first_val = (rs: option(list(TestMap.instance_report))) => { - switch (rs) { - | Some(rs) => - List.map(((d: DHExp.t, _)) => d.term |> TermBase.Exp.show_term, rs) +let vals = (di: option(Dynamics.Info.t)) => { + switch (di) { + | Some(di) => + List.map((d: DHExp.t) => d.term |> TermBase.Exp.show_term, di.vals) |> String.concat(", ") | _ => "Nein" }; @@ -35,7 +35,7 @@ module M: Projector = { div( ~attrs=[ Attr.on_double_click(_ => parent(Remove)), - Attr.title(get_first_val(info.dyn)), + Attr.title(vals(info.dynamics)), ], [text(m.text)], ); diff --git a/src/haz3lcore/zipper/projectors/InfoProj.re b/src/haz3lcore/zipper/projectors/InfoProj.re index 6b467e76b1..315a4ef983 100644 --- a/src/haz3lcore/zipper/projectors/InfoProj.re +++ b/src/haz3lcore/zipper/projectors/InfoProj.re @@ -68,7 +68,7 @@ module M: Projector = { display_ty(model, info) |> totalize_ty |> Typ.pretty_print; let placeholder = (model, info) => - Inline((display(model, info.ci) |> String.length) + 5); + Inline((display(model, info.statics) |> String.length) + 5); let update = (model, a: action) => switch (a, model) { @@ -83,10 +83,10 @@ module M: Projector = { Attr.on_mousedown(_ => local(ToggleDisplay)), ], [ - text("⋱ " ++ display_mode(model, info.ci) ++ " "), + text("⋱ " ++ display_mode(model, info.statics) ++ " "), div( ~attrs=[Attr.classes(["type"])], - [text(display(model, info.ci))], + [text(display(model, info.statics))], ), ], ); diff --git a/src/haz3lweb/app/common/ProjectorView.re b/src/haz3lweb/app/common/ProjectorView.re index 3e3369f892..b8a7a580c4 100644 --- a/src/haz3lweb/app/common/ProjectorView.re +++ b/src/haz3lweb/app/common/ProjectorView.re @@ -118,7 +118,7 @@ let setup_view = id: Id.t, ~cached_statics: CachedStatics.t, ~cached_syntax: Editor.CachedSyntax.t, - ~dyn_map, + ~dynamics, ~inject: Action.t => Ui_effect.t(unit), ~font_metrics, ~indication: option(Direction.t), @@ -126,9 +126,9 @@ let setup_view = : option(Node.t) => { let* p = Id.Map.find_opt(id, cached_syntax.projectors); let* syntax = Some(p.syntax); - let ci = Id.Map.find_opt(id, cached_statics.info_map); - let dyn = TestMap.lookup(id, dyn_map); - let info = {id, ci, dyn, syntax}; + let statics = Statics.Map.lookup(id, cached_statics.info_map); + let dynamics = Dynamics.Map.lookup(id, dynamics); + let info = {id, statics, dynamics, syntax}; let+ measurement = Measured.find_pr_opt(p, cached_syntax.measured); let (module P) = to_module(p.kind); let parent = a => inject(Project(handle(id, a))); @@ -158,7 +158,7 @@ let all = z, ~cached_statics: CachedStatics.t, ~cached_syntax: Editor.CachedSyntax.t, - ~dyn_map, + ~dynamics: Dynamics.Map.t, ~inject, ~font_metrics, ) => { @@ -175,7 +175,7 @@ let all = id, ~cached_statics, ~cached_syntax, - ~dyn_map, + ~dynamics, ~inject, ~font_metrics, ~indication, diff --git a/src/haz3lweb/app/editors/cell/CellEditor.re b/src/haz3lweb/app/editors/cell/CellEditor.re index 332e2414fe..04dfc923f5 100644 --- a/src/haz3lweb/app/editors/cell/CellEditor.re +++ b/src/haz3lweb/app/editors/cell/CellEditor.re @@ -61,7 +61,7 @@ module Update = { ~settings, ~is_edited, ~stitch, - ~dyn_map=TestMap.empty, //TODO(andrew): see below + ~dynamics=Dynamics.Map.empty, //TODO(andrew): see below editor, ); let result = @@ -73,17 +73,13 @@ module Update = { result, ); //TODO(andrew): double-calcing editor... - let dyn_map = - switch (EvalResult.Model.make_test_report(result)) { - | Some(res) => res.test_map - | None => TestMap.empty - }; + let dynamics = EvalResult.Model.dynamics(result); let editor = CodeEditable.Update.calculate( ~settings, ~is_edited, ~stitch, - ~dyn_map, + ~dynamics, editor, ); {editor, result}; @@ -170,11 +166,6 @@ module View = { ~locked, model.result, ); - let dyn_map = - switch (EvalResult.Model.make_test_report(model.result)) { - | Some(res) => res.test_map - | None => TestMap.empty - }; div( ~attrs=[Attr.classes(["cell", locked ? "locked" : "unlocked"])], Option.to_list(caption) @@ -192,7 +183,7 @@ module View = { : (action => inject(MainEditor(action))), ~selected=selected == Some(MainEditor), ~overlays=overlays(model.editor.editor), - ~dyn_map, + ~dynamics=EvalResult.Model.dynamics(model.result), ~sort?, model.editor, ), diff --git a/src/haz3lweb/app/editors/code/Code.re b/src/haz3lweb/app/editors/code/Code.re index 10ba2801df..a2ba69c6e9 100644 --- a/src/haz3lweb/app/editors/code/Code.re +++ b/src/haz3lweb/app/editors/code/Code.re @@ -71,13 +71,14 @@ let of_secondary = } ); -let of_projector = (p, expected_sort, indent, info_map, dyn_map) => +let of_projector = + (p, expected_sort, indent, info_map, dynamics: Dynamics.Map.t) => of_delim'(( [ Projector.placeholder( p, - Id.Map.find_opt(p.id, info_map), - TestMap.lookup(p.id, dyn_map), + Statics.Map.lookup(p.id, info_map), + Dynamics.Map.lookup(p.id, dynamics), ), ], false, @@ -94,7 +95,7 @@ module Text = let map: Measured.t; let settings: Settings.Model.t; let info_map: Statics.Map.t; - let dyn_map: TestMap.t; + let dynamics: Dynamics.Map.t; }, ) => { let m = p => Measured.find_p(~msg="Text", p, M.map); @@ -130,7 +131,7 @@ module Text = expected_sort, m(Projector(p)).origin.col, M.info_map, - M.dyn_map, + M.dynamics, ) }; } @@ -173,13 +174,14 @@ let rec holes = ); let simple_view = (~font_metrics, ~segment, ~settings: Settings.t): Node.t => { - let map = Measured.of_segment(segment, Id.Map.empty, TestMap.empty); + let map = + Measured.of_segment(segment, Statics.Map.empty, Dynamics.Map.empty); module Text = Text({ let map = map; let settings = settings; - let info_map = Id.Map.empty; /* Assume this doesn't contain projectors */ - let dyn_map = TestMap.empty; /* Assume this doesn't contain projectors */ + let info_map = Statics.Map.empty; /* Assume this doesn't contain projectors */ + let dynamics = Dynamics.Map.empty; /* Assume this doesn't contain projectors */ }); let holes = holes(~map, ~font_metrics, segment); div( diff --git a/src/haz3lweb/app/editors/code/CodeEditable.re b/src/haz3lweb/app/editors/code/CodeEditable.re index 4cbb3d12b8..37863f9beb 100644 --- a/src/haz3lweb/app/editors/code/CodeEditable.re +++ b/src/haz3lweb/app/editors/code/CodeEditable.re @@ -244,7 +244,7 @@ module View = { ~selected: bool, ~overlays: list(Node.t)=[], ~sort=?, - ~dyn_map, + ~dynamics: Dynamics.Map.t, model: Model.t, ) => { let edit_decos = { @@ -253,7 +253,7 @@ module View = { let editor = model.editor; let globals = globals; let statics = model.statics; - let dynamics = dyn_map; + let dynamics = dynamics; }); Deco.editor(model.editor.state.zipper, selected); }; @@ -264,11 +264,17 @@ module View = { ~cached_syntax=model.editor.syntax, ~inject=x => inject(Perform(x)), ~font_metrics=globals.font_metrics, - ~dyn_map, + ~dynamics, ); let overlays = edit_decos @ overlays @ [projectors]; let code_view = - CodeWithStatics.View.view(~globals, ~overlays, ~dyn_map, ~sort?, model); + CodeWithStatics.View.view( + ~globals, + ~overlays, + ~dynamics, + ~sort?, + model, + ); let mousedown_overlay = selected && globals.mousedown ? [mousedown_overlay(~globals, ~inject=x => inject(Perform(x)))] diff --git a/src/haz3lweb/app/editors/code/CodeViewable.re b/src/haz3lweb/app/editors/code/CodeViewable.re index 701a3026f1..9205ef3710 100644 --- a/src/haz3lweb/app/editors/code/CodeViewable.re +++ b/src/haz3lweb/app/editors/code/CodeViewable.re @@ -13,7 +13,7 @@ let view = ~segment, ~holes, ~info_map, - ~dyn_map, + ~dynamics, ) : Node.t => { module Text = @@ -21,7 +21,7 @@ let view = let map = measured; let settings = globals.settings; let info_map = info_map; - let dyn_map = dyn_map; + let dynamics = dynamics; }); let code = Text.of_segment(buffer_ids, false, sort, segment); let holes = List.map(Code.of_hole(~measured, ~globals), holes); @@ -57,10 +57,10 @@ let view_segment = ~globals: Globals.t, ~sort: Sort.t, ~info_map, - ~dyn_map, + ~dynamics, segment: Segment.t, ) => { - let measured = Measured.of_segment(segment, info_map, dyn_map); + let measured = Measured.of_segment(segment, info_map, dynamics); let buffer_ids = []; let holes = Segment.holes(segment); view( @@ -70,19 +70,19 @@ let view_segment = ~buffer_ids, ~holes, ~segment, - ~dyn_map, + ~dynamics, ~info_map, ); }; -let view_exp = (~dyn_map, ~globals: Globals.t, ~settings, exp: Exp.t) => { +let view_exp = (~dynamics, ~globals: Globals.t, ~settings, exp: Exp.t) => { exp |> ExpToSegment.exp_to_segment(~settings) - |> view_segment(~dyn_map, ~globals, ~sort=Exp); + |> view_segment(~dynamics, ~globals, ~sort=Exp); }; let view_typ = (~globals: Globals.t, ~settings, typ: Typ.t) => { typ |> ExpToSegment.typ_to_segment(~settings) - |> view_segment(~dyn_map=TestMap.empty, ~globals, ~sort=Typ); + |> view_segment(~dynamics=Dynamics.Map.empty, ~globals, ~sort=Typ); }; diff --git a/src/haz3lweb/app/editors/code/CodeWithStatics.re b/src/haz3lweb/app/editors/code/CodeWithStatics.re index 4820d671bb..f2da33af74 100644 --- a/src/haz3lweb/app/editors/code/CodeWithStatics.re +++ b/src/haz3lweb/app/editors/code/CodeWithStatics.re @@ -59,7 +59,7 @@ module Update = { ~settings, ~is_edited, ~stitch, - ~dyn_map, + ~dynamics: Dynamics.Map.t, {editor, statics: _}: Model.t, ) : Model.t => { @@ -69,7 +69,7 @@ module Update = { ~settings, ~is_edited, statics, - dyn_map, + dynamics, editor, ); {editor, statics}; @@ -85,7 +85,7 @@ module View = { ~globals, ~overlays: list(Node.t)=[], ~sort=Sort.root, - ~dyn_map, + ~dynamics: Dynamics.Map.t, model: Model.t, ) => { let { @@ -107,7 +107,7 @@ module View = { ~segment, ~holes, ~info_map, - ~dyn_map, + ~dynamics, ); let statics_decos = { module Deco = @@ -115,7 +115,7 @@ module View = { let globals = globals; let editor = model.editor; let statics = model.statics; - let dynamics = dyn_map; + let dynamics = dynamics; }); Deco.statics(); }; diff --git a/src/haz3lweb/app/editors/decoration/BackpackView.re b/src/haz3lweb/app/editors/decoration/BackpackView.re index d0f0ed3bca..e629eb99f6 100644 --- a/src/haz3lweb/app/editors/decoration/BackpackView.re +++ b/src/haz3lweb/app/editors/decoration/BackpackView.re @@ -3,17 +3,17 @@ open Node; open Haz3lcore; open Util; -/* Assume this doesn't contain projectors */ let measured_of = seg => - Measured.of_segment(seg, Id.Map.empty, TestMap.empty); + /* Assume this doesn't contain projectors */ + Measured.of_segment(seg, Statics.Map.empty, Dynamics.Map.empty); let text_view = (seg: Segment.t): list(Node.t) => { module Text = Code.Text({ let map = measured_of(seg); let settings = Settings.Model.init; - let info_map = Id.Map.empty; /* Assume this doesn't contain projectors */ - let dyn_map = TestMap.empty; /* Assume this doesn't contain projectors */ + let info_map = Statics.Map.empty; /* Assume this doesn't contain projectors */ + let dynamics = Dynamics.Map.empty; /* Assume this doesn't contain projectors */ }); Text.of_segment([], true, Any, seg); }; diff --git a/src/haz3lweb/app/editors/decoration/Deco.re b/src/haz3lweb/app/editors/decoration/Deco.re index 0ee2db4c40..84406f8cac 100644 --- a/src/haz3lweb/app/editors/decoration/Deco.re +++ b/src/haz3lweb/app/editors/decoration/Deco.re @@ -65,7 +65,7 @@ module HighlightSegment = M: { let measured: Measured.t; let info_map: Statics.Map.t; - let dyn_map: TestMap.t; + let dynamics: Dynamics.Map.t; let font_metrics: FontMetrics.t; }, ) => { @@ -122,9 +122,9 @@ module HighlightSegment = switch (Measured.find_pr_opt(p, M.measured)) { | None => failwith("Deco.of_projector: missing measurement") | Some(_m) => - let ci = Id.Map.find_opt(p.id, M.info_map); - let dyn = TestMap.lookup(p.id, M.dyn_map); - let token = Projector.placeholder(p, ci, dyn); + let ci = Statics.Map.lookup(p.id, M.info_map); + let di = Dynamics.Map.lookup(p.id, M.dynamics); + let token = Projector.placeholder(p, ci, di); /* Handling this internal to ProjectorsView at the moment because the * commented-out strategy doesn't work well, since the inserted str8- * edged lines vertical edge placement doesn't account for whether @@ -184,7 +184,7 @@ module Deco = let globals: Globals.t; let editor: Editor.t; let statics: CachedStatics.t; - let dynamics: TestMap.t; + let dynamics: Dynamics.Map.t; }, ) => { let font_metrics = M.globals.font_metrics; @@ -221,7 +221,7 @@ module Deco = HighlightSegment({ let measured = M.editor.syntax.measured; let info_map = M.statics.info_map; - let dyn_map = M.dynamics; + let dynamics = M.dynamics; let font_metrics = font_metrics; }); diff --git a/src/haz3lweb/app/editors/result/EvalResult.re b/src/haz3lweb/app/editors/result/EvalResult.re index ea715a3ddf..7687b8493f 100644 --- a/src/haz3lweb/app/editors/result/EvalResult.re +++ b/src/haz3lweb/app/editors/result/EvalResult.re @@ -1,4 +1,5 @@ open Util; +open Haz3lcore; /* The result box at the bottom of a cell. This is either the TestResutls kind where only a summary of test results is shown, or the EvalResults kind @@ -82,6 +83,12 @@ module Model = { | NoElab => model.previous_tests }; + let dynamics = (model: t): Dynamics.Map.t => + switch (test_results(model)) { + | Some(result) => Dynamics.Map.mk(result.test_map) + | None => Dynamics.Map.mk(TestMap.empty) + }; + let get_elaboration = (model: t): option(Haz3lcore.Exp.t) => switch (model.result) { | Evaluation({elab, _}) => Some(elab) @@ -265,7 +272,7 @@ module Update = { ~settings, ~stitch=_ => exp, ~is_edited, - ~dyn_map=Haz3lcore.TestMap.empty, //TODO(andrew) + ~dynamics=Dynamics.Map.empty, //TODO(andrew) editor, ) |> (x => (exp, x)) @@ -378,7 +385,7 @@ module View = { ~globals, ~selected, ~sort=Haz3lcore.Sort.root, - ~dyn_map=Haz3lcore.TestMap.empty, //TODO(andrew): this is just the result display, right? + ~dynamics=Dynamics.Map.empty, //This is just the result display editor, ); let exn_view = @@ -534,11 +541,6 @@ module View = { // Just showing elaboration because evaluation is off: | EvalResults when globals.settings.core.elaborate => - let dyn_map = - switch (Model.test_results(model)) { - | Some(result) => result.test_map - | None => Haz3lcore.TestMap.empty - }; let result = [ text("Evaluation disabled, showing elaboration:"), switch (Model.get_elaboration(model)) { @@ -553,8 +555,8 @@ module View = { |> CodeViewable.view_segment( ~globals, ~sort=Exp, - ~info_map=Haz3lcore.Id.Map.empty, - ~dyn_map, + ~info_map=Statics.Map.empty, + ~dynamics=Model.dynamics(model), ) | None => text("No elaboration found") }, diff --git a/src/haz3lweb/app/editors/result/StepperEditor.re b/src/haz3lweb/app/editors/result/StepperEditor.re index 119a0cbeb3..578458d645 100644 --- a/src/haz3lweb/app/editors/result/StepperEditor.re +++ b/src/haz3lweb/app/editors/result/StepperEditor.re @@ -35,7 +35,7 @@ module Update = { ~settings, ~is_edited, ~stitch, - ~dyn_map, + ~dynamics: Dynamics.Map.t, {editor, taken_steps, next_steps}: Model.t, ) : Model.t => { @@ -44,7 +44,7 @@ module Update = { ~settings, ~is_edited, ~stitch, - ~dyn_map, + ~dynamics, editor, ); {editor, taken_steps, next_steps}; @@ -71,7 +71,7 @@ module View = { ~signal: event => 'a, ~overlays=[], ~selected, - ~dyn_map, + ~dynamics: Dynamics.Map.t, model: Model.t, ) => { let overlays = { @@ -80,7 +80,7 @@ module View = { let editor = model.editor.editor; let globals = globals; let statics = model.editor.statics; - let dynamics = dyn_map; + let dynamics = dynamics; }); overlays @ Deco.taken_steps(model.taken_steps) @@ -93,7 +93,7 @@ module View = { ~selected, ~globals, ~overlays, - ~dyn_map, + ~dynamics, model.editor, ); }; diff --git a/src/haz3lweb/app/explainthis/ExplainThis.re b/src/haz3lweb/app/explainthis/ExplainThis.re index 060abc3025..a3ac82ef07 100644 --- a/src/haz3lweb/app/explainthis/ExplainThis.re +++ b/src/haz3lweb/app/explainthis/ExplainThis.re @@ -236,7 +236,7 @@ let expander_deco = let editor = editor; let globals = globals; let statics = CachedStatics.empty; - let dynamics = TestMap.empty; + let dynamics = Dynamics.Map.empty; }); switch (doc.expandable_id, List.length(options)) { | (None, _) @@ -278,8 +278,8 @@ let expander_deco = CodeViewable.view_segment( ~globals, ~sort=Exp, - ~info_map=Id.Map.empty, - ~dyn_map=TestMap.empty, + ~info_map=Statics.Map.empty, + ~dynamics=Dynamics.Map.empty, segment, ); let classes = @@ -498,7 +498,7 @@ let get_doc = editor, ); let statics = CachedStatics.empty; - let dynamics = TestMap.empty; + let dynamics = Dynamics.Map.empty; let highlight_deco = { module Deco = Deco.Deco({ @@ -513,7 +513,7 @@ let get_doc = CodeWithStatics.View.view( ~globals, ~overlays=highlight_deco @ [expander_deco], - ~dyn_map=dynamics, + ~dynamics, ~sort, {editor, statics}, ); diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 06cfa16681..e5c66d80b5 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -193,7 +193,7 @@ module Update = { one of the editors is shown in two cells, so we arbitrarily choose which statics to take */ let editors: Exercise.p('a) = { - let dynamics = TestMap.empty; //TODO(andrew): dynamics for projs in exercise mode + let dynamics = Dynamics.Map.empty; //TODO(andrew): dynamics for projs in exercise mode let calculate = statics => Editor.Update.calculate(~settings, statics, dynamics, ~is_edited); { diff --git a/src/haz3lweb/view/StepperView.re b/src/haz3lweb/view/StepperView.re index cef57c82a3..caa89924c3 100644 --- a/src/haz3lweb/view/StepperView.re +++ b/src/haz3lweb/view/StepperView.re @@ -263,7 +263,7 @@ module Update = { CodeSelectable.Update.calculate( ~settings=settings |> Calc.get_value, ~is_edited=false, - ~dyn_map=TestMap.empty, + ~dynamics=Dynamics.Map.empty, // TODO(andrew): ? ~stitch=x => x ), @@ -442,7 +442,7 @@ module View = { ~globals, ~overlays=[], ~selected=selection == Some(A(i + 1, ())), - ~dyn_map=TestMap.empty, + ~dynamics=Dynamics.Map.empty, ~inject= (x: StepperEditor.Update.t) => inject(StepperEditor(i + 1, x)), @@ -493,7 +493,7 @@ module View = { ~inject= (x: StepperEditor.Update.t) => inject(StepperEditor(current_n, x)), - ~dyn_map=TestMap.empty, + ~dynamics=Dynamics.Map.empty, ~signal= fun | TakeStep(x) => inject(Update.StepForward(x)) From 376f411a28fce6a82e6eec1f499e4d1c39eca557 Mon Sep 17 00:00:00 2001 From: disconcision Date: Mon, 25 Nov 2024 18:52:07 -0500 Subject: [PATCH 05/23] dynamics probes for live projectors. experiment with ExpToSegment --- src/haz3lcore/Measured.re | 7 +-- src/haz3lcore/dynamics/Evaluator.re | 3 ++ src/haz3lcore/dynamics/EvaluatorState.re | 11 ++-- src/haz3lcore/dynamics/EvaluatorState.rei | 4 +- src/haz3lcore/dynamics/EvaluatorStep.re | 5 ++ src/haz3lcore/dynamics/Transition.re | 5 +- src/haz3lcore/dynamics/ValueChecker.re | 1 + src/haz3lcore/pretty/ExpToSegment.re | 35 ++++++------ src/haz3lcore/prog/Dynamics.re | 47 +++++++++++++--- src/haz3lcore/statics/Elaborator.re | 5 +- src/haz3lcore/statics/Statics.re | 42 +++++++-------- src/haz3lcore/zipper/Editor.re | 8 +-- src/haz3lcore/zipper/Printer.re | 4 +- src/haz3lcore/zipper/Projector.re | 47 ++++++++++------ src/haz3lcore/zipper/ZipperBase.re | 8 +++ .../zipper/action/ProjectorPerform.re | 35 ++---------- src/haz3lcore/zipper/projectors/FoldProj.re | 34 ++++++++++-- src/haz3lweb/app/editors/cell/CellEditor.re | 12 +---- src/haz3lweb/app/editors/code/Code.re | 33 +++--------- src/haz3lweb/app/editors/code/CodeViewable.re | 30 +++++------ .../app/editors/code/CodeWithStatics.re | 4 +- .../app/editors/decoration/BackpackView.re | 8 +-- src/haz3lweb/app/editors/decoration/Deco.re | 4 +- src/haz3lweb/app/editors/result/EvalResult.re | 53 ++++++++++++++----- src/haz3lweb/app/explainthis/ExplainThis.re | 3 +- src/haz3lweb/view/StepperView.re | 2 +- 26 files changed, 253 insertions(+), 197 deletions(-) diff --git a/src/haz3lcore/Measured.re b/src/haz3lcore/Measured.re index a9c58a202d..81be465ce7 100644 --- a/src/haz3lcore/Measured.re +++ b/src/haz3lcore/Measured.re @@ -282,8 +282,7 @@ let last_of_token = (token: string, origin: Point.t): Point.t => row: origin.row + StringUtil.num_linebreaks(token), }; -let of_segment = - (seg: Segment.t, statics: Statics.Map.t, dynamics: Dynamics.Map.t): t => { +let of_segment = (seg: Segment.t, token_of_proj: Base.projector => string): t => { let is_indented = is_indented_map(seg); // recursive across seg's bidelimited containers @@ -354,9 +353,7 @@ let of_segment = let map = map |> add_g(g, {origin, last}); (contained_indent, last, map); | Projector(p) => - let si = Id.Map.find_opt(p.id, statics); - let di = Dynamics.Map.lookup(p.id, dynamics); - let token = Projector.placeholder(p, si, di); + let token = token_of_proj(p); let last = last_of_token(token, origin); let map = extra_rows(token, origin, map); let map = add_pr(p, {origin, last}, map); diff --git a/src/haz3lcore/dynamics/Evaluator.re b/src/haz3lcore/dynamics/Evaluator.re index fc4027f4d6..fe8fd2eda8 100644 --- a/src/haz3lcore/dynamics/Evaluator.re +++ b/src/haz3lcore/dynamics/Evaluator.re @@ -41,6 +41,9 @@ module EvaluatorEVMode: { let update_test = (state, id, v) => state := EvaluatorState.add_test(state^, id, v); + let update_probe = (state, id, v) => + state := EvaluatorState.add_probe(state^, id, v); + let req_value = (f, _, x) => switch (f(x)) { | (BoxedValue, x) => (BoxedReady, x) diff --git a/src/haz3lcore/dynamics/EvaluatorState.re b/src/haz3lcore/dynamics/EvaluatorState.re index 2d761f894c..104a9605fd 100644 --- a/src/haz3lcore/dynamics/EvaluatorState.re +++ b/src/haz3lcore/dynamics/EvaluatorState.re @@ -2,13 +2,13 @@ type t = { stats: EvaluatorStats.t, tests: TestMap.t, - probes: Dynamics.Map.t, + probes: Dynamics.Probe.Map.t, }; let init = { stats: EvaluatorStats.initial, tests: TestMap.empty, - probes: Dynamics.Map.empty, + probes: Dynamics.Probe.Map.empty, }; let take_step = ({stats, _} as es) => { @@ -27,4 +27,9 @@ let add_test = ({tests, _} as es, id, report) => { let get_tests = ({tests, _}) => tests; -let put_tests = (tests, es) => {...es, tests}; +let add_probe = ({probes, _} as es, id: Id.t, v: Dynamics.Probe.Info.t) => { + let probes = Dynamics.Probe.Map.extend(id, v, probes); + {...es, probes}; +}; + +let get_probes = ({probes, _}) => probes; diff --git a/src/haz3lcore/dynamics/EvaluatorState.rei b/src/haz3lcore/dynamics/EvaluatorState.rei index 916ac0586b..7ebca35433 100644 --- a/src/haz3lcore/dynamics/EvaluatorState.rei +++ b/src/haz3lcore/dynamics/EvaluatorState.rei @@ -32,4 +32,6 @@ let add_test: (t, Id.t, TestMap.instance_report) => t; let get_tests: t => TestMap.t; -let put_tests: (TestMap.t, t) => t; +let add_probe: (t, Id.t, Dynamics.Probe.Info.t) => t; + +let get_probes: t => Dynamics.Probe.Map.t; diff --git a/src/haz3lcore/dynamics/EvaluatorStep.re b/src/haz3lcore/dynamics/EvaluatorStep.re index cf288a3fc1..f70ec94782 100644 --- a/src/haz3lcore/dynamics/EvaluatorStep.re +++ b/src/haz3lcore/dynamics/EvaluatorStep.re @@ -335,6 +335,8 @@ module Decompose = { let otherwise = (env, o) => (o, Result.BoxedValue, env, ()); let update_test = (state, id, v) => state := EvaluatorState.add_test(state^, id, v); + let update_probe = (state, id, info) => + state := EvaluatorState.add_probe(state^, id, info); }; module Decomp = Transition(DecomposeEVMode); @@ -380,6 +382,9 @@ module TakeStep = { let update_test = (state, id, v) => state := EvaluatorState.add_test(state^, id, v); + + let update_probe = (state, id, info) => + state := EvaluatorState.add_probe(state^, id, info); }; module TakeStepEV = Transition(TakeStepEVMode); diff --git a/src/haz3lcore/dynamics/Transition.re b/src/haz3lcore/dynamics/Transition.re index 8be074f305..f61c08e0d9 100644 --- a/src/haz3lcore/dynamics/Transition.re +++ b/src/haz3lcore/dynamics/Transition.re @@ -138,6 +138,8 @@ module type EV_MODE = { let otherwise: (ClosureEnvironment.t, 'a) => requirements(unit, 'a); let update_test: (state, Id.t, TestMap.instance_report) => unit; + + let update_probe: (state, Id.t, Dynamics.Probe.Info.t) => unit; }; module Transition = (EV: EV_MODE) => { @@ -782,8 +784,7 @@ module Transition = (EV: EV_MODE) => { ); Step({ expr: d', - state_update: () => - update_test(state, DHExp.rep_id(d), (d', Indet)), + state_update: () => update_probe(state, DHExp.rep_id(d), d'), kind: RemoveParens, is_value: true, }); diff --git a/src/haz3lcore/dynamics/ValueChecker.re b/src/haz3lcore/dynamics/ValueChecker.re index a6e5ab30f0..c5fa1d4693 100644 --- a/src/haz3lcore/dynamics/ValueChecker.re +++ b/src/haz3lcore/dynamics/ValueChecker.re @@ -78,6 +78,7 @@ module ValueCheckerEVMode: { }; let update_test = (_, _, _) => (); + let update_probe = (_, _, _) => (); }; module CV = Transition(ValueCheckerEVMode); diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index 17324fb5aa..3cc6af867e 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -112,29 +112,26 @@ let (@) = (seg1: Segment.t, seg2: Segment.t): Segment.t => let fold_if = (condition, pieces) => if (condition) { - [ - ProjectorPerform.Update.init( - Fold, - mk_form("parens_exp", Id.mk(), [pieces]), - ), - ]; + pieces + //[Projector.init(Fold, mk_form("parens_exp", Id.mk(), [pieces]))]; } else { - pieces; + pieces }; - -let fold_fun_if = (condition, f_name: string, pieces) => +// TODO(andrew): uncomment above and below +let fold_fun_if = (condition, _f_name: string, pieces) => if (condition) { - [ - ProjectorPerform.Update.init_from_str( - Fold, - mk_form("parens_exp", Id.mk(), [pieces]), - ({text: f_name}: FoldProj.t) - |> FoldProj.sexp_of_t - |> Sexplib.Sexp.to_string, - ), - ]; + pieces + // [ + // Projector.init_from_str( + // Fold, + // mk_form("parens_exp", Id.mk(), [pieces]), + // ({text: f_name}: FoldProj.t) + // |> FoldProj.sexp_of_t + // |> Sexplib.Sexp.to_string, + // ), + // ]; } else { - pieces; + pieces }; /* We assume that parentheses have already been added as necessary, and diff --git a/src/haz3lcore/prog/Dynamics.re b/src/haz3lcore/prog/Dynamics.re index 2651a38efe..a3c6f2141a 100644 --- a/src/haz3lcore/prog/Dynamics.re +++ b/src/haz3lcore/prog/Dynamics.re @@ -1,22 +1,53 @@ open Util; +module Probe = { + module Info = { + [@deriving (show({with_path: false}), sexp, yojson)] + type t = DHExp.t; + }; + + module Map = { + [@deriving (show({with_path: false}), sexp, yojson)] + type t = Id.Map.t(list(Info.t)); + + let empty = Id.Map.empty; + let lookup = Id.Map.find_opt; + + let extend = (id, report, map: t) => + Id.Map.update( + id, + opt => + switch (opt) { + | Some(a) => Some(a @ [report]) + | None => Some([report]) + }, + map, + ); + }; + + let extend = ((id, report), test_map) => { + switch (List.assoc_opt(id, test_map)) { + | Some(a) => List.remove_assoc(id, test_map) @ [(id, a @ [report])] + | None => test_map @ [(id, [report])] + }; + }; +}; + module Info = { [@deriving (show({with_path: false}), sexp, yojson)] - type t = {vals: list(DHExp.t)}; - - let first_val = (di: t): option(DHExp.t) => ListUtil.hd_opt(di.vals); + type t = {vals: list(Probe.Info.t)}; }; module Map = { [@deriving (show({with_path: false}), sexp, yojson)] - type t = {test_map: TestMap.t}; - let empty: t = {test_map: TestMap.empty}; + type t = {probes: Probe.Map.t}; + let empty: t = {probes: Probe.Map.empty}; - let mk = (test_map: TestMap.t): t => {test_map: test_map}; + let mk = (probes: Probe.Map.t): t => {probes: probes}; let lookup = (id: Id.t, dm: t): option(Info.t) => - switch (TestMap.lookup(id, dm.test_map)) { + switch (Probe.Map.lookup(id, dm.probes)) { | None => None - | Some(vals) => Some({vals: List.map(fst, vals)}) + | Some(vals) => Some({vals: vals}) }; }; diff --git a/src/haz3lcore/statics/Elaborator.re b/src/haz3lcore/statics/Elaborator.re index 7987a36496..8aa39e0ce1 100644 --- a/src/haz3lcore/statics/Elaborator.re +++ b/src/haz3lcore/statics/Elaborator.re @@ -233,11 +233,10 @@ let rec elaborate = (m: Statics.Map.t, uexp: UExp.t): (DHExp.t, Typ.t) => { | Cast(e, _, _) // We remove these casts because they should be re-inserted in the recursive call | FailedCast(e, _, _) | Parens(e, Probe) => - //TODO(andrew): am i casting/rewrapping correctly? + //TODO(andrew): Matthew: Am I casting/rewrapping correctly? let (e', ty) = elaborate(m, e); Parens(e' |> cast_from(ty), Probe) |> rewrap; - | Parens(e, _) => - //TODO(andrew): dont get rid of these? + | Parens(e, Paren) => let (e', ty) = elaborate(m, e); e' |> cast_from(ty); | Deferral(_) => uexp diff --git a/src/haz3lcore/statics/Statics.re b/src/haz3lcore/statics/Statics.re index bb6775fdc3..15bc9f9e6d 100644 --- a/src/haz3lcore/statics/Statics.re +++ b/src/haz3lcore/statics/Statics.re @@ -1,32 +1,32 @@ /* STATICS.re - This module determines the statics semantics of a program. - It makes use of the following modules: + This module determines the statics semantics of a program. + It makes use of the following modules: - INFO.re: Defines the Info.t type which is used to represent the - static STATUS of a term. This STATUS can be either OK or ERROR, - and is determined by reconcilling two sources of typing information, - the MODE and the SELF. + INFO.re: Defines the Info.t type which is used to represent the + static STATUS of a term. This STATUS can be either OK or ERROR, + and is determined by reconcilling two sources of typing information, + the MODE and the SELF. - MODE.re: Defines the Mode.t type which is used to represent the - typing expectations imposed by a term's ancestors. + MODE.re: Defines the Mode.t type which is used to represent the + typing expectations imposed by a term's ancestors. - SELF.re: Define the Self.t type which is used to represent the - type information derivable from the term itself. + SELF.re: Define the Self.t type which is used to represent the + type information derivable from the term itself. - The point of STATICS.re itself is to derive a map between each - term's unique id and that term's static INFO. The below functions - are intended mostly as infrastructure: The point is to define a - traversal through the syntax tree which, for each term, passes - down the MODE, passes up the SELF, calculates the INFO, and adds - it to the map. + The point of STATICS.re itself is to derive a map between each + term's unique id and that term's static INFO. The below functions + are intended mostly as infrastructure: The point is to define a + traversal through the syntax tree which, for each term, passes + down the MODE, passes up the SELF, calculates the INFO, and adds + it to the map. - The architectural intention here is that most type-manipulation - logic is defined in INFO, MODE, and SELF, and the STATICS module - itself is dedicated to the piping necessary to (A) introduce and + The architectural intention here is that most type-manipulation + logic is defined in INFO, MODE, and SELF, and the STATICS module + itself is dedicated to the piping necessary to (A) introduce and (B) propagate the necessary information through the syntax tree. - */ + */ module Info = Info; @@ -279,8 +279,8 @@ and uexp_to_info_map = /* Currently doing this as otherwise it clobbers the statics * for the contained expression as i'm just reusing the same id * in order to associate it through dynamics */ + //TODO(andrew): ponder this go(~mode, e, m) - //TODO(andrew): ponder this | UnOp(Meta(Unquote), e) when is_in_filter => let e: UExp.t = { ids: e.ids, diff --git a/src/haz3lcore/zipper/Editor.re b/src/haz3lcore/zipper/Editor.re index 2ab9c920b0..51e07f2a76 100644 --- a/src/haz3lcore/zipper/Editor.re +++ b/src/haz3lcore/zipper/Editor.re @@ -33,7 +33,7 @@ module CachedSyntax = { let yojson_of_t = _ => failwith("Editor.Meta.yojson_of_t"); let t_of_yojson = _ => failwith("Editor.Meta.t_of_yojson"); - let init = (z, info_map, dyn_map): t => { + let init = (~token_of_proj, z): t => { let segment = Zipper.unselect_and_zip(z); let MakeTerm.{term, terms, projectors} = MakeTerm.go(segment); { @@ -42,7 +42,7 @@ module CachedSyntax = { term_ranges: TermRanges.mk(segment), tiles: TileMap.mk(segment), holes: Segment.holes(segment), - measured: Measured.of_segment(segment, info_map, dyn_map), + measured: Measured.of_segment(segment, token_of_proj), selection_ids: Selection.selection_ids(z.selection), term, terms, @@ -54,7 +54,7 @@ module CachedSyntax = { let calculate = (z: Zipper.t, info_map, dyn_map, old: t) => old.old - ? init(z, info_map, dyn_map) + ? init(z, ~token_of_proj=Projector.token_of_proj(info_map, dyn_map)) : {...old, selection_ids: Selection.selection_ids(z.selection)}; }; @@ -97,7 +97,7 @@ module Model = { col_target: None, }, history: History.empty, - syntax: CachedSyntax.init(zipper, Statics.Map.empty, Dynamics.Map.empty), + syntax: CachedSyntax.init(zipper, ~token_of_proj=_ => ""), }; type persistent = PersistentZipper.t; diff --git a/src/haz3lcore/zipper/Printer.re b/src/haz3lcore/zipper/Printer.re index 9e55e4d22e..107e5c6a4f 100644 --- a/src/haz3lcore/zipper/Printer.re +++ b/src/haz3lcore/zipper/Printer.re @@ -56,9 +56,9 @@ let to_rows = let measured = z => z - |> ProjectorPerform.Update.remove_all + |> ZipperBase.remove_all_projectors |> Zipper.seg_without_buffer - |> Measured.of_segment(_, Statics.Map.empty, Dynamics.Map.empty); + |> Measured.of_segment(_, _ => ""); // No projectors let pretty_print = (~holes: option(string)=Some(""), z: Zipper.t): string => to_rows( diff --git a/src/haz3lcore/zipper/Projector.re b/src/haz3lcore/zipper/Projector.re index 3476d63d4d..e904e9e890 100644 --- a/src/haz3lcore/zipper/Projector.re +++ b/src/haz3lcore/zipper/Projector.re @@ -14,32 +14,45 @@ let to_module = (kind: Base.kind): (module Cooked) => | TextArea => (module Cook(TextAreaProj.M)) }; +/* Currently projection is limited to convex pieces */ +let minimum_projection_condition = (syntax: syntax): bool => + Piece.is_convex(syntax); + +let init = (kind: t, syntax: syntax): syntax => { + /* We set the projector id equal to the Piece id for convienence + * including cursor-info association. We maintain this invariant + * when we update a projector's contained syntax */ + let (module P) = to_module(kind); + switch (P.can_project(syntax) && minimum_projection_condition(syntax)) { + | false => syntax + | true => Projector({id: Piece.id(syntax), kind, model: P.init, syntax}) + }; +}; + +let init_from_str = (kind: t, syntax: syntax, model_str: string): syntax => { + let (module P) = to_module(kind); + switch (P.can_project(syntax) && minimum_projection_condition(syntax)) { + | false => syntax + | true => Projector({id: Piece.id(syntax), kind, model: model_str, syntax}) + }; +}; + let shape = (p: Base.projector, info: info): shape => { let (module P) = to_module(p.kind); P.placeholder(p.model, info); }; -/* A projector is replaced by a placeholder in the underlying - * editor for view purposes. This projector is an all-whitespace - * monotile. Currently there is no explicit notion of placeholders - * in the zipper; a tile consisting of any number of whitespaces - * is considered a placeholder. This could be made more principled. - * Note that a placeholder retains the UUID of the underlying. */ -let placeholder = - ( - p: Base.projector, - statics: option(Statics.Info.t), - dynamics: option(Dynamics.Info.t), - ) - : string => +/* Returns a token consisting of whitespace (possibly including linebreaks) + * representing the space to leave for the projector in the underlying code view */ +let token_of_proj = + (statics: Statics.Map.t, dynamics: Dynamics.Map.t, p: Base.projector) => { + let statics = Statics.Map.lookup(p.id, statics); + let dynamics = Dynamics.Map.lookup(p.id, dynamics); switch (shape(p, {id: p.id, syntax: p.syntax, statics, dynamics})) { | Inline(width) => String.make(width, ' ') | Block({row, col}) => String.make(row - 1, '\n') ++ String.make(col, ' ') }; - -/* Currently projection is limited to convex pieces */ -let minimum_projection_condition = (syntax: syntax): bool => - Piece.is_convex(syntax); +}; /* Returns the projector at the caret, if any */ let indicated = (z: ZipperBase.t) => { diff --git a/src/haz3lcore/zipper/ZipperBase.re b/src/haz3lcore/zipper/ZipperBase.re index cb6311a4b8..f241e24700 100644 --- a/src/haz3lcore/zipper/ZipperBase.re +++ b/src/haz3lcore/zipper/ZipperBase.re @@ -160,3 +160,11 @@ module MapPiece = { go(f, z); }; }; + +let remove_all_projectors = (z: t): t => + MapPiece.go( + fun + | Projector(pr) => pr.syntax + | x => x, + z, + ); diff --git a/src/haz3lcore/zipper/action/ProjectorPerform.re b/src/haz3lcore/zipper/action/ProjectorPerform.re index 55a1d2e8f7..841845163f 100644 --- a/src/haz3lcore/zipper/action/ProjectorPerform.re +++ b/src/haz3lcore/zipper/action/ProjectorPerform.re @@ -10,30 +10,11 @@ module Update = { | x => x }; - let init = (kind: t, syntax: syntax): syntax => { - /* We set the projector id equal to the Piece id for convienence - * including cursor-info association. We maintain this invariant - * when we update a projector's contained syntax */ - let (module P) = to_module(kind); - switch (P.can_project(syntax) && minimum_projection_condition(syntax)) { - | false => syntax - | true => Projector({id: Piece.id(syntax), kind, model: P.init, syntax}) - }; - }; - - let init_from_str = (kind: t, syntax: syntax, model_str: string): syntax => { - let (module P) = to_module(kind); - switch (P.can_project(syntax) && minimum_projection_condition(syntax)) { - | false => syntax - | true => - Projector({id: Piece.id(syntax), kind, model: model_str, syntax}) - }; - }; - let add_projector = (kind: Base.kind, id: Id.t, syntax: syntax) => switch (syntax) { - | Projector(pr) when Piece.id(syntax) == id => init(kind, pr.syntax) - | syntax when Piece.id(syntax) == id => init(kind, syntax) + | Projector(pr) when Piece.id(syntax) == id => + Projector.init(kind, pr.syntax) + | syntax when Piece.id(syntax) == id => Projector.init(kind, syntax) | x => x }; @@ -46,13 +27,7 @@ module Update = { let add_or_remove_projector = (kind: Base.kind, id: Id.t, syntax: syntax) => switch (syntax) { | Projector(pr) when Piece.id(syntax) == id => pr.syntax - | syntax when Piece.id(syntax) == id => init(kind, syntax) - | x => x - }; - - let remove_any_projector = (syntax: syntax) => - switch (syntax) { - | Projector(pr) => pr.syntax + | syntax when Piece.id(syntax) == id => Projector.init(kind, syntax) | x => x }; @@ -71,7 +46,7 @@ module Update = { ZipperBase.MapPiece.fast_local(remove_projector(id), id, z); let remove_all = (z: ZipperBase.t): ZipperBase.t => - ZipperBase.MapPiece.go(remove_any_projector, z); + ZipperBase.remove_all_projectors(z); }; /* If the caret is inside the indicated piece, move it out diff --git a/src/haz3lcore/zipper/projectors/FoldProj.re b/src/haz3lcore/zipper/projectors/FoldProj.re index 2d695c30f4..83d5d4e639 100644 --- a/src/haz3lcore/zipper/projectors/FoldProj.re +++ b/src/haz3lcore/zipper/projectors/FoldProj.re @@ -9,12 +9,40 @@ type t = { text: string, }; -/* Proof of concept value exposure. This isn't getting set right - after actions, only initially */ +/* Proof of concept value exposure. This isn't getting set immediately + after folding for some reason */ let vals = (di: option(Dynamics.Info.t)) => { switch (di) { | Some(di) => - List.map((d: DHExp.t) => d.term |> TermBase.Exp.show_term, di.vals) + List.map( + (d: DHExp.t) => + d |> DHExp.strip_casts |> (d => d.term) |> TermBase.Exp.show_term, + di.vals, + ) + |> String.concat(", ") + | _ => "Nein" + }; +}; + +let vals = (di: option(Dynamics.Info.t)) => { + switch (di) { + | Some(di) => + List.map( + (d: DHExp.t) => + d + |> DHExp.strip_casts + |> ExpToSegment.exp_to_segment( + ~settings={ + inline: false, + fold_case_clauses: false, + fold_fn_bodies: false, + hide_fixpoints: false, + fold_cast_types: false, + }, + ) + |> Printer.of_segment(~holes=None), + di.vals, + ) |> String.concat(", ") | _ => "Nein" }; diff --git a/src/haz3lweb/app/editors/cell/CellEditor.re b/src/haz3lweb/app/editors/cell/CellEditor.re index 04dfc923f5..fd8b87c7d5 100644 --- a/src/haz3lweb/app/editors/cell/CellEditor.re +++ b/src/haz3lweb/app/editors/cell/CellEditor.re @@ -61,7 +61,7 @@ module Update = { ~settings, ~is_edited, ~stitch, - ~dynamics=Dynamics.Map.empty, //TODO(andrew): see below + ~dynamics=EvalResult.Model.dynamics(result), editor, ); let result = @@ -72,16 +72,6 @@ module Update = { editor |> CodeEditable.Model.get_statics, result, ); - //TODO(andrew): double-calcing editor... - let dynamics = EvalResult.Model.dynamics(result); - let editor = - CodeEditable.Update.calculate( - ~settings, - ~is_edited, - ~stitch, - ~dynamics, - editor, - ); {editor, result}; }; }; diff --git a/src/haz3lweb/app/editors/code/Code.re b/src/haz3lweb/app/editors/code/Code.re index a2ba69c6e9..00a8778199 100644 --- a/src/haz3lweb/app/editors/code/Code.re +++ b/src/haz3lweb/app/editors/code/Code.re @@ -71,31 +71,15 @@ let of_secondary = } ); -let of_projector = - (p, expected_sort, indent, info_map, dynamics: Dynamics.Map.t) => - of_delim'(( - [ - Projector.placeholder( - p, - Statics.Map.lookup(p.id, info_map), - Dynamics.Map.lookup(p.id, dynamics), - ), - ], - false, - expected_sort, - true, - true, - indent, - 0, - )); +let of_projector = (expected_sort, indent, token) => + of_delim'(([token], false, expected_sort, true, true, indent, 0)); module Text = ( M: { let map: Measured.t; let settings: Settings.Model.t; - let info_map: Statics.Map.t; - let dynamics: Dynamics.Map.t; + let token_of_proj: Base.projector => string; }, ) => { let m = p => Measured.find_p(~msg="Text", p, M.map); @@ -127,11 +111,9 @@ module Text = of_secondary((content, M.settings.secondary_icons, m(p).last.col)) | Projector(p) => of_projector( - p, expected_sort, m(Projector(p)).origin.col, - M.info_map, - M.dynamics, + M.token_of_proj(p), ) }; } @@ -174,14 +156,13 @@ let rec holes = ); let simple_view = (~font_metrics, ~segment, ~settings: Settings.t): Node.t => { - let map = - Measured.of_segment(segment, Statics.Map.empty, Dynamics.Map.empty); + let token_of_proj = _ => ""; /* Assume this doesn't contain projectors */ + let map = Measured.of_segment(segment, token_of_proj); module Text = Text({ let map = map; let settings = settings; - let info_map = Statics.Map.empty; /* Assume this doesn't contain projectors */ - let dynamics = Dynamics.Map.empty; /* Assume this doesn't contain projectors */ + let token_of_proj = token_of_proj; }); let holes = holes(~map, ~font_metrics, segment); div( diff --git a/src/haz3lweb/app/editors/code/CodeViewable.re b/src/haz3lweb/app/editors/code/CodeViewable.re index 9205ef3710..675d13e0a5 100644 --- a/src/haz3lweb/app/editors/code/CodeViewable.re +++ b/src/haz3lweb/app/editors/code/CodeViewable.re @@ -12,16 +12,14 @@ let view = ~buffer_ids, ~segment, ~holes, - ~info_map, - ~dynamics, + ~token_of_proj, ) : Node.t => { module Text = Code.Text({ let map = measured; let settings = globals.settings; - let info_map = info_map; - let dynamics = dynamics; + let token_of_proj = token_of_proj; }); let code = Text.of_segment(buffer_ids, false, sort, segment); let holes = List.map(Code.of_hole(~measured, ~globals), holes); @@ -53,14 +51,8 @@ let view = // }; let view_segment = - ( - ~globals: Globals.t, - ~sort: Sort.t, - ~info_map, - ~dynamics, - segment: Segment.t, - ) => { - let measured = Measured.of_segment(segment, info_map, dynamics); + (~globals: Globals.t, ~sort: Sort.t, ~token_of_proj, segment: Segment.t) => { + let measured = Measured.of_segment(segment, token_of_proj); let buffer_ids = []; let holes = Segment.holes(segment); view( @@ -70,19 +62,21 @@ let view_segment = ~buffer_ids, ~holes, ~segment, - ~dynamics, - ~info_map, + ~token_of_proj, ); }; -let view_exp = (~dynamics, ~globals: Globals.t, ~settings, exp: Exp.t) => { +let view_exp = + (~dynamics, ~globals: Globals.t, ~settings, ~info_map, exp: Exp.t) => { + let token_of_proj = Projector.token_of_proj(info_map, dynamics); exp |> ExpToSegment.exp_to_segment(~settings) - |> view_segment(~dynamics, ~globals, ~sort=Exp); + |> view_segment(~token_of_proj, ~globals, ~sort=Exp); }; -let view_typ = (~globals: Globals.t, ~settings, typ: Typ.t) => { +let view_typ = (~globals: Globals.t, ~settings, ~info_map, typ: Typ.t) => { + let token_of_proj = Projector.token_of_proj(info_map, Dynamics.Map.empty); typ |> ExpToSegment.typ_to_segment(~settings) - |> view_segment(~dynamics=Dynamics.Map.empty, ~globals, ~sort=Typ); + |> view_segment(~token_of_proj, ~globals, ~sort=Typ); }; diff --git a/src/haz3lweb/app/editors/code/CodeWithStatics.re b/src/haz3lweb/app/editors/code/CodeWithStatics.re index f2da33af74..a6bb65a7ad 100644 --- a/src/haz3lweb/app/editors/code/CodeWithStatics.re +++ b/src/haz3lweb/app/editors/code/CodeWithStatics.re @@ -98,6 +98,7 @@ module View = { }, _, }: Model.t = model; + let token_of_proj = Projector.token_of_proj(info_map, dynamics); let code_text_view = CodeViewable.view( ~globals, @@ -106,8 +107,7 @@ module View = { ~buffer_ids=Selection.is_buffer(z.selection) ? selection_ids : [], ~segment, ~holes, - ~info_map, - ~dynamics, + ~token_of_proj, ); let statics_decos = { module Deco = diff --git a/src/haz3lweb/app/editors/decoration/BackpackView.re b/src/haz3lweb/app/editors/decoration/BackpackView.re index e629eb99f6..205b61c8ef 100644 --- a/src/haz3lweb/app/editors/decoration/BackpackView.re +++ b/src/haz3lweb/app/editors/decoration/BackpackView.re @@ -3,17 +3,19 @@ open Node; open Haz3lcore; open Util; +/* Assume this doesn't contain projectors */ +let token_of_proj = _ => ""; + let measured_of = seg => /* Assume this doesn't contain projectors */ - Measured.of_segment(seg, Statics.Map.empty, Dynamics.Map.empty); + Measured.of_segment(seg, token_of_proj); let text_view = (seg: Segment.t): list(Node.t) => { module Text = Code.Text({ let map = measured_of(seg); let settings = Settings.Model.init; - let info_map = Statics.Map.empty; /* Assume this doesn't contain projectors */ - let dynamics = Dynamics.Map.empty; /* Assume this doesn't contain projectors */ + let token_of_proj = token_of_proj; }); Text.of_segment([], true, Any, seg); }; diff --git a/src/haz3lweb/app/editors/decoration/Deco.re b/src/haz3lweb/app/editors/decoration/Deco.re index 84406f8cac..dac39dc07d 100644 --- a/src/haz3lweb/app/editors/decoration/Deco.re +++ b/src/haz3lweb/app/editors/decoration/Deco.re @@ -122,9 +122,7 @@ module HighlightSegment = switch (Measured.find_pr_opt(p, M.measured)) { | None => failwith("Deco.of_projector: missing measurement") | Some(_m) => - let ci = Statics.Map.lookup(p.id, M.info_map); - let di = Dynamics.Map.lookup(p.id, M.dynamics); - let token = Projector.placeholder(p, ci, di); + let token = Projector.token_of_proj(M.info_map, M.dynamics, p); /* Handling this internal to ProjectorsView at the moment because the * commented-out strategy doesn't work well, since the inserted str8- * edged lines vertical edge placement doesn't account for whether diff --git a/src/haz3lweb/app/editors/result/EvalResult.re b/src/haz3lweb/app/editors/result/EvalResult.re index 7687b8493f..62f655a7ab 100644 --- a/src/haz3lweb/app/editors/result/EvalResult.re +++ b/src/haz3lweb/app/editors/result/EvalResult.re @@ -38,7 +38,8 @@ module Model = { type t = { kind, result, - previous_tests: option(Haz3lcore.TestResults.t) // Stops test results from being cleared on update + previous_tests: option(Haz3lcore.TestResults.t), // Stops test results from being cleared on update + previous_probes: option(Dynamics.Probe.Map.t), }; let make_test_report = (model: t): option(Haz3lcore.TestResults.t) => @@ -61,7 +62,12 @@ module Model = { | NoElab => None }; - let init = {kind: Evaluation, result: NoElab, previous_tests: None}; + let init = { + kind: Evaluation, + result: NoElab, + previous_tests: None, + previous_probes: None, + }; let test_results = (model: t): option(Haz3lcore.TestResults.t) => switch (model.result) { @@ -83,10 +89,25 @@ module Model = { | NoElab => model.previous_tests }; + let probe_results = (model: t): option(Dynamics.Probe.Map.t) => + switch (model.result) { + | Evaluation({result: OldValue(ResultOk((_, state))), _}) + | Evaluation({result: NewValue(ResultOk((_, state))), _}) => + Some(state |> Haz3lcore.EvaluatorState.get_probes) + | Stepper(s) => + Some( + s.history + |> StepperView.Model.get_state + |> Haz3lcore.EvaluatorState.get_probes, + ) + | Evaluation(_) + | NoElab => model.previous_probes + }; + let dynamics = (model: t): Dynamics.Map.t => - switch (test_results(model)) { - | Some(result) => Dynamics.Map.mk(result.test_map) - | None => Dynamics.Map.mk(TestMap.empty) + switch (probe_results(model)) { + | Some(result) => Dynamics.Map.mk(result) + | None => Dynamics.Map.mk(Dynamics.Probe.Map.empty) }; let get_elaboration = (model: t): option(Haz3lcore.Exp.t) => @@ -168,7 +189,13 @@ module Update = { cached_settings, }), } - |> (x => {...x, previous_tests: Model.test_results(x)}) + |> ( + x => { + ...x, + previous_tests: Model.test_results(x), + previous_probes: Model.probe_results(x), + } + ) |> Updated.return | (UpdateResult(_), _) => model |> Updated.return_quiet }; @@ -272,7 +299,7 @@ module Update = { ~settings, ~stitch=_ => exp, ~is_edited, - ~dynamics=Dynamics.Map.empty, //TODO(andrew) + ~dynamics=Dynamics.Map.empty, // Dynamics unneeded here editor, ) |> (x => (exp, x)) @@ -545,6 +572,11 @@ module View = { text("Evaluation disabled, showing elaboration:"), switch (Model.get_elaboration(model)) { | Some(elab) => + let token_of_proj = + Projector.token_of_proj( + Statics.Map.empty, + Model.dynamics(model), + ); elab |> Haz3lcore.ExpToSegment.( exp_to_segment( @@ -552,12 +584,7 @@ module View = { Settings.of_core(~inline=false, globals.settings.core), ) ) - |> CodeViewable.view_segment( - ~globals, - ~sort=Exp, - ~info_map=Statics.Map.empty, - ~dynamics=Model.dynamics(model), - ) + |> CodeViewable.view_segment(~globals, ~sort=Exp, ~token_of_proj); | None => text("No elaboration found") }, ]; diff --git a/src/haz3lweb/app/explainthis/ExplainThis.re b/src/haz3lweb/app/explainthis/ExplainThis.re index a3ac82ef07..dcf2ab0c21 100644 --- a/src/haz3lweb/app/explainthis/ExplainThis.re +++ b/src/haz3lweb/app/explainthis/ExplainThis.re @@ -278,8 +278,7 @@ let expander_deco = CodeViewable.view_segment( ~globals, ~sort=Exp, - ~info_map=Statics.Map.empty, - ~dynamics=Dynamics.Map.empty, + ~token_of_proj=_ => "", // Assume no projectors segment, ); let classes = diff --git a/src/haz3lweb/view/StepperView.re b/src/haz3lweb/view/StepperView.re index caa89924c3..e08640730d 100644 --- a/src/haz3lweb/view/StepperView.re +++ b/src/haz3lweb/view/StepperView.re @@ -263,7 +263,7 @@ module Update = { CodeSelectable.Update.calculate( ~settings=settings |> Calc.get_value, ~is_edited=false, - ~dynamics=Dynamics.Map.empty, // TODO(andrew): ? + ~dynamics=Dynamics.Map.empty, // No projectors in stepper atm ~stitch=x => x ), From 09d08e9c92235c2d8f88118d8968992f6f2147cd Mon Sep 17 00:00:00 2001 From: disconcision Date: Sat, 30 Nov 2024 00:38:20 -0500 Subject: [PATCH 06/23] projector probes have access to values and reference environments --- src/haz3lcore/dynamics/EvalCtx.re | 2 +- src/haz3lcore/dynamics/Transition.re | 12 +- src/haz3lcore/pretty/ExpToSegment.re | 30 +++-- src/haz3lcore/prog/Dynamics.re | 64 +++++++++- src/haz3lcore/statics/Binding.re | 26 ++++ src/haz3lcore/statics/Ctx.re | 7 ++ src/haz3lcore/statics/Elaborator.re | 11 +- src/haz3lcore/statics/MakeTerm.re | 2 +- src/haz3lcore/statics/Statics.re | 43 +++++-- src/haz3lcore/statics/Term.re | 9 ++ src/haz3lcore/statics/TermBase.re | 12 +- src/haz3lcore/tiles/Base.re | 1 + src/haz3lcore/zipper/EditorUtil.re | 33 +++-- src/haz3lcore/zipper/Projector.re | 1 + src/haz3lcore/zipper/ProjectorBase.re | 13 +- .../zipper/projectors/CheckboxProj.re | 8 +- src/haz3lcore/zipper/projectors/FoldProj.re | 46 +------ src/haz3lcore/zipper/projectors/InfoProj.re | 2 +- src/haz3lcore/zipper/projectors/ProbeProj.re | 113 ++++++++++++++++++ .../zipper/projectors/SliderFProj.re | 8 +- src/haz3lcore/zipper/projectors/SliderProj.re | 8 +- .../zipper/projectors/TextAreaProj.re | 2 +- src/haz3lweb/app/common/ProjectorView.re | 28 ++++- src/haz3lweb/app/editors/code/CodeEditable.re | 2 +- src/haz3lweb/app/inspector/ProjectorPanel.re | 2 +- src/haz3lweb/www/style.css | 31 +++++ 26 files changed, 401 insertions(+), 115 deletions(-) create mode 100644 src/haz3lcore/statics/Binding.re create mode 100644 src/haz3lcore/zipper/projectors/ProbeProj.re diff --git a/src/haz3lcore/dynamics/EvalCtx.re b/src/haz3lcore/dynamics/EvalCtx.re index e88aae5bfc..76c6496cc2 100644 --- a/src/haz3lcore/dynamics/EvalCtx.re +++ b/src/haz3lcore/dynamics/EvalCtx.re @@ -23,7 +23,7 @@ type term = | BinOp2(Operators.op_bin, DHExp.t, t) | Tuple(t, (list(DHExp.t), list(DHExp.t))) | Test(t) - | Parens(t, TermBase.paren_tag) + | Parens(t, TermBase.probe_tag) | ListLit(t, (list(DHExp.t), list(DHExp.t))) | MultiHole(t, (list(Any.t), list(Any.t))) | Cons1(t, DHExp.t) diff --git a/src/haz3lcore/dynamics/Transition.re b/src/haz3lcore/dynamics/Transition.re index f61c08e0d9..e18fbf2b46 100644 --- a/src/haz3lcore/dynamics/Transition.re +++ b/src/haz3lcore/dynamics/Transition.re @@ -772,19 +772,21 @@ module Transition = (EV: EV_MODE) => { | Undefined => let. _ = otherwise(env, d); Indet; - | Parens(d'', Probe) => + | Parens(d'', Probe(pr)) => + print_endline("Probe:" ++ TermBase.show_probe(pr)); //TODO(andrew): cleanup - //print_endline("Probe in Transition"); - let. _ = otherwise(env, ((d, _)) => Parens(d, Probe) |> rewrap) + let. _ = otherwise(env, ((d, _)) => Parens(d, Probe(pr)) |> rewrap) and. (d', _is_value) = req_final_or_value( req(state, env), - d => Parens(d, Probe) |> wrap_ctx, + d => Parens(d, Probe(pr)) |> wrap_ctx, d'', ); + //TODO(andrew): should this be inside update closure? + let pi = Dynamics.Probe.Info.mk(d', env, pr); Step({ expr: d', - state_update: () => update_probe(state, DHExp.rep_id(d), d'), + state_update: () => update_probe(state, DHExp.rep_id(d), pi), kind: RemoveParens, is_value: true, }); diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index 3cc6af867e..2df67059cc 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -112,26 +112,24 @@ let (@) = (seg1: Segment.t, seg2: Segment.t): Segment.t => let fold_if = (condition, pieces) => if (condition) { - pieces - //[Projector.init(Fold, mk_form("parens_exp", Id.mk(), [pieces]))]; + [Projector.init(Fold, mk_form("parens_exp", Id.mk(), [pieces]))]; } else { - pieces + pieces; }; -// TODO(andrew): uncomment above and below -let fold_fun_if = (condition, _f_name: string, pieces) => + +let fold_fun_if = (condition, f_name: string, pieces) => if (condition) { - pieces - // [ - // Projector.init_from_str( - // Fold, - // mk_form("parens_exp", Id.mk(), [pieces]), - // ({text: f_name}: FoldProj.t) - // |> FoldProj.sexp_of_t - // |> Sexplib.Sexp.to_string, - // ), - // ]; + [ + Projector.init_from_str( + Fold, + mk_form("parens_exp", Id.mk(), [pieces]), + ({text: f_name}: FoldProj.t) + |> FoldProj.sexp_of_t + |> Sexplib.Sexp.to_string, + ), + ]; } else { - pieces + pieces; }; /* We assume that parentheses have already been added as necessary, and diff --git a/src/haz3lcore/prog/Dynamics.re b/src/haz3lcore/prog/Dynamics.re index a3c6f2141a..f35a9900e0 100644 --- a/src/haz3lcore/prog/Dynamics.re +++ b/src/haz3lcore/prog/Dynamics.re @@ -1,9 +1,71 @@ open Util; module Probe = { + let instrument = + (m: Statics.Map.t, id: Id.t, probe_tag: TermBase.probe_tag) + : TermBase.probe_tag => + switch (probe_tag) { + | Paren => Paren + | Probe(_) => + Probe({ + refs: Statics.Map.refs_in(m, id), + stem: Statics.Map.enclosing_abstractions(m, id), + }) + }; + + let empty = TermBase.{refs: [], stem: []}; + + module Env = { + [@deriving (show({with_path: false}), sexp, yojson)] + type raw = + | Opaque + | Val(DHExp.t); + + [@deriving (show({with_path: false}), sexp, yojson)] + type entry = { + name: string, + id: Id.t, + raw, + }; + + [@deriving (show({with_path: false}), sexp, yojson)] + type t = list(entry); + + let to_raw = (d: DHExp.t) => + switch (d.term) { + | Fun(_) + | FixF(_) => Opaque + | _ => Val(d) + }; + + let mk_entry = (env, {name, id, _}: Binding.t) => + switch (ClosureEnvironment.lookup(env, name)) { + | Some(d) => + //TODO(andrew): does this substitute make sense? + let raw = + d + |> DHExp.strip_casts + |> Exp.substitute_closures(snd(env)) + |> to_raw; + {name, id, raw}; + | None => failwith("Probe: variable not found in environment") + }; + + let mk = (env: ClosureEnvironment.t, refs: Binding.s) => + List.map(mk_entry(env), refs); + }; + module Info = { [@deriving (show({with_path: false}), sexp, yojson)] - type t = DHExp.t; + type t = { + value: DHExp.t, + env: Env.t, + }; + + let mk = (value: DHExp.t, env: ClosureEnvironment.t, pr: TermBase.probe) => { + value, + env: Env.mk(env, pr.refs), + }; }; module Map = { diff --git a/src/haz3lcore/statics/Binding.re b/src/haz3lcore/statics/Binding.re new file mode 100644 index 0000000000..0c4ad5fdb4 --- /dev/null +++ b/src/haz3lcore/statics/Binding.re @@ -0,0 +1,26 @@ +open Util; + +/* The name and UUID of a variable binding site or reference. + * In principle the name is redundant, but it is convienient + * to have it */ +[@deriving (show({with_path: false}), sexp, yojson)] +type t = { + id: Id.t, + name: string, +}; + +/* A list of binding sites or references */ +[@deriving (show({with_path: false}), sexp, yojson)] +type s = list(t); + +/* A binding site as specified by its id and a list of + * the variables bound there*/ +[@deriving (show({with_path: false}), sexp, yojson)] +type site = { + id: Id.t, + bound: s, +}; + +/* An enclosing list of binding sites */ +[@deriving (show({with_path: false}), sexp, yojson)] +type stem = list(site); diff --git a/src/haz3lcore/statics/Ctx.re b/src/haz3lcore/statics/Ctx.re index f213a18cb7..f8a8886047 100644 --- a/src/haz3lcore/statics/Ctx.re +++ b/src/haz3lcore/statics/Ctx.re @@ -177,3 +177,10 @@ let filter_duplicates = (ctx: t): t => let shadows_typ = (ctx: t, name: string): bool => Form.is_base_typ(name) || lookup_tvar(ctx, name) != None; + +/* The binding (binding site id and name) of `name` in `ctx` */ +let binding_of = (ctx: t, name: Var.t): Binding.t => + switch (lookup_var(ctx, name)) { + | Some({id, _}) => {id, name} + | _ => {id: Id.invalid, name} + }; diff --git a/src/haz3lcore/statics/Elaborator.re b/src/haz3lcore/statics/Elaborator.re index 8aa39e0ce1..c19d7baf0a 100644 --- a/src/haz3lcore/statics/Elaborator.re +++ b/src/haz3lcore/statics/Elaborator.re @@ -231,14 +231,17 @@ let rec elaborate = (m: Statics.Map.t, uexp: UExp.t): (DHExp.t, Typ.t) => { |> rewrap |> cast_from(Typ.temp(Unknown(Internal))); | Cast(e, _, _) // We remove these casts because they should be re-inserted in the recursive call - | FailedCast(e, _, _) - | Parens(e, Probe) => - //TODO(andrew): Matthew: Am I casting/rewrapping correctly? + | FailedCast(e, _, _) => let (e', ty) = elaborate(m, e); - Parens(e' |> cast_from(ty), Probe) |> rewrap; + Parens(e' |> cast_from(ty), Paren) |> rewrap; | Parens(e, Paren) => let (e', ty) = elaborate(m, e); e' |> cast_from(ty); + | Parens(e, probe) => + //TODO(andrew): Matthew: Am I casting/rewrapping correctly? + let (e', ty) = elaborate(m, e); + let probe = Dynamics.Probe.instrument(m, Exp.rep_id(uexp), probe); + Parens(e' |> cast_from(ty), probe) |> rewrap; | Deferral(_) => uexp | Int(_) => uexp |> cast_from(Int |> Typ.temp) | Bool(_) => uexp |> cast_from(Bool |> Typ.temp) diff --git a/src/haz3lcore/statics/MakeTerm.re b/src/haz3lcore/statics/MakeTerm.re index d7f5b7ba18..21d1124ca7 100644 --- a/src/haz3lcore/statics/MakeTerm.re +++ b/src/haz3lcore/statics/MakeTerm.re @@ -201,7 +201,7 @@ and exp_term: unsorted => (UExp.term, list(Id.t)) = { | (["(", ")"], [Exp(body)]) => ret(Parens(body, Paren)) | (["@@", "@@"], [Exp(body)]) => // TODO(andrew): apologize for this - ret(Parens(body, Probe)) + ret(Parens(body, Probe(Dynamics.Probe.empty))) | (["[", "]"], [Exp(body)]) => switch (body) { | {ids, copied: false, term: Tuple(es)} => (ListLit(es), ids) diff --git a/src/haz3lcore/statics/Statics.re b/src/haz3lcore/statics/Statics.re index 15bc9f9e6d..7275b67e21 100644 --- a/src/haz3lcore/statics/Statics.re +++ b/src/haz3lcore/statics/Statics.re @@ -46,6 +46,39 @@ module Map = { info_map, [], ); + + /* If id is a closure-creating form, returns the id and the names + * and binding site ids for all variables bound by the closure */ + let abstraction_at = (map: t, id: Id.t): option(Binding.site) => + switch (lookup(id, map)) { + | Some(InfoExp({ctx, term: {term: Fun(pat, _, _, _), _}, _})) => + Some({id, bound: Term.Pat.bound_var_ids(ctx, pat)}) + | _ => None + }; + + /* Returns the ids and other binding site infor for all + * closure-creating forms enclosing the term with the provided id */ + let enclosing_abstractions = (map: t, id: Id.t): list(Binding.site) => + switch (lookup(id, map)) { + | Some(info) => + Info.ancestors_of(info) + |> List.fold_left( + (abstractions, ancestor_id) => + Option.to_list(abstraction_at(map, ancestor_id)) @ abstractions, + [], + ) + | None => [] + }; + + /* The ids of binding sites for for all references in term with `id` */ + let refs_in = (m: t, id: Id.t): Binding.s => + switch (lookup(id, m)) { + | Some(InfoExp({co_ctx, ctx, _})) => + co_ctx + |> VarMap.to_list + |> List.map(((n, _)) => Ctx.binding_of(ctx, n)) + | _ => [] + }; }; let map_m = (f, xs, m: Map.t) => @@ -275,7 +308,7 @@ and uexp_to_info_map = | Parens(e, Paren) => let (e, m) = go(~mode, e, m); add(~self=Just(e.ty), ~co_ctx=e.co_ctx, m); - | Parens(e, Probe) => + | Parens(e, Probe(_)) => /* Currently doing this as otherwise it clobbers the statics * for the contained expression as i'm just reusing the same id * in order to associate it through dynamics */ @@ -1019,11 +1052,3 @@ let get_pat_error_at = (info_map: Map.t, id: Id.t) => { } ); }; - -let collect_errors = (map: Map.t): list((Id.t, Info.error)) => - Id.Map.fold( - (id, info: Info.t, acc) => - Option.to_list(Info.error_of(info) |> Option.map(x => (id, x))) @ acc, - map, - [], - ); diff --git a/src/haz3lcore/statics/Term.re b/src/haz3lcore/statics/Term.re index 4c8bdfbb8a..e1a30a2690 100644 --- a/src/haz3lcore/statics/Term.re +++ b/src/haz3lcore/statics/Term.re @@ -291,6 +291,15 @@ module Pat = { | ListLit(dps) => List.flatten(List.map(bound_vars, dps)) | Ap(_, dp1) => bound_vars(dp1) }; + + let bound_var_ids = (ctx, pat): list(Binding.t) => + bound_vars(pat) + |> List.map(name => + switch (Ctx.lookup_var(ctx, name)) { + | Some({id, _}) => Binding.{id, name} + | None => {id: Id.invalid, name} + } + ); }; module Exp = { diff --git a/src/haz3lcore/statics/TermBase.re b/src/haz3lcore/statics/TermBase.re index 2499c5073d..4452856d5e 100644 --- a/src/haz3lcore/statics/TermBase.re +++ b/src/haz3lcore/statics/TermBase.re @@ -47,9 +47,15 @@ type deferral_position_t = */ [@deriving (show({with_path: false}), sexp, yojson)] -type paren_tag = +type probe = { + refs: Binding.s, + stem: Binding.stem, +}; + +[@deriving (show({with_path: false}), sexp, yojson)] +type probe_tag = | Paren - | Probe; + | Probe(probe); [@deriving (show({with_path: false}), sexp, yojson)] type any_t = @@ -94,7 +100,7 @@ and exp_term = | Test(exp_t) | Filter(stepper_filter_kind_t, exp_t) | Closure([@show.opaque] closure_environment_t, exp_t) - | Parens(exp_t, paren_tag) + | Parens(exp_t, probe_tag) | Cons(exp_t, exp_t) | ListConcat(exp_t, exp_t) | UnOp(Operators.op_un, exp_t) diff --git a/src/haz3lcore/tiles/Base.re b/src/haz3lcore/tiles/Base.re index 8c127d83ba..cfc79efa8f 100644 --- a/src/haz3lcore/tiles/Base.re +++ b/src/haz3lcore/tiles/Base.re @@ -7,6 +7,7 @@ open Util; type kind = | Fold | Info + | Probe | Checkbox | Slider | SliderF diff --git a/src/haz3lcore/zipper/EditorUtil.re b/src/haz3lcore/zipper/EditorUtil.re index 1642332a29..bcc3a1aa5d 100644 --- a/src/haz3lcore/zipper/EditorUtil.re +++ b/src/haz3lcore/zipper/EditorUtil.re @@ -47,20 +47,19 @@ let rec append_exp = (e1: Exp.t, e2: Exp.t): Exp.t => { }; }; -let wrap_filter = (act: FilterAction.action, term: UExp.t): UExp.t => - Exp.{ - term: - Filter( - Filter({ - act: FilterAction.(act, One), - pat: { - term: Constructor("$e", Unknown(Internal) |> Typ.fresh), - copied: false, - ids: [Id.mk()], - }, - }), - term, - ), - copied: false, - ids: [Id.mk()], - }; +let wrap_filter = (act: FilterAction.action, term: UExp.t): UExp.t => { + term: + Filter( + Filter({ + act: FilterAction.(act, One), + pat: { + term: Constructor("$e", Unknown(Internal) |> Typ.fresh), + copied: false, + ids: [Id.mk()], + }, + }), + term, + ), + copied: false, + ids: [Id.mk()], +}; diff --git a/src/haz3lcore/zipper/Projector.re b/src/haz3lcore/zipper/Projector.re index e904e9e890..4ee7ab9f71 100644 --- a/src/haz3lcore/zipper/Projector.re +++ b/src/haz3lcore/zipper/Projector.re @@ -8,6 +8,7 @@ let to_module = (kind: Base.kind): (module Cooked) => switch (kind) { | Fold => (module Cook(FoldProj.M)) | Info => (module Cook(InfoProj.M)) + | Probe => (module Cook(ProbeProj.M)) | Slider => (module Cook(SliderProj.M)) | SliderF => (module Cook(SliderFProj.M)) | Checkbox => (module Cook(CheckboxProj.M)) diff --git a/src/haz3lcore/zipper/ProjectorBase.re b/src/haz3lcore/zipper/ProjectorBase.re index 0adc255776..cc60341f0a 100644 --- a/src/haz3lcore/zipper/ProjectorBase.re +++ b/src/haz3lcore/zipper/ProjectorBase.re @@ -36,6 +36,13 @@ type info = { dynamics: option(Dynamics.Info.t), }; +//TODO(andrew): rm or doc +[@deriving (show({with_path: false}), sexp, yojson)] +type bonus_pack = { + view: (Sort.t, Base.segment) => Node.t, + exp_to_seg: Exp.t => Base.segment, +}; + /* To add a new projector: * 1. Create a new module implementing Projector (e.g. FoldCore) * 2. Add an entry for it in Base.projector_kind @@ -84,7 +91,8 @@ module type Projector = { model, ~info: info, ~local: action => Ui_effect.t(unit), - ~parent: external_action => Ui_effect.t(unit) + ~parent: external_action => Ui_effect.t(unit), + ~bonus_pack: bonus_pack ) => Node.t; /* How much space should be left in the code view for @@ -124,12 +132,13 @@ module Cook = (C: Projector) : Cooked => { let init = C.init |> serialize_m; let can_project = C.can_project; let can_focus = C.can_focus; - let view = (m, ~info, ~local, ~parent) => + let view = (m, ~info, ~local, ~parent, ~bonus_pack) => C.view( deserialize_m(m), ~info, ~local=a => local(serialize_a(a)), ~parent, + ~bonus_pack, ); let placeholder = m => m |> Sexplib.Sexp.of_string |> C.model_of_sexp |> C.placeholder; diff --git a/src/haz3lcore/zipper/projectors/CheckboxProj.re b/src/haz3lcore/zipper/projectors/CheckboxProj.re index b1d734a8cf..28160358e9 100644 --- a/src/haz3lcore/zipper/projectors/CheckboxProj.re +++ b/src/haz3lcore/zipper/projectors/CheckboxProj.re @@ -25,7 +25,13 @@ let put = (bool: bool): Piece.t => bool |> string_of_bool |> mk_mono(Exp); let toggle = (piece: Piece.t) => put(!get(piece)); let view = - (_, ~info, ~local as _, ~parent: external_action => Ui_effect.t(unit)) => + ( + _, + ~info, + ~local as _, + ~parent: external_action => Ui_effect.t(unit), + ~bonus_pack as _, + ) => Node.input( ~attrs= [ diff --git a/src/haz3lcore/zipper/projectors/FoldProj.re b/src/haz3lcore/zipper/projectors/FoldProj.re index 83d5d4e639..fb1e4dae24 100644 --- a/src/haz3lcore/zipper/projectors/FoldProj.re +++ b/src/haz3lcore/zipper/projectors/FoldProj.re @@ -9,45 +9,6 @@ type t = { text: string, }; -/* Proof of concept value exposure. This isn't getting set immediately - after folding for some reason */ -let vals = (di: option(Dynamics.Info.t)) => { - switch (di) { - | Some(di) => - List.map( - (d: DHExp.t) => - d |> DHExp.strip_casts |> (d => d.term) |> TermBase.Exp.show_term, - di.vals, - ) - |> String.concat(", ") - | _ => "Nein" - }; -}; - -let vals = (di: option(Dynamics.Info.t)) => { - switch (di) { - | Some(di) => - List.map( - (d: DHExp.t) => - d - |> DHExp.strip_casts - |> ExpToSegment.exp_to_segment( - ~settings={ - inline: false, - fold_case_clauses: false, - fold_fn_bodies: false, - hide_fixpoints: false, - fold_cast_types: false, - }, - ) - |> Printer.of_segment(~holes=None), - di.vals, - ) - |> String.concat(", ") - | _ => "Nein" - }; -}; - module M: Projector = { [@deriving (show({with_path: false}), sexp, yojson)] type model = t; @@ -59,12 +20,9 @@ module M: Projector = { let placeholder = (m, _) => Inline(m.text == "⋱" ? 2 : m.text |> String.length); let update = (m, _) => m; - let view = (m: model, ~info, ~local as _, ~parent) => + let view = (m: model, ~info as _, ~local as _, ~parent, ~bonus_pack as _) => div( - ~attrs=[ - Attr.on_double_click(_ => parent(Remove)), - Attr.title(vals(info.dynamics)), - ], + ~attrs=[Attr.on_double_click(_ => parent(Remove))], [text(m.text)], ); let focus = _ => (); diff --git a/src/haz3lcore/zipper/projectors/InfoProj.re b/src/haz3lcore/zipper/projectors/InfoProj.re index 315a4ef983..55983b0446 100644 --- a/src/haz3lcore/zipper/projectors/InfoProj.re +++ b/src/haz3lcore/zipper/projectors/InfoProj.re @@ -76,7 +76,7 @@ module M: Projector = { | (ToggleDisplay, Self) => Expected }; - let view = (model, ~info, ~local, ~parent as _) => + let view = (model, ~info, ~local, ~parent as _, ~bonus_pack as _) => div( ~attrs=[ Attr.classes(["info", "code"]), diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re new file mode 100644 index 0000000000..ca9c86c21c --- /dev/null +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -0,0 +1,113 @@ +open Util; +open ProjectorBase; +open Virtual_dom.Vdom; +open Node; + +[@deriving (show({with_path: false}), sexp, yojson)] +type t = { + [@default "⋱"] + text: string, +}; + +/* Proof of concept value exposure. This isn't getting set immediately + after folding for some reason */ +let _vals = (di: option(Dynamics.Info.t)) => { + switch (di) { + | Some(di) => + List.map( + (pi: Dynamics.Probe.Info.t) => + pi.value + |> DHExp.strip_casts + |> (d => d.term) + |> TermBase.Exp.show_term, + di.vals, + ) + |> String.concat(", ") + | _ => "Nein" + }; +}; + +let vals = (di: option(Dynamics.Info.t), exp_to_seg) => { + switch (di) { + | Some(di) => + List.map( + (pi: Dynamics.Probe.Info.t) => + pi.value + |> DHExp.strip_casts + |> exp_to_seg + |> Printer.of_segment(~holes=None), + di.vals, + ) + |> String.concat(", ") + | _ => "Nein" + }; +}; + +let vals_div = (di: option(Dynamics.Info.t), bonus_pack: bonus_pack) => { + switch (di) { + | Some(di) => + Node.div( + ~attrs=[Attr.classes(["live-offside"])], + List.map( + (pi: Dynamics.Probe.Info.t) => { + print_endline("pi.env:" ++ Dynamics.Probe.Env.show(pi.env)); + pi.value + |> DHExp.strip_casts + |> bonus_pack.exp_to_seg + |> bonus_pack.view(Exp); + }, + di.vals, + ), + ) + | _ => + Node.div(~attrs=[Attr.classes(["live-offside"])], [Node.text("?")]) + }; +}; + +let _ = Zipper.base_point; +//let _ = Editor.show; + +/* Plan for extended dynamics: + in principle we could track: + - prev 'stack frame': id of prev expr that began execution? + - env (or just the part of the env that's relevant to the current expr) + aka the co-ctx + + */ + +/* Plan for selectable (editable as well maybe): + + selectable: + create own mini version of editor, use it as model + update fn updates model after applying mini editor action + hopefully can use same actions + + + */ + +let code_str = (info: info) => + [info.syntax] |> Printer.of_segment(~holes=None); + +let view = (_m: t, ~info, ~local as _, ~parent, ~bonus_pack: bonus_pack) => + div( + ~attrs=[ + Attr.on_double_click(_ => parent(Remove)), + Attr.title(vals(info.dynamics, bonus_pack.exp_to_seg)), + ], + [vals_div(info.dynamics, bonus_pack), text("🔍 " ++ code_str(info))], + ); + +module M: Projector = { + [@deriving (show({with_path: false}), sexp, yojson)] + type model = t; + [@deriving (show({with_path: false}), sexp, yojson)] + type action = unit; + let init = {text: "🔍"}; + let can_project = _ => true; + let can_focus = false; + let placeholder = (_m, info) => + Inline(3 + String.length(code_str(info))); + let update = (m, _) => m; + let view = view; + let focus = _ => (); +}; diff --git a/src/haz3lcore/zipper/projectors/SliderFProj.re b/src/haz3lcore/zipper/projectors/SliderFProj.re index 4ad36621ad..c436662b6f 100644 --- a/src/haz3lcore/zipper/projectors/SliderFProj.re +++ b/src/haz3lcore/zipper/projectors/SliderFProj.re @@ -27,7 +27,13 @@ module M: Projector = { let placeholder = (_, _) => Inline(10); let update = (model, _) => model; let view = - (_, ~info, ~local as _, ~parent: external_action => Ui_effect.t(unit)) => + ( + _, + ~info, + ~local as _, + ~parent: external_action => Ui_effect.t(unit), + ~bonus_pack as _, + ) => Util.Web.range( ~attrs=[Attr.on_input((_, v) => parent(SetSyntax(put(v))))], get(info.syntax) |> Printf.sprintf("%.2f"), diff --git a/src/haz3lcore/zipper/projectors/SliderProj.re b/src/haz3lcore/zipper/projectors/SliderProj.re index 2a73c6d012..0fc04a9ed6 100644 --- a/src/haz3lcore/zipper/projectors/SliderProj.re +++ b/src/haz3lcore/zipper/projectors/SliderProj.re @@ -24,7 +24,13 @@ module M: Projector = { let placeholder = (_, _) => Inline(10); let update = (model, _) => model; let view = - (_, ~info, ~local as _, ~parent: external_action => Ui_effect.t(unit)) => + ( + _, + ~info, + ~local as _, + ~parent: external_action => Ui_effect.t(unit), + ~bonus_pack as _, + ) => Util.Web.range( ~attrs=[Attr.on_input((_, v) => parent(SetSyntax(put(v))))], get(info.syntax), diff --git a/src/haz3lcore/zipper/projectors/TextAreaProj.re b/src/haz3lcore/zipper/projectors/TextAreaProj.re index d6488afd86..d255ff0ba1 100644 --- a/src/haz3lcore/zipper/projectors/TextAreaProj.re +++ b/src/haz3lcore/zipper/projectors/TextAreaProj.re @@ -77,7 +77,7 @@ let textarea = [], ); -let view = (_, ~info, ~local as _, ~parent) => { +let view = (_, ~info, ~local as _, ~parent, ~bonus_pack as _) => { let text = info.syntax |> get |> Form.strip_quotes; Node.div( ~attrs=[Attr.classes(["wrapper"])], diff --git a/src/haz3lweb/app/common/ProjectorView.re b/src/haz3lweb/app/common/ProjectorView.re index b8a7a580c4..42db5560d0 100644 --- a/src/haz3lweb/app/common/ProjectorView.re +++ b/src/haz3lweb/app/common/ProjectorView.re @@ -16,6 +16,7 @@ let name = (p: kind): string => switch (p) { | Fold => "fold" | Info => "type" + | Probe => "probe" | Checkbox => "check" | Slider => "slider" | SliderF => "sliderf" @@ -29,6 +30,7 @@ let of_name = (p: string): kind => switch (p) { | "fold" => Fold | "type" => Info + | "probe" => Probe | "check" => Checkbox | "slider" => Slider | "sliderf" => SliderF @@ -120,10 +122,26 @@ let setup_view = ~cached_syntax: Editor.CachedSyntax.t, ~dynamics, ~inject: Action.t => Ui_effect.t(unit), - ~font_metrics, + ~globals: Globals.t, ~indication: option(Direction.t), ) : option(Node.t) => { + let bonus_pack = { + view: (sort, seg) => + CodeViewable.view_segment(~globals, ~sort, ~token_of_proj=_ => "", seg), + exp_to_seg: exp => + exp + |> DHExp.strip_casts + |> ExpToSegment.exp_to_segment( + ~settings={ + inline: false, + fold_case_clauses: false, + fold_fn_bodies: false, + hide_fixpoints: false, + fold_cast_types: false, + }, + ), + }; let* p = Id.Map.find_opt(id, cached_syntax.projectors); let* syntax = Some(p.syntax); let statics = Statics.Map.lookup(id, cached_statics.info_map); @@ -135,13 +153,13 @@ let setup_view = let local = a => inject(Project(SetModel(id, P.update(p.model, a)))); view_wrapper( ~inject, - ~font_metrics, + ~font_metrics=globals.font_metrics, ~measurement, ~indication, ~info, ~selected=List.mem(id, cached_syntax.selection_ids), p, - P.view(p.model, ~info, ~local, ~parent), + P.view(p.model, ~info, ~local, ~parent, ~bonus_pack), ); }; @@ -156,11 +174,11 @@ let indication = (z, id) => let all = ( z, + ~globals: Globals.t, ~cached_statics: CachedStatics.t, ~cached_syntax: Editor.CachedSyntax.t, ~dynamics: Dynamics.Map.t, ~inject, - ~font_metrics, ) => { // print_endline( // "cardinal: " @@ -177,7 +195,7 @@ let all = ~cached_syntax, ~dynamics, ~inject, - ~font_metrics, + ~globals, ~indication, ); }, diff --git a/src/haz3lweb/app/editors/code/CodeEditable.re b/src/haz3lweb/app/editors/code/CodeEditable.re index 37863f9beb..9fba95753b 100644 --- a/src/haz3lweb/app/editors/code/CodeEditable.re +++ b/src/haz3lweb/app/editors/code/CodeEditable.re @@ -260,10 +260,10 @@ module View = { let projectors = ProjectorView.all( model.editor.state.zipper, + ~globals, ~cached_statics=model.statics, ~cached_syntax=model.editor.syntax, ~inject=x => inject(Perform(x)), - ~font_metrics=globals.font_metrics, ~dynamics, ); let overlays = edit_decos @ overlays @ [projectors]; diff --git a/src/haz3lweb/app/inspector/ProjectorPanel.re b/src/haz3lweb/app/inspector/ProjectorPanel.re index 6a19da402c..458c1bbe6c 100644 --- a/src/haz3lweb/app/inspector/ProjectorPanel.re +++ b/src/haz3lweb/app/inspector/ProjectorPanel.re @@ -36,7 +36,7 @@ let applicable_projectors: option(Info.t) => list(Base.kind) = @ ( switch (ci) { | InfoExp(_) - | InfoPat(_) => [(Info: Base.kind)] + | InfoPat(_) => [Probe, Info] | _ => [] } ); diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index d70e70a422..46d30149cc 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -143,3 +143,34 @@ ninja-keys { --ninja-modal-shadow: 0px 10px 20px var(--menu-shadow); --ninja-overflow-background: none; } + +.live-offside { + display: flex; + flex-direction: row; + position: absolute; + left: 10em; + background-color: #c7e6c6; + border-radius: 0.3em; + border-top: 1px solid white; + filter: drop-shadow(0.7px 0.7px 0px green); + opacity: 60%; +} + +.projector.probe.indicated .live-offside { + background-color: #a4d7a2; + opacity: 100% +} + +.projector.probe > svg { + fill: #c7e6c6; + filter: drop-shadow(0.7px 0.7px 0px green); +} + +.projector.probe.indicated > svg { + fill: #a4d7a2; +} + +.projector.probe { + font-family: var(--code-font); + color: var(--STONE); +} \ No newline at end of file From 4ce84f10852a7df577795358be539ea2917bbb32 Mon Sep 17 00:00:00 2001 From: disconcision Date: Wed, 4 Dec 2024 15:23:45 -0500 Subject: [PATCH 07/23] surface projector probe reference environment display on hover --- src/haz3lcore/dynamics/Transition.re | 12 ++-- src/haz3lcore/statics/TermBase.re | 10 ++- src/haz3lcore/zipper/ProjectorBase.re | 1 + src/haz3lcore/zipper/projectors/ProbeProj.re | 74 ++++++++++++++++++-- src/haz3lweb/app/common/ProjectorView.re | 46 +++++++++--- src/haz3lweb/www/style.css | 31 -------- src/haz3lweb/www/style/editor.css | 5 +- src/haz3lweb/www/style/projectors.css | 60 ++++++++++++++++ 8 files changed, 183 insertions(+), 56 deletions(-) diff --git a/src/haz3lcore/dynamics/Transition.re b/src/haz3lcore/dynamics/Transition.re index e18fbf2b46..f11cb428df 100644 --- a/src/haz3lcore/dynamics/Transition.re +++ b/src/haz3lcore/dynamics/Transition.re @@ -773,7 +773,7 @@ module Transition = (EV: EV_MODE) => { let. _ = otherwise(env, d); Indet; | Parens(d'', Probe(pr)) => - print_endline("Probe:" ++ TermBase.show_probe(pr)); + // print_endline("Probe:" ++ TermBase.show_probe(pr)); //TODO(andrew): cleanup let. _ = otherwise(env, ((d, _)) => Parens(d, Probe(pr)) |> rewrap) and. (d', _is_value) = @@ -782,11 +782,15 @@ module Transition = (EV: EV_MODE) => { d => Parens(d, Probe(pr)) |> wrap_ctx, d'', ); - //TODO(andrew): should this be inside update closure? - let pi = Dynamics.Probe.Info.mk(d', env, pr); + Step({ expr: d', - state_update: () => update_probe(state, DHExp.rep_id(d), pi), + state_update: () => { + //TODO(andrew): should I be putting the env inside update closure? + // are there perf / mem implications? + let pi = Dynamics.Probe.Info.mk(d', env, pr); + update_probe(state, DHExp.rep_id(d), pi); + }, kind: RemoveParens, is_value: true, }); diff --git a/src/haz3lcore/statics/TermBase.re b/src/haz3lcore/statics/TermBase.re index 4452856d5e..e35c3382bb 100644 --- a/src/haz3lcore/statics/TermBase.re +++ b/src/haz3lcore/statics/TermBase.re @@ -158,7 +158,7 @@ and rul_term = | Rules(exp_t, list((pat_t, exp_t))) and rul_t = IdTagged.t(rul_term) and environment_t = VarBstMap.Ordered.t_(exp_t) -and closure_environment_t = (Id.t, environment_t) +and closure_environment_t = (Id.t, environment_t) //TODO(andrew): option(clj_env), option(Id) //callsite and stepper_filter_kind_t = | Filter(filter) | Residue(int, FilterAction.t) @@ -365,9 +365,13 @@ and Exp: { let rec fast_equal = (e1, e2) => switch (e1 |> IdTagged.term_of, e2 |> IdTagged.term_of) { | (DynamicErrorHole(x, _), _) - | (Parens(x, _), _) => fast_equal(x, e2) + | (Parens(x, Paren), _) => fast_equal(x, e2) | (_, DynamicErrorHole(x, _)) - | (_, Parens(x, _)) => fast_equal(e1, x) + | (_, Parens(x, Paren)) => fast_equal(e1, x) + //TODO(andrew): clarify below cases (basically syntactic equality) + // this is necessary to make EvalResult.calculate go after adding a projector + | (Parens(x1, Probe(_)), Parens(x2, Probe(_))) => fast_equal(x1, x2) + | (Parens(_, Probe(_)), _) => false | (EmptyHole, EmptyHole) => true | (Undefined, Undefined) => true | (Invalid(s1), Invalid(s2)) => s1 == s2 diff --git a/src/haz3lcore/zipper/ProjectorBase.re b/src/haz3lcore/zipper/ProjectorBase.re index cc60341f0a..702a156256 100644 --- a/src/haz3lcore/zipper/ProjectorBase.re +++ b/src/haz3lcore/zipper/ProjectorBase.re @@ -41,6 +41,7 @@ type info = { type bonus_pack = { view: (Sort.t, Base.segment) => Node.t, exp_to_seg: Exp.t => Base.segment, + offside_offset: float /* to position something to rhs of code */ }; /* To add a new projector: diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index ca9c86c21c..ffc6f4dd21 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -43,21 +43,83 @@ let vals = (di: option(Dynamics.Info.t), exp_to_seg) => { }; }; +let env_val = (en: Dynamics.Probe.Env.entry, bonus_pack: bonus_pack) => { + Node.div( + ~attrs=[Attr.classes(["live-env-entry"])], + [ + Node.text(en.name ++ " = "), + switch (en.raw) { + | Opaque => Node.text("Opaque") + | Val(d) => d |> bonus_pack.exp_to_seg |> bonus_pack.view(Exp) + }, + ], + ); +}; + +let rm_opaques = + List.filter_map((en: Dynamics.Probe.Env.entry) => + switch (en.raw) { + | Opaque => None + | Val(_) => Some(en) + } + ); + +let rm_fst = (xs: list('a)) => + switch (xs) { + | [] => [] + | [_, ...rest] => rest + }; + +let env_div2 = (pi: Dynamics.Probe.Info.t, bonus_pack: bonus_pack) => { + Node.div( + ~attrs=[Attr.classes(["live-env"])], + pi.env |> rm_fst |> rm_opaques |> List.map(en => env_val(en, bonus_pack)), + ); +}; + +let env_div = (di: Dynamics.Info.t, bonus_pack: bonus_pack) => { + Node.div( + ~attrs=[Attr.classes(["live-env"])], + List.concat_map( + (pi: Dynamics.Probe.Info.t) => { + pi.env |> List.map(en => env_val(en, bonus_pack)) + }, + di.vals, + ), + ); +}; + let vals_div = (di: option(Dynamics.Info.t), bonus_pack: bonus_pack) => { switch (di) { | Some(di) => Node.div( - ~attrs=[Attr.classes(["live-offside"])], + ~attrs=[ + Attr.classes(["live-offside"]), + Attr.create( + "style", + Printf.sprintf( + "position: absolute; left: %fpx;", + bonus_pack.offside_offset, + ), + ), + ], List.map( (pi: Dynamics.Probe.Info.t) => { - print_endline("pi.env:" ++ Dynamics.Probe.Env.show(pi.env)); - pi.value - |> DHExp.strip_casts - |> bonus_pack.exp_to_seg - |> bonus_pack.view(Exp); + // print_endline("pi.env:" ++ Dynamics.Probe.Env.show(pi.env)); + div( + ~attrs=[Attr.classes(["wrap"])], + [ + pi.value + |> DHExp.strip_casts + |> bonus_pack.exp_to_seg + |> bonus_pack.view(Exp), + env_div2(pi, bonus_pack), + ], + ) }, di.vals, ), + //@ [env_div(di, bonus_pack)], ) | _ => Node.div(~attrs=[Attr.classes(["live-offside"])], [Node.text("?")]) diff --git a/src/haz3lweb/app/common/ProjectorView.re b/src/haz3lweb/app/common/ProjectorView.re index 42db5560d0..32d7c14574 100644 --- a/src/haz3lweb/app/common/ProjectorView.re +++ b/src/haz3lweb/app/common/ProjectorView.re @@ -111,6 +111,34 @@ let handle = (id, action: external_action): Action.project => | SetSyntax(f) => SetSyntax(id, f) }; +let code_width = (measured: Measured.t) => + IntMap.fold( + (_, {max_col, _}: Measured.Rows.shape, acc) => max(max_col, acc), + measured.rows, + 0, + ); + +let projector_start_row_width = + (measurement: Measured.measurement, measured: Measured.t) => + switch (IntMap.find_opt(measurement.origin.row, measured.rows)) { + | None => 0 + | Some(row) => row.max_col + }; + +let offside = + ( + font_metrics: FontMetrics.t, + measurement: Measured.measurement, + measured: Measured.t, + ) => { + font_metrics.col_width + *. float_of_int( + projector_start_row_width(measurement, measured) + + 4 + - measurement.origin.col, + ); +}; + /* Extracts projector-instance-specific metadata necessary to * render the view, instantiates appropriate action handlers, * renders the view, and then wraps it so as to position it @@ -126,6 +154,12 @@ let setup_view = ~indication: option(Direction.t), ) : option(Node.t) => { + let* p = Id.Map.find_opt(id, cached_syntax.projectors); + let* syntax = Some(p.syntax); + let statics = Statics.Map.lookup(id, cached_statics.info_map); + let dynamics = Dynamics.Map.lookup(id, dynamics); + let info = {id, statics, dynamics, syntax}; + let+ measurement = Measured.find_pr_opt(p, cached_syntax.measured); let bonus_pack = { view: (sort, seg) => CodeViewable.view_segment(~globals, ~sort, ~token_of_proj=_ => "", seg), @@ -141,13 +175,9 @@ let setup_view = fold_cast_types: false, }, ), + offside_offset: + offside(globals.font_metrics, measurement, cached_syntax.measured), }; - let* p = Id.Map.find_opt(id, cached_syntax.projectors); - let* syntax = Some(p.syntax); - let statics = Statics.Map.lookup(id, cached_statics.info_map); - let dynamics = Dynamics.Map.lookup(id, dynamics); - let info = {id, statics, dynamics, syntax}; - let+ measurement = Measured.find_pr_opt(p, cached_syntax.measured); let (module P) = to_module(p.kind); let parent = a => inject(Project(handle(id, a))); let local = a => inject(Project(SetModel(id, P.update(p.model, a)))); @@ -180,10 +210,6 @@ let all = ~dynamics: Dynamics.Map.t, ~inject, ) => { - // print_endline( - // "cardinal: " - // ++ (meta.projected.projectors |> Id.Map.cardinal |> string_of_int), - // ); div_c( "projectors", List.filter_map( diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index 46d30149cc..d70e70a422 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -143,34 +143,3 @@ ninja-keys { --ninja-modal-shadow: 0px 10px 20px var(--menu-shadow); --ninja-overflow-background: none; } - -.live-offside { - display: flex; - flex-direction: row; - position: absolute; - left: 10em; - background-color: #c7e6c6; - border-radius: 0.3em; - border-top: 1px solid white; - filter: drop-shadow(0.7px 0.7px 0px green); - opacity: 60%; -} - -.projector.probe.indicated .live-offside { - background-color: #a4d7a2; - opacity: 100% -} - -.projector.probe > svg { - fill: #c7e6c6; - filter: drop-shadow(0.7px 0.7px 0px green); -} - -.projector.probe.indicated > svg { - fill: #a4d7a2; -} - -.projector.probe { - font-family: var(--code-font); - color: var(--STONE); -} \ No newline at end of file diff --git a/src/haz3lweb/www/style/editor.css b/src/haz3lweb/www/style/editor.css index 13ac029688..a01b275992 100644 --- a/src/haz3lweb/www/style/editor.css +++ b/src/haz3lweb/www/style/editor.css @@ -20,9 +20,10 @@ /* This is a hack to compensating for default linebreak handling * preventing a trailing linebreak in code editors from leaving a -* trailing blank line, as per https://stackoverflow.com/questions/43492826/ */ +* trailing blank line, as per https://stackoverflow.com/questions/43492826/. +* The mysterious chracter below is a zero-width space */ .code-text:after { - content: " "; + content: "​"; } /* TOKEN COLORS */ diff --git a/src/haz3lweb/www/style/projectors.css b/src/haz3lweb/www/style/projectors.css index 8d187f01a0..b70e4fc950 100644 --- a/src/haz3lweb/www/style/projectors.css +++ b/src/haz3lweb/www/style/projectors.css @@ -168,3 +168,63 @@ color: var(--BLACK); background-color: var(--shard-selected); } + +/* Probe */ + +.live-offside { + display: flex; + flex-direction: row; + position: absolute; + left: 10em; + gap: 0.15em; +} + +.live-offside .code { + background-color: #c7e6c6; + border-radius: 0.3em; + border-top: 1px solid white; + filter: drop-shadow(0.7px 0.7px 0px green); + opacity: 60%; +} + +.projector.probe.indicated .live-offside .code { + background-color: #a4d7a2; + opacity: 100% +} + +.projector.probe > svg { + fill: #c7e6c6; + filter: drop-shadow(0.7px 0.7px 0px green); +} + +.projector.probe.indicated > svg { + fill: #a4d7a2; +} + +.projector.probe { + font-family: var(--code-font); + color: var(--STONE); +} + +.projector.probe .live-env { + background-color: #e7e1cf; + border-radius: 0 0.2em 0.2em 0.2em; + padding: 0.2em; + display: none; + position: absolute; + top: 1.6em; + z-index: 5000; +} + +.projector.probe .live-env .live-env-entry { + display: flex; + flex-direction: row; + gap: 0.3em; +} + +.projector.probe .wrap:hover .live-env { + display: block; +} +.projector.probe .wrap:hover > .code { + border-radius: 0.2em 0.2em 0.2em 0; +} \ No newline at end of file From 323338f96e33354030b98a09687587b1a8a39237 Mon Sep 17 00:00:00 2001 From: disconcision Date: Fri, 6 Dec 2024 17:08:03 -0500 Subject: [PATCH 08/23] typo --- src/haz3lcore/zipper/projectors/ProbeProj.re | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index ffc6f4dd21..c99e9fcb92 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -47,7 +47,7 @@ let env_val = (en: Dynamics.Probe.Env.entry, bonus_pack: bonus_pack) => { Node.div( ~attrs=[Attr.classes(["live-env-entry"])], [ - Node.text(en.name ++ " = "), + Node.text(en.name ++ "="), switch (en.raw) { | Opaque => Node.text("Opaque") | Val(d) => d |> bonus_pack.exp_to_seg |> bonus_pack.view(Exp) From 48ee429fd4d5ad85d20936800bb76995ac3350c6 Mon Sep 17 00:00:00 2001 From: disconcision Date: Sat, 7 Dec 2024 02:14:51 -0500 Subject: [PATCH 09/23] basic value abbreviator --- src/haz3lcore/pretty/Abbreviate.re | 164 +++++++++++++++++++++++++++ src/haz3lcore/pretty/ExpToSegment.re | 5 +- 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/haz3lcore/pretty/Abbreviate.re diff --git a/src/haz3lcore/pretty/Abbreviate.re b/src/haz3lcore/pretty/Abbreviate.re new file mode 100644 index 0000000000..3aa0f910dc --- /dev/null +++ b/src/haz3lcore/pretty/Abbreviate.re @@ -0,0 +1,164 @@ +open Util; +open PrettySegment; +open Base; + +let abbreviate_str = (min_len: int, s: string): string => { + let len = String.length(s); + let ellipsis = "..."; + if (len <= min_len) { + s; + } else { + String.sub(s, 0, min_len - String.length(ellipsis)) ++ ellipsis; + }; +}; + +let rec abbreviate_exp = (~available=12, exp: Exp.t): Exp.t => { + let rewrap = (term: Exp.term): Exp.t => { + {...exp, term}; + }; + let abbreviate_str = abbreviate_str(available); + let comp_elipses = "..."; + let ellipses_term = () => IdTagged.fresh(Invalid(comp_elipses): Exp.term); + let indet_term: Exp.term = Invalid(""); + let go = (~available) => + abbreviate_exp(~available=available - String.length(comp_elipses)); + let term: Exp.term = + switch (exp |> Exp.term_of) { + | Fun(_p, _e, _, Some(s)) => Invalid("<" ++ s ++ ">") + | Fun(_p, _e, _, None) => Invalid("") + | BuiltinFun(_f) => Invalid("") + | Tuple([_]) => failwith("Singleton Tuples are not allowed") + //TODO(andrew): show exp below? + | DynamicErrorHole(_exp, err) => + Invalid("<" ++ InvalidOperationError.show(err) ++ ">") + // Atomic string cases + | Invalid(x) => Invalid(abbreviate_str(x)) + | String(s) => String(abbreviate_str(s)) + | Var(v) => Var(abbreviate_str(v)) + | Constructor(c, t) => Constructor(abbreviate_str(c), t) + + // Atomic Fixed cases + //TODO: length check these cases: + | EmptyHole => EmptyHole + | ListLit([]) => ListLit([]) + | Tuple([]) => Tuple([]) + | Bool(b) => Bool(b) + | Undefined => Undefined + | Int(n) => Int(n) + | Float(f) => Float(f) + + // composite literal cases + | ListLit([x, ..._xs]) => + //TODO: return used length from call, use that to make incorporate next elems + ListLit([go(~available, x), ellipses_term()]) + + | Tuple([x, ..._xs]) => + //TODO: return used length from call, use that to make incorporate next elems + Tuple([go(~available, x), ellipses_term()]) + | Ap(Forward, {term: Constructor(_), _} as konst, _e2) => + //TODO: return used length from call, use that to make incorporate next elems + let available = available - 5; //chars for ap delimiters, ellipses + Ap(Forward, abbreviate_exp(~available, konst), ellipses_term()); + | Cons(e1, _e2) => + //TODO: return used length from call, use that to make incorporate next elems + let available = available - 2; //chars for cons op + Cons(abbreviate_exp(~available, e1), ellipses_term()); + + | Parens(e, pt) => + let available = available - 2; //chars for parens + Parens(abbreviate_exp(~available, e), pt); + + //TODO(andrew) + | Filter(_) => failwith("TODO(andrew): Filter") + | Closure(_) => failwith("TODO(andrew): Closure") + | MultiHole(_es) => failwith("TODO(andrew)") + | TypFun(_tp, _e, _) => failwith("TODO(andrew)") + | FailedCast(_e, _, _t) => failwith("TODO(andrew)") + | Cast(_e, _, _t) => failwith("TODO(andrew)") + + //non-value + | Ap(Forward, _e1, _e2) => indet_term + | Ap(Reverse, _e1, _e2) => indet_term + | Deferral(_d) => indet_term + | BinOp(_op, _l, _r) => indet_term + | Let(_p, _e1, _e2) => indet_term + | FixF(_p, _e, _) => indet_term + | TyAlias(_tp, _t, _e) => indet_term + | TypAp(_e, _t) => indet_term + | DeferredAp(_e, _es) => indet_term + | If(_e1, _e2, _e3) => indet_term + | Seq(_e1, _e2) => indet_term + | Test(_e) => indet_term + | ListConcat(_e1, _e2) => indet_term + | UnOp(Bool(Not), _e) => indet_term + | UnOp(Int(Minus), _e) => indet_term + | UnOp(Meta(Unquote), _e) => indet_term + | Match(_e, _rs) => indet_term + }; + rewrap(term); +} +and abbreviate_pat = (pat: Pat.t): Pat.t => { + switch (pat |> Pat.term_of) { + | Invalid(_t) => failwith("abbreviate_pat") + | EmptyHole => failwith("abbreviate_pat") + | Wild => failwith("abbreviate_pat") + | Var(_v) => failwith("abbreviate_pat") + | Int(_n) => failwith("abbreviate_pat") + | Float(_f) => failwith("abbreviate_pat") + | Bool(_b) => failwith("abbreviate_pat") + | String(_s) => failwith("abbreviate_pat") + | Constructor(_c, _) => failwith("abbreviate_pat") + | ListLit([]) => failwith("abbreviate_pat") + | ListLit([_x, ..._xs]) => failwith("abbreviate_pat") + | Cons(_p1, _p2) => failwith("abbreviate_pat") + | Tuple([]) => failwith("abbreviate_pat") + | Tuple([_]) => failwith("Singleton Tuples are not allowed") + | Tuple([_x, ..._xs]) => failwith("abbreviate_pat") + | Parens(_p) => failwith("abbreviate_pat") + | MultiHole(_es) => failwith("abbreviate_pat") + | Ap(_p1, _p2) => failwith("abbreviate_pat") + | Cast(_p, _t, _) => failwith("abbreviate_pat") + }; +} +and abbreviate_typ = (typ: Typ.t): Typ.t => { + switch (typ |> Typ.term_of) { + | Unknown(Hole(Invalid(_s))) => failwith("abbreviate_typ") + | Unknown(_) => failwith("abbreviate_typ") + | Var(_) => failwith("abbreviate_typ") + | Int => failwith("abbreviate_typ") + | Float => failwith("abbreviate_typ") + | Bool => failwith("abbreviate_typ") + | String => failwith("abbreviate_typ") + | List(_t) => failwith("abbreviate_typ") + | Prod([]) => failwith("abbreviate_typ") + | Prod([_]) => failwith("Singleton Prods are not allowed") + | Prod([_t, ..._ts]) => failwith("abbreviate_typ") + | Parens(_t) => failwith("abbreviate_typ") + | Ap(_t1, _t2) => failwith("abbreviate_typ") + | Rec(_tp, _t) => failwith("abbreviate_typ") + | Forall(_tp, _t) => failwith("abbreviate_typ") + | Arrow(_t1, _t2) => failwith("abbreviate_typ") + | Sum([]) => failwith("abbreviate_typ") + | Sum([_t]) => failwith("abbreviate_typ") + | Sum([_t, ..._ts]) => failwith("abbreviate_typ") + }; +} +and abbreviate_tpat = (tpat: TPat.t): TPat.t => { + switch (tpat |> IdTagged.term_of) { + | Invalid(_t) => failwith("abbreviate_tpat") + | EmptyHole => failwith("abbreviate_tpat") + | MultiHole(_xs) => failwith("abbreviate_tpat") + | Var(_v) => failwith("abbreviate_tpat") + }; +} +and abbreviate_any = (any: Any.t): Any.t => { + switch (any) { + | Exp(e) => Exp(abbreviate_exp(e)) + | Pat(p) => Pat(abbreviate_pat(p)) + | Typ(t) => Typ(abbreviate_typ(t)) + | TPat(tp) => TPat(abbreviate_tpat(tp)) + | Any(_) + | Nul(_) + | Rul(_) => failwith("TODO: abbreviate_any: Rul | Any | Nul") + }; +}; diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index 4a1a5f44ed..59bce8c988 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -670,6 +670,9 @@ let rec external_precedence = (exp: Exp.t): Precedence.t => { | EmptyHole | Deferral(_) | BuiltinFun(_) + | Constructor(_) + //matt says: Constructor is here because we currently always add a type annotation to constructors + //TODO(andrew) says I move this from cast section as sometimes we strip casts? | Undefined => Precedence.max // Same goes for forms which are already surrounded @@ -680,7 +683,7 @@ let rec external_precedence = (exp: Exp.t): Precedence.t => { // Other forms | UnOp(Meta(Unquote), _) => Precedence.unquote - | Constructor(_) // Constructor is here because we currently always add a type annotation to constructors + | Cast(_) | FailedCast(_) => Precedence.cast | Ap(Forward, _, _) From ce355ab0a03bc965de52f74870146c65f99f5973 Mon Sep 17 00:00:00 2001 From: disconcision Date: Mon, 9 Dec 2024 16:31:17 -0500 Subject: [PATCH 10/23] drag to resize value abbreviations --- src/haz3lcore/FontMetrics.re | 25 +++ src/haz3lcore/pretty/Abbreviate.re | 167 ++++++++++++++---- src/haz3lcore/pretty/ExpToSegment.re | 1 + src/haz3lcore/zipper/ProjectorBase.re | 1 + src/haz3lcore/zipper/projectors/ProbeProj.re | 90 ++++++++-- src/haz3lweb/app/common/FontMetrics.re | 10 +- src/haz3lweb/app/common/ProjectorView.re | 1 + src/haz3lweb/app/editors/code/CodeEditable.re | 27 +-- src/haz3lweb/www/style/projectors.css | 6 +- src/util/JsUtil.re | 25 +++ 10 files changed, 275 insertions(+), 78 deletions(-) create mode 100644 src/haz3lcore/FontMetrics.re diff --git a/src/haz3lcore/FontMetrics.re b/src/haz3lcore/FontMetrics.re new file mode 100644 index 0000000000..dbc5e612f5 --- /dev/null +++ b/src/haz3lcore/FontMetrics.re @@ -0,0 +1,25 @@ +open Util; +open Js_of_ocaml; + +[@deriving (show({with_path: false}), sexp, yojson)] +type t = { + row_height: float, + col_width: float, +}; + +let init = {row_height: 10., col_width: 10.}; + +let get_goal = + ( + ~font_metrics: t, + text_box: Js.t(Dom_html.element), + e: Js.t(Dom_html.mouseEvent), + ) + : Point.t => { + open Float; + let x_rel = of_int(e##.clientX) -. text_box##getBoundingClientRect##.left; + let y_rel = of_int(e##.clientY) -. text_box##getBoundingClientRect##.top; + let row = to_int(y_rel /. font_metrics.row_height); + let col = to_int(round(x_rel /. font_metrics.col_width)); + {row, col}; +}; diff --git a/src/haz3lcore/pretty/Abbreviate.re b/src/haz3lcore/pretty/Abbreviate.re index 3aa0f910dc..195bb1c256 100644 --- a/src/haz3lcore/pretty/Abbreviate.re +++ b/src/haz3lcore/pretty/Abbreviate.re @@ -1,27 +1,60 @@ -open Util; -open PrettySegment; -open Base; +// open Util; +// open PrettySegment; +// open Base; + +/* + To do this with projectors, we'd need: + + 1. way to collapse part of a n-ary form e.g. [1,2,<3,4,5>] => [1, 2, <...>] + 2. way to gather ids (from term?) to collapse + 3. way to apply projectors (to segment) given ids + */ + +let comp_elipses = "⋱"; +let flat_ellipses = "…"; +let ellipses_term = () => IdTagged.fresh(Invalid(comp_elipses): Exp.term); +let flat_ellipses_term = () => + IdTagged.fresh(Invalid(flat_ellipses): Exp.term); +let available = ref(0); let abbreviate_str = (min_len: int, s: string): string => { let len = String.length(s); - let ellipsis = "..."; - if (len <= min_len) { + let ellipsis = "…"; + if (len <= min_len || min_len < 1) { + available := available^ - len; s; + } else if (min_len < 1) { + let str = String.sub(s, 0, 1) ++ ellipsis; + available := available^ - String.length(str); + str; } else { - String.sub(s, 0, min_len - String.length(ellipsis)) ++ ellipsis; + let str = String.sub(s, 0, min_len - 1) ++ ellipsis; + available := available^ - String.length(str); + str; }; }; -let rec abbreviate_exp = (~available=12, exp: Exp.t): Exp.t => { +let rec abbreviate_exp = (exp: Exp.t): Exp.t => { + /* + Maybe we can also use this to format, ie insert linebreaks? + Hard when it's just exp but maybe we can track them via + some inserted form, or as a side effect? eg emit ids + to insert lb after during ExpToSeg? + */ + // print_endline("abbreviate_exp"); let rewrap = (term: Exp.term): Exp.t => { {...exp, term}; }; - let abbreviate_str = abbreviate_str(available); - let comp_elipses = "..."; - let ellipses_term = () => IdTagged.fresh(Invalid(comp_elipses): Exp.term); + + let wrap_or = (term, str): Exp.term => + if (available^ > String.length(str)) { + available := available^ - String.length(str); + term; + } else { + Invalid(abbreviate_str(available^, str)); + }; + let indet_term: Exp.term = Invalid(""); - let go = (~available) => - abbreviate_exp(~available=available - String.length(comp_elipses)); let term: Exp.term = switch (exp |> Exp.term_of) { | Fun(_p, _e, _, Some(s)) => Invalid("<" ++ s ++ ">") @@ -31,42 +64,94 @@ let rec abbreviate_exp = (~available=12, exp: Exp.t): Exp.t => { //TODO(andrew): show exp below? | DynamicErrorHole(_exp, err) => Invalid("<" ++ InvalidOperationError.show(err) ++ ">") + // Atomic string cases - | Invalid(x) => Invalid(abbreviate_str(x)) - | String(s) => String(abbreviate_str(s)) - | Var(v) => Var(abbreviate_str(v)) - | Constructor(c, t) => Constructor(abbreviate_str(c), t) + | Invalid(x) => Invalid(abbreviate_str(available^, x)) + | String(s) => String(abbreviate_str(available^, s)) + | Var(v) => Var(abbreviate_str(available^, v)) + | Constructor(c, t) => Constructor(abbreviate_str(available^, c), t) - // Atomic Fixed cases - //TODO: length check these cases: + // Other atomic cases | EmptyHole => EmptyHole | ListLit([]) => ListLit([]) | Tuple([]) => Tuple([]) - | Bool(b) => Bool(b) - | Undefined => Undefined - | Int(n) => Int(n) - | Float(f) => Float(f) + | Undefined => wrap_or(Undefined, "undefined") + | Bool(b) => wrap_or(Bool(b), string_of_bool(b)) + | Int(n) => + //TODO: smarter number summarization? + wrap_or(Int(n), string_of_int(n)) + | Float(f) => + //TODO: smarter number summarization? + wrap_or(Float(f), string_of_float(f)) // composite literal cases - | ListLit([x, ..._xs]) => - //TODO: return used length from call, use that to make incorporate next elems - ListLit([go(~available, x), ellipses_term()]) + | ListLit(xs) => + if (available^ < 6) { + ListLit([flat_ellipses_term()]); + } else { + available := available^ - 2; + let rec go = xs => + switch (xs) { + | [] => [] + | [x] => [abbreviate_exp(x)] + | [x, ...xs] => + let hd = abbreviate_exp(x); + let tl = + if (available^ > 0) { + go(xs); + } else { + [flat_ellipses_term()]; + }; + [hd, ...tl]; + }; + ListLit(go(xs)); + } - | Tuple([x, ..._xs]) => - //TODO: return used length from call, use that to make incorporate next elems - Tuple([go(~available, x), ellipses_term()]) - | Ap(Forward, {term: Constructor(_), _} as konst, _e2) => - //TODO: return used length from call, use that to make incorporate next elems - let available = available - 5; //chars for ap delimiters, ellipses - Ap(Forward, abbreviate_exp(~available, konst), ellipses_term()); + | Tuple(xs) => + available := available^ - 2; + let rec go = xs => + switch (xs) { + | [] => [] + | [x] => + if (available^ > 1) { + [abbreviate_exp(x)]; + } else { + [flat_ellipses_term()]; + } + | [x, ...xs] => + let hd = abbreviate_exp(x); + let tl = + if (available^ > 0) { + available := available^ - 2; + go(xs); + } else { + [flat_ellipses_term()]; + }; + [hd, ...tl]; + }; + Tuple(go(xs)); + | Ap(Forward, {term: Constructor(_str, _), _} as konst, arg) => + // if (String.length(str) + 3 >= available^) { + // abbreviate_exp(konst).term; + // } else { + let konst = abbreviate_exp(konst); + available := available^ - 2; + let arg = + if (available^ > 0) { + abbreviate_exp(arg); + } else { + ellipses_term(); + }; + Ap(Forward, konst, arg); + // } | Cons(e1, _e2) => //TODO: return used length from call, use that to make incorporate next elems - let available = available - 2; //chars for cons op - Cons(abbreviate_exp(~available, e1), ellipses_term()); + available := available^ - (2 + String.length(comp_elipses)); + Cons(abbreviate_exp(e1), ellipses_term()); | Parens(e, pt) => - let available = available - 2; //chars for parens - Parens(abbreviate_exp(~available, e), pt); + available := available^ - 2; + Parens(abbreviate_exp(e), pt); //TODO(andrew) | Filter(_) => failwith("TODO(andrew): Filter") @@ -162,3 +247,13 @@ and abbreviate_any = (any: Any.t): Any.t => { | Rul(_) => failwith("TODO: abbreviate_any: Rul | Any | Nul") }; }; + +let abbreviate_exp = (~available as a=12, exp: Exp.t): (Exp.t, bool) => { + available := a; + available^ <= 1 + ? (ellipses_term(), false) + : { + let exp = abbreviate_exp(exp); + (exp, available^ < 0); + }; +}; diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index 59bce8c988..99b285dccf 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -49,6 +49,7 @@ let should_add_space = (s1, s2) => && !Form.is_keyword(s1) && String.starts_with(s2, ~prefix="(") => false + | _ when String.ends_with(s1, ~suffix="…") => false | _ => true }; diff --git a/src/haz3lcore/zipper/ProjectorBase.re b/src/haz3lcore/zipper/ProjectorBase.re index 702a156256..4818961c09 100644 --- a/src/haz3lcore/zipper/ProjectorBase.re +++ b/src/haz3lcore/zipper/ProjectorBase.re @@ -40,6 +40,7 @@ type info = { [@deriving (show({with_path: false}), sexp, yojson)] type bonus_pack = { view: (Sort.t, Base.segment) => Node.t, + font_metrics: FontMetrics.t, exp_to_seg: Exp.t => Base.segment, offside_offset: float /* to position something to rhs of code */ }; diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index c99e9fcb92..2746869385 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -2,13 +2,19 @@ open Util; open ProjectorBase; open Virtual_dom.Vdom; open Node; +open Js_of_ocaml; [@deriving (show({with_path: false}), sexp, yojson)] type t = { [@default "⋱"] text: string, + len: int, }; +[@deriving (show({with_path: false}), sexp, yojson)] +type a = + | ChangeLength(int); + /* Proof of concept value exposure. This isn't getting set immediately after folding for some reason */ let _vals = (di: option(Dynamics.Info.t)) => { @@ -34,6 +40,7 @@ let vals = (di: option(Dynamics.Info.t), exp_to_seg) => { (pi: Dynamics.Probe.Info.t) => pi.value |> DHExp.strip_casts + |> Abbreviate.abbreviate_exp |> exp_to_seg |> Printer.of_segment(~holes=None), di.vals, @@ -50,7 +57,12 @@ let env_val = (en: Dynamics.Probe.Env.entry, bonus_pack: bonus_pack) => { Node.text(en.name ++ "="), switch (en.raw) { | Opaque => Node.text("Opaque") - | Val(d) => d |> bonus_pack.exp_to_seg |> bonus_pack.view(Exp) + | Val(d) => + d + |> Abbreviate.abbreviate_exp + |> fst + |> bonus_pack.exp_to_seg + |> bonus_pack.view(Exp) }, ], ); @@ -89,7 +101,11 @@ let env_div = (di: Dynamics.Info.t, bonus_pack: bonus_pack) => { ); }; -let vals_div = (di: option(Dynamics.Info.t), bonus_pack: bonus_pack) => { +let mousedown: ref(option(Js.t(Dom_html.element))) = ref(Option.None); + +let vals_div = + (di: option(Dynamics.Info.t), ~model, ~local, ~bonus_pack: bonus_pack) => { + //dprint_endline("available:" ++ string_of_int(model.len)); switch (di) { | Some(di) => Node.div( @@ -107,10 +123,53 @@ let vals_div = (di: option(Dynamics.Info.t), bonus_pack: bonus_pack) => { (pi: Dynamics.Probe.Info.t) => { // print_endline("pi.env:" ++ Dynamics.Probe.Env.show(pi.env)); div( - ~attrs=[Attr.classes(["wrap"])], + ~attrs=[ + Attr.classes(["wrap"]), + Attr.on_pointerdown(e => { + // print_endline("pointerdown"); + let target = + e##.target |> Js.Opt.get(_, _ => failwith("no target")); + JsUtil.setPointerCapture(target, e##.pointerId) |> ignore; + mousedown := Some(target); + Effect.Ignore; + }), + Attr.on_pointerup(e => { + // print_endline("pointerup"); + switch (mousedown^) { + | Some(target) => + JsUtil.releasePointerCapture(target, e##.pointerId) + |> ignore + | _ => () + }; + mousedown := None; + Effect.Ignore; + }), + Attr.on_mousemove(e => + switch (mousedown^) { + | Some(_elem) => + /* Ideally this would be onpointermove and we could just use hasPointerCapture... */ + // print_endline("mousemove:down"); + let goal = + FontMetrics.get_goal( + ~font_metrics=bonus_pack.font_metrics, + e##.currentTarget + |> Js.Opt.get(_, _ => failwith("")) + |> JsUtil.get_child_with_class(_, "code") + |> Option.get, + e |> Js.Unsafe.coerce, + ); + local(ChangeLength(goal.col)); + | _ => + print_endline("mousemove:up"); + Effect.Ignore; + } + ), + ], [ pi.value |> DHExp.strip_casts + |> Abbreviate.abbreviate_exp(~available=model.len) + |> fst |> bonus_pack.exp_to_seg |> bonus_pack.view(Exp), env_div2(pi, bonus_pack), @@ -150,26 +209,35 @@ let _ = Zipper.base_point; let code_str = (info: info) => [info.syntax] |> Printer.of_segment(~holes=None); -let view = (_m: t, ~info, ~local as _, ~parent, ~bonus_pack: bonus_pack) => +let view = (model: t, ~info, ~local, ~parent as _, ~bonus_pack: bonus_pack) => div( - ~attrs=[ - Attr.on_double_click(_ => parent(Remove)), - Attr.title(vals(info.dynamics, bonus_pack.exp_to_seg)), + ~attrs=[], //Attr.on_double_click(_ => parent(Remove)), + //Attr.title(vals(info.dynamics, bonus_pack.exp_to_seg)), + [ + vals_div(info.dynamics, ~model, ~local, ~bonus_pack), + text("🔍 " ++ code_str(info)), ], - [vals_div(info.dynamics, bonus_pack), text("🔍 " ++ code_str(info))], ); module M: Projector = { [@deriving (show({with_path: false}), sexp, yojson)] type model = t; [@deriving (show({with_path: false}), sexp, yojson)] - type action = unit; - let init = {text: "🔍"}; + type action = a; + let init = {text: "🔍", len: 12}; let can_project = _ => true; let can_focus = false; let placeholder = (_m, info) => Inline(3 + String.length(code_str(info))); - let update = (m, _) => m; + let update = (m, a) => { + print_endline("update: action:" ++ show_a(a)); + let ChangeLength(len) = a; + if (len > (-1)) { + {...m, len}; + } else { + m; + }; + }; let view = view; let focus = _ => (); }; diff --git a/src/haz3lweb/app/common/FontMetrics.re b/src/haz3lweb/app/common/FontMetrics.re index 4ed525c176..a72aa95763 100644 --- a/src/haz3lweb/app/common/FontMetrics.re +++ b/src/haz3lweb/app/common/FontMetrics.re @@ -1,10 +1,2 @@ -open Util; - -[@warning "-33"] [@deriving (show({with_path: false}), sexp, yojson)] -type t = { - row_height: float, - col_width: float, -}; - -let init = {row_height: 10., col_width: 10.}; +include Haz3lcore.FontMetrics; diff --git a/src/haz3lweb/app/common/ProjectorView.re b/src/haz3lweb/app/common/ProjectorView.re index 32d7c14574..21a3b16ae9 100644 --- a/src/haz3lweb/app/common/ProjectorView.re +++ b/src/haz3lweb/app/common/ProjectorView.re @@ -175,6 +175,7 @@ let setup_view = fold_cast_types: false, }, ), + font_metrics: globals.font_metrics, offside_offset: offside(globals.font_metrics, measurement, cached_syntax.measured), }; diff --git a/src/haz3lweb/app/editors/code/CodeEditable.re b/src/haz3lweb/app/editors/code/CodeEditable.re index 9fba95753b..800a869689 100644 --- a/src/haz3lweb/app/editors/code/CodeEditable.re +++ b/src/haz3lweb/app/editors/code/CodeEditable.re @@ -162,24 +162,6 @@ module View = { type event = | MakeActive; - let get_goal = - ( - ~font_metrics: FontMetrics.t, - text_box: Js.t(Dom_html.element), - e: Js.t(Dom_html.mouseEvent), - ) => { - let rect = text_box##getBoundingClientRect; - let goal_x = float_of_int(e##.clientX); - let goal_y = float_of_int(e##.clientY); - Point.{ - row: Float.to_int((goal_y -. rect##.top) /. font_metrics.row_height), - col: - Float.( - to_int(round((goal_x -. rect##.left) /. font_metrics.col_width)) - ), - }; - }; - let mousedown_overlay = (~globals: Globals.t, ~inject) => Node.div( ~attrs= @@ -187,6 +169,7 @@ module View = { id("mousedown-overlay"), on_mouseup(_ => globals.inject_global(SetMousedown(false))), on_mousemove(e => { + let _ = ignore(e##.button); let mouse_handler = e##.target |> Js.Opt.get(_, _ => failwith("no target")); let text_box = @@ -198,7 +181,11 @@ module View = { ) |> Option.get; let goal = - get_goal(~font_metrics=globals.font_metrics, text_box, e); + FontMetrics.get_goal( + ~font_metrics=globals.font_metrics, + text_box, + e, + ); inject(Action.Select(Resize(Goal(Point(goal))))); }), ], @@ -207,7 +194,7 @@ module View = { let mousedown_handler = (~globals: Globals.t, ~signal, ~inject, evt) => { let goal = - get_goal( + FontMetrics.get_goal( ~font_metrics=globals.font_metrics, evt##.currentTarget |> Js.Opt.get(_, _ => failwith("")) diff --git a/src/haz3lweb/www/style/projectors.css b/src/haz3lweb/www/style/projectors.css index b70e4fc950..3c6ea28e35 100644 --- a/src/haz3lweb/www/style/projectors.css +++ b/src/haz3lweb/www/style/projectors.css @@ -181,8 +181,8 @@ .live-offside .code { background-color: #c7e6c6; - border-radius: 0.3em; - border-top: 1px solid white; + border-radius: 0.2em; + /* border-top: 1px solid white; */ filter: drop-shadow(0.7px 0.7px 0px green); opacity: 60%; } @@ -203,6 +203,7 @@ .projector.probe { font-family: var(--code-font); + font-size: var(--base-font-size); color: var(--STONE); } @@ -227,4 +228,5 @@ } .projector.probe .wrap:hover > .code { border-radius: 0.2em 0.2em 0.2em 0; + cursor: col-resize; } \ No newline at end of file diff --git a/src/util/JsUtil.re b/src/util/JsUtil.re index 36890d518d..4002e92596 100644 --- a/src/util/JsUtil.re +++ b/src/util/JsUtil.re @@ -190,3 +190,28 @@ module Fragment = { Url.Current.get() |> Option.map(fragment_of_url); }; }; + +let setPointerCapture = (e: Js.t(Dom_html.element), pointerId: int) => + Js.Unsafe.meth_call( + e, + "setPointerCapture", + [|Js.Unsafe.inject(pointerId)|], + ); + +let releasePointerCapture = (e: Js.t(Dom_html.element), pointerId: int) => + Js.Unsafe.meth_call( + e, + "releasePointerCapture", + [|Js.Unsafe.inject(pointerId)|], + ); + +// let hasPointerCapture = (e: Js.t(Dom_html.element), pointerId: int): bool => +// Js.Unsafe.meth_call( +// e, +// "hasPointerCapture", +// [|Js.Unsafe.inject(pointerId)|], +// ); + +// let on_pointermove = +// (handler: Js.t(Dom_html.pointerEvent) => Effect.t(unit)): Attr.t => +// Virtual_dom.Vdom.Attr.property("onpointermove", Js.Unsafe.inject(handler)); From 4415b811504818f7a3007836d8d8cb79402a0d86 Mon Sep 17 00:00:00 2001 From: disconcision Date: Tue, 10 Dec 2024 14:35:59 -0500 Subject: [PATCH 11/23] simple persistent closure cursor --- src/haz3lcore/dynamics/Transition.re | 48 +++++++--- src/haz3lcore/prog/Dynamics.re | 4 +- src/haz3lcore/statics/TermBase.re | 44 ++++++++- src/haz3lcore/zipper/projectors/ProbeProj.re | 93 +++++++++++++++++--- src/haz3lweb/www/style/projectors.css | 12 +++ 5 files changed, 173 insertions(+), 28 deletions(-) diff --git a/src/haz3lcore/dynamics/Transition.re b/src/haz3lcore/dynamics/Transition.re index f11cb428df..91faf9d74d 100644 --- a/src/haz3lcore/dynamics/Transition.re +++ b/src/haz3lcore/dynamics/Transition.re @@ -75,12 +75,23 @@ type step_kind = | RemoveTypeAlias | RemoveParens; let evaluate_extend_env = - (new_bindings: Environment.t, to_extend: ClosureEnvironment.t) + (~ap=None, new_bindings: Environment.t, to_extend: ClosureEnvironment.t) : ClosureEnvironment.t => { - to_extend - |> ClosureEnvironment.map_of - |> Environment.union(new_bindings) - |> ClosureEnvironment.of_environment; + let ce = + to_extend + |> ClosureEnvironment.map_of + |> Environment.union(new_bindings) + |> ClosureEnvironment.of_environment; + ce + |> ClosureEnvironment.update_stack(_stack => + switch (ap) { + | None => ClosureEnvironment.stack_of(to_extend) + | Some(ap) => [ + {env_id: ce |> ClosureEnvironment.id_of, ap_id: ap}, + ...ClosureEnvironment.stack_of(to_extend), + ] + } + ); }; type rule = @@ -348,13 +359,26 @@ module Transition = (EV: EV_MODE) => { switch (DHExp.term_of(d1')) { | Constructor(_) => Constructor | Fun(dp, d3, Some(env'), _) => - let.match env'' = (env', matches(dp, d2')); - Step({ - expr: Closure(env'', d3) |> fresh, - state_update, - kind: FunAp, - is_value: false, - }); + switch (matches(dp, d2')) { + | IndetMatch + | DoesNotMatch => Indet + | Matches(env'') => + let env'' = + evaluate_extend_env(~ap=Some(Term.Exp.rep_id(d)), env'', env'); + Step({ + expr: Closure(env'', d3) |> fresh, + state_update, + kind: FunAp, + is_value: false, + }); + } + // let.match env'' = (env', matches(dp, d2')); + // Step({ + // expr: Closure(env'', d3) |> fresh, + // state_update, + // kind: FunAp, + // is_value: false, + // }); | Cast( d3', {term: Arrow(ty1, ty2), _}, diff --git a/src/haz3lcore/prog/Dynamics.re b/src/haz3lcore/prog/Dynamics.re index f35a9900e0..66dc5aa2ce 100644 --- a/src/haz3lcore/prog/Dynamics.re +++ b/src/haz3lcore/prog/Dynamics.re @@ -45,7 +45,7 @@ module Probe = { let raw = d |> DHExp.strip_casts - |> Exp.substitute_closures(snd(env)) + |> Exp.substitute_closures(ClosureEnvironment.map_of(env)) |> to_raw; {name, id, raw}; | None => failwith("Probe: variable not found in environment") @@ -60,10 +60,12 @@ module Probe = { type t = { value: DHExp.t, env: Env.t, + stack: TermBase.probe_stack, }; let mk = (value: DHExp.t, env: ClosureEnvironment.t, pr: TermBase.probe) => { value, + stack: ClosureEnvironment.stack_of(env), env: Env.mk(env, pr.refs), }; }; diff --git a/src/haz3lcore/statics/TermBase.re b/src/haz3lcore/statics/TermBase.re index e35c3382bb..7e59543bf0 100644 --- a/src/haz3lcore/statics/TermBase.re +++ b/src/haz3lcore/statics/TermBase.re @@ -57,6 +57,29 @@ type probe_tag = | Paren | Probe(probe); +[@deriving (show({with_path: false}), sexp, yojson)] +type probe_frame = { + env_id: Id.t, + ap_id: Id.t, +}; + +[@deriving (show({with_path: false}), sexp, yojson)] +type probe_stack = list(probe_frame); + +//TODO(andrew): cleanup +let rec show_probe_stack = (stack: probe_stack): string => + switch (stack) { + | [] => "" + | [{env_id, ap_id: _}, ...tl] => + "[" + // ++ String.sub(Id.to_string(ap_id), 0, 4) + // ++ ", env:" + ++ String.sub(Id.to_string(env_id), 0, 4) + ++ "]" + ++ "\n" + ++ show_probe_stack(tl) + }; + [@deriving (show({with_path: false}), sexp, yojson)] type any_t = | Exp(exp_t) @@ -158,7 +181,11 @@ and rul_term = | Rules(exp_t, list((pat_t, exp_t))) and rul_t = IdTagged.t(rul_term) and environment_t = VarBstMap.Ordered.t_(exp_t) -and closure_environment_t = (Id.t, environment_t) //TODO(andrew): option(clj_env), option(Id) //callsite +and closure_environment_t = { + id: Id.t, + env: environment_t, + stack: probe_stack, +} and stepper_filter_kind_t = | Filter(filter) | Residue(int, FilterAction.t) @@ -929,6 +956,8 @@ and ClosureEnvironment: { let id_of: t => Id.t; let map_of: t => Environment.t; + let stack_of: t => list(probe_frame); + let update_stack: (list(probe_frame) => list(probe_frame), t) => t; let to_list: t => list((Var.t, Exp.t)); @@ -967,14 +996,21 @@ and ClosureEnvironment: { let id_of: t => Id.t; let map_of: t => Environment.t; + let stack_of: t => list(probe_frame); + let update_stack: (list(probe_frame) => list(probe_frame), t) => t; } = { [@deriving (show({with_path: false}), sexp, yojson)] type t = closure_environment_t; - let wrap = (ei, map): t => (ei, map); + let wrap = (ei, map): t => {id: ei, env: map, stack: []}; - let id_of = ((ei, _)) => ei; - let map_of = ((_, map)) => map; + let id_of = t => t.id; + let map_of = t => t.env; + let stack_of = t => t.stack; + + let update_stack = (f, t) => { + {...t, stack: f(t.stack)}; + }; let (sexp_of_t, t_of_sexp) = StructureShareSexp.structure_share_here(id_of, sexp_of_t, t_of_sexp); }; diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index 2746869385..66ef089cc4 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -9,11 +9,13 @@ type t = { [@default "⋱"] text: string, len: int, + show_all_vals: bool, }; [@deriving (show({with_path: false}), sexp, yojson)] type a = - | ChangeLength(int); + | ChangeLength(int) + | ToggleShowAllVals; /* Proof of concept value exposure. This isn't getting set immediately after folding for some reason */ @@ -33,6 +35,18 @@ let _vals = (di: option(Dynamics.Info.t)) => { }; }; +let stack = (di: option(Dynamics.Info.t)) => { + switch (di) { + | Some(di) => + List.map( + (pi: Dynamics.Probe.Info.t) => pi.stack |> TermBase.show_probe_stack, + di.vals, + ) + |> String.concat(", ") + | _ => "Nein" + }; +}; + let vals = (di: option(Dynamics.Info.t), exp_to_seg) => { switch (di) { | Some(di) => @@ -101,11 +115,59 @@ let env_div = (di: Dynamics.Info.t, bonus_pack: bonus_pack) => { ); }; +let env_cursor: ref(list(Id.t)) = ref([]); + let mousedown: ref(option(Js.t(Dom_html.element))) = ref(Option.None); +let env_cursor_of_stack = List.map((en: TermBase.probe_frame) => en.env_id); + +/* given two env_cursors,return their maximum common suffix */ +let max_common_suffix = (a: list('a), b: list('a)) => { + let rec loop = (a, b, acc) => + switch (a, b) { + | ([], _) + | (_, []) => acc + | ([ha, ...ta], [hb, ...tb]) when ha == hb => + loop(ta, tb, [ha, ...acc]) + | _ => acc + }; + loop(List.rev(a), List.rev(b), []); +}; + +let show_indicator = stack => { + let local = stack |> env_cursor_of_stack; + if (env_cursor^ == local) { + true; + } else if (max_common_suffix(env_cursor^, local) != []) { + !( + List.length(env_cursor^) == List.length(local) && env_cursor^ != local + ); + } else { + false; + }; +}; + +let comparor = (a: Dynamics.Probe.Info.t, b: Dynamics.Probe.Info.t) => { + compare( + List.length( + max_common_suffix(env_cursor^, env_cursor_of_stack(b.stack)), + ), + List.length( + max_common_suffix(env_cursor^, env_cursor_of_stack(a.stack)), + ), + ); +}; + let vals_div = (di: option(Dynamics.Info.t), ~model, ~local, ~bonus_pack: bonus_pack) => { //dprint_endline("available:" ++ string_of_int(model.len)); + let filter_vals = vals => { + switch (List.sort(comparor, vals)) { + | [] => [] + | [hd, ..._] when !model.show_all_vals => [hd] + | _ => vals + }; + }; switch (di) { | Some(di) => Node.div( @@ -124,13 +186,16 @@ let vals_div = // print_endline("pi.env:" ++ Dynamics.Probe.Env.show(pi.env)); div( ~attrs=[ - Attr.classes(["wrap"]), + Attr.classes( + ["wrap"] @ (show_indicator(pi.stack) ? ["cursor"] : []), + ), Attr.on_pointerdown(e => { // print_endline("pointerdown"); let target = e##.target |> Js.Opt.get(_, _ => failwith("no target")); JsUtil.setPointerCapture(target, e##.pointerId) |> ignore; mousedown := Some(target); + env_cursor := pi.stack |> env_cursor_of_stack; Effect.Ignore; }), Attr.on_pointerup(e => { @@ -172,11 +237,12 @@ let vals_div = |> fst |> bonus_pack.exp_to_seg |> bonus_pack.view(Exp), + //pi.stack |> TermBase.show_probe_stack |> Node.text, env_div2(pi, bonus_pack), ], ) }, - di.vals, + filter_vals(di.vals), ), //@ [env_div(di, bonus_pack)], ) @@ -211,8 +277,10 @@ let code_str = (info: info) => let view = (model: t, ~info, ~local, ~parent as _, ~bonus_pack: bonus_pack) => div( - ~attrs=[], //Attr.on_double_click(_ => parent(Remove)), - //Attr.title(vals(info.dynamics, bonus_pack.exp_to_seg)), + ~attrs=[ + Attr.title(stack(info.dynamics)), + Attr.on_double_click(_ => local(ToggleShowAllVals)), + ], [ vals_div(info.dynamics, ~model, ~local, ~bonus_pack), text("🔍 " ++ code_str(info)), @@ -224,18 +292,21 @@ module M: Projector = { type model = t; [@deriving (show({with_path: false}), sexp, yojson)] type action = a; - let init = {text: "🔍", len: 12}; + let init = {text: "🔍", len: 12, show_all_vals: true}; let can_project = _ => true; let can_focus = false; let placeholder = (_m, info) => Inline(3 + String.length(code_str(info))); let update = (m, a) => { print_endline("update: action:" ++ show_a(a)); - let ChangeLength(len) = a; - if (len > (-1)) { - {...m, len}; - } else { - m; + switch (a) { + | ChangeLength(len) => + if (len > (-1)) { + {...m, len}; + } else { + m; + } + | ToggleShowAllVals => {...m, show_all_vals: !m.show_all_vals} }; }; let view = view; diff --git a/src/haz3lweb/www/style/projectors.css b/src/haz3lweb/www/style/projectors.css index 3c6ea28e35..fbe2d753b3 100644 --- a/src/haz3lweb/www/style/projectors.css +++ b/src/haz3lweb/www/style/projectors.css @@ -217,15 +217,27 @@ z-index: 5000; } + .projector.probe .live-env .live-env-entry { display: flex; flex-direction: row; gap: 0.3em; } +.projector.probe .wrap.cursor { + outline: 1px solid var(--R1); + border-radius: 0.2em; +} +.projector.probe.indicated .wrap:not(.cursor) { + opacity: 60%; +} + .projector.probe .wrap:hover .live-env { display: block; } +.projector.probe .wrap:hover .live-env:empty { + display: none; +} .projector.probe .wrap:hover > .code { border-radius: 0.2em 0.2em 0.2em 0; cursor: col-resize; From 5e93bc6a20d36231ba336e0013c445d3662b33a9 Mon Sep 17 00:00:00 2001 From: disconcision Date: Tue, 10 Dec 2024 16:10:43 -0500 Subject: [PATCH 12/23] closureenv cleanup. probe style tweaks --- src/haz3lcore/dynamics/EvalCtx.re | 2 +- src/haz3lcore/dynamics/FilterMatcher.re | 9 +- src/haz3lcore/dynamics/Probe.re | 23 +++ src/haz3lcore/dynamics/Substitution.re | 47 +++--- src/haz3lcore/dynamics/Transition.re | 26 +--- src/haz3lcore/prog/Dynamics.re | 9 +- src/haz3lcore/statics/MakeTerm.re | 2 +- src/haz3lcore/statics/TermBase.re | 153 +++++-------------- src/haz3lcore/zipper/projectors/ProbeProj.re | 6 +- src/haz3lweb/www/style/projectors.css | 40 ++--- 10 files changed, 122 insertions(+), 195 deletions(-) create mode 100644 src/haz3lcore/dynamics/Probe.re diff --git a/src/haz3lcore/dynamics/EvalCtx.re b/src/haz3lcore/dynamics/EvalCtx.re index 76c6496cc2..bfafc131e0 100644 --- a/src/haz3lcore/dynamics/EvalCtx.re +++ b/src/haz3lcore/dynamics/EvalCtx.re @@ -23,7 +23,7 @@ type term = | BinOp2(Operators.op_bin, DHExp.t, t) | Tuple(t, (list(DHExp.t), list(DHExp.t))) | Test(t) - | Parens(t, TermBase.probe_tag) + | Parens(t, Probe.tag) | ListLit(t, (list(DHExp.t), list(DHExp.t))) | MultiHole(t, (list(Any.t), list(Any.t))) | Cons1(t, DHExp.t) diff --git a/src/haz3lcore/dynamics/FilterMatcher.re b/src/haz3lcore/dynamics/FilterMatcher.re index b0884497fa..aeac72d1a8 100644 --- a/src/haz3lcore/dynamics/FilterMatcher.re +++ b/src/haz3lcore/dynamics/FilterMatcher.re @@ -1,11 +1,4 @@ -let evaluate_extend_env = - (new_bindings: Environment.t, to_extend: ClosureEnvironment.t) - : ClosureEnvironment.t => { - to_extend - |> ClosureEnvironment.map_of - |> Environment.union(new_bindings) - |> ClosureEnvironment.of_environment; -}; +let evaluate_extend_env = ClosureEnvironment.extend_eval; let evaluate_extend_env_with_pat = ( diff --git a/src/haz3lcore/dynamics/Probe.re b/src/haz3lcore/dynamics/Probe.re new file mode 100644 index 0000000000..9175f68f5d --- /dev/null +++ b/src/haz3lcore/dynamics/Probe.re @@ -0,0 +1,23 @@ +open Util; + +[@deriving (show({with_path: false}), sexp, yojson)] +type t = { + refs: Binding.s, + stem: Binding.stem, +}; + +[@deriving (show({with_path: false}), sexp, yojson)] +type tag = + | Paren + | Probe(t); + +[@deriving (show({with_path: false}), sexp, yojson)] +type frame = { + env_id: Id.t, + frame_id: Id.t, +}; + +[@deriving (show({with_path: false}), sexp, yojson)] +type stack = list(frame); + +let empty = {refs: [], stem: []}; diff --git a/src/haz3lcore/dynamics/Substitution.re b/src/haz3lcore/dynamics/Substitution.re index 350bfc450d..53d3faaab5 100644 --- a/src/haz3lcore/dynamics/Substitution.re +++ b/src/haz3lcore/dynamics/Substitution.re @@ -132,33 +132,28 @@ let rec subst_var = (m, d1: DHExp.t, x: Var.t, d2: DHExp.t): DHExp.t => { and subst_var_env = (m, d1: DHExp.t, x: Var.t, env: ClosureEnvironment.t) : ClosureEnvironment.t => { - let id = env |> ClosureEnvironment.id_of; - let map = - env - |> ClosureEnvironment.map_of - |> Environment.foldo( - ((x', d': DHExp.t), map) => { - let d' = - switch (DHExp.term_of(d')) { - /* Substitute each previously substituted binding into the - * fixpoint. */ - | FixF(_) => - map - |> Environment.foldo( - ((x'', d''), d) => subst_var(m, d'', x'', d), - d', - ) - | _ => d' - }; + Environment.foldo( + ((x', d': DHExp.t), map) => { + let d' = + switch (DHExp.term_of(d')) { + /* Substitute each previously substituted binding into the + * fixpoint. */ + | FixF(_) => + map + |> Environment.foldo( + ((x'', d''), d) => subst_var(m, d'', x'', d), + d', + ) + | _ => d' + }; - /* Substitute. */ - let d' = subst_var(m, d1, x, d'); - Environment.extend(map, (x', d')); - }, - Environment.empty, - ); - - ClosureEnvironment.wrap(id, map); + /* Substitute. */ + let d' = subst_var(m, d1, x, d'); + Environment.extend(map, (x', d')); + }, + Environment.empty, + ) + |> ClosureEnvironment.update_env(_, env); } and subst_var_filter = diff --git a/src/haz3lcore/dynamics/Transition.re b/src/haz3lcore/dynamics/Transition.re index 91faf9d74d..934441b721 100644 --- a/src/haz3lcore/dynamics/Transition.re +++ b/src/haz3lcore/dynamics/Transition.re @@ -74,25 +74,7 @@ type step_kind = | Cast | RemoveTypeAlias | RemoveParens; -let evaluate_extend_env = - (~ap=None, new_bindings: Environment.t, to_extend: ClosureEnvironment.t) - : ClosureEnvironment.t => { - let ce = - to_extend - |> ClosureEnvironment.map_of - |> Environment.union(new_bindings) - |> ClosureEnvironment.of_environment; - ce - |> ClosureEnvironment.update_stack(_stack => - switch (ap) { - | None => ClosureEnvironment.stack_of(to_extend) - | Some(ap) => [ - {env_id: ce |> ClosureEnvironment.id_of, ap_id: ap}, - ...ClosureEnvironment.stack_of(to_extend), - ] - } - ); -}; +let evaluate_extend_env = ClosureEnvironment.extend_eval; type rule = | Step({ @@ -364,7 +346,11 @@ module Transition = (EV: EV_MODE) => { | DoesNotMatch => Indet | Matches(env'') => let env'' = - evaluate_extend_env(~ap=Some(Term.Exp.rep_id(d)), env'', env'); + evaluate_extend_env( + ~frame=Some(Term.Exp.rep_id(d)), + env'', + env', + ); Step({ expr: Closure(env'', d3) |> fresh, state_update, diff --git a/src/haz3lcore/prog/Dynamics.re b/src/haz3lcore/prog/Dynamics.re index 66dc5aa2ce..25f4887d40 100644 --- a/src/haz3lcore/prog/Dynamics.re +++ b/src/haz3lcore/prog/Dynamics.re @@ -2,8 +2,7 @@ open Util; module Probe = { let instrument = - (m: Statics.Map.t, id: Id.t, probe_tag: TermBase.probe_tag) - : TermBase.probe_tag => + (m: Statics.Map.t, id: Id.t, probe_tag: Probe.tag): Probe.tag => switch (probe_tag) { | Paren => Paren | Probe(_) => @@ -13,8 +12,6 @@ module Probe = { }) }; - let empty = TermBase.{refs: [], stem: []}; - module Env = { [@deriving (show({with_path: false}), sexp, yojson)] type raw = @@ -60,10 +57,10 @@ module Probe = { type t = { value: DHExp.t, env: Env.t, - stack: TermBase.probe_stack, + stack: Probe.stack, }; - let mk = (value: DHExp.t, env: ClosureEnvironment.t, pr: TermBase.probe) => { + let mk = (value: DHExp.t, env: ClosureEnvironment.t, pr: Probe.t) => { value, stack: ClosureEnvironment.stack_of(env), env: Env.mk(env, pr.refs), diff --git a/src/haz3lcore/statics/MakeTerm.re b/src/haz3lcore/statics/MakeTerm.re index 21d1124ca7..9ef1538c47 100644 --- a/src/haz3lcore/statics/MakeTerm.re +++ b/src/haz3lcore/statics/MakeTerm.re @@ -201,7 +201,7 @@ and exp_term: unsorted => (UExp.term, list(Id.t)) = { | (["(", ")"], [Exp(body)]) => ret(Parens(body, Paren)) | (["@@", "@@"], [Exp(body)]) => // TODO(andrew): apologize for this - ret(Parens(body, Probe(Dynamics.Probe.empty))) + ret(Parens(body, Probe(Probe.empty))) | (["[", "]"], [Exp(body)]) => switch (body) { | {ids, copied: false, term: Tuple(es)} => (ListLit(es), ids) diff --git a/src/haz3lcore/statics/TermBase.re b/src/haz3lcore/statics/TermBase.re index 7e59543bf0..09681b7350 100644 --- a/src/haz3lcore/statics/TermBase.re +++ b/src/haz3lcore/statics/TermBase.re @@ -46,40 +46,6 @@ type deferral_position_t = the id of the closure. */ -[@deriving (show({with_path: false}), sexp, yojson)] -type probe = { - refs: Binding.s, - stem: Binding.stem, -}; - -[@deriving (show({with_path: false}), sexp, yojson)] -type probe_tag = - | Paren - | Probe(probe); - -[@deriving (show({with_path: false}), sexp, yojson)] -type probe_frame = { - env_id: Id.t, - ap_id: Id.t, -}; - -[@deriving (show({with_path: false}), sexp, yojson)] -type probe_stack = list(probe_frame); - -//TODO(andrew): cleanup -let rec show_probe_stack = (stack: probe_stack): string => - switch (stack) { - | [] => "" - | [{env_id, ap_id: _}, ...tl] => - "[" - // ++ String.sub(Id.to_string(ap_id), 0, 4) - // ++ ", env:" - ++ String.sub(Id.to_string(env_id), 0, 4) - ++ "]" - ++ "\n" - ++ show_probe_stack(tl) - }; - [@deriving (show({with_path: false}), sexp, yojson)] type any_t = | Exp(exp_t) @@ -123,7 +89,7 @@ and exp_term = | Test(exp_t) | Filter(stepper_filter_kind_t, exp_t) | Closure([@show.opaque] closure_environment_t, exp_t) - | Parens(exp_t, probe_tag) + | Parens(exp_t, Probe.tag) | Cons(exp_t, exp_t) | ListConcat(exp_t, exp_t) | UnOp(Operators.op_un, exp_t) @@ -184,7 +150,7 @@ and environment_t = VarBstMap.Ordered.t_(exp_t) and closure_environment_t = { id: Id.t, env: environment_t, - stack: probe_stack, + stack: Probe.stack, } and stepper_filter_kind_t = | Filter(filter) @@ -952,65 +918,42 @@ and ClosureEnvironment: { [@deriving (show({with_path: false}), sexp, yojson)] type t = closure_environment_t; - let wrap: (Id.t, Environment.t) => t; + let empty: t; + + let of_environment: Environment.t => t; let id_of: t => Id.t; let map_of: t => Environment.t; - let stack_of: t => list(probe_frame); - let update_stack: (list(probe_frame) => list(probe_frame), t) => t; - - let to_list: t => list((Var.t, Exp.t)); - - let of_environment: Environment.t => t; + let stack_of: t => list(Probe.frame); let id_equal: (closure_environment_t, closure_environment_t) => bool; - let empty: t; - let is_empty: t => bool; - let length: t => int; - let lookup: (t, Var.t) => option(Exp.t); - let contains: (t, Var.t) => bool; - let update: (Environment.t => Environment.t, t) => t; - let update_keep_id: (Environment.t => Environment.t, t) => t; - let extend: (t, (Var.t, Exp.t)) => t; - let extend_keep_id: (t, (Var.t, Exp.t)) => t; - let union: (t, t) => t; - let union_keep_id: (t, t) => t; - let map: (((Var.t, Exp.t)) => Exp.t, t) => t; - let map_keep_id: (((Var.t, Exp.t)) => Exp.t, t) => t; - let filter: (((Var.t, Exp.t)) => bool, t) => t; - let filter_keep_id: (((Var.t, Exp.t)) => bool, t) => t; - let fold: (((Var.t, Exp.t), 'b) => 'b, 'b, t) => 'b; + let update_env: (Environment.t => Environment.t, t) => t; + let extend_eval: (~frame: option(Id.t)=?, Environment.t, t) => t; + let to_list: t => list((Var.t, Exp.t)); let without_keys: (list(Var.t), t) => t; - let with_symbolic_keys: (list(Var.t), t) => t; - - let placeholder: t; } = { module Inner: { [@deriving (show({with_path: false}), sexp, yojson)] type t = closure_environment_t; - let wrap: (Id.t, Environment.t) => t; + let wrap: (Id.t, Environment.t, Probe.stack) => t; let id_of: t => Id.t; let map_of: t => Environment.t; - let stack_of: t => list(probe_frame); - let update_stack: (list(probe_frame) => list(probe_frame), t) => t; + let stack_of: t => list(Probe.frame); } = { [@deriving (show({with_path: false}), sexp, yojson)] type t = closure_environment_t; - let wrap = (ei, map): t => {id: ei, env: map, stack: []}; + let wrap = (id, env, stack): t => {id, env, stack}; let id_of = t => t.id; let map_of = t => t.env; let stack_of = t => t.stack; - let update_stack = (f, t) => { - {...t, stack: f(t.stack)}; - }; let (sexp_of_t, t_of_sexp) = StructureShareSexp.structure_share_here(id_of, sexp_of_t, t_of_sexp); }; @@ -1018,10 +961,7 @@ and ClosureEnvironment: { let to_list = env => env |> map_of |> Environment.to_listo; - let of_environment = map => { - let ei = Id.mk(); - wrap(ei, map); - }; + let of_environment = env => wrap(Id.mk(), env, []); /* Equals only needs to check environment id's (faster than structural equality * checking.) */ @@ -1029,52 +969,39 @@ and ClosureEnvironment: { let empty = Environment.empty |> of_environment; - let is_empty = env => env |> map_of |> Environment.is_empty; - - let length = env => Environment.length(map_of(env)); - let lookup = (env, x) => env |> map_of |> (map => Environment.lookup(map, x)); - let contains = (env, x) => - env |> map_of |> (map => Environment.contains(map, x)); - - let update = (f, env) => env |> map_of |> f |> of_environment; - - let update_keep_id = (f, env) => env |> map_of |> f |> wrap(env |> id_of); - - let extend = (env, xr) => - env |> update(map => Environment.extend(map, xr)); - - let extend_keep_id = (env, xr) => - env |> update_keep_id(map => Environment.extend(map, xr)); + let update_env = (f, env) => env |> map_of |> f |> of_environment; - let union = (env1, env2) => - env2 |> update(map2 => Environment.union(env1 |> map_of, map2)); + let without_keys = keys => update_env(Environment.without_keys(keys)); - let union_keep_id = (env1, env2) => - env2 |> update_keep_id(map2 => Environment.union(env1 |> map_of, map2)); - - let map = (f, env) => env |> update(Environment.mapo(f)); - - let map_keep_id = (f, env) => env |> update_keep_id(Environment.mapo(f)); - - let filter = (f, env) => env |> update(Environment.filtero(f)); - - let filter_keep_id = (f, env) => - env |> update_keep_id(Environment.filtero(f)); - - let fold = (f, init, env) => env |> map_of |> Environment.foldo(f, init); - - let placeholder = wrap(Id.invalid, Environment.empty); + let update_stack = (frame: option(Id.t), env) => { + let stack = + switch (frame) { + | None => stack_of(env) + | Some(frame) => [ + {env_id: id_of(env), frame_id: frame}, + ...stack_of(env), + ] + }; + {...env, stack}; + }; - let without_keys = keys => update(Environment.without_keys(keys)); - let with_symbolic_keys = (keys, env) => - List.fold_right( - (key, env) => extend(env, (key, Var(key) |> IdTagged.fresh)), - keys, - env, - ); + /* Extend the environment with new bindings. ~frame is an optional argument which + * will add an entry in a stack of environment ids, used to track closure + * indications for live value probes. Currently this is used to create new + * frames for each environment created by a function application, and the + * syntax ids of those applications are used as the Id for the frame */ + let extend_eval = + (~frame: option(Id.t)=None, new_bindings: Environment.t, to_extend: t) + : t => + { + id: Id.mk(), + env: Environment.union(new_bindings, map_of(to_extend)), + stack: stack_of(to_extend), + } + |> update_stack(frame); } and StepperFilterKind: { [@deriving (show({with_path: false}), sexp, yojson)] diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index 66ef089cc4..ad064dbd86 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -39,7 +39,7 @@ let stack = (di: option(Dynamics.Info.t)) => { switch (di) { | Some(di) => List.map( - (pi: Dynamics.Probe.Info.t) => pi.stack |> TermBase.show_probe_stack, + (pi: Dynamics.Probe.Info.t) => pi.stack |> Probe.show_stack, di.vals, ) |> String.concat(", ") @@ -119,7 +119,7 @@ let env_cursor: ref(list(Id.t)) = ref([]); let mousedown: ref(option(Js.t(Dom_html.element))) = ref(Option.None); -let env_cursor_of_stack = List.map((en: TermBase.probe_frame) => en.env_id); +let env_cursor_of_stack = List.map((en: Probe.frame) => en.env_id); /* given two env_cursors,return their maximum common suffix */ let max_common_suffix = (a: list('a), b: list('a)) => { @@ -211,7 +211,7 @@ let vals_div = }), Attr.on_mousemove(e => switch (mousedown^) { - | Some(_elem) => + | Some(_elem) when e##.shiftKey |> Js.to_bool => /* Ideally this would be onpointermove and we could just use hasPointerCapture... */ // print_endline("mousemove:down"); let goal = diff --git a/src/haz3lweb/www/style/projectors.css b/src/haz3lweb/www/style/projectors.css index fbe2d753b3..cf48ea2ec1 100644 --- a/src/haz3lweb/www/style/projectors.css +++ b/src/haz3lweb/www/style/projectors.css @@ -171,7 +171,7 @@ /* Probe */ -.live-offside { +.projector.probe .live-offside { display: flex; flex-direction: row; position: absolute; @@ -179,17 +179,30 @@ gap: 0.15em; } -.live-offside .code { +.projector.probe .wrap .code-text { background-color: #c7e6c6; - border-radius: 0.2em; - /* border-top: 1px solid white; */ + border-radius: 0.1em; filter: drop-shadow(0.7px 0.7px 0px green); - opacity: 60%; } - -.projector.probe.indicated .live-offside .code { - background-color: #a4d7a2; - opacity: 100% +.projector.probe .wrap:hover > .code-text { + border-radius: 0.2em 0.2em 0.2em 0; + cursor: col-resize; +} +.projector.probe .wrap.cursor .code-text { + outline: 1px solid var(--GB1); + outline-style: dotted; + filter: none; +} +.projector.probe .wrap .code-text { + opacity: 50%; +} +.projector.probe .wrap.cursor .code-text, +.projector.probe.indicated .wrap .code-text { + opacity: 100%; +} +.projector.probe.indicated .wrap.cursor .code-text { + opacity: 100%; + outline: 1px solid var(--GB1); } .projector.probe > svg { @@ -224,10 +237,7 @@ gap: 0.3em; } -.projector.probe .wrap.cursor { - outline: 1px solid var(--R1); - border-radius: 0.2em; -} + .projector.probe.indicated .wrap:not(.cursor) { opacity: 60%; } @@ -238,7 +248,3 @@ .projector.probe .wrap:hover .live-env:empty { display: none; } -.projector.probe .wrap:hover > .code { - border-radius: 0.2em 0.2em 0.2em 0; - cursor: col-resize; -} \ No newline at end of file From 18c475167f54e0a0b649ac0dba5c7ddf01786365 Mon Sep 17 00:00:00 2001 From: disconcision Date: Tue, 10 Dec 2024 17:22:32 -0500 Subject: [PATCH 13/23] live projectors cleanup --- src/haz3lcore/Measured.re | 7 ++ src/haz3lcore/statics/MakeTerm.re | 1 + src/haz3lcore/zipper/Editor.re | 3 - src/haz3lcore/zipper/Projector.re | 13 --- src/haz3lcore/zipper/ProjectorBase.re | 26 ++++-- src/haz3lcore/zipper/action/Indicated.re | 12 +++ .../zipper/action/ProjectorPerform.re | 2 +- .../zipper/projectors/CheckboxProj.re | 2 +- src/haz3lcore/zipper/projectors/FoldProj.re | 2 +- src/haz3lcore/zipper/projectors/InfoProj.re | 2 +- src/haz3lcore/zipper/projectors/ProbeProj.re | 32 +++---- .../zipper/projectors/SliderFProj.re | 2 +- src/haz3lcore/zipper/projectors/SliderProj.re | 2 +- .../zipper/projectors/TextAreaProj.re | 2 +- src/haz3lweb/app/common/ProjectorView.re | 89 ++++++++++--------- .../app/editors/decoration/BackpackView.re | 7 +- src/haz3lweb/app/inspector/ProjectorPanel.re | 4 +- 17 files changed, 113 insertions(+), 95 deletions(-) diff --git a/src/haz3lcore/Measured.re b/src/haz3lcore/Measured.re index 81be465ce7..a524971079 100644 --- a/src/haz3lcore/Measured.re +++ b/src/haz3lcore/Measured.re @@ -402,3 +402,10 @@ let length = (seg: Segment.t, map: t): int => let last = find_p(ListUtil.last(tl), map); last.last.col - first.origin.col; }; + +/* Width in characters of row at measurement.origin */ +let start_row_width = (measurement: measurement, measured: t): int => + switch (IntMap.find_opt(measurement.origin.row, measured.rows)) { + | None => 0 + | Some(row) => row.max_col + }; diff --git a/src/haz3lcore/statics/MakeTerm.re b/src/haz3lcore/statics/MakeTerm.re index 9ef1538c47..c8bd300d42 100644 --- a/src/haz3lcore/statics/MakeTerm.re +++ b/src/haz3lcore/statics/MakeTerm.re @@ -112,6 +112,7 @@ let return = (wrap, ids, tm) => { /* Map to collect projector ids */ let projectors: ref(Id.Map.t(Piece.projector)) = ref(Id.Map.empty); +/* Temporary form to persist projector dynamics probe to segments */ let mk_probe = (id, e: list(Piece.t)) => Piece.mk_tile_id( id, diff --git a/src/haz3lcore/zipper/Editor.re b/src/haz3lcore/zipper/Editor.re index 51e07f2a76..6ad2f796a7 100644 --- a/src/haz3lcore/zipper/Editor.re +++ b/src/haz3lcore/zipper/Editor.re @@ -129,9 +129,6 @@ module Model = { }; }; }; - - let indicated_projector = (editor: t) => - Projector.indicated(editor.state.zipper); }; module Update = { diff --git a/src/haz3lcore/zipper/Projector.re b/src/haz3lcore/zipper/Projector.re index 4ee7ab9f71..c80a10acc5 100644 --- a/src/haz3lcore/zipper/Projector.re +++ b/src/haz3lcore/zipper/Projector.re @@ -54,16 +54,3 @@ let token_of_proj = | Block({row, col}) => String.make(row - 1, '\n') ++ String.make(col, ' ') }; }; - -/* Returns the projector at the caret, if any */ -let indicated = (z: ZipperBase.t) => { - open Util.OptUtil.Syntax; - let* id = Indicated.index(z); - let* (p, _, _) = Indicated.piece(z); - let+ projector = - switch (p) { - | Projector(pr) => Some(pr) - | _ => None - }; - (id, projector); -}; diff --git a/src/haz3lcore/zipper/ProjectorBase.re b/src/haz3lcore/zipper/ProjectorBase.re index 4818961c09..24b224222c 100644 --- a/src/haz3lcore/zipper/ProjectorBase.re +++ b/src/haz3lcore/zipper/ProjectorBase.re @@ -36,13 +36,25 @@ type info = { dynamics: option(Dynamics.Info.t), }; -//TODO(andrew): rm or doc +/* Utility functions/values for to projector views. + * These should be considered unstable/experimental + * features which have yet to be integrated into the + * projector API in a disciplined way */ [@deriving (show({with_path: false}), sexp, yojson)] -type bonus_pack = { - view: (Sort.t, Base.segment) => Node.t, +type utility = { + /* The current font metrics for the editor, usable + * to coordinate with the parent coordinate grid */ font_metrics: FontMetrics.t, + /* X position in pixels of the end of the row where + * where the projector starts; usable to position part + * of the projector UI at the end of the row */ + offside_offset: float, + /* Non-interactive view for segments, included here + * because of cyclic dependency issues*/ + view: (Sort.t, Base.segment) => Node.t, + /* Convert an expression to a segment, included here + * because of cyclic dependency issues*/ exp_to_seg: Exp.t => Base.segment, - offside_offset: float /* to position something to rhs of code */ }; /* To add a new projector: @@ -94,7 +106,7 @@ module type Projector = { ~info: info, ~local: action => Ui_effect.t(unit), ~parent: external_action => Ui_effect.t(unit), - ~bonus_pack: bonus_pack + ~utility: utility ) => Node.t; /* How much space should be left in the code view for @@ -134,13 +146,13 @@ module Cook = (C: Projector) : Cooked => { let init = C.init |> serialize_m; let can_project = C.can_project; let can_focus = C.can_focus; - let view = (m, ~info, ~local, ~parent, ~bonus_pack) => + let view = (m, ~info, ~local, ~parent, ~utility) => C.view( deserialize_m(m), ~info, ~local=a => local(serialize_a(a)), ~parent, - ~bonus_pack, + ~utility, ); let placeholder = m => m |> Sexplib.Sexp.of_string |> C.model_of_sexp |> C.placeholder; diff --git a/src/haz3lcore/zipper/action/Indicated.re b/src/haz3lcore/zipper/action/Indicated.re index 6cfbc5f997..47fd56d945 100644 --- a/src/haz3lcore/zipper/action/Indicated.re +++ b/src/haz3lcore/zipper/action/Indicated.re @@ -91,6 +91,18 @@ let index = (z: ZipperBase.t): option(Id.t) => | Some((p, _, _)) => Some(Piece.id(p)) }; +/* Returns the projector at the caret, if any */ +let projector = (z: ZipperBase.t) => { + let* id = index(z); + let* (p, _, _) = piece(z); + let+ projector = + switch (p) { + | Projector(pr) => Some(pr) + | _ => None + }; + (id, projector); +}; + let piece'' = piece'(~no_ws=true, ~ign=Piece.is_secondary); let ci_of = diff --git a/src/haz3lcore/zipper/action/ProjectorPerform.re b/src/haz3lcore/zipper/action/ProjectorPerform.re index 841845163f..647802269a 100644 --- a/src/haz3lcore/zipper/action/ProjectorPerform.re +++ b/src/haz3lcore/zipper/action/ProjectorPerform.re @@ -101,7 +101,7 @@ let go = jump_to_id_indicated(z, id) |> Option.value(~default=z) | Some(_) => z }; - switch (Projector.indicated(z)) { + switch (Indicated.projector(z)) { | Some((_, p)) => let (module P) = to_module(p.kind); P.focus((id, d)); diff --git a/src/haz3lcore/zipper/projectors/CheckboxProj.re b/src/haz3lcore/zipper/projectors/CheckboxProj.re index 28160358e9..73c2c60b12 100644 --- a/src/haz3lcore/zipper/projectors/CheckboxProj.re +++ b/src/haz3lcore/zipper/projectors/CheckboxProj.re @@ -30,7 +30,7 @@ let view = ~info, ~local as _, ~parent: external_action => Ui_effect.t(unit), - ~bonus_pack as _, + ~utility as _, ) => Node.input( ~attrs= diff --git a/src/haz3lcore/zipper/projectors/FoldProj.re b/src/haz3lcore/zipper/projectors/FoldProj.re index fb1e4dae24..af47bb2558 100644 --- a/src/haz3lcore/zipper/projectors/FoldProj.re +++ b/src/haz3lcore/zipper/projectors/FoldProj.re @@ -20,7 +20,7 @@ module M: Projector = { let placeholder = (m, _) => Inline(m.text == "⋱" ? 2 : m.text |> String.length); let update = (m, _) => m; - let view = (m: model, ~info as _, ~local as _, ~parent, ~bonus_pack as _) => + let view = (m: model, ~info as _, ~local as _, ~parent, ~utility as _) => div( ~attrs=[Attr.on_double_click(_ => parent(Remove))], [text(m.text)], diff --git a/src/haz3lcore/zipper/projectors/InfoProj.re b/src/haz3lcore/zipper/projectors/InfoProj.re index 55983b0446..5f9fde0273 100644 --- a/src/haz3lcore/zipper/projectors/InfoProj.re +++ b/src/haz3lcore/zipper/projectors/InfoProj.re @@ -76,7 +76,7 @@ module M: Projector = { | (ToggleDisplay, Self) => Expected }; - let view = (model, ~info, ~local, ~parent as _, ~bonus_pack as _) => + let view = (model, ~info, ~local, ~parent as _, ~utility as _) => div( ~attrs=[ Attr.classes(["info", "code"]), diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index ad064dbd86..66a33347b7 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -64,7 +64,7 @@ let vals = (di: option(Dynamics.Info.t), exp_to_seg) => { }; }; -let env_val = (en: Dynamics.Probe.Env.entry, bonus_pack: bonus_pack) => { +let env_val = (en: Dynamics.Probe.Env.entry, utility: utility) => { Node.div( ~attrs=[Attr.classes(["live-env-entry"])], [ @@ -75,8 +75,8 @@ let env_val = (en: Dynamics.Probe.Env.entry, bonus_pack: bonus_pack) => { d |> Abbreviate.abbreviate_exp |> fst - |> bonus_pack.exp_to_seg - |> bonus_pack.view(Exp) + |> utility.exp_to_seg + |> utility.view(Exp) }, ], ); @@ -96,19 +96,19 @@ let rm_fst = (xs: list('a)) => | [_, ...rest] => rest }; -let env_div2 = (pi: Dynamics.Probe.Info.t, bonus_pack: bonus_pack) => { +let env_div2 = (pi: Dynamics.Probe.Info.t, utility: utility) => { Node.div( ~attrs=[Attr.classes(["live-env"])], - pi.env |> rm_fst |> rm_opaques |> List.map(en => env_val(en, bonus_pack)), + pi.env |> rm_fst |> rm_opaques |> List.map(en => env_val(en, utility)), ); }; -let env_div = (di: Dynamics.Info.t, bonus_pack: bonus_pack) => { +let env_div = (di: Dynamics.Info.t, utility: utility) => { Node.div( ~attrs=[Attr.classes(["live-env"])], List.concat_map( (pi: Dynamics.Probe.Info.t) => { - pi.env |> List.map(en => env_val(en, bonus_pack)) + pi.env |> List.map(en => env_val(en, utility)) }, di.vals, ), @@ -159,7 +159,7 @@ let comparor = (a: Dynamics.Probe.Info.t, b: Dynamics.Probe.Info.t) => { }; let vals_div = - (di: option(Dynamics.Info.t), ~model, ~local, ~bonus_pack: bonus_pack) => { + (di: option(Dynamics.Info.t), ~model, ~local, ~utility: utility) => { //dprint_endline("available:" ++ string_of_int(model.len)); let filter_vals = vals => { switch (List.sort(comparor, vals)) { @@ -177,7 +177,7 @@ let vals_div = "style", Printf.sprintf( "position: absolute; left: %fpx;", - bonus_pack.offside_offset, + utility.offside_offset, ), ), ], @@ -216,7 +216,7 @@ let vals_div = // print_endline("mousemove:down"); let goal = FontMetrics.get_goal( - ~font_metrics=bonus_pack.font_metrics, + ~font_metrics=utility.font_metrics, e##.currentTarget |> Js.Opt.get(_, _ => failwith("")) |> JsUtil.get_child_with_class(_, "code") @@ -235,16 +235,16 @@ let vals_div = |> DHExp.strip_casts |> Abbreviate.abbreviate_exp(~available=model.len) |> fst - |> bonus_pack.exp_to_seg - |> bonus_pack.view(Exp), + |> utility.exp_to_seg + |> utility.view(Exp), //pi.stack |> TermBase.show_probe_stack |> Node.text, - env_div2(pi, bonus_pack), + env_div2(pi, utility), ], ) }, filter_vals(di.vals), ), - //@ [env_div(di, bonus_pack)], + //@ [env_div(di, utility)], ) | _ => Node.div(~attrs=[Attr.classes(["live-offside"])], [Node.text("?")]) @@ -275,14 +275,14 @@ let _ = Zipper.base_point; let code_str = (info: info) => [info.syntax] |> Printer.of_segment(~holes=None); -let view = (model: t, ~info, ~local, ~parent as _, ~bonus_pack: bonus_pack) => +let view = (model: t, ~info, ~local, ~parent as _, ~utility: utility) => div( ~attrs=[ Attr.title(stack(info.dynamics)), Attr.on_double_click(_ => local(ToggleShowAllVals)), ], [ - vals_div(info.dynamics, ~model, ~local, ~bonus_pack), + vals_div(info.dynamics, ~model, ~local, ~utility), text("🔍 " ++ code_str(info)), ], ); diff --git a/src/haz3lcore/zipper/projectors/SliderFProj.re b/src/haz3lcore/zipper/projectors/SliderFProj.re index c436662b6f..713315d395 100644 --- a/src/haz3lcore/zipper/projectors/SliderFProj.re +++ b/src/haz3lcore/zipper/projectors/SliderFProj.re @@ -32,7 +32,7 @@ module M: Projector = { ~info, ~local as _, ~parent: external_action => Ui_effect.t(unit), - ~bonus_pack as _, + ~utility as _, ) => Util.Web.range( ~attrs=[Attr.on_input((_, v) => parent(SetSyntax(put(v))))], diff --git a/src/haz3lcore/zipper/projectors/SliderProj.re b/src/haz3lcore/zipper/projectors/SliderProj.re index 0fc04a9ed6..5d7b3fe5a4 100644 --- a/src/haz3lcore/zipper/projectors/SliderProj.re +++ b/src/haz3lcore/zipper/projectors/SliderProj.re @@ -29,7 +29,7 @@ module M: Projector = { ~info, ~local as _, ~parent: external_action => Ui_effect.t(unit), - ~bonus_pack as _, + ~utility as _, ) => Util.Web.range( ~attrs=[Attr.on_input((_, v) => parent(SetSyntax(put(v))))], diff --git a/src/haz3lcore/zipper/projectors/TextAreaProj.re b/src/haz3lcore/zipper/projectors/TextAreaProj.re index d255ff0ba1..63748ea0f9 100644 --- a/src/haz3lcore/zipper/projectors/TextAreaProj.re +++ b/src/haz3lcore/zipper/projectors/TextAreaProj.re @@ -77,7 +77,7 @@ let textarea = [], ); -let view = (_, ~info, ~local as _, ~parent, ~bonus_pack as _) => { +let view = (_, ~info, ~local as _, ~parent, ~utility as _) => { let text = info.syntax |> get |> Form.strip_quotes; Node.div( ~attrs=[Attr.classes(["wrapper"])], diff --git a/src/haz3lweb/app/common/ProjectorView.re b/src/haz3lweb/app/common/ProjectorView.re index 21a3b16ae9..5212457892 100644 --- a/src/haz3lweb/app/common/ProjectorView.re +++ b/src/haz3lweb/app/common/ProjectorView.re @@ -7,12 +7,10 @@ open Util; open Util.OptUtil.Syntax; open Util.Web; -type kind = Base.kind; - /* A friendly name for each projector. This is used * both for identifying a projector in the CSS and for * selecting projectors in the projector panel menu */ -let name = (p: kind): string => +let name = (p: Base.kind): string => switch (p) { | Fold => "fold" | Info => "type" @@ -26,7 +24,7 @@ let name = (p: kind): string => /* This must be updated and kept 1-to-1 with the above * name function in order to be able to select the * projector in the projector panel menu */ -let of_name = (p: string): kind => +let of_name = (p: string): Base.kind => switch (p) { | "fold" => Fold | "type" => Info @@ -111,32 +109,56 @@ let handle = (id, action: external_action): Action.project => | SetSyntax(f) => SetSyntax(id, f) }; -let code_width = (measured: Measured.t) => - IntMap.fold( - (_, {max_col, _}: Measured.Rows.shape, acc) => max(max_col, acc), - measured.rows, - 0, - ); - -let projector_start_row_width = - (measurement: Measured.measurement, measured: Measured.t) => - switch (IntMap.find_opt(measurement.origin.row, measured.rows)) { - | None => 0 - | Some(row) => row.max_col - }; - +/* Position in pixels for the position offset characters to the + * right of the end of the row at measurement.origin. */ let offside = ( + ~offset: int, font_metrics: FontMetrics.t, measurement: Measured.measurement, measured: Measured.t, - ) => { + ) + : float => font_metrics.col_width *. float_of_int( - projector_start_row_width(measurement, measured) - + 4 + Measured.start_row_width(measurement, measured) + + offset - measurement.origin.col, ); + +/* Gather utility functions/values to be passed to the projector. + * See ProjectorBase.utility definition for more information */ +let collate_utility = + ( + globals: Globals.t, + measurement: Measured.measurement, + cached_syntax: Editor.CachedSyntax.t, + ) + : ProjectorBase.utility => { + { + font_metrics: globals.font_metrics, + offside_offset: + offside( + ~offset=4, + globals.font_metrics, + measurement, + cached_syntax.measured, + ), + view: (sort, seg) => + CodeViewable.view_segment(~globals, ~sort, ~token_of_proj=_ => "", seg), + exp_to_seg: exp => + exp + |> DHExp.strip_casts + |> ExpToSegment.exp_to_segment( + ~settings={ + inline: false, + fold_case_clauses: false, + fold_fn_bodies: false, + hide_fixpoints: false, + fold_cast_types: false, + }, + ), + }; }; /* Extracts projector-instance-specific metadata necessary to @@ -160,25 +182,7 @@ let setup_view = let dynamics = Dynamics.Map.lookup(id, dynamics); let info = {id, statics, dynamics, syntax}; let+ measurement = Measured.find_pr_opt(p, cached_syntax.measured); - let bonus_pack = { - view: (sort, seg) => - CodeViewable.view_segment(~globals, ~sort, ~token_of_proj=_ => "", seg), - exp_to_seg: exp => - exp - |> DHExp.strip_casts - |> ExpToSegment.exp_to_segment( - ~settings={ - inline: false, - fold_case_clauses: false, - fold_fn_bodies: false, - hide_fixpoints: false, - fold_cast_types: false, - }, - ), - font_metrics: globals.font_metrics, - offside_offset: - offside(globals.font_metrics, measurement, cached_syntax.measured), - }; + let utility = collate_utility(globals, measurement, cached_syntax); let (module P) = to_module(p.kind); let parent = a => inject(Project(handle(id, a))); let local = a => inject(Project(SetModel(id, P.update(p.model, a)))); @@ -190,10 +194,11 @@ let setup_view = ~info, ~selected=List.mem(id, cached_syntax.selection_ids), p, - P.view(p.model, ~info, ~local, ~parent, ~bonus_pack), + P.view(p.model, ~info, ~local, ~parent, ~utility), ); }; +/* Is the piece with id indicated? If so, where is it wrt the caret? */ let indication = (z, id) => switch (Indicated.piece(z)) { | Some((p, d, _)) when Piece.id(p) == id => Some(Direction.toggle(d)) @@ -240,7 +245,7 @@ let all = * For example, without the modifiers check, this would break selection * around a projector. */ let key_handoff = (editor: Editor.t, key: Key.t): option(Action.project) => - switch (Editor.Model.indicated_projector(editor)) { + switch (Indicated.projector(editor.state.zipper)) { | None => None | Some((id, p)) => let* (_, d, _) = Indicated.piece(editor.state.zipper); diff --git a/src/haz3lweb/app/editors/decoration/BackpackView.re b/src/haz3lweb/app/editors/decoration/BackpackView.re index 205b61c8ef..5ab468e0bf 100644 --- a/src/haz3lweb/app/editors/decoration/BackpackView.re +++ b/src/haz3lweb/app/editors/decoration/BackpackView.re @@ -3,12 +3,9 @@ open Node; open Haz3lcore; open Util; -/* Assume this doesn't contain projectors */ -let token_of_proj = _ => ""; +let token_of_proj = _ => ""; /* Assume this doesn't contain projectors */ -let measured_of = seg => - /* Assume this doesn't contain projectors */ - Measured.of_segment(seg, token_of_proj); +let measured_of = seg => Measured.of_segment(seg, token_of_proj); let text_view = (seg: Segment.t): list(Node.t) => { module Text = diff --git a/src/haz3lweb/app/inspector/ProjectorPanel.re b/src/haz3lweb/app/inspector/ProjectorPanel.re index 458c1bbe6c..6280f91931 100644 --- a/src/haz3lweb/app/inspector/ProjectorPanel.re +++ b/src/haz3lweb/app/inspector/ProjectorPanel.re @@ -75,14 +75,14 @@ let toggle_view = let kind = (editor: option(Editor.t)) => { let* editor = editor; - let+ (_, p) = Editor.Model.indicated_projector(editor); + let+ (_, p) = Indicated.projector(editor.state.zipper); p.kind; }; let id = (editor: option(Editor.t)) => { { let* editor = editor; - let+ (id, _) = Editor.Model.indicated_projector(editor); + let+ (id, _) = Indicated.projector(editor.state.zipper); id; } |> Option.value(~default=Id.invalid); From 2f94e7a1b3c5d1483d667da24c88e75d24ab6b91 Mon Sep 17 00:00:00 2001 From: disconcision Date: Tue, 10 Dec 2024 20:55:23 -0500 Subject: [PATCH 14/23] live projectors view cleanup --- src/haz3lcore/zipper/projectors/ProbeProj.re | 398 +++++++++---------- src/haz3lweb/www/style/projectors.css | 104 +++-- 2 files changed, 252 insertions(+), 250 deletions(-) diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index 66a33347b7..e2d6cb1a79 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -4,6 +4,25 @@ open Virtual_dom.Vdom; open Node; open Js_of_ocaml; +//let _ = Zipper.base_point; //not cyclical +//let _ = Editor.show; //cyclical + +/* Plan for extended dynamics: + in principle we could track: + - prev 'stack frame': id of prev expr that began execution? + - env (or just the part of the env that's relevant to the current expr) + aka the co-ctx + + */ + +/* Plan for selectable (editable as well maybe): + + selectable: + create own mini version of editor, use it as model + update fn updates model after applying mini editor action + hopefully can use same actions + */ + [@deriving (show({with_path: false}), sexp, yojson)] type t = { [@default "⋱"] @@ -17,24 +36,6 @@ type a = | ChangeLength(int) | ToggleShowAllVals; -/* Proof of concept value exposure. This isn't getting set immediately - after folding for some reason */ -let _vals = (di: option(Dynamics.Info.t)) => { - switch (di) { - | Some(di) => - List.map( - (pi: Dynamics.Probe.Info.t) => - pi.value - |> DHExp.strip_casts - |> (d => d.term) - |> TermBase.Exp.show_term, - di.vals, - ) - |> String.concat(", ") - | _ => "Nein" - }; -}; - let stack = (di: option(Dynamics.Info.t)) => { switch (di) { | Some(di) => @@ -47,23 +48,105 @@ let stack = (di: option(Dynamics.Info.t)) => { }; }; -let vals = (di: option(Dynamics.Info.t), exp_to_seg) => { - switch (di) { - | Some(di) => - List.map( - (pi: Dynamics.Probe.Info.t) => - pi.value - |> DHExp.strip_casts - |> Abbreviate.abbreviate_exp - |> exp_to_seg - |> Printer.of_segment(~holes=None), - di.vals, - ) - |> String.concat(", ") - | _ => "Nein" - }; +/* given two env_cursors,return their maximum common suffix */ +let max_common_suffix = (a: list('a), b: list('a)) => { + let rec loop = (a, b, acc) => + switch (a, b) { + | ([], _) + | (_, []) => acc + | ([ha, ...ta], [hb, ...tb]) when ha == hb => + loop(ta, tb, [ha, ...acc]) + | _ => acc + }; + loop(List.rev(a), List.rev(b), []); }; +let env_cursor: ref(list(Id.t)) = ref([]); +let last_target: ref(list('a)) = ref([]); +let mousedown: ref(option(Js.t(Dom_html.element))) = ref(Option.None); +let env_cursor_of_stack = List.map((en: Probe.frame) => en.env_id); + +let show_indicator = stack => { + let local = env_cursor_of_stack(stack); + env_cursor^ == local + || (max_common_suffix(env_cursor^, local) != [] || local == []) + && List.length(env_cursor^) != List.length(local); +}; + +let common_suffix_length = (s1, s2) => + List.length(max_common_suffix(s1, s2)); + +let comparor = (a: Dynamics.Probe.Info.t, b: Dynamics.Probe.Info.t) => { + compare( + common_suffix_length(env_cursor^, env_cursor_of_stack(b.stack)), + common_suffix_length(env_cursor^, env_cursor_of_stack(a.stack)), + ); +}; + +let seg_view = (utility, available, seg) => + seg + |> DHExp.strip_casts + |> Abbreviate.abbreviate_exp(~available) + |> fst + |> utility.exp_to_seg + |> utility.view(Exp); + +let get_goal = (utility: utility, e: Js.t(Dom_html.mouseEvent)) => + FontMetrics.get_goal( + ~font_metrics=utility.font_metrics, + e##.currentTarget + |> Js.Opt.get(_, _ => failwith("")) + |> JsUtil.get_child_with_class(_, "code") + |> Option.get, + e |> Js.Unsafe.coerce, + ); + +let resizable_val = + (~resizable=false, model, utility, local, pi: Dynamics.Probe.Info.t) => + div( + ~attrs=[ + Attr.classes( + ["val-resize"] @ (show_indicator(pi.stack) ? ["cursor"] : []), + ), + Attr.on_pointerdown(e => { + // print_endline("pointerdown"); + let target = e##.target |> Js.Opt.get(_, _ => failwith("no target")); + JsUtil.setPointerCapture(target, e##.pointerId) |> ignore; + mousedown := Some(target); + if (last_target^ == [target]) { + env_cursor := []; + last_target := []; + } else { + env_cursor := env_cursor_of_stack(pi.stack); + last_target := [target]; + }; + Effect.Ignore; + }), + Attr.on_pointerup(e => { + // print_endline("pointerup"); + switch (mousedown^) { + | Some(target) => + JsUtil.releasePointerCapture(target, e##.pointerId) |> ignore + | None => () + }; + mousedown := None; + Effect.Ignore; + }), + Attr.on_mousemove(e => + switch (mousedown^) { + | Some(_elem) when Js.to_bool(e##.shiftKey) && resizable => + /* Ideally this would be onpointermove and we could just use hasPointerCapture... */ + let goal = get_goal(utility, e); + local(ChangeLength(goal.col)); + | _ => + // print_endline("mousemove:up"); + Effect.Ignore + } + ), + ], + [seg_view(utility, model.len, pi.value)], + ); + let env_val = (en: Dynamics.Probe.Env.entry, utility: utility) => { Node.div( ~attrs=[Attr.classes(["live-env-entry"])], @@ -71,12 +154,7 @@ let env_val = (en: Dynamics.Probe.Env.entry, utility: utility) => { Node.text(en.name ++ "="), switch (en.raw) { | Opaque => Node.text("Opaque") - | Val(d) => - d - |> Abbreviate.abbreviate_exp - |> fst - |> utility.exp_to_seg - |> utility.view(Exp) + | Val(d) => seg_view(utility, 12, d) }, ], ); @@ -90,202 +168,94 @@ let rm_opaques = } ); -let rm_fst = (xs: list('a)) => - switch (xs) { - | [] => [] - | [_, ...rest] => rest +let is_var_ref = info => + switch (MakeTerm.go([info.syntax]).term.term) { + | Var(_) => true + | _ => false }; -let env_div2 = (pi: Dynamics.Probe.Info.t, utility: utility) => { +let env_view = (pi: Dynamics.Probe.Info.t, utility: utility) => Node.div( ~attrs=[Attr.classes(["live-env"])], - pi.env |> rm_fst |> rm_opaques |> List.map(en => env_val(en, utility)), + pi.env |> rm_opaques |> List.map(en => env_val(en, utility)), ); -}; -let env_div = (di: Dynamics.Info.t, utility: utility) => { - Node.div( - ~attrs=[Attr.classes(["live-env"])], - List.concat_map( - (pi: Dynamics.Probe.Info.t) => { - pi.env |> List.map(en => env_val(en, utility)) - }, - di.vals, - ), +let closure_view = + ( + info, + utility: utility, + model: t, + local, + index, + pi: Dynamics.Probe.Info.t, + ) => + div( + ~attrs=[ + Attr.classes( + ["closure"] @ (show_indicator(pi.stack) ? ["cursor"] : []), + ), + ], + [resizable_val(~resizable=index == 0, model, utility, local, pi)] + @ (is_var_ref(info) ? [] : [env_view(pi, utility)]), ); -}; -let env_cursor: ref(list(Id.t)) = ref([]); - -let mousedown: ref(option(Js.t(Dom_html.element))) = ref(Option.None); - -let env_cursor_of_stack = List.map((en: Probe.frame) => en.env_id); - -/* given two env_cursors,return their maximum common suffix */ -let max_common_suffix = (a: list('a), b: list('a)) => { - let rec loop = (a, b, acc) => - switch (a, b) { - | ([], _) - | (_, []) => acc - | ([ha, ...ta], [hb, ...tb]) when ha == hb => - loop(ta, tb, [ha, ...acc]) - | _ => acc - }; - loop(List.rev(a), List.rev(b), []); -}; - -let show_indicator = stack => { - let local = stack |> env_cursor_of_stack; - if (env_cursor^ == local) { - true; - } else if (max_common_suffix(env_cursor^, local) != []) { - !( - List.length(env_cursor^) == List.length(local) && env_cursor^ != local - ); - } else { - false; +let select_vals = (model: t, vals) => { + switch (List.sort(comparor, vals)) { + | [] => [] + | [hd, ..._] when !model.show_all_vals => [hd] + | _ => vals }; }; -let comparor = (a: Dynamics.Probe.Info.t, b: Dynamics.Probe.Info.t) => { - compare( - List.length( - max_common_suffix(env_cursor^, env_cursor_of_stack(b.stack)), - ), - List.length( - max_common_suffix(env_cursor^, env_cursor_of_stack(a.stack)), - ), +let offside_pos = utility => + Attr.create( + "style", + Printf.sprintf("position: absolute; left: %fpx;", utility.offside_offset), ); -}; -let vals_div = - (di: option(Dynamics.Info.t), ~model, ~local, ~utility: utility) => { - //dprint_endline("available:" ++ string_of_int(model.len)); - let filter_vals = vals => { - switch (List.sort(comparor, vals)) { - | [] => [] - | [hd, ..._] when !model.show_all_vals => [hd] - | _ => vals - }; - }; - switch (di) { - | Some(di) => - Node.div( - ~attrs=[ - Attr.classes(["live-offside"]), - Attr.create( - "style", - Printf.sprintf( - "position: absolute; left: %fpx;", - utility.offside_offset, - ), - ), - ], - List.map( - (pi: Dynamics.Probe.Info.t) => { - // print_endline("pi.env:" ++ Dynamics.Probe.Env.show(pi.env)); - div( - ~attrs=[ - Attr.classes( - ["wrap"] @ (show_indicator(pi.stack) ? ["cursor"] : []), - ), - Attr.on_pointerdown(e => { - // print_endline("pointerdown"); - let target = - e##.target |> Js.Opt.get(_, _ => failwith("no target")); - JsUtil.setPointerCapture(target, e##.pointerId) |> ignore; - mousedown := Some(target); - env_cursor := pi.stack |> env_cursor_of_stack; - Effect.Ignore; - }), - Attr.on_pointerup(e => { - // print_endline("pointerup"); - switch (mousedown^) { - | Some(target) => - JsUtil.releasePointerCapture(target, e##.pointerId) - |> ignore - | _ => () - }; - mousedown := None; - Effect.Ignore; - }), - Attr.on_mousemove(e => - switch (mousedown^) { - | Some(_elem) when e##.shiftKey |> Js.to_bool => - /* Ideally this would be onpointermove and we could just use hasPointerCapture... */ - // print_endline("mousemove:down"); - let goal = - FontMetrics.get_goal( - ~font_metrics=utility.font_metrics, - e##.currentTarget - |> Js.Opt.get(_, _ => failwith("")) - |> JsUtil.get_child_with_class(_, "code") - |> Option.get, - e |> Js.Unsafe.coerce, - ); - local(ChangeLength(goal.col)); - | _ => - print_endline("mousemove:up"); - Effect.Ignore; - } - ), - ], - [ - pi.value - |> DHExp.strip_casts - |> Abbreviate.abbreviate_exp(~available=model.len) - |> fst - |> utility.exp_to_seg - |> utility.view(Exp), - //pi.stack |> TermBase.show_probe_stack |> Node.text, - env_div2(pi, utility), - ], - ) - }, - filter_vals(di.vals), - ), - //@ [env_div(di, utility)], - ) - | _ => - Node.div(~attrs=[Attr.classes(["live-offside"])], [Node.text("?")]) - }; +let offside_view = (info, ~model, ~local, ~utility: utility) => { + Node.div( + ~attrs=[Attr.classes(["live-offside"]), offside_pos(utility)], + switch (info.dynamics) { + | Some(di) => + List.mapi( + closure_view(info, utility, model, local), + select_vals(model, di.vals), + ) + | _ => [] + }, + ); }; -let _ = Zipper.base_point; -//let _ = Editor.show; - -/* Plan for extended dynamics: - in principle we could track: - - prev 'stack frame': id of prev expr that began execution? - - env (or just the part of the env that's relevant to the current expr) - aka the co-ctx - - */ - -/* Plan for selectable (editable as well maybe): - - selectable: - create own mini version of editor, use it as model - update fn updates model after applying mini editor action - hopefully can use same actions - +let num_closures = (info: info) => + switch (info.dynamics) { + | Some(di) => List.length(di.vals) + | _ => 0 + }; - */ +let num_closures_view = (info: info) => + div( + ~attrs=[Attr.classes(["num-closures"])], + [text(string_of_int(num_closures(info)))], + ); let code_str = (info: info) => [info.syntax] |> Printer.of_segment(~holes=None); +let icon = div(~attrs=[Attr.classes(["icon"])], [text("🔍")]); + let view = (model: t, ~info, ~local, ~parent as _, ~utility: utility) => - div( - ~attrs=[ - Attr.title(stack(info.dynamics)), - Attr.on_double_click(_ => local(ToggleShowAllVals)), - ], - [ - vals_div(info.dynamics, ~model, ~local, ~utility), - text("🔍 " ++ code_str(info)), - ], - ); + div([ + //Attr.title(stack(info.dynamics)), + offside_view(info, ~model, ~local, ~utility), + div( + ~attrs=[ + Attr.classes(["main"]), + Attr.on_double_click(_ => local(ToggleShowAllVals)), + ], + [icon, text(code_str(info)), num_closures_view(info)], + ), + ]); module M: Projector = { [@deriving (show({with_path: false}), sexp, yojson)] diff --git a/src/haz3lweb/www/style/projectors.css b/src/haz3lweb/www/style/projectors.css index cf48ea2ec1..f410999e78 100644 --- a/src/haz3lweb/www/style/projectors.css +++ b/src/haz3lweb/www/style/projectors.css @@ -171,6 +171,48 @@ /* Probe */ +.projector.probe { + font-family: var(--code-font); + font-size: var(--base-font-size); + color: var(--STONE); +} + +.projector.probe .main { + display: flex; + flex-direction: row; + gap: 0.4em; + cursor: pointer; +} + +@keyframes rotate { + to { + transform: rotate(360deg); + } +} + +.projector.probe .live-offside:empty + * .icon { + opacity: 30%; + animation: rotate 5s linear infinite; +} + +.projector.probe > svg { + fill: #c7e6c6; + filter: drop-shadow(0.7px 0.7px 0px green); +} + +.projector.probe.indicated > svg { + fill: #a4d7a2; +} + +.projector.probe .num-closures { + position: absolute; + right: -0.5em; + top: -0.3em; + scale: 0.5; + color: var(--G0); + font-family: var(--ui-font); +} + .projector.probe .live-offside { display: flex; flex-direction: row; @@ -179,72 +221,62 @@ gap: 0.15em; } -.projector.probe .wrap .code-text { +.projector.probe .live-offside:empty { + display: none; +} + +.projector.probe .val-resize .code-text { background-color: #c7e6c6; border-radius: 0.1em; filter: drop-shadow(0.7px 0.7px 0px green); } -.projector.probe .wrap:hover > .code-text { - border-radius: 0.2em 0.2em 0.2em 0; +.projector.probe .closure:first-child .val-resize:hover .code-text { cursor: col-resize; } -.projector.probe .wrap.cursor .code-text { +.projector.probe .val-resize.cursor .code-text { outline: 1px solid var(--GB1); outline-style: dotted; filter: none; } -.projector.probe .wrap .code-text { - opacity: 50%; +.projector.probe .val-resize .code-text { + opacity: 40%; } -.projector.probe .wrap.cursor .code-text, -.projector.probe.indicated .wrap .code-text { - opacity: 100%; +.projector.probe .val-resize.cursor .code-text, +.projector.probe.indicated .val-resize .code-text { + opacity: 70%; } -.projector.probe.indicated .wrap.cursor .code-text { +.projector.probe.indicated .val-resize.cursor .code-text { opacity: 100%; outline: 1px solid var(--GB1); } -.projector.probe > svg { - fill: #c7e6c6; - filter: drop-shadow(0.7px 0.7px 0px green); +.projector.probe.indicated .closure.cursor .live-env { + display: block; } - -.projector.probe.indicated > svg { - fill: #a4d7a2; +.projector.probe.indicated .closure.cursor .live-env:empty { + display: none; } - -.projector.probe { - font-family: var(--code-font); - font-size: var(--base-font-size); - color: var(--STONE); +/* .projector.probe .closure:hover .live-env { + display: block; +} */ +.projector.probe .closure:hover .live-env:empty { + display: none; } .projector.probe .live-env { - background-color: #e7e1cf; + background-color: var(--T3); border-radius: 0 0.2em 0.2em 0.2em; padding: 0.2em; display: none; position: absolute; - top: 1.6em; + top: 1.4em; z-index: 5000; } - .projector.probe .live-env .live-env-entry { display: flex; flex-direction: row; gap: 0.3em; + align-items: baseline; + color: var(--GB0); } - - -.projector.probe.indicated .wrap:not(.cursor) { - opacity: 60%; -} - -.projector.probe .wrap:hover .live-env { - display: block; -} -.projector.probe .wrap:hover .live-env:empty { - display: none; -} From 0ee836b6620d05c36113e25a15a6f32a7cdfb14d Mon Sep 17 00:00:00 2001 From: disconcision Date: Tue, 10 Dec 2024 21:35:09 -0500 Subject: [PATCH 15/23] live projectors in exercises mode --- src/haz3lcore/dynamics/Transition.re | 6 +---- src/haz3lcore/pretty/Abbreviate.re | 38 +++++++--------------------- src/haz3lcore/pretty/ExpToSegment.re | 2 -- src/haz3lcore/prog/Dynamics.re | 1 - src/haz3lcore/statics/Elaborator.re | 1 - src/haz3lcore/statics/MakeTerm.re | 2 +- src/haz3lcore/statics/Statics.re | 1 - src/haz3lcore/statics/TermBase.re | 4 +-- src/haz3lweb/view/ExerciseMode.re | 28 +++++++++++++++----- 9 files changed, 35 insertions(+), 48 deletions(-) diff --git a/src/haz3lcore/dynamics/Transition.re b/src/haz3lcore/dynamics/Transition.re index 934441b721..15644aa99f 100644 --- a/src/haz3lcore/dynamics/Transition.re +++ b/src/haz3lcore/dynamics/Transition.re @@ -783,10 +783,8 @@ module Transition = (EV: EV_MODE) => { let. _ = otherwise(env, d); Indet; | Parens(d'', Probe(pr)) => - // print_endline("Probe:" ++ TermBase.show_probe(pr)); - //TODO(andrew): cleanup let. _ = otherwise(env, ((d, _)) => Parens(d, Probe(pr)) |> rewrap) - and. (d', _is_value) = + and. (d', _) = req_final_or_value( req(state, env), d => Parens(d, Probe(pr)) |> wrap_ctx, @@ -796,8 +794,6 @@ module Transition = (EV: EV_MODE) => { Step({ expr: d', state_update: () => { - //TODO(andrew): should I be putting the env inside update closure? - // are there perf / mem implications? let pi = Dynamics.Probe.Info.mk(d', env, pr); update_probe(state, DHExp.rep_id(d), pi); }, diff --git a/src/haz3lcore/pretty/Abbreviate.re b/src/haz3lcore/pretty/Abbreviate.re index 195bb1c256..69f9cf0787 100644 --- a/src/haz3lcore/pretty/Abbreviate.re +++ b/src/haz3lcore/pretty/Abbreviate.re @@ -1,15 +1,3 @@ -// open Util; -// open PrettySegment; -// open Base; - -/* - To do this with projectors, we'd need: - - 1. way to collapse part of a n-ary form e.g. [1,2,<3,4,5>] => [1, 2, <...>] - 2. way to gather ids (from term?) to collapse - 3. way to apply projectors (to segment) given ids - */ - let comp_elipses = "⋱"; let flat_ellipses = "…"; let ellipses_term = () => IdTagged.fresh(Invalid(comp_elipses): Exp.term); @@ -61,7 +49,6 @@ let rec abbreviate_exp = (exp: Exp.t): Exp.t => { | Fun(_p, _e, _, None) => Invalid("") | BuiltinFun(_f) => Invalid("") | Tuple([_]) => failwith("Singleton Tuples are not allowed") - //TODO(andrew): show exp below? | DynamicErrorHole(_exp, err) => Invalid("<" ++ InvalidOperationError.show(err) ++ ">") @@ -86,6 +73,7 @@ let rec abbreviate_exp = (exp: Exp.t): Exp.t => { // composite literal cases | ListLit(xs) => + //TODO(andrew): improve this logic if (available^ < 6) { ListLit([flat_ellipses_term()]); } else { @@ -131,9 +119,6 @@ let rec abbreviate_exp = (exp: Exp.t): Exp.t => { }; Tuple(go(xs)); | Ap(Forward, {term: Constructor(_str, _), _} as konst, arg) => - // if (String.length(str) + 3 >= available^) { - // abbreviate_exp(konst).term; - // } else { let konst = abbreviate_exp(konst); available := available^ - 2; let arg = @@ -143,25 +128,20 @@ let rec abbreviate_exp = (exp: Exp.t): Exp.t => { ellipses_term(); }; Ap(Forward, konst, arg); - // } - | Cons(e1, _e2) => - //TODO: return used length from call, use that to make incorporate next elems - available := available^ - (2 + String.length(comp_elipses)); - Cons(abbreviate_exp(e1), ellipses_term()); - | Parens(e, pt) => available := available^ - 2; Parens(abbreviate_exp(e), pt); - //TODO(andrew) - | Filter(_) => failwith("TODO(andrew): Filter") - | Closure(_) => failwith("TODO(andrew): Closure") - | MultiHole(_es) => failwith("TODO(andrew)") - | TypFun(_tp, _e, _) => failwith("TODO(andrew)") - | FailedCast(_e, _, _t) => failwith("TODO(andrew)") - | Cast(_e, _, _t) => failwith("TODO(andrew)") + //unhandled atm + | Closure(_) => indet_term + | MultiHole(_es) => indet_term + | TypFun(_tp, _e, _) => indet_term + | FailedCast(_e, _, _t) => indet_term + | Cast(_e, _, _t) => indet_term //non-value + | Cons(_) => indet_term + | Filter(_) => indet_term | Ap(Forward, _e1, _e2) => indet_term | Ap(Reverse, _e1, _e2) => indet_term | Deferral(_d) => indet_term diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index 99b285dccf..c11da79308 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -672,8 +672,6 @@ let rec external_precedence = (exp: Exp.t): Precedence.t => { | Deferral(_) | BuiltinFun(_) | Constructor(_) - //matt says: Constructor is here because we currently always add a type annotation to constructors - //TODO(andrew) says I move this from cast section as sometimes we strip casts? | Undefined => Precedence.max // Same goes for forms which are already surrounded diff --git a/src/haz3lcore/prog/Dynamics.re b/src/haz3lcore/prog/Dynamics.re index 25f4887d40..01f0eae25f 100644 --- a/src/haz3lcore/prog/Dynamics.re +++ b/src/haz3lcore/prog/Dynamics.re @@ -38,7 +38,6 @@ module Probe = { let mk_entry = (env, {name, id, _}: Binding.t) => switch (ClosureEnvironment.lookup(env, name)) { | Some(d) => - //TODO(andrew): does this substitute make sense? let raw = d |> DHExp.strip_casts diff --git a/src/haz3lcore/statics/Elaborator.re b/src/haz3lcore/statics/Elaborator.re index c19d7baf0a..7ac4eb811c 100644 --- a/src/haz3lcore/statics/Elaborator.re +++ b/src/haz3lcore/statics/Elaborator.re @@ -238,7 +238,6 @@ let rec elaborate = (m: Statics.Map.t, uexp: UExp.t): (DHExp.t, Typ.t) => { let (e', ty) = elaborate(m, e); e' |> cast_from(ty); | Parens(e, probe) => - //TODO(andrew): Matthew: Am I casting/rewrapping correctly? let (e', ty) = elaborate(m, e); let probe = Dynamics.Probe.instrument(m, Exp.rep_id(uexp), probe); Parens(e' |> cast_from(ty), probe) |> rewrap; diff --git a/src/haz3lcore/statics/MakeTerm.re b/src/haz3lcore/statics/MakeTerm.re index c8bd300d42..31283b1dc1 100644 --- a/src/haz3lcore/statics/MakeTerm.re +++ b/src/haz3lcore/statics/MakeTerm.re @@ -201,7 +201,7 @@ and exp_term: unsorted => (UExp.term, list(Id.t)) = { ret(Constructor(t, Unknown(Internal) |> Typ.temp)) | (["(", ")"], [Exp(body)]) => ret(Parens(body, Paren)) | (["@@", "@@"], [Exp(body)]) => - // TODO(andrew): apologize for this + // Temporary wrapping form to persist projector probes ret(Parens(body, Probe(Probe.empty))) | (["[", "]"], [Exp(body)]) => switch (body) { diff --git a/src/haz3lcore/statics/Statics.re b/src/haz3lcore/statics/Statics.re index 7275b67e21..ac8dcdb122 100644 --- a/src/haz3lcore/statics/Statics.re +++ b/src/haz3lcore/statics/Statics.re @@ -312,7 +312,6 @@ and uexp_to_info_map = /* Currently doing this as otherwise it clobbers the statics * for the contained expression as i'm just reusing the same id * in order to associate it through dynamics */ - //TODO(andrew): ponder this go(~mode, e, m) | UnOp(Meta(Unquote), e) when is_in_filter => let e: UExp.t = { diff --git a/src/haz3lcore/statics/TermBase.re b/src/haz3lcore/statics/TermBase.re index 09681b7350..8cecdfd92c 100644 --- a/src/haz3lcore/statics/TermBase.re +++ b/src/haz3lcore/statics/TermBase.re @@ -361,8 +361,8 @@ and Exp: { | (Parens(x, Paren), _) => fast_equal(x, e2) | (_, DynamicErrorHole(x, _)) | (_, Parens(x, Paren)) => fast_equal(e1, x) - //TODO(andrew): clarify below cases (basically syntactic equality) - // this is necessary to make EvalResult.calculate go after adding a projector + /* Below is kind of a hack to make EvalResult.calculate go after adding a projector. + * We should clarify syntactic/semantic equality here */ | (Parens(x1, Probe(_)), Parens(x2, Probe(_))) => fast_equal(x1, x2) | (Parens(_, Probe(_)), _) => false | (EmptyHole, EmptyHole) => true diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index e5c66d80b5..7787cdd666 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -193,9 +193,9 @@ module Update = { one of the editors is shown in two cells, so we arbitrarily choose which statics to take */ let editors: Exercise.p('a) = { - let dynamics = Dynamics.Map.empty; //TODO(andrew): dynamics for projs in exercise mode - let calculate = statics => - Editor.Update.calculate(~settings, statics, dynamics, ~is_edited); + let calculate = (statics, dynamics, ed) => + Editor.Update.calculate(~settings, statics, dynamics, ~is_edited, ed); + { title: model.editors.title, version: model.editors.version, @@ -203,29 +203,44 @@ module Update = { prompt: model.editors.prompt, point_distribution: model.editors.point_distribution, prelude: - calculate(cells.prelude.editor.statics, model.editors.prelude), + calculate( + cells.prelude.editor.statics, + EvalResult.Model.dynamics(cells.prelude.result), + model.editors.prelude, + ), correct_impl: calculate( cells.test_validation.editor.statics, + EvalResult.Model.dynamics(cells.test_validation.result), model.editors.correct_impl, ), your_tests: { tests: calculate( cells.user_tests.editor.statics, + EvalResult.Model.dynamics(cells.user_tests.result), model.editors.your_tests.tests, ), required: model.editors.your_tests.required, provided: model.editors.your_tests.provided, }, your_impl: - calculate(cells.user_impl.editor.statics, model.editors.your_impl), + calculate( + cells.user_impl.editor.statics, + EvalResult.Model.dynamics(cells.user_impl.result), + model.editors.your_impl, + ), hidden_bugs: List.map2( (cell: CellEditor.Model.t, editor: Exercise.wrong_impl('a)): Exercise.wrong_impl('a) => { - impl: calculate(cell.editor.statics, editor.impl), + impl: + calculate( + cell.editor.statics, + EvalResult.Model.dynamics(cell.result), + editor.impl, + ), hint: editor.hint, }, cells.hidden_bugs, @@ -235,6 +250,7 @@ module Update = { tests: calculate( cells.hidden_tests.editor.statics, + EvalResult.Model.dynamics(cells.hidden_tests.result), model.editors.hidden_tests.tests, ), hints: model.editors.hidden_tests.hints, From 0af99296bf32c74cd68970356abf0ff7f9872887 Mon Sep 17 00:00:00 2001 From: disconcision Date: Wed, 11 Dec 2024 15:33:03 -0500 Subject: [PATCH 16/23] alt/option-v to toggle probes. partial fix to projector panel desync bug --- src/haz3lweb/Keyboard.re | 5 ++ src/haz3lweb/app/inspector/ProjectorPanel.re | 58 ++++++++++++++++---- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/haz3lweb/Keyboard.re b/src/haz3lweb/Keyboard.re index 2d39ac24a0..f219b69a1f 100644 --- a/src/haz3lweb/Keyboard.re +++ b/src/haz3lweb/Keyboard.re @@ -103,6 +103,11 @@ let handle_key_event = (k: Key.t): option(Action.t) => { | {key: D("ƒ"), sys: Mac, shift: Up, meta: Up, ctrl: Up, alt: Down} => /* Curly ƒ is what holding option turns f into on Mac */ Some(Project(ToggleIndicated(Fold))) + | {key: D("v"), sys: PC, shift: Up, meta: Up, ctrl: Up, alt: Down} => + Some(Project(ToggleIndicated(Probe))) + | {key: D("√"), sys: Mac, shift: Up, meta: Up, ctrl: Up, alt: Down} => + /* √ is what holding option turns f into on Mac */ + Some(Project(ToggleIndicated(Probe))) | {key: D(key), sys: _, shift: Up, meta: Up, ctrl: Up, alt: Down} => switch (key) { | "ArrowLeft" => now(MoveToBackpackTarget(Left(ByToken))) diff --git a/src/haz3lweb/app/inspector/ProjectorPanel.re b/src/haz3lweb/app/inspector/ProjectorPanel.re index 6280f91931..f52e1a4864 100644 --- a/src/haz3lweb/app/inspector/ProjectorPanel.re +++ b/src/haz3lweb/app/inspector/ProjectorPanel.re @@ -6,11 +6,11 @@ open Util.OptUtil.Syntax; open Util.Web; /* The projector selection panel on the right of the bottom bar */ -let option_view = (name, n) => - option( - ~attrs=n == name ? [Attr.create("selected", "selected")] : [], - [text(n)], - ); +let option_view = (_name, n) => + option([ + //~attrs=n == name ? [Attr.create("selected", "selected")] : [], + text(n), + ]); /* Decide which projectors are applicable based on the cursor info. * This is slightly inside-out as elsewhere it depends on the underlying @@ -35,8 +35,8 @@ let applicable_projectors: option(Info.t) => list(Base.kind) = @ [Base.Fold] @ ( switch (ci) { - | InfoExp(_) - | InfoPat(_) => [Probe, Info] + | InfoExp(_) => [Info, Probe] + | InfoPat(_) => [Info] | _ => [] } ); @@ -103,24 +103,60 @@ let might_project: Cursor.cursor(Editors.Update.t) => bool = let currently_selected = editor => option_view( switch (kind(editor)) { - | None => "Fold" + | None => "fold" | Some(k) => ProjectorView.name(k) }, ); +let currently_selected_str = (editor, _): string => + switch (kind(editor)) { + | None => "fold" + | Some(k) => ProjectorView.name(k) + }; + +let currently_selected_str_opt = (editor, _): option(string) => + switch (kind(editor)) { + | None => None + | Some(k) => Some(ProjectorView.name(k)) + }; + let view = (~inject, cursor: Cursor.cursor(Editors.Update.t)) => { let applicable_projectors = applicable_projectors(cursor.info); let should_show = might_project(cursor) && applicable_projectors != []; + //TODO(andrew): cleanup + // print_endline("currentrly: " ++ currently_selected_str(cursor.editor, ())); + let applicable_projector_strings = + (might_project(cursor) ? applicable_projectors : [Fold]) + |> List.map(ProjectorView.name); + let applicable_projector_strings = + switch (currently_selected_str_opt(cursor.editor, ())) { + | None => applicable_projector_strings + | Some(guy) => + List.cons( + guy, + List.filter(x => x != guy, applicable_projector_strings), + ) + }; + let value = + switch (kind(cursor.editor)) { + | Some(k) => ProjectorView.name(k) + | None => + applicable_projector_strings + //|> List.map(currently_selected_str(cursor.editor)) + |> List.hd + }; + // print_endline("value: " ++ value); let select_view = Node.select( ~attrs=[ Attr.on_change((_, name) => inject(Action.SetIndicated(ProjectorView.of_name(name))) ), + Attr.string_property("value", value), ], - (might_project(cursor) ? applicable_projectors : []) - |> List.map(ProjectorView.name) - |> List.map(currently_selected(cursor.editor)), + applicable_projector_strings + |> List.map(option_view("garbageTODO(andrew)")), + //|> List.map(currently_selected(cursor.editor)), ); let toggle_view = toggle_view( From 8dd2f7831bb9c94b51ffff41e48c424ddd46fdeb Mon Sep 17 00:00:00 2001 From: disconcision Date: Thu, 12 Dec 2024 23:16:59 -0500 Subject: [PATCH 17/23] probe projectors: better logic for closure co-inhabitation. better style --- src/haz3lcore/dynamics/Probe.re | 2 + src/haz3lcore/dynamics/Transition.re | 1 - src/haz3lcore/zipper/projectors/ProbeProj.re | 229 +++++++++++-------- src/haz3lweb/www/style/projectors.css | 54 +++-- src/haz3lweb/www/style/variables.css | 2 + src/util/ListUtil.re | 13 ++ 6 files changed, 190 insertions(+), 111 deletions(-) diff --git a/src/haz3lcore/dynamics/Probe.re b/src/haz3lcore/dynamics/Probe.re index 9175f68f5d..be7e51a66b 100644 --- a/src/haz3lcore/dynamics/Probe.re +++ b/src/haz3lcore/dynamics/Probe.re @@ -21,3 +21,5 @@ type frame = { type stack = list(frame); let empty = {refs: [], stem: []}; + +let env_stack = List.map((en: frame) => en.env_id); diff --git a/src/haz3lcore/dynamics/Transition.re b/src/haz3lcore/dynamics/Transition.re index 15644aa99f..c60fa53678 100644 --- a/src/haz3lcore/dynamics/Transition.re +++ b/src/haz3lcore/dynamics/Transition.re @@ -790,7 +790,6 @@ module Transition = (EV: EV_MODE) => { d => Parens(d, Probe(pr)) |> wrap_ctx, d'', ); - Step({ expr: d', state_update: () => { diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index e2d6cb1a79..e586263923 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -36,53 +36,43 @@ type a = | ChangeLength(int) | ToggleShowAllVals; -let stack = (di: option(Dynamics.Info.t)) => { - switch (di) { - | Some(di) => - List.map( - (pi: Dynamics.Probe.Info.t) => pi.stack |> Probe.show_stack, - di.vals, - ) - |> String.concat(", ") - | _ => "Nein" - }; -}; - -/* given two env_cursors,return their maximum common suffix */ -let max_common_suffix = (a: list('a), b: list('a)) => { - let rec loop = (a, b, acc) => - switch (a, b) { - | ([], _) - | (_, []) => acc - | ([ha, ...ta], [hb, ...tb]) when ha == hb => - loop(ta, tb, [ha, ...acc]) - | _ => acc - }; - loop(List.rev(a), List.rev(b), []); -}; +let stack = stack => + stack + |> List.rev + |> List.map(({env_id, frame_id}: Probe.frame) => + "" + ++ String.sub(Id.to_string(env_id), 0, 2) + ++ "\n" + ++ String.sub(Id.to_string(frame_id), 0, 2) + ) + |> String.concat("\n"); let env_cursor: ref(list(Id.t)) = ref([]); let last_target: ref(list('a)) = ref([]); let mousedown: ref(option(Js.t(Dom_html.element))) = ref(Option.None); -let env_cursor_of_stack = List.map((en: Probe.frame) => en.env_id); - -let show_indicator = stack => { - let local = env_cursor_of_stack(stack); - env_cursor^ == local - || (max_common_suffix(env_cursor^, local) != [] || local == []) - && List.length(env_cursor^) != List.length(local); -}; let common_suffix_length = (s1, s2) => - List.length(max_common_suffix(s1, s2)); + List.length(ListUtil.max_common_suffix(s1, s2)); + +let one_is_suffix_of_other = (s1, s2) => + common_suffix_length(s1, s2) == List.length(s1) + || common_suffix_length(s1, s2) == List.length(s2); let comparor = (a: Dynamics.Probe.Info.t, b: Dynamics.Probe.Info.t) => { compare( - common_suffix_length(env_cursor^, env_cursor_of_stack(b.stack)), - common_suffix_length(env_cursor^, env_cursor_of_stack(a.stack)), + common_suffix_length(env_cursor^, Probe.env_stack(b.stack)), + common_suffix_length(env_cursor^, Probe.env_stack(a.stack)), ); }; +let show_indicator = stack => { + let local = Probe.env_stack(stack); + env_cursor^ == [] + && local == [] + || env_cursor^ != [] + && one_is_suffix_of_other(env_cursor^, local); +}; + let seg_view = (utility, available, seg) => seg |> DHExp.strip_casts @@ -102,50 +92,63 @@ let get_goal = (utility: utility, e: Js.t(Dom_html.mouseEvent)) => ); let resizable_val = - (~resizable=false, model, utility, local, pi: Dynamics.Probe.Info.t) => + (~resizable=false, model, utility, local, pi: Dynamics.Probe.Info.t) => { + let val_pointerdown = (e: Js.t(Dom_html.pointerEvent)) => { + // print_endline("pointerdown"); + let target = e##.target |> Js.Opt.get(_, _ => failwith("no target")); + JsUtil.setPointerCapture(target, e##.pointerId) |> ignore; + mousedown := Some(target); + // if (last_target^ == [target] && !Js.to_bool(e##.shiftKey)) { + // env_cursor := []; + // last_target := []; + // } else { + env_cursor := Probe.env_stack(pi.stack); + last_target := [target]; + // }; + Effect.Ignore; + }; + + let val_pointerup = (e: Js.t(Dom_html.pointerEvent)) => { + // print_endline("pointerup"); + switch (mousedown^) { + | Some(target) => + JsUtil.releasePointerCapture(target, e##.pointerId) |> ignore + | None => () + }; + mousedown := None; + Effect.Ignore; + }; + + //TODO: refactor to pointermove when supported + let val_mousemove = (e: Js.t(Dom_html.mouseEvent)) => + switch (mousedown^) { + | Some(_elem) when Js.to_bool(e##.shiftKey) && resizable => + /* Ideally we could just use hasPointerCapture... */ + let goal = get_goal(utility, e); + local(ChangeLength(goal.col)); + // print_endline("mousemove: resizing"); + | _ => + // print_endline("mousemove: not resizing"); + Effect.Ignore + }; + div( ~attrs=[ + Attr.title(stack(pi.stack)), Attr.classes( ["val-resize"] @ (show_indicator(pi.stack) ? ["cursor"] : []), ), - Attr.on_pointerdown(e => { - // print_endline("pointerdown"); - let target = e##.target |> Js.Opt.get(_, _ => failwith("no target")); - JsUtil.setPointerCapture(target, e##.pointerId) |> ignore; - mousedown := Some(target); - if (last_target^ == [target]) { - env_cursor := []; - last_target := []; - } else { - env_cursor := env_cursor_of_stack(pi.stack); - last_target := [target]; - }; - Effect.Ignore; - }), - Attr.on_pointerup(e => { - // print_endline("pointerup"); - switch (mousedown^) { - | Some(target) => - JsUtil.releasePointerCapture(target, e##.pointerId) |> ignore - | None => () - }; - mousedown := None; - Effect.Ignore; - }), - Attr.on_mousemove(e => - switch (mousedown^) { - | Some(_elem) when Js.to_bool(e##.shiftKey) && resizable => - /* Ideally this would be onpointermove and we could just use hasPointerCapture... */ - let goal = get_goal(utility, e); - local(ChangeLength(goal.col)); - | _ => - // print_endline("mousemove:up"); - Effect.Ignore - } - ), + Attr.on_double_click(_ => local(ToggleShowAllVals)), + Attr.on_pointerdown(val_pointerdown), + Attr.on_pointerup(val_pointerup), + Attr.on_mousemove(val_mousemove), + ], + [ + seg_view(utility, model.len, pi.value), + //, text(stack(pi.stack)) ], - [seg_view(utility, model.len, pi.value)], ); +}; let env_val = (en: Dynamics.Probe.Env.entry, utility: utility) => { Node.div( @@ -160,7 +163,9 @@ let env_val = (en: Dynamics.Probe.Env.entry, utility: utility) => { ); }; -let rm_opaques = +/* Remove opaque values like function literals */ +let rm_opaques: + list(Dynamics.Probe.Env.entry) => list(Dynamics.Probe.Env.entry) = List.filter_map((en: Dynamics.Probe.Env.entry) => switch (en.raw) { | Opaque => None @@ -168,13 +173,14 @@ let rm_opaques = } ); -let is_var_ref = info => +/* Is the underlying syntax a variable reference? */ +let is_var_ref = (info: info): bool => switch (MakeTerm.go([info.syntax]).term.term) { | Var(_) => true | _ => false }; -let env_view = (pi: Dynamics.Probe.Info.t, utility: utility) => +let env_view = (pi: Dynamics.Probe.Info.t, utility: utility): Node.t => Node.div( ~attrs=[Attr.classes(["live-env"])], pi.env |> rm_opaques |> List.map(en => env_val(en, utility)), @@ -233,30 +239,71 @@ let num_closures = (info: info) => | _ => 0 }; -let num_closures_view = (info: info) => +let num_closures_view = (info: info) => { + let num_closures = num_closures(info); + let description = num_closures < 1000 ? string_of_int(num_closures) : "1k+"; div( - ~attrs=[Attr.classes(["num-closures"])], - [text(string_of_int(num_closures(info)))], + ~attrs=[ + Attr.title(string_of_int(num_closures)), + Attr.classes(["num-closures"]), + ], + [text(description)], ); +}; + +let syntax_str = (info: info) => { + let max_len = 30; + let str = Printer.of_segment(~holes=None, [info.syntax]); + let str = Re.Str.global_replace(Re.Str.regexp("\n"), " ", str); + String.length(str) > max_len ? String.sub(str, 0, max_len) ++ "..." : str; +}; + +let syntax_view = (info: info) => info |> syntax_str |> text; -let code_str = (info: info) => - [info.syntax] |> Printer.of_segment(~holes=None); +let placeholder = (_m, info) => + Inline(3 + String.length(syntax_str(info))); let icon = div(~attrs=[Attr.classes(["icon"])], [text("🔍")]); +// let icon = +// img( +// ~attrs=[ +// Attr.classes(["icon"]), +// Attr.create("height", "18px"), +// Attr.create("width", "18px"), +// Attr.src("img/noun-search-5661270.svg"), +// Attr.alt("probe"), +// ], +// (), +// ); let view = (model: t, ~info, ~local, ~parent as _, ~utility: utility) => div([ - //Attr.title(stack(info.dynamics)), offside_view(info, ~model, ~local, ~utility), div( ~attrs=[ Attr.classes(["main"]), - Attr.on_double_click(_ => local(ToggleShowAllVals)), + Attr.on_click(_ => { + env_cursor := []; + Effect.Ignore; + }), ], - [icon, text(code_str(info)), num_closures_view(info)], + [icon, syntax_view(info), num_closures_view(info)], ), ]); +let update = (m: t, a: a) => { + print_endline("update: action:" ++ show_a(a)); + switch (a) { + | ChangeLength(len) => + if (len > (-1)) { + {...m, len}; + } else { + m; + } + | ToggleShowAllVals => {...m, show_all_vals: !m.show_all_vals} + }; +}; + module M: Projector = { [@deriving (show({with_path: false}), sexp, yojson)] type model = t; @@ -265,20 +312,8 @@ module M: Projector = { let init = {text: "🔍", len: 12, show_all_vals: true}; let can_project = _ => true; let can_focus = false; - let placeholder = (_m, info) => - Inline(3 + String.length(code_str(info))); - let update = (m, a) => { - print_endline("update: action:" ++ show_a(a)); - switch (a) { - | ChangeLength(len) => - if (len > (-1)) { - {...m, len}; - } else { - m; - } - | ToggleShowAllVals => {...m, show_all_vals: !m.show_all_vals} - }; - }; + let placeholder = placeholder; + let update = update; let view = view; let focus = _ => (); }; diff --git a/src/haz3lweb/www/style/projectors.css b/src/haz3lweb/www/style/projectors.css index f410999e78..bfcfa12cee 100644 --- a/src/haz3lweb/www/style/projectors.css +++ b/src/haz3lweb/www/style/projectors.css @@ -182,6 +182,7 @@ flex-direction: row; gap: 0.4em; cursor: pointer; + align-items: center; } @keyframes rotate { @@ -191,7 +192,7 @@ } .projector.probe .live-offside:empty + * .icon { - opacity: 30%; + /* opacity: 30%; */ animation: rotate 5s linear infinite; } @@ -201,16 +202,29 @@ } .projector.probe.indicated > svg { - fill: #a4d7a2; + fill: var(--G0); + /* fill: #a4d7a2; */ } .projector.probe .num-closures { position: absolute; right: -0.5em; top: -0.3em; - scale: 0.5; - color: var(--G0); + transform-origin: top right; + scale: 0.6; + color: oklch(1 0 0); font-family: var(--ui-font); + display: flex; + justify-content: center; + background-color: var(--G0); + border-radius: 2em; + font-size: 0.7em; + padding: 0.3em; + min-width: 1em; + z-index: var(--code-emblems-z); +} +.projector.probe.selected .num-closures { + background-color: var(--Y1); } .projector.probe .live-offside { @@ -225,32 +239,46 @@ display: none; } +.projector.probe .val-resize { + cursor: pointer; +} + .projector.probe .val-resize .code-text { background-color: #c7e6c6; - border-radius: 0.1em; + border-radius: 0.4em 0.1em 0.1em 0.1em; filter: drop-shadow(0.7px 0.7px 0px green); } .projector.probe .closure:first-child .val-resize:hover .code-text { cursor: col-resize; } .projector.probe .val-resize.cursor .code-text { - outline: 1px solid var(--GB1); + outline: 1.6px solid var(--G0); outline-style: dotted; filter: none; + opacity: 100%; } + .projector.probe .val-resize .code-text { opacity: 40%; } -.projector.probe .val-resize.cursor .code-text, +.projector.probe .val-resize:not(.cursor) .code-text .token { + filter: saturate(0); +} +/* .projector.probe .val-resize.cursor .code-text { + opacity: 80%; +} */ .projector.probe.indicated .val-resize .code-text { - opacity: 70%; + opacity: 60%; +} +.projector.probe.indicated .main { + color: white; } .projector.probe.indicated .val-resize.cursor .code-text { opacity: 100%; - outline: 1px solid var(--GB1); + /* outline: 1px solid var(--GB1); */ + outline: 2.8px solid oklch(0.7 0.15 150.31); } - -.projector.probe.indicated .closure.cursor .live-env { +.projector.probe.indicated .closure:hover .live-env { display: block; } .projector.probe.indicated .closure.cursor .live-env:empty { @@ -269,8 +297,8 @@ padding: 0.2em; display: none; position: absolute; - top: 1.4em; - z-index: 5000; + top: 1.5em; + z-index: var(--code-hovers-z); } .projector.probe .live-env .live-env-entry { diff --git a/src/haz3lweb/www/style/variables.css b/src/haz3lweb/www/style/variables.css index 96c6225816..9c84eeb3eb 100644 --- a/src/haz3lweb/www/style/variables.css +++ b/src/haz3lweb/www/style/variables.css @@ -209,7 +209,9 @@ /* ABOVE CODE LEVEL */ --backpack-targets-z: 11; + --code-hovers-z: 11; --caret-z: 20; + --code-emblems-z: 21; /* TOP LEVEL UI */ --context-inspector-z: 29; /* keep below bottom bar */ diff --git a/src/util/ListUtil.re b/src/util/ListUtil.re index 1e7e87a1af..e6a5c6a6c5 100644 --- a/src/util/ListUtil.re +++ b/src/util/ListUtil.re @@ -591,3 +591,16 @@ let minimum = (f: 'a => int, xs: list('a)): option('a) => }; loop(x, f(x), xs); }; + +/* Given two lists, return their maximum common suffix */ +let max_common_suffix = (a: list('a), b: list('a)) => { + let rec loop = (a, b, acc) => + switch (a, b) { + | ([], _) + | (_, []) => acc + | ([ha, ...ta], [hb, ...tb]) when ha == hb => + loop(ta, tb, [ha, ...acc]) + | _ => acc + }; + loop(List.rev(a), List.rev(b), []); +}; From fffd0131fff76e3fa99eeed6b71083de03a78bff Mon Sep 17 00:00:00 2001 From: disconcision Date: Sun, 15 Dec 2024 21:09:23 -0500 Subject: [PATCH 18/23] clean up live projectors maketerm --- src/haz3lcore/dynamics/Probe.re | 26 +++++++++++--- src/haz3lcore/statics/MakeTerm.re | 37 ++++++++++++-------- src/haz3lcore/statics/TermBase.re | 8 ++--- src/haz3lcore/zipper/projectors/ProbeProj.re | 6 ++-- 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/haz3lcore/dynamics/Probe.re b/src/haz3lcore/dynamics/Probe.re index be7e51a66b..bf4da4dc95 100644 --- a/src/haz3lcore/dynamics/Probe.re +++ b/src/haz3lcore/dynamics/Probe.re @@ -1,5 +1,13 @@ open Util; +/* A syntax probe is inserted into syntax to capture + * information during evaluation. The tag type below, + * for the probe case, is used to collect binding ids + * which are used to faciltate capturing the values + * of certain variables in the environment. This captured + * information is, for a given closure, encoded in + * the `frame` type. */ + [@deriving (show({with_path: false}), sexp, yojson)] type t = { refs: Binding.s, @@ -11,15 +19,25 @@ type tag = | Paren | Probe(t); +/* Information about the evaluation of an ap */ [@deriving (show({with_path: false}), sexp, yojson)] type frame = { - env_id: Id.t, - frame_id: Id.t, + closure_id: Id.t, /* Primary ID (Unique) */ + ap_id: Id.t, /* Syntax ID of the ap */ + env_id: Id.t /* ID of ClosureEnv created by ap */ }; +/* List of applications prior to some evaluation */ [@deriving (show({with_path: false}), sexp, yojson)] type stack = list(frame); -let empty = {refs: [], stem: []}; +let empty: t = {refs: [], stem: []}; + +let env_stack: list(frame) => list(Id.t) = + List.map((en: frame) => en.env_id); -let env_stack = List.map((en: frame) => en.env_id); +let mk_frame = (~env_id: Id.t, ~ap_id: Id.t): frame => { + closure_id: Id.mk(), + env_id, + ap_id, +}; diff --git a/src/haz3lcore/statics/MakeTerm.re b/src/haz3lcore/statics/MakeTerm.re index 31283b1dc1..1a651ec1de 100644 --- a/src/haz3lcore/statics/MakeTerm.re +++ b/src/haz3lcore/statics/MakeTerm.re @@ -13,13 +13,23 @@ open Util; open Any; +/* Hack: Temporary construct internal to maketerm + * to handle probe parsing; see `tokens` below */ +let probe_wrap = ["@@@@@", "@@@@@"]; +let is_probe_wrap = (==)(probe_wrap); + // TODO make less hacky let tokens = Piece.get( _ => [], _ => [" "], (t: Tile.t) => t.shards |> List.map(List.nth(t.label)), - _ => [], + _ => + /* Hack: These act as temporary wrappers for projectors, + * which are retained through maketerm so as to be used in + * dynamics. These are inserted and removed entirely internal + * to maketerm. */ + probe_wrap, ); [@deriving (show({with_path: false}), sexp, yojson)] @@ -112,21 +122,13 @@ let return = (wrap, ids, tm) => { /* Map to collect projector ids */ let projectors: ref(Id.Map.t(Piece.projector)) = ref(Id.Map.empty); -/* Temporary form to persist projector dynamics probe to segments */ -let mk_probe = (id, e: list(Piece.t)) => - Piece.mk_tile_id( - id, - Form.mk(Form.ii, ["@@", "@@"], Mold.mk_op(Exp, [Exp])), - [e], - ); - /* Strip a projector from a segment and log it in the map */ -let rm_and_log_projectors = (seg: Segment.t): Segment.t => +let log_projectors = (seg: Segment.t): Segment.t => List.map( fun | Piece.Projector(pr) => { projectors := Id.Map.add(pr.id, pr, projectors^); - mk_probe(pr.id, [pr.syntax]); + Piece.Projector(pr); } | x => x, seg, @@ -200,7 +202,7 @@ and exp_term: unsorted => (UExp.term, list(Id.t)) = { | ([t], []) when Form.is_ctr(t) => ret(Constructor(t, Unknown(Internal) |> Typ.temp)) | (["(", ")"], [Exp(body)]) => ret(Parens(body, Paren)) - | (["@@", "@@"], [Exp(body)]) => + | (label, [Exp(body)]) when is_probe_wrap(label) => // Temporary wrapping form to persist projector probes ret(Parens(body, Probe(Probe.empty))) | (["[", "]"], [Exp(body)]) => @@ -365,6 +367,7 @@ and pat_term: unsorted => (UPat.term, list(Id.t)) = { | ([t], []) when t != " " && !Form.is_explicit_hole(t) => Invalid(t) | (["(", ")"], [Pat(body)]) => Parens(body) + | (label, [Pat(body)]) when is_probe_wrap(label) => Parens(body) | (["[", "]"], [Pat(body)]) => switch (body) { | {term: Tuple(ps), _} => ListLit(ps) @@ -425,6 +428,7 @@ and typ_term: unsorted => (UTyp.term, list(Id.t)) = { | (["String"], []) => String | ([t], []) when Form.is_typ_var(t) => Var(t) | (["(", ")"], [Typ(body)]) => Parens(body) + | (label, [Typ(body)]) when is_probe_wrap(label) => Parens(body) | (["[", "]"], [Typ(body)]) => List(body) | ([t], []) when t != " " && !Form.is_explicit_hole(t) => Unknown(Hole(Invalid(t))) @@ -534,12 +538,17 @@ and rul = (unsorted: unsorted): Rul.t => { and unsorted = (skel: Skel.t, seg: Segment.t): unsorted => { /* Remove projectors. We do this here as opposed to removing * them in an external call to save a whole-syntax pass. */ - let seg = rm_and_log_projectors(seg); + let _ = log_projectors(seg); let tile_kids = (p: Piece.t): list(Term.Any.t) => switch (p) { | Secondary(_) | Grout(_) => [] - | Projector(_) => [] + | Projector({syntax, id: _, _}) => + let sort = Piece.sort(syntax) |> fst; + let seg = [syntax]; + //let seg = [mk_probe(id, seg)]; + //print_endline("unsorted projector case"); + [go_s(sort, Segment.skel(seg), seg)]; | Tile({mold, shards, children, _}) => Aba.aba_triples(Aba.mk(shards, children)) |> List.map(((l, kid, r)) => { diff --git a/src/haz3lcore/statics/TermBase.re b/src/haz3lcore/statics/TermBase.re index 8cecdfd92c..e3e34020ec 100644 --- a/src/haz3lcore/statics/TermBase.re +++ b/src/haz3lcore/statics/TermBase.re @@ -976,12 +976,12 @@ and ClosureEnvironment: { let without_keys = keys => update_env(Environment.without_keys(keys)); - let update_stack = (frame: option(Id.t), env) => { + let update_stack = (ap_id: option(Id.t), env) => { let stack = - switch (frame) { + switch (ap_id) { | None => stack_of(env) - | Some(frame) => [ - {env_id: id_of(env), frame_id: frame}, + | Some(ap_id) => [ + Probe.mk_frame(~env_id=id_of(env), ~ap_id), ...stack_of(env), ] }; diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index e586263923..e846755dfb 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -39,11 +39,13 @@ type a = let stack = stack => stack |> List.rev - |> List.map(({env_id, frame_id}: Probe.frame) => + |> List.map(({env_id, ap_id, closure_id}: Probe.frame) => "" + ++ String.sub(Id.to_string(closure_id), 0, 2) + ++ "\n" ++ String.sub(Id.to_string(env_id), 0, 2) ++ "\n" - ++ String.sub(Id.to_string(frame_id), 0, 2) + ++ String.sub(Id.to_string(ap_id), 0, 2) ) |> String.concat("\n"); From dc8ebfe484d9bfe26716f501dc33606f10997619 Mon Sep 17 00:00:00 2001 From: disconcision Date: Sun, 15 Dec 2024 21:19:14 -0500 Subject: [PATCH 19/23] projectors live cleanup --- src/haz3lcore/dynamics/Transition.re | 9 +---- src/haz3lcore/pretty/ExpToSegment.re | 4 +- src/haz3lcore/statics/MakeTerm.re | 7 +--- src/haz3lcore/statics/Statics.re | 38 +++++++++---------- src/haz3lweb/app/editors/result/EvalResult.re | 2 +- 5 files changed, 27 insertions(+), 33 deletions(-) diff --git a/src/haz3lcore/dynamics/Transition.re b/src/haz3lcore/dynamics/Transition.re index c60fa53678..583b07cbb1 100644 --- a/src/haz3lcore/dynamics/Transition.re +++ b/src/haz3lcore/dynamics/Transition.re @@ -358,13 +358,6 @@ module Transition = (EV: EV_MODE) => { is_value: false, }); } - // let.match env'' = (env', matches(dp, d2')); - // Step({ - // expr: Closure(env'', d3) |> fresh, - // state_update, - // kind: FunAp, - // is_value: false, - // }); | Cast( d3', {term: Arrow(ty1, ty2), _}, @@ -783,6 +776,8 @@ module Transition = (EV: EV_MODE) => { let. _ = otherwise(env, d); Indet; | Parens(d'', Probe(pr)) => + /* When evaluated, a probe adds a dynamics info entry + * reflecting the evaluation of the contained expression */ let. _ = otherwise(env, ((d, _)) => Parens(d, Probe(pr)) |> rewrap) and. (d', _) = req_final_or_value( diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index c11da79308..f1e3791856 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -49,7 +49,9 @@ let should_add_space = (s1, s2) => && !Form.is_keyword(s1) && String.starts_with(s2, ~prefix="(") => false - | _ when String.ends_with(s1, ~suffix="…") => false + | _ when String.ends_with(s1, ~suffix="…") => + /* Hack case for probe projector abbreviations */ + false | _ => true }; diff --git a/src/haz3lcore/statics/MakeTerm.re b/src/haz3lcore/statics/MakeTerm.re index 1a651ec1de..38c00f5951 100644 --- a/src/haz3lcore/statics/MakeTerm.re +++ b/src/haz3lcore/statics/MakeTerm.re @@ -15,7 +15,7 @@ open Any; /* Hack: Temporary construct internal to maketerm * to handle probe parsing; see `tokens` below */ -let probe_wrap = ["@@@@@", "@@@@@"]; +let probe_wrap = ["PROBE_WRAP", "PROBE_WRAP"]; let is_probe_wrap = (==)(probe_wrap); // TODO make less hacky @@ -545,10 +545,7 @@ and unsorted = (skel: Skel.t, seg: Segment.t): unsorted => { | Grout(_) => [] | Projector({syntax, id: _, _}) => let sort = Piece.sort(syntax) |> fst; - let seg = [syntax]; - //let seg = [mk_probe(id, seg)]; - //print_endline("unsorted projector case"); - [go_s(sort, Segment.skel(seg), seg)]; + [go_s(sort, Segment.skel([syntax]), [syntax])]; | Tile({mold, shards, children, _}) => Aba.aba_triples(Aba.mk(shards, children)) |> List.map(((l, kid, r)) => { diff --git a/src/haz3lcore/statics/Statics.re b/src/haz3lcore/statics/Statics.re index ac8dcdb122..f20e3f8e57 100644 --- a/src/haz3lcore/statics/Statics.re +++ b/src/haz3lcore/statics/Statics.re @@ -1,29 +1,29 @@ /* STATICS.re - This module determines the statics semantics of a program. - It makes use of the following modules: + This module determines the statics semantics of a program. + It makes use of the following modules: - INFO.re: Defines the Info.t type which is used to represent the - static STATUS of a term. This STATUS can be either OK or ERROR, - and is determined by reconcilling two sources of typing information, - the MODE and the SELF. + INFO.re: Defines the Info.t type which is used to represent the + static STATUS of a term. This STATUS can be either OK or ERROR, + and is determined by reconcilling two sources of typing information, + the MODE and the SELF. - MODE.re: Defines the Mode.t type which is used to represent the - typing expectations imposed by a term's ancestors. + MODE.re: Defines the Mode.t type which is used to represent the + typing expectations imposed by a term's ancestors. - SELF.re: Define the Self.t type which is used to represent the - type information derivable from the term itself. + SELF.re: Define the Self.t type which is used to represent the + type information derivable from the term itself. - The point of STATICS.re itself is to derive a map between each - term's unique id and that term's static INFO. The below functions - are intended mostly as infrastructure: The point is to define a - traversal through the syntax tree which, for each term, passes - down the MODE, passes up the SELF, calculates the INFO, and adds - it to the map. + The point of STATICS.re itself is to derive a map between each + term's unique id and that term's static INFO. The below functions + are intended mostly as infrastructure: The point is to define a + traversal through the syntax tree which, for each term, passes + down the MODE, passes up the SELF, calculates the INFO, and adds + it to the map. - The architectural intention here is that most type-manipulation - logic is defined in INFO, MODE, and SELF, and the STATICS module - itself is dedicated to the piping necessary to (A) introduce and + The architectural intention here is that most type-manipulation + logic is defined in INFO, MODE, and SELF, and the STATICS module + itself is dedicated to the piping necessary to (A) introduce and (B) propagate the necessary information through the syntax tree. */ diff --git a/src/haz3lweb/app/editors/result/EvalResult.re b/src/haz3lweb/app/editors/result/EvalResult.re index 62f655a7ab..840f2b0961 100644 --- a/src/haz3lweb/app/editors/result/EvalResult.re +++ b/src/haz3lweb/app/editors/result/EvalResult.re @@ -93,7 +93,7 @@ module Model = { switch (model.result) { | Evaluation({result: OldValue(ResultOk((_, state))), _}) | Evaluation({result: NewValue(ResultOk((_, state))), _}) => - Some(state |> Haz3lcore.EvaluatorState.get_probes) + Some(Haz3lcore.EvaluatorState.get_probes(state)) | Stepper(s) => Some( s.history From d64e739a86f4c5aa72c412223e887c537888eef8 Mon Sep 17 00:00:00 2001 From: disconcision Date: Sun, 15 Dec 2024 22:12:41 -0500 Subject: [PATCH 20/23] projectors live cleanup --- src/haz3lcore/statics/MakeTerm.re | 21 +- src/haz3lcore/statics/Statics.re | 2 +- src/haz3lcore/tiles/Piece.re | 10 - src/haz3lcore/zipper/projectors/ProbeProj.re | 36 +-- src/haz3lweb/app/explainthis/Example.re | 2 - src/haz3lweb/app/inspector/ProjectorPanel.re | 111 +++---- src/haz3lweb/www/style.css | 3 +- src/haz3lweb/www/style/editor.css | 4 +- src/haz3lweb/www/style/projectors-panel.css | 29 -- src/haz3lweb/www/style/projectors.css | 310 ------------------- src/util/JsUtil.re | 8 +- src/util/ListUtil.re | 2 +- 12 files changed, 60 insertions(+), 478 deletions(-) delete mode 100644 src/haz3lweb/www/style/projectors-panel.css delete mode 100644 src/haz3lweb/www/style/projectors.css diff --git a/src/haz3lcore/statics/MakeTerm.re b/src/haz3lcore/statics/MakeTerm.re index 38c00f5951..85e5b069c6 100644 --- a/src/haz3lcore/statics/MakeTerm.re +++ b/src/haz3lcore/statics/MakeTerm.re @@ -123,16 +123,9 @@ let return = (wrap, ids, tm) => { let projectors: ref(Id.Map.t(Piece.projector)) = ref(Id.Map.empty); /* Strip a projector from a segment and log it in the map */ -let log_projectors = (seg: Segment.t): Segment.t => - List.map( - fun - | Piece.Projector(pr) => { - projectors := Id.Map.add(pr.id, pr, projectors^); - Piece.Projector(pr); - } - | x => x, - seg, - ); +let log_projector = (pr: Base.projector): unit => { + projectors := Id.Map.add(pr.id, pr, projectors^); +}; let parse_sum_term: UTyp.t => ConstructorMap.variant(UTyp.t) = fun @@ -367,7 +360,7 @@ and pat_term: unsorted => (UPat.term, list(Id.t)) = { | ([t], []) when t != " " && !Form.is_explicit_hole(t) => Invalid(t) | (["(", ")"], [Pat(body)]) => Parens(body) - | (label, [Pat(body)]) when is_probe_wrap(label) => Parens(body) + | (label, [Pat(body)]) when is_probe_wrap(label) => body.term | (["[", "]"], [Pat(body)]) => switch (body) { | {term: Tuple(ps), _} => ListLit(ps) @@ -428,7 +421,7 @@ and typ_term: unsorted => (UTyp.term, list(Id.t)) = { | (["String"], []) => String | ([t], []) when Form.is_typ_var(t) => Var(t) | (["(", ")"], [Typ(body)]) => Parens(body) - | (label, [Typ(body)]) when is_probe_wrap(label) => Parens(body) + | (label, [Typ(body)]) when is_probe_wrap(label) => body.term | (["[", "]"], [Typ(body)]) => List(body) | ([t], []) when t != " " && !Form.is_explicit_hole(t) => Unknown(Hole(Invalid(t))) @@ -538,12 +531,12 @@ and rul = (unsorted: unsorted): Rul.t => { and unsorted = (skel: Skel.t, seg: Segment.t): unsorted => { /* Remove projectors. We do this here as opposed to removing * them in an external call to save a whole-syntax pass. */ - let _ = log_projectors(seg); let tile_kids = (p: Piece.t): list(Term.Any.t) => switch (p) { | Secondary(_) | Grout(_) => [] - | Projector({syntax, id: _, _}) => + | Projector({syntax, id: _, _} as pr) => + let _ = log_projector(pr); let sort = Piece.sort(syntax) |> fst; [go_s(sort, Segment.skel([syntax]), [syntax])]; | Tile({mold, shards, children, _}) => diff --git a/src/haz3lcore/statics/Statics.re b/src/haz3lcore/statics/Statics.re index f20e3f8e57..9d9abde74b 100644 --- a/src/haz3lcore/statics/Statics.re +++ b/src/haz3lcore/statics/Statics.re @@ -26,7 +26,7 @@ itself is dedicated to the piping necessary to (A) introduce and (B) propagate the necessary information through the syntax tree. - */ + */ module Info = Info; diff --git a/src/haz3lcore/tiles/Piece.re b/src/haz3lcore/tiles/Piece.re index 96b02e1004..fc6207a979 100644 --- a/src/haz3lcore/tiles/Piece.re +++ b/src/haz3lcore/tiles/Piece.re @@ -186,13 +186,3 @@ let is_term = (p: t) => | Secondary(_) => false // debatable | _ => false }; - -let mk_tile_id: (Id.t, Form.t, list(list(t))) => t = - (id, form, children) => - Tile({ - id, - label: form.label, - mold: form.mold, - shards: List.mapi((i, _) => i, form.label), - children, - }); diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index e846755dfb..0ce589301d 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -4,25 +4,6 @@ open Virtual_dom.Vdom; open Node; open Js_of_ocaml; -//let _ = Zipper.base_point; //not cyclical -//let _ = Editor.show; //cyclical - -/* Plan for extended dynamics: - in principle we could track: - - prev 'stack frame': id of prev expr that began execution? - - env (or just the part of the env that's relevant to the current expr) - aka the co-ctx - - */ - -/* Plan for selectable (editable as well maybe): - - selectable: - create own mini version of editor, use it as model - update fn updates model after applying mini editor action - hopefully can use same actions - */ - [@deriving (show({with_path: false}), sexp, yojson)] type t = { [@default "⋱"] @@ -96,22 +77,15 @@ let get_goal = (utility: utility, e: Js.t(Dom_html.mouseEvent)) => let resizable_val = (~resizable=false, model, utility, local, pi: Dynamics.Probe.Info.t) => { let val_pointerdown = (e: Js.t(Dom_html.pointerEvent)) => { - // print_endline("pointerdown"); let target = e##.target |> Js.Opt.get(_, _ => failwith("no target")); JsUtil.setPointerCapture(target, e##.pointerId) |> ignore; mousedown := Some(target); - // if (last_target^ == [target] && !Js.to_bool(e##.shiftKey)) { - // env_cursor := []; - // last_target := []; - // } else { env_cursor := Probe.env_stack(pi.stack); last_target := [target]; - // }; Effect.Ignore; }; let val_pointerup = (e: Js.t(Dom_html.pointerEvent)) => { - // print_endline("pointerup"); switch (mousedown^) { | Some(target) => JsUtil.releasePointerCapture(target, e##.pointerId) |> ignore @@ -128,10 +102,7 @@ let resizable_val = /* Ideally we could just use hasPointerCapture... */ let goal = get_goal(utility, e); local(ChangeLength(goal.col)); - // print_endline("mousemove: resizing"); - | _ => - // print_endline("mousemove: not resizing"); - Effect.Ignore + | _ => Effect.Ignore }; div( @@ -145,10 +116,7 @@ let resizable_val = Attr.on_pointerup(val_pointerup), Attr.on_mousemove(val_mousemove), ], - [ - seg_view(utility, model.len, pi.value), - //, text(stack(pi.stack)) - ], + [seg_view(utility, model.len, pi.value)], ); }; diff --git a/src/haz3lweb/app/explainthis/Example.re b/src/haz3lweb/app/explainthis/Example.re index 3941063a23..9408167cf6 100644 --- a/src/haz3lweb/app/explainthis/Example.re +++ b/src/haz3lweb/app/explainthis/Example.re @@ -22,8 +22,6 @@ let int = n => mk_monotile(Form.mk_atomic(Exp, n)); let exp = v => mk_monotile(Form.mk_atomic(Exp, v)); let pat = v => mk_monotile(Form.mk_atomic(Pat, v)); let mk_parens_exp = mk_tile(Form.get("parens_exp")); -let mk_probe = (e: list(Piece.t)) => - mk_tile(Form.mk(Form.ii, ["@@", "@@"], Mold.mk_op(Exp, [Exp])), [e]); let mk_fun = mk_tile(Form.get("fun_")); let mk_fun_ancestor = mk_ancestor(Form.get("fun_")); let mk_parens_ancestor = mk_ancestor(Form.get("parens_exp")); diff --git a/src/haz3lweb/app/inspector/ProjectorPanel.re b/src/haz3lweb/app/inspector/ProjectorPanel.re index f52e1a4864..271363b4fe 100644 --- a/src/haz3lweb/app/inspector/ProjectorPanel.re +++ b/src/haz3lweb/app/inspector/ProjectorPanel.re @@ -5,12 +5,7 @@ open Projector; open Util.OptUtil.Syntax; open Util.Web; -/* The projector selection panel on the right of the bottom bar */ -let option_view = (_name, n) => - option([ - //~attrs=n == name ? [Attr.create("selected", "selected")] : [], - text(n), - ]); +// The projector selection panel on the right of the bottom bar /* Decide which projectors are applicable based on the cursor info. * This is slightly inside-out as elsewhere it depends on the underlying @@ -100,74 +95,56 @@ let might_project: Cursor.cursor(Editors.Update.t) => bool = } }; -let currently_selected = editor => - option_view( - switch (kind(editor)) { - | None => "fold" - | Some(k) => ProjectorView.name(k) - }, - ); - -let currently_selected_str = (editor, _): string => - switch (kind(editor)) { - | None => "fold" - | Some(k) => ProjectorView.name(k) - }; +let lift = (str, strs) => List.cons(str, List.filter((!=)(str), strs)); -let currently_selected_str_opt = (editor, _): option(string) => - switch (kind(editor)) { - | None => None - | Some(k) => Some(ProjectorView.name(k)) +/* The string names of all projectors applicable to the currently + * indicated syntax, with the currently applied projection (if any) + * lifted to the top of the list */ +let applicable_projector_strings = (cursor: Cursor.cursor(Editors.Update.t)) => { + let strs = + applicable_projectors(cursor.info) |> List.map(ProjectorView.name); + switch (kind(cursor.editor)) { + | None => strs + | Some(k) => lift(ProjectorView.name(k), strs) }; +}; -let view = (~inject, cursor: Cursor.cursor(Editors.Update.t)) => { - let applicable_projectors = applicable_projectors(cursor.info); - let should_show = might_project(cursor) && applicable_projectors != []; - //TODO(andrew): cleanup - // print_endline("currentrly: " ++ currently_selected_str(cursor.editor, ())); - let applicable_projector_strings = - (might_project(cursor) ? applicable_projectors : [Fold]) - |> List.map(ProjectorView.name); +/* A selection input for contetually applicable projectors */ +let select_view = + ( + ~inject: Action.project => Ui_effect.t(unit), + cursor: Cursor.cursor(Editors.Update.t), + ) => { let applicable_projector_strings = - switch (currently_selected_str_opt(cursor.editor, ())) { - | None => applicable_projector_strings - | Some(guy) => - List.cons( - guy, - List.filter(x => x != guy, applicable_projector_strings), - ) - }; + might_project(cursor) ? applicable_projector_strings(cursor) : []; let value = - switch (kind(cursor.editor)) { - | Some(k) => ProjectorView.name(k) - | None => - applicable_projector_strings - //|> List.map(currently_selected_str(cursor.editor)) - |> List.hd + switch (applicable_projector_strings) { + | [] => "" + | [hd, ..._] => hd }; - // print_endline("value: " ++ value); - let select_view = - Node.select( - ~attrs=[ - Attr.on_change((_, name) => - inject(Action.SetIndicated(ProjectorView.of_name(name))) - ), - Attr.string_property("value", value), - ], - applicable_projector_strings - |> List.map(option_view("garbageTODO(andrew)")), - //|> List.map(currently_selected(cursor.editor)), - ); - let toggle_view = - toggle_view( - ~inject, - cursor.info, - id(cursor.editor), - kind(cursor.editor) != None, - might_project(cursor), - ); + Node.select( + ~attrs=[ + Attr.on_change((_, name) => + inject(SetIndicated(ProjectorView.of_name(name))) + ), + Attr.string_property("value", value), + ], + applicable_projector_strings |> List.map(n => option([text(n)])), + ); +}; + +let toggle_view = (~inject, cursor: Cursor.cursor(Editors.Update.t)) => + toggle_view( + ~inject, + cursor.info, + id(cursor.editor), + kind(cursor.editor) != None, + might_project(cursor), + ); + +let view = (~inject, cursor: Cursor.cursor(Editors.Update.t)) => { div( ~attrs=[Attr.id("projectors")], - (should_show ? [select_view] : []) @ [toggle_view], + [select_view(~inject, cursor)] @ [toggle_view(~inject, cursor)], ); }; diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index d70e70a422..e38e08bfab 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -3,11 +3,10 @@ @import "style/animations.css"; @import "style/loading.css"; @import "style/editor.css"; -@import "style/projectors.css"; +@import "style/projectors/base.css"; @import "style/nut-menu.css"; @import "style/cursor-inspector.css"; @import "style/type-display.css"; -@import "style/projectors-panel.css"; @import "style/explainthis.css"; @import "style/exercise-mode.css"; @import "style/cell.css"; diff --git a/src/haz3lweb/www/style/editor.css b/src/haz3lweb/www/style/editor.css index a01b275992..767fa6196e 100644 --- a/src/haz3lweb/www/style/editor.css +++ b/src/haz3lweb/www/style/editor.css @@ -21,7 +21,9 @@ /* This is a hack to compensating for default linebreak handling * preventing a trailing linebreak in code editors from leaving a * trailing blank line, as per https://stackoverflow.com/questions/43492826/. -* The mysterious chracter below is a zero-width space */ +* The mysterious chracter below is a zero-width space, used here +* to avoid inserting an extraneous whitespace context for inline +* syntax views, e.g. type views in the cursor inspector */ .code-text:after { content: "​"; } diff --git a/src/haz3lweb/www/style/projectors-panel.css b/src/haz3lweb/www/style/projectors-panel.css deleted file mode 100644 index 77c184bf59..0000000000 --- a/src/haz3lweb/www/style/projectors-panel.css +++ /dev/null @@ -1,29 +0,0 @@ -/* PROJECTORS PANEL */ - -#projectors { - display: flex; - gap: 0.75em; - align-items: center; - padding-left: 0.75em; - padding-right: 0.75em; - white-space: nowrap; - border-radius: 1.1em 0 0 0; - outline: 0.3px solid var(--BR2); -} - -#projectors img { - width: 0.7em; -} - -#projectors option { - font-size: 1em; -} - -#projectors .toggle-switch { - display: flex; - align-items: center; -} - -#projectors .toggle-switch.inactive { - opacity: 40%; -} diff --git a/src/haz3lweb/www/style/projectors.css b/src/haz3lweb/www/style/projectors.css deleted file mode 100644 index bfcfa12cee..0000000000 --- a/src/haz3lweb/www/style/projectors.css +++ /dev/null @@ -1,310 +0,0 @@ -/* PROJECTORS */ - -/* Turn off caret when a block projector is focused */ -#caret:has(~ .projectors .projector.block *:focus) { - display: none; -} - -/* Default projector styles */ - -.projector { - position: absolute; - display: flex; - justify-content: center; - align-items: center; - color: var(--BR2); -} -.projector > *:not(svg) { - z-index: var(--projector-z); -} -.projector > svg { - z-index: var(--projector-backing-z); - fill: var(--shard_projector); -} -.projector.block > svg { - stroke-width: 0.5px; - stroke: var(--BR2); -} -.projector.indicated > svg { - fill: var(--shard-caret-exp); -} -.projector > svg > path { - vector-effect: non-scaling-stroke; -} -.projector.block.selected > svg > path { - vector-effect: non-scaling-stroke; - stroke: var(--Y3); -} -.projector.selected > svg, -.projector.selected.indicated > svg, -.projector.block.selected.indicated > svg { - fill: var(--shard-selected); -} - -/* PROJECTOR: FOLD */ - -.projector.fold:hover, -.projector.indicated.fold { - color: --var(BLACK); -} -.projector.fold { - cursor: pointer; - font-family: var(--code-font); -} - -.result .projector.fold { - cursor: default; -} - -/* PROJECTOR: INFO */ - -.projector.type .type { - color: var(--token-typ); -} -.projector.type { - cursor: pointer; -} -.projector.type .info { - display: flex; -} -.projector.type:hover { - color: var(--BLACK); -} - -/* PROJECTOR: CHECKBOX */ - -.projector.check input { - margin: 0; - filter: sepia(1); - cursor: pointer; - outline: none; -} -.projector.check:not(.indicated):not(:hover) > svg { - fill: var(--NONE); -} - -/* PROJECTOR: SLIDERS (INT & FLOAT) */ - -.projector.slider input, -.projector.sliderf input { - margin: 0; - filter: sepia(1); - cursor: pointer; - outline: none; - width: 90%; -} -.projector.slider:not(.indicated):not(:hover) > svg, -.projector.sliderf:not(.indicated):not(:hover) > svg { - fill: var(--NONE); -} - -/* PROJECTOR: TEXTAREA */ - -.projector.text { - cursor: default; -} - -.projector.text .wrapper { - height: 100%; - width: 100%; - border-radius: 0.1em; -} - -.projector.text.indicated .cols, -.projector.text:has(textarea:focus) .cols { - color: var(--R1); -} - -.projector.text.indicated > svg { - fill: var(--textarea-indicated); -} - -.projector.text .cols { - height: 100%; - margin-left: 2px; - margin-right: 2px; - display: flex; - border-radius: 0 0.1em 0.1em 0.1em; - color: var(--SAND); - background: repeating-linear-gradient( - var(--NONE), - var(--NONE) 1.41em, - var(--textarea-h-stripe) 1.41em, - var(--textarea-h-stripe) 1.47em /* line-height */ - ); -} - -.projector.text.selected .cols { - background: repeating-linear-gradient( - var(--NONE), - var(--NONE) 1.41em, - var(--textarea-h-strip-selected) 1.41em, - var(--textarea-h-strip-selected) 1.47em /* line-height */ - ); -} -.projector.text.selected > svg { - z-index: 9; -} - -.projector.text textarea { - outline: none; - resize: none; - width: 100%; - caret-color: var(--caret-color); - padding: 0; - margin: 0; - margin-left: -2px; - line-height: var(--line-height); - font-family: var(--code-font); - font-size: inherit; - border: none; - color: var(--textarea-text); - background: none; - box-shadow: inset 1px 0 0 var(--textarea-v-stripe); - overflow: hidden; /* hack: scrolls in chrome without this? */ -} - -.projector.text textarea::selection { - color: var(--BLACK); - background-color: var(--shard-selected); -} - -/* Probe */ - -.projector.probe { - font-family: var(--code-font); - font-size: var(--base-font-size); - color: var(--STONE); -} - -.projector.probe .main { - display: flex; - flex-direction: row; - gap: 0.4em; - cursor: pointer; - align-items: center; -} - -@keyframes rotate { - to { - transform: rotate(360deg); - } -} - -.projector.probe .live-offside:empty + * .icon { - /* opacity: 30%; */ - animation: rotate 5s linear infinite; -} - -.projector.probe > svg { - fill: #c7e6c6; - filter: drop-shadow(0.7px 0.7px 0px green); -} - -.projector.probe.indicated > svg { - fill: var(--G0); - /* fill: #a4d7a2; */ -} - -.projector.probe .num-closures { - position: absolute; - right: -0.5em; - top: -0.3em; - transform-origin: top right; - scale: 0.6; - color: oklch(1 0 0); - font-family: var(--ui-font); - display: flex; - justify-content: center; - background-color: var(--G0); - border-radius: 2em; - font-size: 0.7em; - padding: 0.3em; - min-width: 1em; - z-index: var(--code-emblems-z); -} -.projector.probe.selected .num-closures { - background-color: var(--Y1); -} - -.projector.probe .live-offside { - display: flex; - flex-direction: row; - position: absolute; - left: 10em; - gap: 0.15em; -} - -.projector.probe .live-offside:empty { - display: none; -} - -.projector.probe .val-resize { - cursor: pointer; -} - -.projector.probe .val-resize .code-text { - background-color: #c7e6c6; - border-radius: 0.4em 0.1em 0.1em 0.1em; - filter: drop-shadow(0.7px 0.7px 0px green); -} -.projector.probe .closure:first-child .val-resize:hover .code-text { - cursor: col-resize; -} -.projector.probe .val-resize.cursor .code-text { - outline: 1.6px solid var(--G0); - outline-style: dotted; - filter: none; - opacity: 100%; -} - -.projector.probe .val-resize .code-text { - opacity: 40%; -} -.projector.probe .val-resize:not(.cursor) .code-text .token { - filter: saturate(0); -} -/* .projector.probe .val-resize.cursor .code-text { - opacity: 80%; -} */ -.projector.probe.indicated .val-resize .code-text { - opacity: 60%; -} -.projector.probe.indicated .main { - color: white; -} -.projector.probe.indicated .val-resize.cursor .code-text { - opacity: 100%; - /* outline: 1px solid var(--GB1); */ - outline: 2.8px solid oklch(0.7 0.15 150.31); -} -.projector.probe.indicated .closure:hover .live-env { - display: block; -} -.projector.probe.indicated .closure.cursor .live-env:empty { - display: none; -} -/* .projector.probe .closure:hover .live-env { - display: block; -} */ -.projector.probe .closure:hover .live-env:empty { - display: none; -} - -.projector.probe .live-env { - background-color: var(--T3); - border-radius: 0 0.2em 0.2em 0.2em; - padding: 0.2em; - display: none; - position: absolute; - top: 1.5em; - z-index: var(--code-hovers-z); -} - -.projector.probe .live-env .live-env-entry { - display: flex; - flex-direction: row; - gap: 0.3em; - align-items: baseline; - color: var(--GB0); -} diff --git a/src/util/JsUtil.re b/src/util/JsUtil.re index 4002e92596..e84c34b69e 100644 --- a/src/util/JsUtil.re +++ b/src/util/JsUtil.re @@ -205,13 +205,7 @@ let releasePointerCapture = (e: Js.t(Dom_html.element), pointerId: int) => [|Js.Unsafe.inject(pointerId)|], ); -// let hasPointerCapture = (e: Js.t(Dom_html.element), pointerId: int): bool => -// Js.Unsafe.meth_call( -// e, -// "hasPointerCapture", -// [|Js.Unsafe.inject(pointerId)|], -// ); - +// TODO: Figure out why this doesn't work // let on_pointermove = // (handler: Js.t(Dom_html.pointerEvent) => Effect.t(unit)): Attr.t => // Virtual_dom.Vdom.Attr.property("onpointermove", Js.Unsafe.inject(handler)); diff --git a/src/util/ListUtil.re b/src/util/ListUtil.re index e6a5c6a6c5..300d280b3b 100644 --- a/src/util/ListUtil.re +++ b/src/util/ListUtil.re @@ -593,7 +593,7 @@ let minimum = (f: 'a => int, xs: list('a)): option('a) => }; /* Given two lists, return their maximum common suffix */ -let max_common_suffix = (a: list('a), b: list('a)) => { +let max_common_suffix = (a: list('a), b: list('a)): list('a) => { let rec loop = (a, b, acc) => switch (a, b) { | ([], _) From 18ceb80e3f27799747749115cb00c65df4420abf Mon Sep 17 00:00:00 2001 From: disconcision Date: Sun, 15 Dec 2024 22:17:15 -0500 Subject: [PATCH 21/23] accidentally a dir --- src/haz3lweb/www/style/projectors/base.css | 181 ++++++++++++++++++++ src/haz3lweb/www/style/projectors/panel.css | 29 ++++ src/haz3lweb/www/style/projectors/probe.css | 136 +++++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 src/haz3lweb/www/style/projectors/base.css create mode 100644 src/haz3lweb/www/style/projectors/panel.css create mode 100644 src/haz3lweb/www/style/projectors/probe.css diff --git a/src/haz3lweb/www/style/projectors/base.css b/src/haz3lweb/www/style/projectors/base.css new file mode 100644 index 0000000000..49735cd2b5 --- /dev/null +++ b/src/haz3lweb/www/style/projectors/base.css @@ -0,0 +1,181 @@ +/* PROJECTORS */ + +@import "panel.css"; +@import "probe.css"; + +/* Turn off caret when a block projector is focused */ +#caret:has(~ .projectors .projector.block *:focus) { + display: none; +} + +/* Default projector styles */ + +.projector { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + color: var(--BR2); +} + +.projector > *:not(svg) { + z-index: var(--projector-z); +} + +.projector > svg { + z-index: var(--projector-backing-z); + fill: var(--shard_projector); +} + +.projector.block > svg { + stroke-width: 0.5px; + stroke: var(--BR2); +} + +.projector.indicated > svg { + fill: var(--shard-caret-exp); +} + +.projector > svg > path { + vector-effect: non-scaling-stroke; +} + +.projector.block.selected > svg > path { + vector-effect: non-scaling-stroke; + stroke: var(--Y3); +} + +.projector.selected > svg, +.projector.selected.indicated > svg, +.projector.block.selected.indicated > svg { + fill: var(--shard-selected); +} + +/* PROJECTOR: FOLD */ + +.projector.fold:hover, +.projector.indicated.fold { + color: --var(BLACK); +} +.projector.fold { + cursor: pointer; + font-family: var(--code-font); +} + +.result .projector.fold { + cursor: default; +} + +/* PROJECTOR: INFO */ + +.projector.type .type { + color: var(--token-typ); +} +.projector.type { + cursor: pointer; +} +.projector.type .info { + display: flex; +} +.projector.type:hover { + color: var(--BLACK); +} + +/* PROJECTOR: CHECKBOX */ + +.projector.check input { + margin: 0; + filter: sepia(1); + cursor: pointer; + outline: none; +} +.projector.check:not(.indicated):not(:hover) > svg { + fill: var(--NONE); +} + +/* PROJECTOR: SLIDERS (INT & FLOAT) */ + +.projector.slider input, +.projector.sliderf input { + margin: 0; + filter: sepia(1); + cursor: pointer; + outline: none; + width: 90%; +} +.projector.slider:not(.indicated):not(:hover) > svg, +.projector.sliderf:not(.indicated):not(:hover) > svg { + fill: var(--NONE); +} + +/* PROJECTOR: TEXTAREA */ + +.projector.text { + cursor: default; +} + +.projector.text .wrapper { + height: 100%; + width: 100%; + border-radius: 0.1em; +} + +.projector.text.indicated .cols, +.projector.text:has(textarea:focus) .cols { + color: var(--R1); +} + +.projector.text.indicated > svg { + fill: var(--textarea-indicated); +} + +.projector.text .cols { + height: 100%; + margin-left: 2px; + margin-right: 2px; + display: flex; + border-radius: 0 0.1em 0.1em 0.1em; + color: var(--SAND); + background: repeating-linear-gradient( + var(--NONE), + var(--NONE) 1.41em, + var(--textarea-h-stripe) 1.41em, + var(--textarea-h-stripe) 1.47em /* line-height */ + ); +} + +.projector.text.selected .cols { + background: repeating-linear-gradient( + var(--NONE), + var(--NONE) 1.41em, + var(--textarea-h-strip-selected) 1.41em, + var(--textarea-h-strip-selected) 1.47em /* line-height */ + ); +} +.projector.text.selected > svg { + z-index: 9; +} + +.projector.text textarea { + outline: none; + resize: none; + width: 100%; + caret-color: var(--caret-color); + padding: 0; + margin: 0; + margin-left: -2px; + line-height: var(--line-height); + font-family: var(--code-font); + font-size: inherit; + border: none; + color: var(--textarea-text); + background: none; + box-shadow: inset 1px 0 0 var(--textarea-v-stripe); + overflow: hidden; /* hack: scrolls in chrome without this? */ +} + +.projector.text textarea::selection { + color: var(--BLACK); + background-color: var(--shard-selected); +} + diff --git a/src/haz3lweb/www/style/projectors/panel.css b/src/haz3lweb/www/style/projectors/panel.css new file mode 100644 index 0000000000..77c184bf59 --- /dev/null +++ b/src/haz3lweb/www/style/projectors/panel.css @@ -0,0 +1,29 @@ +/* PROJECTORS PANEL */ + +#projectors { + display: flex; + gap: 0.75em; + align-items: center; + padding-left: 0.75em; + padding-right: 0.75em; + white-space: nowrap; + border-radius: 1.1em 0 0 0; + outline: 0.3px solid var(--BR2); +} + +#projectors img { + width: 0.7em; +} + +#projectors option { + font-size: 1em; +} + +#projectors .toggle-switch { + display: flex; + align-items: center; +} + +#projectors .toggle-switch.inactive { + opacity: 40%; +} diff --git a/src/haz3lweb/www/style/projectors/probe.css b/src/haz3lweb/www/style/projectors/probe.css new file mode 100644 index 0000000000..60e747dc38 --- /dev/null +++ b/src/haz3lweb/www/style/projectors/probe.css @@ -0,0 +1,136 @@ +/* Probe Projector */ + +.projector.probe { + font-family: var(--code-font); + font-size: var(--base-font-size); + color: var(--STONE); +} + +.projector.probe > svg { + fill: #c7e6c6; + filter: drop-shadow(0.7px 0.7px 0px green); +} + +.projector.probe .main { + display: flex; + flex-direction: row; + gap: 0.4em; + cursor: pointer; + align-items: center; +} + +@keyframes rotate { + to { + transform: rotate(360deg); + } +} +.projector.probe .live-offside:empty + * .icon { + animation: rotate 5s linear infinite; +} + +.projector.probe .num-closures { + position: absolute; + right: -0.5em; + top: -0.3em; + transform-origin: top right; + scale: 0.6; + color: oklch(1 0 0); + font-family: var(--ui-font); + display: flex; + justify-content: center; + background-color: var(--G0); + border-radius: 2em; + font-size: 0.7em; + padding: 0.3em; + min-width: 1em; + z-index: var(--code-emblems-z); +} +.projector.probe.selected .num-closures { + background-color: var(--Y1); +} + +.projector.probe .live-offside { + display: flex; + flex-direction: row; + position: absolute; + left: 10em; + gap: 0.15em; +} + +.projector.probe .live-offside:empty { + display: none; +} + +.projector.probe .closure:hover .live-env:empty { + display: none; +} + +.projector.probe .val-resize { + cursor: pointer; +} + +.projector.probe .val-resize .code-text { + background-color: #c7e6c6; /* TODO(andrew) */ + border-radius: 0.4em 0.1em 0.1em 0.1em; + filter: drop-shadow(0.7px 0.7px 0px green); +} +.projector.probe .closure:first-child .val-resize:hover .code-text { + cursor: col-resize; +} +.projector.probe .val-resize.cursor .code-text { + outline: 1.6px solid var(--G0); + outline-style: dotted; + filter: none; + opacity: 100%; +} + +.projector.probe .val-resize .code-text { + opacity: 40%; +} +.projector.probe .val-resize:not(.cursor) .code-text .token { + filter: saturate(0); +} + +.projector.probe .live-env { + background-color: var(--T3); + border-radius: 0 0.2em 0.2em 0.2em; + padding: 0.2em; + display: none; + position: absolute; + top: 1.5em; + z-index: var(--code-hovers-z); +} + +.projector.probe .live-env .live-env-entry { + display: flex; + flex-direction: row; + gap: 0.3em; + align-items: baseline; + color: var(--GB0); +} + +.projector.probe.indicated > svg { + fill: var(--G0); +} + +.projector.probe.indicated .val-resize .code-text { + opacity: 60%; +} + +.projector.probe.indicated .main { + color: white; +} + +.projector.probe.indicated .val-resize.cursor .code-text { + opacity: 100%; + /* outline: 1px solid var(--GB1); */ + outline: 2.8px solid oklch(0.7 0.15 150.31); +} + +.projector.probe.indicated .closure:hover .live-env { + display: block; +} + +.projector.probe.indicated .closure.cursor .live-env:empty { + display: none; +} From 1ea2ac452109ef53be2543428757401ebb3f0a5c Mon Sep 17 00:00:00 2001 From: disconcision Date: Sun, 15 Dec 2024 23:18:03 -0500 Subject: [PATCH 22/23] live projectors cleanup --- src/haz3lcore/dynamics/Evaluator.re | 2 +- src/haz3lcore/dynamics/EvaluatorState.re | 6 +- src/haz3lcore/dynamics/EvaluatorState.rei | 2 +- src/haz3lcore/dynamics/EvaluatorStep.re | 4 +- src/haz3lcore/dynamics/Probe.re | 7 +- src/haz3lcore/dynamics/Transition.re | 6 +- src/haz3lcore/pretty/Abbreviate.re | 80 ++------------ src/haz3lcore/prog/Dynamics.re | 105 +++++++++++-------- src/haz3lcore/zipper/projectors/ProbeProj.re | 44 ++++---- 9 files changed, 104 insertions(+), 152 deletions(-) diff --git a/src/haz3lcore/dynamics/Evaluator.re b/src/haz3lcore/dynamics/Evaluator.re index fe8fd2eda8..b91f76107b 100644 --- a/src/haz3lcore/dynamics/Evaluator.re +++ b/src/haz3lcore/dynamics/Evaluator.re @@ -42,7 +42,7 @@ module EvaluatorEVMode: { state := EvaluatorState.add_test(state^, id, v); let update_probe = (state, id, v) => - state := EvaluatorState.add_probe(state^, id, v); + state := EvaluatorState.add_closure(state^, id, v); let req_value = (f, _, x) => switch (f(x)) { diff --git a/src/haz3lcore/dynamics/EvaluatorState.re b/src/haz3lcore/dynamics/EvaluatorState.re index 104a9605fd..f3dd98daaa 100644 --- a/src/haz3lcore/dynamics/EvaluatorState.re +++ b/src/haz3lcore/dynamics/EvaluatorState.re @@ -27,9 +27,9 @@ let add_test = ({tests, _} as es, id, report) => { let get_tests = ({tests, _}) => tests; -let add_probe = ({probes, _} as es, id: Id.t, v: Dynamics.Probe.Info.t) => { - let probes = Dynamics.Probe.Map.extend(id, v, probes); - {...es, probes}; +let add_closure = ({probes, _} as es, id: Id.t, v: Dynamics.Probe.Closure.t) => { + ...es, + probes: Dynamics.Probe.Map.extend(id, v, probes), }; let get_probes = ({probes, _}) => probes; diff --git a/src/haz3lcore/dynamics/EvaluatorState.rei b/src/haz3lcore/dynamics/EvaluatorState.rei index 7ebca35433..a20810396d 100644 --- a/src/haz3lcore/dynamics/EvaluatorState.rei +++ b/src/haz3lcore/dynamics/EvaluatorState.rei @@ -32,6 +32,6 @@ let add_test: (t, Id.t, TestMap.instance_report) => t; let get_tests: t => TestMap.t; -let add_probe: (t, Id.t, Dynamics.Probe.Info.t) => t; +let add_closure: (t, Id.t, Dynamics.Probe.Closure.t) => t; let get_probes: t => Dynamics.Probe.Map.t; diff --git a/src/haz3lcore/dynamics/EvaluatorStep.re b/src/haz3lcore/dynamics/EvaluatorStep.re index f70ec94782..c473af4476 100644 --- a/src/haz3lcore/dynamics/EvaluatorStep.re +++ b/src/haz3lcore/dynamics/EvaluatorStep.re @@ -336,7 +336,7 @@ module Decompose = { let update_test = (state, id, v) => state := EvaluatorState.add_test(state^, id, v); let update_probe = (state, id, info) => - state := EvaluatorState.add_probe(state^, id, info); + state := EvaluatorState.add_closure(state^, id, info); }; module Decomp = Transition(DecomposeEVMode); @@ -384,7 +384,7 @@ module TakeStep = { state := EvaluatorState.add_test(state^, id, v); let update_probe = (state, id, info) => - state := EvaluatorState.add_probe(state^, id, info); + state := EvaluatorState.add_closure(state^, id, info); }; module TakeStepEV = Transition(TakeStepEVMode); diff --git a/src/haz3lcore/dynamics/Probe.re b/src/haz3lcore/dynamics/Probe.re index bf4da4dc95..5530c941da 100644 --- a/src/haz3lcore/dynamics/Probe.re +++ b/src/haz3lcore/dynamics/Probe.re @@ -22,7 +22,6 @@ type tag = /* Information about the evaluation of an ap */ [@deriving (show({with_path: false}), sexp, yojson)] type frame = { - closure_id: Id.t, /* Primary ID (Unique) */ ap_id: Id.t, /* Syntax ID of the ap */ env_id: Id.t /* ID of ClosureEnv created by ap */ }; @@ -36,8 +35,4 @@ let empty: t = {refs: [], stem: []}; let env_stack: list(frame) => list(Id.t) = List.map((en: frame) => en.env_id); -let mk_frame = (~env_id: Id.t, ~ap_id: Id.t): frame => { - closure_id: Id.mk(), - env_id, - ap_id, -}; +let mk_frame = (~env_id: Id.t, ~ap_id: Id.t): frame => {env_id, ap_id}; diff --git a/src/haz3lcore/dynamics/Transition.re b/src/haz3lcore/dynamics/Transition.re index 583b07cbb1..ab733e2821 100644 --- a/src/haz3lcore/dynamics/Transition.re +++ b/src/haz3lcore/dynamics/Transition.re @@ -132,7 +132,7 @@ module type EV_MODE = { let update_test: (state, Id.t, TestMap.instance_report) => unit; - let update_probe: (state, Id.t, Dynamics.Probe.Info.t) => unit; + let update_probe: (state, Id.t, Dynamics.Probe.Closure.t) => unit; }; module Transition = (EV: EV_MODE) => { @@ -788,8 +788,8 @@ module Transition = (EV: EV_MODE) => { Step({ expr: d', state_update: () => { - let pi = Dynamics.Probe.Info.mk(d', env, pr); - update_probe(state, DHExp.rep_id(d), pi); + let closure = Dynamics.Probe.Closure.mk(d', env, pr); + update_probe(state, DHExp.rep_id(d), closure); }, kind: RemoveParens, is_value: true, diff --git a/src/haz3lcore/pretty/Abbreviate.re b/src/haz3lcore/pretty/Abbreviate.re index 69f9cf0787..1b9d24b88f 100644 --- a/src/haz3lcore/pretty/Abbreviate.re +++ b/src/haz3lcore/pretty/Abbreviate.re @@ -1,3 +1,9 @@ +/* Abbreviate a term for display, specifically for the live + * value probe projector. This is currently specialized for + * expressions which are (at least partially) values. This + * is a bit rough right now, and should be redone when we + * projectors (in particular, fold) within value displays */ + let comp_elipses = "⋱"; let flat_ellipses = "…"; let ellipses_term = () => IdTagged.fresh(Invalid(comp_elipses): Exp.term); @@ -23,13 +29,6 @@ let abbreviate_str = (min_len: int, s: string): string => { }; let rec abbreviate_exp = (exp: Exp.t): Exp.t => { - /* - Maybe we can also use this to format, ie insert linebreaks? - Hard when it's just exp but maybe we can track them via - some inserted form, or as a side effect? eg emit ids - to insert lb after during ExpToSeg? - */ - // print_endline("abbreviate_exp"); let rewrap = (term: Exp.term): Exp.t => { {...exp, term}; }; @@ -73,7 +72,7 @@ let rec abbreviate_exp = (exp: Exp.t): Exp.t => { // composite literal cases | ListLit(xs) => - //TODO(andrew): improve this logic + //TODO: improve this logic if (available^ < 6) { ListLit([flat_ellipses_term()]); } else { @@ -161,71 +160,6 @@ let rec abbreviate_exp = (exp: Exp.t): Exp.t => { | Match(_e, _rs) => indet_term }; rewrap(term); -} -and abbreviate_pat = (pat: Pat.t): Pat.t => { - switch (pat |> Pat.term_of) { - | Invalid(_t) => failwith("abbreviate_pat") - | EmptyHole => failwith("abbreviate_pat") - | Wild => failwith("abbreviate_pat") - | Var(_v) => failwith("abbreviate_pat") - | Int(_n) => failwith("abbreviate_pat") - | Float(_f) => failwith("abbreviate_pat") - | Bool(_b) => failwith("abbreviate_pat") - | String(_s) => failwith("abbreviate_pat") - | Constructor(_c, _) => failwith("abbreviate_pat") - | ListLit([]) => failwith("abbreviate_pat") - | ListLit([_x, ..._xs]) => failwith("abbreviate_pat") - | Cons(_p1, _p2) => failwith("abbreviate_pat") - | Tuple([]) => failwith("abbreviate_pat") - | Tuple([_]) => failwith("Singleton Tuples are not allowed") - | Tuple([_x, ..._xs]) => failwith("abbreviate_pat") - | Parens(_p) => failwith("abbreviate_pat") - | MultiHole(_es) => failwith("abbreviate_pat") - | Ap(_p1, _p2) => failwith("abbreviate_pat") - | Cast(_p, _t, _) => failwith("abbreviate_pat") - }; -} -and abbreviate_typ = (typ: Typ.t): Typ.t => { - switch (typ |> Typ.term_of) { - | Unknown(Hole(Invalid(_s))) => failwith("abbreviate_typ") - | Unknown(_) => failwith("abbreviate_typ") - | Var(_) => failwith("abbreviate_typ") - | Int => failwith("abbreviate_typ") - | Float => failwith("abbreviate_typ") - | Bool => failwith("abbreviate_typ") - | String => failwith("abbreviate_typ") - | List(_t) => failwith("abbreviate_typ") - | Prod([]) => failwith("abbreviate_typ") - | Prod([_]) => failwith("Singleton Prods are not allowed") - | Prod([_t, ..._ts]) => failwith("abbreviate_typ") - | Parens(_t) => failwith("abbreviate_typ") - | Ap(_t1, _t2) => failwith("abbreviate_typ") - | Rec(_tp, _t) => failwith("abbreviate_typ") - | Forall(_tp, _t) => failwith("abbreviate_typ") - | Arrow(_t1, _t2) => failwith("abbreviate_typ") - | Sum([]) => failwith("abbreviate_typ") - | Sum([_t]) => failwith("abbreviate_typ") - | Sum([_t, ..._ts]) => failwith("abbreviate_typ") - }; -} -and abbreviate_tpat = (tpat: TPat.t): TPat.t => { - switch (tpat |> IdTagged.term_of) { - | Invalid(_t) => failwith("abbreviate_tpat") - | EmptyHole => failwith("abbreviate_tpat") - | MultiHole(_xs) => failwith("abbreviate_tpat") - | Var(_v) => failwith("abbreviate_tpat") - }; -} -and abbreviate_any = (any: Any.t): Any.t => { - switch (any) { - | Exp(e) => Exp(abbreviate_exp(e)) - | Pat(p) => Pat(abbreviate_pat(p)) - | Typ(t) => Typ(abbreviate_typ(t)) - | TPat(tp) => TPat(abbreviate_tpat(tp)) - | Any(_) - | Nul(_) - | Rul(_) => failwith("TODO: abbreviate_any: Rul | Any | Nul") - }; }; let abbreviate_exp = (~available as a=12, exp: Exp.t): (Exp.t, bool) => { diff --git a/src/haz3lcore/prog/Dynamics.re b/src/haz3lcore/prog/Dynamics.re index 01f0eae25f..1f5b56ec20 100644 --- a/src/haz3lcore/prog/Dynamics.re +++ b/src/haz3lcore/prog/Dynamics.re @@ -1,49 +1,58 @@ open Util; -module Probe = { - let instrument = - (m: Statics.Map.t, id: Id.t, probe_tag: Probe.tag): Probe.tag => - switch (probe_tag) { - | Paren => Paren - | Probe(_) => - Probe({ - refs: Statics.Map.refs_in(m, id), - stem: Statics.Map.enclosing_abstractions(m, id), - }) - }; +/* Semantic information gathered during evaluation. This aspirationally + * unifies all evaluator output, in the same sense as Statics does for + * static information gathering, but right now it specifically handles + * closure gathering for probe projectors */ +module Probe = { module Env = { + /* To avoid unnecessary de/serialization from evaluation worker, + * we refrain from retaining certain large un-educational values, + * such as closures. Which values are made opaque can be modulated + * via the below `elide` function */ [@deriving (show({with_path: false}), sexp, yojson)] - type raw = + type elided_value = | Opaque | Val(DHExp.t); + /* A probe environment entry is a variable binding + * along with its corresponding elided value */ [@deriving (show({with_path: false}), sexp, yojson)] type entry = { - name: string, - id: Id.t, - raw, + binding: Binding.t, + value: elided_value, }; + /* A probe environment is a summarized version of the + * dynamic environment of the probed expression */ [@deriving (show({with_path: false}), sexp, yojson)] type t = list(entry); - let to_raw = (d: DHExp.t) => + /* Selectively elide dynamic information not currently + * being used in the live probe UI, for (putative, unbenchmarked) + * performance purposes for worker de/serialization */ + let elide = (env: ClosureEnvironment.t, d: DHExp.t) => switch (d.term) { | Fun(_) | FixF(_) => Opaque - | _ => Val(d) + | _ => + Val( + d + |> DHExp.strip_casts + |> Exp.substitute_closures(ClosureEnvironment.map_of(env)), + ) }; - let mk_entry = (env, {name, id, _}: Binding.t) => + let mk_entry = (env: ClosureEnvironment.t, {name, id, _}: Binding.t) => switch (ClosureEnvironment.lookup(env, name)) { - | Some(d) => - let raw = - d - |> DHExp.strip_casts - |> Exp.substitute_closures(ClosureEnvironment.map_of(env)) - |> to_raw; - {name, id, raw}; + | Some(d) => { + binding: { + name, + id, + }, + value: elide(env, d), + } | None => failwith("Probe: variable not found in environment") }; @@ -51,24 +60,32 @@ module Probe = { List.map(mk_entry(env), refs); }; - module Info = { + /* A probe closure records an elided value and environment, + * in the above senses, along with a `stack` which records + * partial information about the execution trace prior to + * the creation of the closure */ + module Closure = { [@deriving (show({with_path: false}), sexp, yojson)] type t = { + closure_id: Id.t, /* Primary ID (Unique) */ value: DHExp.t, env: Env.t, stack: Probe.stack, }; let mk = (value: DHExp.t, env: ClosureEnvironment.t, pr: Probe.t) => { + closure_id: Id.mk(), value, stack: ClosureEnvironment.stack_of(env), env: Env.mk(env, pr.refs), }; }; + /* Closures recorded during evaluation, indexed by the + * syntax ids of their initial expressions */ module Map = { [@deriving (show({with_path: false}), sexp, yojson)] - type t = Id.Map.t(list(Info.t)); + type t = Id.Map.t(list(Closure.t)); let empty = Id.Map.empty; let lookup = Id.Map.find_opt; @@ -85,29 +102,31 @@ module Probe = { ); }; - let extend = ((id, report), test_map) => { - switch (List.assoc_opt(id, test_map)) { - | Some(a) => List.remove_assoc(id, test_map) @ [(id, a @ [report])] - | None => test_map @ [(id, [report])] + /* Intercepts a probe form and adds in static semantic information + * to guide dynamic information gathering */ + let instrument = + (m: Statics.Map.t, id: Id.t, probe_tag: Probe.tag): Probe.tag => + switch (probe_tag) { + | Paren => Paren + | Probe(_) => + Probe({ + refs: Statics.Map.refs_in(m, id), + stem: Statics.Map.enclosing_abstractions(m, id), + }) }; - }; }; module Info = { + /* Collected closures for a given id */ [@deriving (show({with_path: false}), sexp, yojson)] - type t = {vals: list(Probe.Info.t)}; + type t = list(Probe.Closure.t); }; module Map = { + /* Just a wrapping around the Probe map (for now) */ [@deriving (show({with_path: false}), sexp, yojson)] - type t = {probes: Probe.Map.t}; - let empty: t = {probes: Probe.Map.empty}; - - let mk = (probes: Probe.Map.t): t => {probes: probes}; - - let lookup = (id: Id.t, dm: t): option(Info.t) => - switch (Probe.Map.lookup(id, dm.probes)) { - | None => None - | Some(vals) => Some({vals: vals}) - }; + type t = Probe.Map.t; + let empty: t = Probe.Map.empty; + let mk: t => t = Fun.id; + let lookup = Probe.Map.lookup; }; diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index 0ce589301d..292360aaca 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -20,10 +20,8 @@ type a = let stack = stack => stack |> List.rev - |> List.map(({env_id, ap_id, closure_id}: Probe.frame) => + |> List.map(({env_id, ap_id}: Probe.frame) => "" - ++ String.sub(Id.to_string(closure_id), 0, 2) - ++ "\n" ++ String.sub(Id.to_string(env_id), 0, 2) ++ "\n" ++ String.sub(Id.to_string(ap_id), 0, 2) @@ -41,7 +39,7 @@ let one_is_suffix_of_other = (s1, s2) => common_suffix_length(s1, s2) == List.length(s1) || common_suffix_length(s1, s2) == List.length(s2); -let comparor = (a: Dynamics.Probe.Info.t, b: Dynamics.Probe.Info.t) => { +let comparor = (a: Dynamics.Probe.Closure.t, b: Dynamics.Probe.Closure.t) => { compare( common_suffix_length(env_cursor^, Probe.env_stack(b.stack)), common_suffix_length(env_cursor^, Probe.env_stack(a.stack)), @@ -75,12 +73,18 @@ let get_goal = (utility: utility, e: Js.t(Dom_html.mouseEvent)) => ); let resizable_val = - (~resizable=false, model, utility, local, pi: Dynamics.Probe.Info.t) => { + ( + ~resizable=false, + model, + utility, + local, + closure: Dynamics.Probe.Closure.t, + ) => { let val_pointerdown = (e: Js.t(Dom_html.pointerEvent)) => { let target = e##.target |> Js.Opt.get(_, _ => failwith("no target")); JsUtil.setPointerCapture(target, e##.pointerId) |> ignore; mousedown := Some(target); - env_cursor := Probe.env_stack(pi.stack); + env_cursor := Probe.env_stack(closure.stack); last_target := [target]; Effect.Ignore; }; @@ -107,16 +111,16 @@ let resizable_val = div( ~attrs=[ - Attr.title(stack(pi.stack)), + Attr.title(stack(closure.stack)), Attr.classes( - ["val-resize"] @ (show_indicator(pi.stack) ? ["cursor"] : []), + ["val-resize"] @ (show_indicator(closure.stack) ? ["cursor"] : []), ), Attr.on_double_click(_ => local(ToggleShowAllVals)), Attr.on_pointerdown(val_pointerdown), Attr.on_pointerup(val_pointerup), Attr.on_mousemove(val_mousemove), ], - [seg_view(utility, model.len, pi.value)], + [seg_view(utility, model.len, closure.value)], ); }; @@ -124,8 +128,8 @@ let env_val = (en: Dynamics.Probe.Env.entry, utility: utility) => { Node.div( ~attrs=[Attr.classes(["live-env-entry"])], [ - Node.text(en.name ++ "="), - switch (en.raw) { + Node.text(en.binding.name ++ "="), + switch (en.value) { | Opaque => Node.text("Opaque") | Val(d) => seg_view(utility, 12, d) }, @@ -137,7 +141,7 @@ let env_val = (en: Dynamics.Probe.Env.entry, utility: utility) => { let rm_opaques: list(Dynamics.Probe.Env.entry) => list(Dynamics.Probe.Env.entry) = List.filter_map((en: Dynamics.Probe.Env.entry) => - switch (en.raw) { + switch (en.value) { | Opaque => None | Val(_) => Some(en) } @@ -150,10 +154,10 @@ let is_var_ref = (info: info): bool => | _ => false }; -let env_view = (pi: Dynamics.Probe.Info.t, utility: utility): Node.t => +let env_view = (closure: Dynamics.Probe.Closure.t, utility: utility): Node.t => Node.div( ~attrs=[Attr.classes(["live-env"])], - pi.env |> rm_opaques |> List.map(en => env_val(en, utility)), + closure.env |> rm_opaques |> List.map(en => env_val(en, utility)), ); let closure_view = @@ -163,16 +167,16 @@ let closure_view = model: t, local, index, - pi: Dynamics.Probe.Info.t, + closure: Dynamics.Probe.Closure.t, ) => div( ~attrs=[ Attr.classes( - ["closure"] @ (show_indicator(pi.stack) ? ["cursor"] : []), + ["closure"] @ (show_indicator(closure.stack) ? ["cursor"] : []), ), ], - [resizable_val(~resizable=index == 0, model, utility, local, pi)] - @ (is_var_ref(info) ? [] : [env_view(pi, utility)]), + [resizable_val(~resizable=index == 0, model, utility, local, closure)] + @ (is_var_ref(info) ? [] : [env_view(closure, utility)]), ); let select_vals = (model: t, vals) => { @@ -196,7 +200,7 @@ let offside_view = (info, ~model, ~local, ~utility: utility) => { | Some(di) => List.mapi( closure_view(info, utility, model, local), - select_vals(model, di.vals), + select_vals(model, di), ) | _ => [] }, @@ -205,7 +209,7 @@ let offside_view = (info, ~model, ~local, ~utility: utility) => { let num_closures = (info: info) => switch (info.dynamics) { - | Some(di) => List.length(di.vals) + | Some(di) => List.length(di) | _ => 0 }; From 953e4a2b82ef338f9d12b46a21efb0bc37387a1b Mon Sep 17 00:00:00 2001 From: disconcision Date: Thu, 19 Dec 2024 00:01:39 -0500 Subject: [PATCH 23/23] truncate probe projector closures lists after 30 items, controls to scroll. live value styling --- src/haz3lcore/zipper/projectors/ProbeProj.re | 136 ++++++++++++------ src/haz3lweb/app/common/ProjectorView.re | 29 ++-- src/haz3lweb/www/img/noun-arrow-1813676.svg | 4 + src/haz3lweb/www/img/noun-arrow-3130579.svg | 4 + src/haz3lweb/www/img/noun-magnify-5699400.svg | 4 + src/haz3lweb/www/img/noun-search-5661270.svg | 6 + src/haz3lweb/www/style/projectors/base.css | 4 + src/haz3lweb/www/style/projectors/probe.css | 69 +++++++-- src/util/ListUtil.re | 28 ++++ 9 files changed, 218 insertions(+), 66 deletions(-) create mode 100644 src/haz3lweb/www/img/noun-arrow-1813676.svg create mode 100644 src/haz3lweb/www/img/noun-arrow-3130579.svg create mode 100644 src/haz3lweb/www/img/noun-magnify-5699400.svg create mode 100644 src/haz3lweb/www/img/noun-search-5661270.svg diff --git a/src/haz3lcore/zipper/projectors/ProbeProj.re b/src/haz3lcore/zipper/projectors/ProbeProj.re index 292360aaca..09c148d51f 100644 --- a/src/haz3lcore/zipper/projectors/ProbeProj.re +++ b/src/haz3lcore/zipper/projectors/ProbeProj.re @@ -5,28 +5,45 @@ open Node; open Js_of_ocaml; [@deriving (show({with_path: false}), sexp, yojson)] -type t = { +type model' = { [@default "⋱"] text: string, len: int, show_all_vals: bool, + max_closures: int, + index_offset: int, }; [@deriving (show({with_path: false}), sexp, yojson)] -type a = +type action' = | ChangeLength(int) + | Offset(int) | ToggleShowAllVals; -let stack = stack => - stack - |> List.rev - |> List.map(({env_id, ap_id}: Probe.frame) => - "" - ++ String.sub(Id.to_string(env_id), 0, 2) - ++ "\n" - ++ String.sub(Id.to_string(ap_id), 0, 2) - ) - |> String.concat("\n"); +let init = { + text: "🔍", + len: 12, + show_all_vals: true, + max_closures: 30, + index_offset: 0, +}; + +let model'_of_sexp = (sexp): model' => + switch (model'_of_sexp(sexp)) { + | exception _ => init + | x => x + }; + +// let stack = stack => +// stack +// |> List.rev +// |> List.map(({env_id, ap_id}: Probe.frame) => +// "" +// ++ String.sub(Id.to_string(env_id), 0, 2) +// ++ "\n" +// ++ String.sub(Id.to_string(ap_id), 0, 2) +// ) +// |> String.concat("\n"); let env_cursor: ref(list(Id.t)) = ref([]); let last_target: ref(list('a)) = ref([]); @@ -111,7 +128,7 @@ let resizable_val = div( ~attrs=[ - Attr.title(stack(closure.stack)), + //Attr.title(stack(closure.stack)), Attr.classes( ["val-resize"] @ (show_indicator(closure.stack) ? ["cursor"] : []), ), @@ -164,7 +181,7 @@ let closure_view = ( info, utility: utility, - model: t, + model: model', local, index, closure: Dynamics.Probe.Closure.t, @@ -179,11 +196,17 @@ let closure_view = @ (is_var_ref(info) ? [] : [env_view(closure, utility)]), ); -let select_vals = (model: t, vals) => { +let select_vals = (model: model', vals) => { switch (List.sort(comparor, vals)) { | [] => [] - | [hd, ..._] when !model.show_all_vals => [hd] - | _ => vals + //| [hd, ..._] when !model.show_all_vals => [hd] + | _ => + // print_endline( + // "Select vals: rotateing by: " ++ string_of_int(model.index_offset), + // ); + vals + |> ListUtil.remove_first_n(model.index_offset) + |> ListUtil.truncate(model.max_closures) }; }; @@ -193,15 +216,48 @@ let offside_pos = utility => Printf.sprintf("position: absolute; left: %fpx;", utility.offside_offset), ); -let offside_view = (info, ~model, ~local, ~utility: utility) => { +let offside_view = (info, ~model: model', ~local, ~utility: utility) => { Node.div( ~attrs=[Attr.classes(["live-offside"]), offside_pos(utility)], switch (info.dynamics) { | Some(di) => - List.mapi( - closure_view(info, utility, model, local), - select_vals(model, di), - ) + let vals = select_vals(model, di); + let left_cond = + model.index_offset >= List.length(di) - model.max_closures; + let hd = + List.length(di) > model.max_closures + ? [ + Node.div( + ~attrs=[ + Attr.classes( + ["closures-header"] @ (left_cond ? ["disabled"] : []), + ), + Attr.on_click(_ => + left_cond ? Effect.Ignore : local(Offset(1)) + ), + ], + [Node.text("<")], + ), + ] + : []; + let right_cond = model.index_offset <= 0; + let tl = + List.length(di) > model.max_closures + ? [ + Node.div( + ~attrs=[ + Attr.classes( + ["closures-tail"] @ (right_cond ? ["disabled"] : []), + ), + Attr.on_click(_ => + right_cond ? Effect.Ignore : local(Offset(-1)) + ), + ], + [Node.text(">")], + ), + ] + : []; + hd @ tl @ List.mapi(closure_view(info, utility, model, local), vals); | _ => [] }, ); @@ -238,19 +294,9 @@ let placeholder = (_m, info) => Inline(3 + String.length(syntax_str(info))); let icon = div(~attrs=[Attr.classes(["icon"])], [text("🔍")]); -// let icon = -// img( -// ~attrs=[ -// Attr.classes(["icon"]), -// Attr.create("height", "18px"), -// Attr.create("width", "18px"), -// Attr.src("img/noun-search-5661270.svg"), -// Attr.alt("probe"), -// ], -// (), -// ); - -let view = (model: t, ~info, ~local, ~parent as _, ~utility: utility) => +let icon = div(~attrs=[Attr.classes(["icon"])], []); + +let view = (model: model', ~info, ~local, ~parent as _, ~utility: utility) => div([ offside_view(info, ~model, ~local, ~utility), div( @@ -261,12 +307,12 @@ let view = (model: t, ~info, ~local, ~parent as _, ~utility: utility) => Effect.Ignore; }), ], - [icon, syntax_view(info), num_closures_view(info)], + [syntax_view(info), icon, num_closures_view(info)], ), ]); -let update = (m: t, a: a) => { - print_endline("update: action:" ++ show_a(a)); +let update = (m: model', a: action') => { + print_endline("update: action:" ++ show_action'(a)); switch (a) { | ChangeLength(len) => if (len > (-1)) { @@ -274,16 +320,22 @@ let update = (m: t, a: a) => { } else { m; } - | ToggleShowAllVals => {...m, show_all_vals: !m.show_all_vals} + | ToggleShowAllVals => + //{...m, show_all_vals: !m.show_all_vals} + {...m, max_closures: m.max_closures == 1 ? 30 : 1} + | Offset(offset) => + let index_offset = m.index_offset + offset; + let index_offset = index_offset < 0 ? 0 : index_offset; + {...m, index_offset}; }; }; module M: Projector = { [@deriving (show({with_path: false}), sexp, yojson)] - type model = t; + type model = model'; [@deriving (show({with_path: false}), sexp, yojson)] - type action = a; - let init = {text: "🔍", len: 12, show_all_vals: true}; + type action = action'; + let init = init; let can_project = _ => true; let can_focus = false; let placeholder = placeholder; diff --git a/src/haz3lweb/app/common/ProjectorView.re b/src/haz3lweb/app/common/ProjectorView.re index 5212457892..6f94d9b60c 100644 --- a/src/haz3lweb/app/common/ProjectorView.re +++ b/src/haz3lweb/app/common/ProjectorView.re @@ -218,21 +218,20 @@ let all = ) => { div_c( "projectors", - List.filter_map( - ((id, _)) => { - let indication = indication(z, id); - setup_view( - id, - ~cached_statics, - ~cached_syntax, - ~dynamics, - ~inject, - ~globals, - ~indication, - ); - }, - Id.Map.bindings(cached_syntax.projectors) |> List.rev, - ), + Id.Map.bindings(cached_syntax.projectors) + |> List.filter_map(((id, _)) => { + let indication = indication(z, id); + setup_view( + id, + ~cached_statics, + ~cached_syntax, + ~dynamics, + ~inject, + ~globals, + ~indication, + ); + }) + |> List.rev, ); }; diff --git a/src/haz3lweb/www/img/noun-arrow-1813676.svg b/src/haz3lweb/www/img/noun-arrow-1813676.svg new file mode 100644 index 0000000000..87d93b96cb --- /dev/null +++ b/src/haz3lweb/www/img/noun-arrow-1813676.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/haz3lweb/www/img/noun-arrow-3130579.svg b/src/haz3lweb/www/img/noun-arrow-3130579.svg new file mode 100644 index 0000000000..1c2331afd2 --- /dev/null +++ b/src/haz3lweb/www/img/noun-arrow-3130579.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/haz3lweb/www/img/noun-magnify-5699400.svg b/src/haz3lweb/www/img/noun-magnify-5699400.svg new file mode 100644 index 0000000000..58089e271d --- /dev/null +++ b/src/haz3lweb/www/img/noun-magnify-5699400.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/haz3lweb/www/img/noun-search-5661270.svg b/src/haz3lweb/www/img/noun-search-5661270.svg new file mode 100644 index 0000000000..98adfa2ae5 --- /dev/null +++ b/src/haz3lweb/www/img/noun-search-5661270.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/haz3lweb/www/style/projectors/base.css b/src/haz3lweb/www/style/projectors/base.css index 49735cd2b5..345edf0884 100644 --- a/src/haz3lweb/www/style/projectors/base.css +++ b/src/haz3lweb/www/style/projectors/base.css @@ -18,6 +18,10 @@ color: var(--BR2); } +.projector.indicated { + z-index: var(--code-hovers-z); +} + .projector > *:not(svg) { z-index: var(--projector-z); } diff --git a/src/haz3lweb/www/style/projectors/probe.css b/src/haz3lweb/www/style/projectors/probe.css index 60e747dc38..76f6a0a1bc 100644 --- a/src/haz3lweb/www/style/projectors/probe.css +++ b/src/haz3lweb/www/style/projectors/probe.css @@ -27,6 +27,13 @@ .projector.probe .live-offside:empty + * .icon { animation: rotate 5s linear infinite; } +.projector.probe .main .icon { + width: 16px; + height: 16px; + background-image: url(../../img/noun-search-5661270.svg); + background-size: cover; + filter: invert(1) brightness(0.4) sepia(1) saturate(1.8) hue-rotate(75deg); +} .projector.probe .num-closures { position: absolute; @@ -48,13 +55,18 @@ .projector.probe.selected .num-closures { background-color: var(--Y1); } +.projector.probe.indicated.Right .num-closures { + background-color: var(--R1); + top: -0.8em; +} .projector.probe .live-offside { display: flex; flex-direction: row; position: absolute; left: 10em; - gap: 0.15em; + gap: 2px; + align-items: center; } .projector.probe .live-offside:empty { @@ -70,16 +82,18 @@ } .projector.probe .val-resize .code-text { - background-color: #c7e6c6; /* TODO(andrew) */ - border-radius: 0.4em 0.1em 0.1em 0.1em; - filter: drop-shadow(0.7px 0.7px 0px green); + background-color: #9eca9b; + /* background-color: #c7e6c6; */ + border-bottom: 1px solid oklch(0.56 0.16 149.14); + border-radius: 0.3em 0.1em 0.1em 0.1em; + /* filter: drop-shadow(0.7px 0.7px 0px green); */ } .projector.probe .closure:first-child .val-resize:hover .code-text { cursor: col-resize; } .projector.probe .val-resize.cursor .code-text { - outline: 1.6px solid var(--G0); - outline-style: dotted; + /* outline: 1.6px solid var(--G0); + outline-style: dotted; */ filter: none; opacity: 100%; } @@ -87,8 +101,13 @@ .projector.probe .val-resize .code-text { opacity: 40%; } +.projector.probe .val-resize .code-text:hover { + filter: brightness(1.05) saturate(1.05); +} .projector.probe .val-resize:not(.cursor) .code-text .token { - filter: saturate(0); + /* filter: saturate(0); */ + /* filter: sepia(1) hue-rotate(61deg) saturate(2); */ + filter: invert(1) brightness(10); } .projector.probe .live-env { @@ -97,8 +116,8 @@ padding: 0.2em; display: none; position: absolute; - top: 1.5em; - z-index: var(--code-hovers-z); + top: 1.35em; + /* z-index: var(--code-hovers-z); */ } .projector.probe .live-env .live-env-entry { @@ -123,6 +142,7 @@ .projector.probe.indicated .val-resize.cursor .code-text { opacity: 100%; + background: none; /* outline: 1px solid var(--GB1); */ outline: 2.8px solid oklch(0.7 0.15 150.31); } @@ -134,3 +154,34 @@ .projector.probe.indicated .closure.cursor .live-env:empty { display: none; } + +.projector.probe .closures-header, +.projector.probe .closures-tail { + width: 10px; + height: 13px; + cursor: pointer; + color: var(--G0); + background: url(../../img/noun-arrow-3130579.svg); + background-size: cover; + color: #0000; + filter: invert(1) brightness(0.5) sepia(1) hue-rotate(61deg); +} +.projector.probe .closures-tail { + transform: rotate(180deg); +} + +.projector.probe:not(.indicated) .closures-header, +.projector.probe:not(.indicated) .closures-tail { + pointer-events: none; + opacity: 0% !important; +} + +.projector.probe .closures-header.disabled, +.projector.probe .closures-tail.disabled { + opacity: 15%; +} + +.projector.probe .closures-header:hover, +.projector.probe .closures-tail:hover { + filter: brightness(1.05) saturate(1.05); +} diff --git a/src/util/ListUtil.re b/src/util/ListUtil.re index 300d280b3b..027334f0d8 100644 --- a/src/util/ListUtil.re +++ b/src/util/ListUtil.re @@ -604,3 +604,31 @@ let max_common_suffix = (a: list('a), b: list('a)): list('a) => { }; loop(List.rev(a), List.rev(b), []); }; + +/* list truncated after at most n elementsnts */ +let truncate = (n: int, xs: list('a)): list('a) => { + let rec loop = (n, xs, acc) => + switch (n, xs) { + | (0, _) => acc + | (_, []) => acc + | (n, [x, ...xs]) => loop(n - 1, xs, [x, ...acc]) + }; + loop(n, xs, []); +}; + +/* list without the first n elements, recurse into list until 0 then return rest */ +let rec remove_first_n = (n: int, xs: list('a)): list('a) => { + switch (n, xs) { + | (0, _) => xs + | (_, []) => [] + | (n, [_x, ...xs]) => remove_first_n(n - 1, xs) + }; +}; + +let rec rotate_n = (n: int, xs: list('a)): list('a) => { + let n = IntUtil.modulo(n, List.length(xs)); + switch (n) { + | 0 => xs + | _ => rotate_n(n - 1, rotate(xs)) + }; +};