Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 39 additions & 26 deletions src/v4/compiler/02_parse.dag
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,9 @@ import v4.std.diagnostic {
Some,
Unavailable,
UserInputBoundary,
diagnostics_merge,
diagnostics_singleton,
outcome_accepted,
port_locus,
rejected_with_pending
port_locus
}
import v4.std.node {
Atom,
Expand All @@ -42,7 +40,7 @@ import v4.std.node {
Unanchored,
well_formed
}
import v4.std.provenance { make_node_span_anchored, merge_node_spans }
import v4.std.provenance { fold_node_tree_span, make_node_span_anchored }
import v4.std.collection { List, Map, Set, empty_map, map_insert }
import v4.std.algebra { Cons, Empty, TailAbsent, TailFound, contains, fold_list, fold_list_right, is_empty, length, list_append, list_snoc_item, list_tail }
import v4.std.logic { Bool }
Expand Down Expand Up @@ -296,32 +294,47 @@ fn parse_node_span_from_token(tok: Token) -> Outcome<NodeSpan> {
make_node_span_anchored(file: tok.file, start: tok.start, end: tok.end)
}

fn parse_merge_captured_spans(left: Node, right: Node) -> Outcome<NodeSpan> {
merge_node_spans(left: left.span, right: right.span)
}

fn parse_non_empty_diagnostics_to_list(ned: NonEmptyDiagnostics) -> List<Diagnostic> {
list_append(left: [ned.head], right: ned.tail)
}

fn parse_fold_node_span_step(acc: Outcome<NodeSpan>, n: Node) -> Outcome<NodeSpan> {
match acc {
Rejected { diagnostics: r } => Rejected { diagnostics: r }
Accepted { value: current, diagnostics: pending } =>
match merge_node_spans(left: current, right: n.span) {
Rejected { diagnostics: r } =>
Rejected { diagnostics: rejected_with_pending(pending: pending, rejected: r) }
Accepted { value: merged, diagnostics: d } =>
Accepted {
value: merged,
diagnostics: diagnostics_merge(outer: pending, inner: d)
}
}
}
// 🟢 substrate catamorphism consumer — feature: T-8 captured-node tree span merge —
// bind node://adhoc-2145db6b-69a — first consumer of v4.std.provenance.fold_node_tree_span:
// Conj-wrapped capture children merge via the substrate NodeFold rather than hand-written
// flat span folds (parse_merge_captured_spans / parse_fold_node_spans).
fn parse_conj_captured_tree_span(children: List<Edge>) -> Outcome<NodeSpan> {
fold_node_tree_span(
n: Node {
kind: TypeNode { connective: Conj },
children: children,
span: Unanchored
}
)
}

fn parse_fold_node_spans(nodes: List<Node>, acc: NodeSpan) -> Outcome<NodeSpan> {
fold_list(xs: nodes, empty: outcome_accepted(value: acc), cons: parse_fold_node_span_step)
fn parse_sequence_captured_span(cap1: Node, cap2: Node) -> Outcome<NodeSpan> {
parse_conj_captured_tree_span(children: [
grammar_named_edge(name: ^grammar_sequence_left_node_projection, target: cap1),
grammar_named_edge(name: ^grammar_sequence_right_node_projection, target: cap2)
])
}

fn parse_repeat_captured_span(acc_nodes: List<Node>) -> Outcome<NodeSpan> {
parse_conj_captured_tree_span(
children: fold_list(
xs: acc_nodes,
empty: Empty,
cons: fn(acc, n) {
list_snoc_item(
xs: acc,
item: Edge {
label: Named { name: ^grammar_repeat_element_node_projection },
target: n
}
)
}
)
)
}

fn parse_wrap_production_captured(production: GrammarProduction, captured: Node) -> Node {
Expand Down Expand Up @@ -827,7 +840,7 @@ fn parse_expr_run_walk(
ParseExprRejected { diagnostics: diags2 } =>
ParseExprRejected { diagnostics: list_append(left: diags1, right: diags2) }
ParseExprAccepted { remaining: rest2, captured: cap2, diagnostics: diags2 } =>
match parse_merge_captured_spans(left: cap1, right: cap2) {
match parse_sequence_captured_span(cap1: cap1, cap2: cap2) {
Rejected { diagnostics: r } =>
ParseExprRejected {
diagnostics: list_append(
Expand Down Expand Up @@ -931,7 +944,7 @@ fn parse_expr_run_walk(
}
}
)
match parse_fold_node_spans(nodes: result.acc_nodes, acc: Unanchored) {
match parse_repeat_captured_span(acc_nodes: result.acc_nodes) {
Rejected { diagnostics: r } =>
ParseExprRejected {
diagnostics: list_append(
Expand Down
85 changes: 84 additions & 1 deletion src/v4/test/claim/parse/parse_table_claims.dag
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,17 @@ import v4.std.logic { Bool }
import v4.std.collection { List, Set }
import v4.std.algebra { Cons, Empty, length }
import v4.std.lexing { Token }
import v4.std.node { Symbol }
import v4.std.diagnostic { Accepted, Rejected }
import v4.std.node {
Anchored,
Atom,
Node,
NodeSpan,
SourceSpan,
Symbol,
TypeNode,
Unanchored
}
import v4.std.grammar {
Choice,
GrammarExpr,
Expand All @@ -42,6 +52,8 @@ import v4.compiler.parse {
expr_direct_left_corner_list,
expr_has_undefined_nonterminal,
expr_is_nullable_given_set,
parse_repeat_captured_span,
parse_sequence_captured_span,
token_position_indices
}

Expand Down Expand Up @@ -347,3 +359,74 @@ data unified_claim_parse_table_undefined_nonterminal_morphism: UnifiedTestClaim
function: parse_table_undefined_nonterminal_morphism_holds
}
}


fn parse_table_span_fixture_node(class: Symbol, file: Symbol, start: Int, end: Int) -> Node {
Node {
kind: TypeNode { connective: Atom { identity: class } },
children: [],
span: Anchored { span: SourceSpan { file: file, start: start, end: end } }
}
}


fn parse_table_node_span_is_anchored_extent(span: NodeSpan, file: Symbol, start: Int, end: Int) -> Bool {
match span {
Unanchored => false
Anchored { span: s } => (s.file == file) && (s.start == start) && (s.end == end)
}
}


// Receipt F (parse_sequence_captured_span / parse_repeat_captured_span are correct
// fold_node_tree_span consumers): EXECUTES the substrate tree-span catamorphism over Conj-wrapped
// capture fixtures and discriminates:
// - Sequence same-file: merged byte extent [0, 10)
// - Sequence cross-file: fail-closed Rejected
// - Repeat two same-file nodes: merged [2, 12)
// - Repeat empty: Unanchored
// Mutating fold_node_tree_span's merge algebra flips at least one conjunct → red.
fn parse_table_captured_tree_span_morphism_holds() -> Bool {
let file_a = ^pt_span_fixture_file_a
let file_b = ^pt_span_fixture_file_b
let cap1 = parse_table_span_fixture_node(class: ^pt_span_tok_a, file: file_a, start: 0, end: 5)
let cap2 = parse_table_span_fixture_node(class: ^pt_span_tok_b, file: file_a, start: 5, end: 10)
let cap3 = parse_table_span_fixture_node(class: ^pt_span_tok_c, file: file_b, start: 0, end: 5)
let rep0 = parse_table_span_fixture_node(class: ^pt_span_tok_d, file: file_a, start: 2, end: 7)
let rep1 = parse_table_span_fixture_node(class: ^pt_span_tok_e, file: file_a, start: 7, end: 12)
(
match parse_sequence_captured_span(cap1: cap1, cap2: cap2) {
Accepted { value: span, diagnostics: _ } =>
parse_table_node_span_is_anchored_extent(span: span, file: file_a, start: 0, end: 10)
Rejected { diagnostics: _ } => false
}
)
&& (
match parse_sequence_captured_span(cap1: cap1, cap2: cap3) {
Rejected { diagnostics: _ } => true
Accepted { value: _, diagnostics: _ } => false
}
)
&& (
match parse_repeat_captured_span(acc_nodes: Cons { head: rep0, tail: Cons { head: rep1, tail: Empty } }) {
Accepted { value: span, diagnostics: _ } =>
parse_table_node_span_is_anchored_extent(span: span, file: file_a, start: 2, end: 12)
Rejected { diagnostics: _ } => false
}
)
&& (
match parse_repeat_captured_span(acc_nodes: Empty) {
Accepted { value: span, diagnostics: _ } => span == Unanchored
Rejected { diagnostics: _ } => false
}
)
}


// Co-located BoolWitnessClaim for fold_node_tree_span capture-span migration (Level-1c).
data unified_claim_parse_table_captured_tree_span_morphism: UnifiedTestClaim = BoolWitnessClaim {
witness: BoolWitness {
entry: "src/v4/test/claim/parse/parse_table_claims.dag",
function: parse_table_captured_tree_span_morphism_holds
}
}
Loading