Skip to content

Commit 9f2d25d

Browse files
hyperpolymathclaude
andcommitted
feat(parser): weave is an expression, not only a statement (#88 B)
Four of the five unparsed conformance/valid programs (v02_weave, v08_crossing, v09_twist, v12_weave_expr) use weave as a DEFINITION BODY: def simple_crossing = weave strands a:Q, b:Q into (a > b) yield strands b:Q, a:Q The grammar only allowed it as a top-level statement, so all four failed to parse. Typed strands and `yield strands` were never the problem — those already worked; only the position did. ## Why the expression form is the right fix As a statement, weave is INERT. Evaluation is a no-op returning (env, None), and the typechecker computes its Tangle[A,B] and then discards it: let _weave_ty = TTangle (input_boundary, output_boundary) in gamma So the construct could be written but never bound, never used, never observed. Binding it to a name is the only thing that makes it reachable — which is exactly what the conformance corpus has always assumed. The statement form is kept, so this is purely additive. ## Scope Weave is OUTSIDE the mechanised core: 0 occurrences in proofs/Tangle.lean and 0 in the TG-3 corpus. So this touches no proof obligation, and Weave is listed with the other non-core constructors in tg3_emit's explicit rejection branch. Threaded through ast (new `Weave of weave_block`), parser (primary_expr), typecheck (types as the Tangle[A,B] it denotes; body checked in the strand context as the statement form does), eval (the body IS the value), and pretty (prints in re-parseable form). `expr_calls` also traverses into a weave body, or recursion inside one would be invisible to the #91 fixpoint search. Two strand printers moved above pp_expr — they had no dependency on it and pp_expr now needs them. ## What is NOT fixed: v11_add_block `add{ 1 + 2 + 3 }` is not a missing parse rule. It is the Harvard DATA SUB-LANGUAGE: its own expression grammar, its own type system (Int / Float / Rational / Hex / Binary / Bool / String / Symbolic), its own environments (Pi) and visibility rules, a separate typing judgement, Embed/Unembed between Harvard and Tangle types, and bidirectional calling. 288 lines of FORMAL-SEMANTICS.md across 12 sections. That is a feature, raised separately; it stays in the corpus manifest as the single remaining known gap. conformance: 14/19 -> 18/19. (It was reporting a fake 3/19 before #89 fixed the runner.) Corpus manifest tightened 5 -> 1 — the ratchet demanded it, which is what it is for. Tests: weave as a definition body, the TG-4 round-trip property (pretty then re-parse yields the same AST), and a guard that the statement form still parses. spec/grammar.ebnf updated to match, with the rationale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 4e6903f commit 9f2d25d

9 files changed

Lines changed: 138 additions & 22 deletions

File tree

compiler/lib/ast.ml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,18 @@ and expr =
108108
(* ---- Crossings (weave context) ---- *)
109109
| Crossing of string * crossing_op * string
110110

111+
(* ---- Weave as an EXPRESSION ----
112+
* `weave ... into ... yield ...` was a statement only, per spec/grammar.ebnf.
113+
* As a statement it is inert: eval returns (env, None) and the typechecker
114+
* discards the Tangle[A,B] it computes, so the construct could be written
115+
* but never bound or used. Allowing it in expression position is what makes
116+
* it reachable — and is how conformance/valid/v02, v08, v09 and v12 have
117+
* always been written (`def x = weave ...`). The statement form is kept, so
118+
* this is purely additive. Weave is outside the mechanised Lean core (0
119+
* occurrences in proofs/Tangle.lean) and outside the TG-3 corpus, so this
120+
* touches no proof obligation. *)
121+
| Weave of weave_block
122+
111123
(** Binary operator tag. *)
112124
and binop =
113125
| Add (** + *)

compiler/lib/eval.ml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,18 @@ let rec eval_expr (env : env) (e : expr) : value =
384384
| _ -> eval_error "Cannot simplify %s" (pp_value v)
385385
end
386386

387+
| Weave wb ->
388+
(* The statement form is a no-op returning (env, None) — the value was
389+
unreachable, which is what made weave inert. In expression position the
390+
body IS the value: it denotes the morphism from the input strands to the
391+
output strands, so evaluate it and present it as a tangle. *)
392+
begin match eval_expr env wb.weave_body with
393+
| VTangle _ as t -> t
394+
| VBraid gens -> VTangle { tv_word = gens; tv_closed = false }
395+
| v -> eval_error "Weave body must evaluate to a braid or tangle, got %s"
396+
(pp_value v)
397+
end
398+
387399
| Cap (_e1, _e2) ->
388400
(* Cap creates a tangle that absorbs two strands — a single-crossing
389401
cup/cap pair. Represented as an empty closed tangle. *)

compiler/lib/parser.mly

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ primary_expr:
321321
{ e }
322322
| LBRACE e = expr RBRACE
323323
{ e }
324+
| w = weave_block
325+
{ Weave w }
324326
;
325327

326328
(* ---- Crossings: (a > b) or (a < b) ---- *)

compiler/lib/pretty.ml

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,18 @@ let rec pp_pattern ctx = function
8888
(* Expressions *)
8989
(* ------------------------------------------------------------------ *)
9090

91+
let pp_typed_strand ctx ts =
92+
emit ctx ts.strand_name;
93+
match ts.strand_type with
94+
| Some t -> emit ctx ": "; emit ctx t
95+
| None -> ()
96+
97+
let pp_strand_list ctx strands =
98+
List.iteri (fun i s ->
99+
if i > 0 then emit ctx ", ";
100+
pp_typed_strand ctx s
101+
) strands
102+
91103
let rec pp_expr ctx = function
92104
| Match (scrut, arms) ->
93105
emit ctx "match ";
@@ -254,6 +266,16 @@ let rec pp_expr ctx = function
254266
) args;
255267
emit ctx ")"
256268

269+
| Weave w ->
270+
(* Same surface syntax as the statement form; in expression position it
271+
re-parses via primary_expr, so TG-4's round-trip property holds. *)
272+
emit ctx "weave strands ";
273+
pp_strand_list ctx w.weave_inputs;
274+
emit ctx " into ";
275+
pp_expr ctx w.weave_body;
276+
emit ctx " yield strands ";
277+
pp_strand_list ctx w.weave_outputs
278+
257279
| Crossing (a, op, b) ->
258280
emit ctx "(";
259281
emit ctx a;
@@ -267,18 +289,6 @@ let rec pp_expr ctx = function
267289
(* Typed strands *)
268290
(* ------------------------------------------------------------------ *)
269291

270-
let pp_typed_strand ctx ts =
271-
emit ctx ts.strand_name;
272-
match ts.strand_type with
273-
| Some t -> emit ctx ": "; emit ctx t
274-
| None -> ()
275-
276-
let pp_strand_list ctx strands =
277-
List.iteri (fun i s ->
278-
if i > 0 then emit ctx ", ";
279-
pp_typed_strand ctx s
280-
) strands
281-
282292
(* ------------------------------------------------------------------ *)
283293
(* Statements *)
284294
(* ------------------------------------------------------------------ *)

compiler/lib/typecheck.ml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,35 @@ let rec infer_expr (gamma : env) (sigma : strand_ctx) (e : expr) : ty =
189189
let n = width_of_generators gens in
190190
TWord n
191191

192+
(* [T-Weave] in expression position.
193+
The statement form computed this same Tangle[A,B] and then DISCARDED it
194+
(`let _weave_ty = ... in gamma`), which is why weave was inert. Here the
195+
type is the expression's type, so the result can be bound and used. *)
196+
| Weave wb ->
197+
let sigma' = List.mapi (fun i ts ->
198+
let sty = match ts.strand_type with
199+
| Some name -> StrandNamed name
200+
| None -> StrandDefault
201+
in
202+
(ts.strand_name, { strand_pos = i + 1; strand_ty = sty })
203+
) wb.weave_inputs in
204+
let input_boundary = List.map (fun (_, se) -> se.strand_ty) sigma' in
205+
(* The body is checked in the strand context, exactly as the statement
206+
form does — strand names are only meaningful there. *)
207+
let body_ty = infer_expr gamma sigma' wb.weave_body in
208+
begin match body_ty with
209+
| TTangle (_, _) | TWord _ -> () (* words coerce to tangles *)
210+
| _ ->
211+
type_error "Weave body must produce a Word or Tangle type, got %s"
212+
(pp_ty body_ty)
213+
end;
214+
let output_boundary = List.map (fun ts ->
215+
match ts.strand_type with
216+
| Some name -> StrandNamed name
217+
| None -> StrandDefault
218+
) wb.weave_outputs in
219+
TTangle (input_boundary, output_boundary)
220+
192221
(* ---- Variables [T-Var] ---- *)
193222

194223
| Var name ->
@@ -668,6 +697,7 @@ let rec expr_calls (f : string) (e : expr) : bool =
668697
| EchoAdd (e1, e2) | EchoEq (e1, e2) -> go e1 || go e2
669698
| UnaryOp (_, e1) | Close e1 | Mirror e1 | Reverse e1 | Simplify e1
670699
| Twist e1 | EchoClose e1 | Lower e1 | Residue e1 | Fst e1 | Snd e1 -> go e1
700+
| Weave wb -> go wb.weave_body
671701
| BraidLit _ | Identity | BoolLit _ | IntLit _ | FloatLit _
672702
| StringLit _ | Var _ | Crossing _ -> false
673703

compiler/test/test_parser.ml

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,47 @@ let test_weave_blocks () =
176176
(List.nth w.weave_inputs 0).strand_type;
177177
assert_eq "output1 type" (Some "Strand")
178178
(List.nth w.weave_outputs 0).strand_type
179-
| _ -> failwith "expected single WeaveBlock")
179+
| _ -> failwith "expected single WeaveBlock");
180+
181+
(* ---------------------------------------------------------------- *)
182+
(* #88(B): weave in EXPRESSION position. *)
183+
(* It was a statement only, and as a statement it is inert — eval *)
184+
(* returns (env, None) and the typechecker discards the Tangle[A,B] *)
185+
(* it computes, so the construct could be written but never bound. *)
186+
(* conformance/valid/v02, v08, v09 and v12 have always used the *)
187+
(* `def x = weave ...` form; they could not parse until now. *)
188+
(* ---------------------------------------------------------------- *)
189+
190+
test "#88 weave as a definition body (the conformance form)" (fun () ->
191+
let prog = parse_ok
192+
"def simple_crossing = weave strands a:Q, b:Q into (a > b) yield strands b:Q, a:Q" in
193+
match prog with
194+
| [Definition d] ->
195+
(match d.def_body with
196+
| Weave w ->
197+
assert_eq "inputs" 2 (List.length w.weave_inputs);
198+
assert_eq "outputs" 2 (List.length w.weave_outputs);
199+
assert_eq "input1 type" (Some "Q") (List.nth w.weave_inputs 0).strand_type
200+
| _ -> failwith "expected the body to be a Weave expression")
201+
| _ -> failwith "expected a single Definition");
202+
203+
test "#88 weave expression round-trips (TG-4 property)" (fun () ->
204+
(* The pretty-printed form must re-parse to the same AST, or TG-4's
205+
parse(pretty(e)) = e obligation would no longer hold for weave. *)
206+
let src =
207+
"def x = weave strands a: Q, b: Q into (a > b) yield strands b: Q, a: Q" in
208+
let p1 = parse_ok src in
209+
let printed = Tangle.Pretty.program_to_string p1 in
210+
let p2 = parse_ok printed in
211+
assert_eq "pretty then re-parse yields the same AST" p1 p2);
212+
213+
test "#88 weave is still valid as a statement" (fun () ->
214+
(* Regression guard: the change is additive. *)
215+
let prog = parse_ok
216+
"weave strands a, b into (a > b) yield strands c" in
217+
match prog with
218+
| [WeaveBlock _] -> ()
219+
| _ -> failwith "expected the statement form to still parse")
180220

181221
(* ================================================================== *)
182222
(* 3. Invariant computations *)

compiler/test/tg3/tg3_emit.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ let rec lean_expr (scope : string list) (e : expr) : string =
133133
(* Non-core: must never appear in the corpus (close is the boundary gateway). *)
134134
| FloatLit _ | BinOp ((Sub | Mul | Div | Isotopy), _, _) | UnaryOp _
135135
| Close _ | Mirror _ | Reverse _ | Simplify _ | Cap _ | Cup _ | Twist _
136-
| Match _ | Call _ | Crossing _ ->
136+
| Match _ | Call _ | Crossing _ | Weave _ ->
137137
failwith "TG-3: non-core constructor in corpus term"
138138

139139
(* ================================================================== *)

scripts/check-corpus.sh

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,18 @@ EXAMPLES_KNOWN_UNTYPED=(
6262
turing_complete.tangle
6363
)
6464

65-
# conformance/valid programs the parser does not yet accept. These specify
66-
# language surface that exists in the spec but not in parser.mly. Tracked in #88.
67-
# v02/v08/v09/v12 : `weave strands a:Q, b:Q into (...) yield strands ...`
68-
# v11 : `add{ ... }` blocks (JTV data injection)
65+
# conformance/valid programs the parser does not yet accept.
66+
# v11 : `add{ ... }` — the Harvard DATA SUB-LANGUAGE, not a parse rule. It has
67+
# its own expression grammar, type system (Int/Float/Rational/Hex/
68+
# Binary/Bool/String/Symbolic), environments (Pi), visibility rules, a
69+
# separate typing judgement, Embed/Unembed, and bidirectional calling
70+
# with Tangle — 288 lines of FORMAL-SEMANTICS.md across 12 sections.
71+
# A feature, tracked separately.
72+
#
73+
# v02/v08/v09/v12 were here for `weave ... into ... yield ...` used as a
74+
# DEFINITION BODY. Fixed: weave is now an expression as well as a statement.
6975
CONFORMANCE_KNOWN_UNPARSED=(
70-
v02_weave.tangle
71-
v08_crossing.tangle
72-
v09_twist.tangle
7376
v11_add_block.tangle
74-
v12_weave_expr.tangle
7577
)
7678

7779
fail=0

spec/grammar.ebnf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,16 @@ primary = braid_literal
121121
| crossing
122122
| identifier, [ "(", arg_list, ")" ]
123123
| literal
124+
| weave_block
124125
| "(", expr, ")" ;
125126
127+
(* weave_block appears BOTH as a statement (above) and as a primary
128+
expression. As a statement alone it was inert: its Tangle[A,B] type was
129+
computed and discarded, and evaluation was a no-op, so the construct could
130+
be written but never bound or used. In expression position it denotes the
131+
morphism, which is how `def x = weave ... into ... yield ...` works and how
132+
conformance/valid/v02, v08, v09 and v12 have always been written. *)
133+
126134
arg_list = expr, { ",", expr } ;
127135
128136
(* -------------------- Braid Literals ------------------------- )

0 commit comments

Comments
 (0)