Skip to content

Commit 7417579

Browse files
authored
Merge branch 'dev' into patchwork
2 parents 5669837 + b90744a commit 7417579

33 files changed

+456
-125
lines changed

src/haz3lcore/lang/MakeTerm.re

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -886,11 +886,37 @@ and unsorted = (sort: Sort.t, skel: Skel.t, seg: Segment.t): unsorted => {
886886
switch (p) {
887887
| Secondary(_)
888888
| Grout(_) => []
889-
| Projector({syntax, _} as pr) =>
889+
| Projector({id, kind, model, syntax} as pr) =>
890890
let _ = log_projector(pr);
891891
let sort = Piece.sort(syntax) |> fst;
892892
let seg = Piece.unparenthesize(syntax);
893-
[go_s(sort, Segment.skel(seg), seg)];
893+
let inner = go_s(sort, Segment.skel(seg), seg);
894+
/* Construct Projector term with proper annotation, preserving
895+
* projector metadata (kind, model) in the term for round-tripping */
896+
let projector_data: Grammar.projector_data = {
897+
kind,
898+
model,
899+
};
900+
let wrapped =
901+
switch (inner) {
902+
| Grammar.Exp(e) =>
903+
Grammar.Exp({
904+
term: Projector(projector_data, e),
905+
annotation: IdTagged.IdTag.mk([id], get_secondary([id])),
906+
})
907+
| Grammar.Pat(p) =>
908+
Grammar.Pat({
909+
term: Projector(projector_data, p),
910+
annotation: IdTagged.IdTag.mk([id], get_secondary([id])),
911+
})
912+
| Grammar.Typ(t) =>
913+
Grammar.Typ({
914+
term: Projector(projector_data, t),
915+
annotation: IdTagged.IdTag.mk([id], get_secondary([id])),
916+
})
917+
| _ => inner
918+
};
919+
[wrapped];
894920
| Tile({mold, shards, children, _}) =>
895921
Aba.aba_triples(Aba.mk(shards, children))
896922
|> List.map(((l, kid, r)) => {

src/haz3lcore/pretty/ExpToSegment.re

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ let rec external_precedence = (exp: Exp.t): Precedence.t => {
120120

121121
// Same goes for forms which are already surrounded
122122
| Parens(_)
123+
| Projector(_)
123124
| ListLit(_)
124125
| Test(_)
125126
| HintedTest(_)
@@ -175,7 +176,8 @@ let external_precedence_pat = (dp: Pat.t) =>
175176

176177
// Same goes for forms which are already surrounded
177178
| ListLit(_)
178-
| Parens(_) => Precedence.max
179+
| Parens(_)
180+
| Projector(_) => Precedence.max
179181

180182
// Other forms
181183
| Cons(_) => Precedence.cons
@@ -203,6 +205,7 @@ let external_precedence_typ = (tp: Typ.t) =>
203205
| ProdExtension(_) => Precedence.ap
204206
// Same goes for forms which are already surrounded
205207
| Parens(_)
208+
| Projector(_)
206209
| ProofOf(_)
207210
| List(_) => Precedence.max
208211

@@ -508,6 +511,8 @@ let rec parenthesize =
508511
| Parens(e) =>
509512
Parens(parenthesize(~already_paren=true, e) |> paren_at(Precedence.min))
510513
|> rewrap
514+
| Projector(data, e) =>
515+
Projector(data, parenthesize(e) |> paren_at(Precedence.min)) |> rewrap
511516
| Cons(e1, e2) =>
512517
Cons(
513518
parenthesize(e1) |> paren_at(Precedence.cons),
@@ -598,6 +603,9 @@ and parenthesize_pat =
598603
|> paren_pat_at(Precedence.min),
599604
)
600605
|> rewrap
606+
| Projector(data, p) =>
607+
Projector(data, parenthesize_pat(p) |> paren_pat_at(Precedence.min))
608+
|> rewrap
601609
| Cons(p1, p2) =>
602610
Cons(
603611
parenthesize_pat(p1) |> paren_pat_at(Precedence.cons),
@@ -675,6 +683,9 @@ and parenthesize_typ =
675683
|> paren_typ_at(Precedence.min),
676684
)
677685
|> rewrap
686+
| Projector(data, t) =>
687+
Projector(data, parenthesize_typ(t) |> paren_typ_at(Precedence.min))
688+
|> rewrap
678689
| List(t) =>
679690
List(parenthesize_typ(t) |> paren_typ_at(Precedence.min)) |> rewrap
680691
| Prod([]) => typ
@@ -1508,6 +1519,14 @@ let rec exp_to_pretty = (~settings: Settings.t, exp: Exp.t): pretty => {
15081519
let id = exp |> Exp.rep_id;
15091520
let+ e = go(e);
15101521
wrap(exp, [mk_form(ParensExp, id, [e])]);
1522+
| Projector({kind, model}, e) =>
1523+
let id = exp |> Exp.rep_id;
1524+
let+ inner_seg = go(e);
1525+
let syntax = Segment.parenthesize(inner_seg);
1526+
wrap(
1527+
exp,
1528+
[Piece.Projector(ProjectorCore.mk(~id, kind, syntax, model))],
1529+
);
15111530
| Cons(e1, e2) =>
15121531
// TODO: Add optional newlines
15131532
let id = exp |> Exp.rep_id;
@@ -1710,6 +1729,14 @@ and pat_to_pretty = (~settings: Settings.t, pat: Pat.t): pretty => {
17101729
let id = pat |> Pat.rep_id;
17111730
let+ p = go(p);
17121731
wrap(pat, [mk_form(ParensPat, id, [p])]);
1732+
| Projector({kind, model}, p) =>
1733+
let id = pat |> Pat.rep_id;
1734+
let+ inner_seg = go(p);
1735+
let syntax = Segment.parenthesize(inner_seg);
1736+
wrap(
1737+
pat,
1738+
[Piece.Projector(ProjectorCore.mk(~id, kind, syntax, model))],
1739+
);
17131740
| MultiHole(es) =>
17141741
let+ es = es |> List.map(any_to_pretty(~settings: Settings.t)) |> all;
17151742
/* Use IDs from the term for grout pieces, like Tuple uses for commas. */
@@ -1943,6 +1970,14 @@ and typ_to_pretty = (~settings: Settings.t, typ: Typ.t): pretty => {
19431970
let id = typ |> Typ.rep_id;
19441971
let+ t = go(t);
19451972
wrap(typ, [mk_form(ParensTyp, id, [t])]);
1973+
| Projector({kind, model}, t) =>
1974+
let id = typ |> Typ.rep_id;
1975+
let+ inner_seg = go(t);
1976+
let syntax = Segment.parenthesize(inner_seg);
1977+
wrap(
1978+
typ,
1979+
[Piece.Projector(ProjectorCore.mk(~id, kind, syntax, model))],
1980+
);
19461981
| Rec(tp, t) =>
19471982
let id = typ |> Typ.rep_id;
19481983
let+ tp = tpat_to_pretty(~settings: Settings.t, tp)

src/haz3lcore/projectors/ProjectorCore.re

Lines changed: 3 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -12,77 +12,9 @@ open Util;
1212
* ProjectorInfo depends on ProjectorBase but not on ProjectorInit
1313
* (to avoid cyclical dependencies due to MakeTerm and ExpToSegment) */
1414

15-
module Kind = {
16-
/* The different kinds of projector. New projector
17-
* types need to be registered here in order to be
18-
* able to create and update their instances */
19-
[@deriving (show({with_path: false}), sexp, yojson, eq, enumerate)]
20-
type t =
21-
| Fold
22-
| Probe
23-
| Statics
24-
| Checkbox
25-
| Slider
26-
| SliderF
27-
| Card
28-
| Livelit
29-
| TextArea
30-
| Csv;
31-
32-
let livelit_projectors: list(t) = [
33-
Checkbox,
34-
Slider,
35-
SliderF,
36-
TextArea,
37-
Csv, /* Competes with Card for empty list */
38-
Card, /* Competes with Csv for empty list */
39-
Livelit,
40-
];
41-
42-
/* Note: Probe intentionally excluded - probes use separate action path */
43-
let projectors: list(t) = livelit_projectors @ [Fold];
44-
45-
/* Refractors are like probes - additive decorations, not syntax-replacing */
46-
let refractors: list(t) = [Probe, Statics];
47-
let is_refractor = (kind: t) => List.mem(kind, refractors);
48-
49-
/* A friendly name for each projector. This is used
50-
* both for identifying a projector in the CSS and for
51-
* selecting projectors in the projector panel menu */
52-
let name = (p: t): string =>
53-
switch (p) {
54-
| Fold => "fold"
55-
| Probe => "probe"
56-
| Statics => "statics"
57-
| Checkbox => "check"
58-
| Slider => "slider"
59-
| SliderF => "sliderf"
60-
| Card => "card"
61-
| Livelit => "livelit"
62-
| TextArea => "text"
63-
| Csv => "csv"
64-
};
65-
66-
/* This must be updated and kept 1-to-1 with the above
67-
* name function in order to be able to select the
68-
* projector in the projector panel menu */
69-
let of_name = (p: string): t =>
70-
switch (p) {
71-
| "fold" => Fold
72-
| "probe" => Probe
73-
| "statics" => Statics
74-
| "check" => Checkbox
75-
| "slider" => Slider
76-
| "sliderf" => SliderF
77-
| "text" => TextArea
78-
| "livelit" => Livelit
79-
| "card" => Card
80-
| "csv" => Csv
81-
| _ => failwith("Unknown projector kind")
82-
};
83-
84-
let is_name = str => List.mem(str, List.map(name, all));
85-
};
15+
/* Kind is now defined in src/language/ProjectorKind.re to allow
16+
* sharing with Grammar.re (which is in the language library) */
17+
module Kind = Language.ProjectorKind;
8618

8719
/* Projectors in syntax */
8820
[@deriving (show({with_path: false}), sexp, yojson, eq)]

src/haz3lcore/tiles/Segment.re

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -836,11 +836,59 @@ module SecondaryCollection = {
836836
| _ => None
837837
};
838838

839-
/* Recursively collect secondary from a segment (for compound tile children) */
840-
let rec collect_from_seg = (child_seg: t, acc: secondary_map): secondary_map =>
839+
/* Collect secondary from a segment by directly walking through pieces.
840+
This handles segments with embedded Secondary pieces (like projector children)
841+
where skel() would throw Input_contains_secondary. */
842+
let rec collect_from_seg_direct =
843+
(seg: t, acc: secondary_map): secondary_map => {
844+
/* Find all non-secondary pieces and collect their before/after secondary */
845+
let rec find_pieces = (idx, pieces_acc) =>
846+
if (idx >= List.length(seg)) {
847+
List.rev(pieces_acc);
848+
} else {
849+
switch (List.nth(seg, idx)) {
850+
| Piece.Secondary(_) => find_pieces(idx + 1, pieces_acc)
851+
| _ =>
852+
find_pieces(idx + 1, [(idx, List.nth(seg, idx)), ...pieces_acc])
853+
};
854+
};
855+
856+
let pieces = find_pieces(0, []);
857+
List.fold_left(
858+
(acc, (idx, piece)) => {
859+
let before = collect_before(seg, idx);
860+
let after = collect_after(seg, idx);
861+
switch (piece) {
862+
| Piece.Tile({id, children, _}) =>
863+
/* Add secondary for this tile and recurse into children */
864+
let acc = Id.Map.add(id, (before, after), acc);
865+
List.fold_left(
866+
(acc, child_seg) => collect_from_seg(child_seg, acc),
867+
acc,
868+
children,
869+
);
870+
| Piece.Projector({id, syntax, _}) =>
871+
/* Add secondary for projector and recurse into its content */
872+
let acc = Id.Map.add(id, (before, after), acc);
873+
let inner_seg = Piece.unparenthesize(syntax);
874+
collect_from_seg(inner_seg, acc);
875+
| Piece.Grout({id, _}) => Id.Map.add(id, (before, after), acc)
876+
| _ => acc
877+
};
878+
},
879+
acc,
880+
pieces,
881+
);
882+
}
883+
884+
/* Recursively collect secondary from a segment (for compound tile children).
885+
Falls back to direct collection when the segment contains Secondary pieces. */
886+
and collect_from_seg = (child_seg: t, acc: secondary_map): secondary_map =>
841887
try(collect_from_skel(child_seg, skel(child_seg), acc)) {
842-
| Skel.Nonconvex_segment
843-
| Skel.Input_contains_secondary => acc
888+
| Skel.Nonconvex_segment => acc
889+
| Skel.Input_contains_secondary =>
890+
/* Segment has embedded Secondary pieces, use direct collection */
891+
collect_from_seg_direct(child_seg, acc)
844892
}
845893
/* Recursively collect secondary for all terms in the skeleton.
846894
@@ -900,6 +948,11 @@ module SecondaryCollection = {
900948
acc,
901949
children,
902950
)
951+
| Some(Piece.Projector({syntax, _})) =>
952+
/* Projectors wrap their content in parentheses. Extract the inner
953+
segment and recursively collect secondary from it. */
954+
let inner_seg = Piece.unparenthesize(syntax);
955+
collect_from_seg(inner_seg, acc);
903956
| _ => acc
904957
},
905958
acc,

src/haz3lcore/zipper/action/Triggers.re

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,6 @@ let expand_projector = (z: t): option(t) => {
5555
...rest,
5656
]
5757
when Token.is_projector_invoke(name) =>
58-
/* Trim only need because of grout/whitespace transmutation when syntax is hole */
59-
let syntax =
60-
syntax |> Segment.trim_secondary(Right) |> Segment.trim_secondary(Left);
6158
let+ piece = invoked_projector(name, syntax);
6259
Zipper.update_siblings(
6360
((_, r)) => ([piece, ...rest] |> List.rev, r),

src/language/ProjectorKind.re

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/* Projector kinds shared between Grammar.re and ProjectorCore.re.
2+
* This module exists to break the dependency cycle between
3+
* the language and haz3lcore libraries. */
4+
5+
/* The different kinds of projector. New projector
6+
* types need to be registered here in order to be
7+
* able to create and update their instances */
8+
[@deriving (show({with_path: false}), sexp, yojson, eq, enumerate)]
9+
type t =
10+
| Fold
11+
| Probe
12+
| Statics
13+
| Checkbox
14+
| Slider
15+
| SliderF
16+
| Card
17+
| Livelit
18+
| TextArea
19+
| Csv;
20+
21+
let livelit_projectors: list(t) = [
22+
Csv, /* Competes with Card for empty list */
23+
Card, /* Competes with Csv for empty list */
24+
Checkbox,
25+
Slider,
26+
SliderF,
27+
TextArea,
28+
Card,
29+
Livelit,
30+
];
31+
32+
/* Note: Probe intentionally excluded - probes use separate action path */
33+
let projectors: list(t) = livelit_projectors @ [Fold];
34+
35+
/* Refractors are like probes - additive decorations, not syntax-replacing */
36+
let refractors: list(t) = [Probe, Statics];
37+
let is_refractor = (kind: t) => List.mem(kind, refractors);
38+
39+
/* A friendly name for each projector. This is used
40+
* both for identifying a projector in the CSS and for
41+
* selecting projectors in the projector panel menu */
42+
let name = (p: t): string =>
43+
switch (p) {
44+
| Fold => "fold"
45+
| Probe => "probe"
46+
| Statics => "statics"
47+
| Checkbox => "check"
48+
| Slider => "slider"
49+
| SliderF => "sliderf"
50+
| Card => "card"
51+
| Livelit => "livelit"
52+
| TextArea => "text"
53+
| Csv => "csv"
54+
};
55+
56+
/* This must be updated and kept 1-to-1 with the above
57+
* name function in order to be able to select the
58+
* projector in the projector panel menu */
59+
let of_name = (p: string): t =>
60+
switch (p) {
61+
| "fold" => Fold
62+
| "probe" => Probe
63+
| "statics" => Statics
64+
| "check" => Checkbox
65+
| "slider" => Slider
66+
| "sliderf" => SliderF
67+
| "text" => TextArea
68+
| "livelit" => Livelit
69+
| "card" => Card
70+
| "csv" => Csv
71+
| _ => failwith("Unknown projector kind")
72+
};
73+
74+
let is_name = str => List.mem(str, List.map(name, all));

0 commit comments

Comments
 (0)