Skip to content
31 changes: 22 additions & 9 deletions src/v2/cli/compile_cli.dag
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import v2.compiler.self_host.compiler_closure_emit {
import v2.compiler.source_authority { SourceRootIngest }
import v2.extdeps.languages.rust { rust_target_model }
import v2.std.algebra { fold_list, length, list_reverse }
import v2.std.diagnostic { Accepted, Rejected }
import v2.std.lens_verdict { lens_verdict_diagnostic_locus_module }
import v2.std.compilers.lexing { symbol_lexeme }
import v2.std.diagnostic { Accepted, Diagnostic, NonEmptyDiagnostics, Rejected }
import v2.std.lens_verdict { lens_verdict_diagnostic_locus_text }
import v2.std.node { Symbol }
import v2.std.qualified_name { qualified_name_from_dotted_string }
import v2.std.text { String }
Expand Down Expand Up @@ -256,11 +256,24 @@ type CliRunOutcome
// boundary. Measured on the built binary at gunbc#11507's head -- three different entries
// (`v2.std.text`, this module, `v2.compiler.compile`) all refused with the same nine words.
//
// THE LOCUS PROJECTION IS BORROWED RATHER THAN RECOPIED. `v2.std.lens_verdict`
// `lens_verdict_diagnostic_locus_module` is already the Locus -> module-name fold over all four
// arms; its name says lens because that is where it was first needed, not because the projection is
// lens-specific, and authoring a second `match d.at` here would be the fork (DESIGN section 3)
// rather than the reuse.
// THE WHOLE ORDERED CHAIN, BECAUSE THE HEAD IS NOT THE CAUSE. Rendering only the head answers
// `parse_grammar_choice_overlap_residue` for every entry tried; that is a grammar-global ADVISORY
// and never the fatal. The fatal diagnostic is the last one, and `parse_g0_tokens_remain_diagnostic`
// builds its locus from the first remaining token. File+extent is
// `lens_verdict_diagnostic_locus_text` — not a second `match d.at` here (DESIGN section 3).

fn cli_fatal_diagnostic(d: NonEmptyDiagnostics) -> Diagnostic {
fold_list(xs: d.tail, empty: d.head, cons: fn(acc, diag) { diag })
}

fn cli_diagnostic_chain(d: NonEmptyDiagnostics) -> String {
fold_list(
xs: d.tail,
empty: symbol_lexeme(sym: d.head.reason),
cons: fn(acc, diag) { concat(concat(acc, " -> "), symbol_lexeme(sym: diag.reason)) }
)
}

// THE EMPTY INGEST IS A REFUSAL AND NOT AN EMPTY PROGRAM, and this arm is the whole reason
// `v2_cli_run` does not call the emitter first and ask questions after.
// `emit_compiler_import_closure_from_ingest` is TOTAL over whatever ingest it is handed -- its
Expand Down Expand Up @@ -298,8 +311,8 @@ fn v2_cli_run(plan: CliPlan, ingest: SourceRootIngest) -> CliRunOutcome {
CliRunRefused {
reason: d.head.reason,
detail: concat(
concat("the closure did not emit: ", symbol_lexeme(sym: d.head.reason)),
concat(" at ", lens_verdict_diagnostic_locus_module(d: d.head))
concat("the closure did not emit; diagnostic chain: ", cli_diagnostic_chain(d: d)),
concat(" | FATAL AT ", lens_verdict_diagnostic_locus_text(d: cli_fatal_diagnostic(d: d)))
)
}
}
Expand Down
22 changes: 21 additions & 1 deletion src/v2/compiler/02_parse.dag
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ import v2.compiler.materialization_carriers {
parse_table_memo_materialization,
materialization_allows_memo_store,
}
import v2.extdeps.languages.dag { dag_int_literal_node_from_lexeme }
import v2.extdeps.languages.dag {
dag_float_literal_node_from_lexeme,
dag_int_literal_node_from_lexeme
}
import v2.std.compilers.lexing {
Lexeme,
Token,
Expand Down Expand Up @@ -2790,6 +2793,23 @@ fn parse_expr_terminal(
table: table
}
}
} else if token_class == ^dag_token_float_literal {
let minted = parse_mint_from_source(
prov: prov,
file: tok.file,
start: tok.start,
end: tok.end
)
ParseExprAccepted {
remaining: remaining,
captured: dag_float_literal_node_from_lexeme(
lexeme: tok.lexeme,
occurrence_id: node_occurrence_identity_minted(id: minted.id)
),
diagnostics: std.algebra.Empty,
prov: minted.prov,
table: table
}
} else {
let stamped = parse_stamp_terminal(tok: tok, stamp: stamp, prov: prov)
ParseExprAccepted {
Expand Down
15 changes: 15 additions & 0 deletions src/v2/compiler/03_resolve.dag
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import std.disposition { Disposition, Scaffold, SingleAuthority }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import v2.extdeps.languages.dag {
dag_language_model_canonical_symbols,
dag_node_is_float_literal_atom,
dag_node_is_int_literal_atom,
dag_node_is_module_root_conj,
dag_resolve_preserve_module_metadata_subtree,
Expand All @@ -31,10 +32,12 @@ import v2.std.optional {
import v2.std.compilers.target_model { target_model_canonical_operation_wire_node }
import v2.std.diagnostic {
Correction,
CorrectionNotModeled,
Diagnostic,
node_locus,
Outcome,
UserInputBoundary,
Unavailable,
Accepted,
None,
diagnostics_merge,
Expand Down Expand Up @@ -348,6 +351,14 @@ fn unbound_symbol_diagnostic(n: Node) -> Diagnostic {
}
}

fn float_literal_not_lowered_diagnostic(n: Node) -> Diagnostic {
Diagnostic {
reason: ^float_literal_not_lowered,
at: node_locus(node: n),
correction: Unavailable { reason: CorrectionNotModeled }
}
}

fn ambiguous_symbol_diagnostic(n: Node) -> Diagnostic {
Diagnostic {
reason: ^resolve_reason_ambiguous_symbol,
Expand Down Expand Up @@ -459,6 +470,10 @@ fn try_resolve_qualified_name_node(ctx: ResolveContext, n: Node) -> Optional<Out
fn resolve_atom(ctx: ResolveContext, n: Node, identity: Symbol) -> Outcome<Node> {
if dag_node_is_int_literal_atom(node: n) {
Accepted { value: n, diagnostics: None }
} else if dag_node_is_float_literal_atom(node: n) {
Rejected {
diagnostics: diagnostics_singleton(d: float_literal_not_lowered_diagnostic(n: n))
}
} else {
match lookup_chain(s: ctx.scope, name: identity) {
Accepted { value: found, diagnostics: pending } => match found {
Expand Down
3 changes: 3 additions & 0 deletions src/v2/compiler/reference_site_collector.dag
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import v2.std.qualified_name {
}
import v2.compiler.resolution_provenance { ReferenceSite }
import v2.extdeps.languages.dag {
dag_node_is_float_literal_atom,
dag_node_is_int_literal_atom,
dag_surface_module_header_metadata_edge,
namespace_graft_spine_segment_edge_optional,
Expand Down Expand Up @@ -210,6 +211,8 @@ fn reference_sites_in_node(
TypeNode { connective: Atom { identity: sym } } =>
if dag_node_is_int_literal_atom(node: node) {
acc
} else if dag_node_is_float_literal_atom(node: node) {
acc
} else {
collection_snoc_site(
acc: acc,
Expand Down
68 changes: 66 additions & 2 deletions src/v2/extdeps/languages/dag.dag
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ import v2.std.compilers.lexing {
TokenRule,
TriviaRule,
WhitespaceChar,
lex_rule_set_token_class_member
lex_rule_set_token_class_member,
symbol_intern_lexeme
}
import v2.std.node {
Arrow,
Expand Down Expand Up @@ -165,6 +166,8 @@ import v2.std.diagnostic {
invariant_port_locus
}
import std.algebra { Empty }
import std.decl_ref { decl_ref }
import std.dissolution { DissolutionCondition, retires_dissolution }
import v2.std.integer {
decimal_digits_to_magnitude,
integer_decimal_magnitude_node,
Expand Down Expand Up @@ -445,6 +448,27 @@ fn dag_int_literal_lex_rule() -> LexRule {
}
}

// v1 `scan_number` (v1.compiler.tokenize): digits, then `.` followed by at least one digit, is
// ShLitFloat. v2 G0 had only the integer arm, so `1.8` tokenized as int + dot + int. Postfix
// field access demands a binding name after `.`, so the primary failed, the fn_decl failed as a
// top-level item, the module `repeat` stopped, and `parse_g0_tokens_remain` pointed at leftover
// `fn` — the first remaining token, not the float. The float pattern is strictly longer than the
// integer prefix, so longest-match selects it regardless of list order. Equal-length ties go to
// the LATER rule (`v2.compiler.tokenize`); these two patterns never tie, so this row is not
// placed to win a tie.
fn dag_float_literal_lex_rule() -> LexRule {
TokenRule {
token_class: ^dag_token_float_literal,
pattern: SequencePattern {
left: OneOrMorePattern { element: CharClassPattern { class: DigitChar } },
right: SequencePattern {
left: LiteralPattern { text: "." },
right: OneOrMorePattern { element: CharClassPattern { class: DigitChar } }
}
}
}
}

fn dag_string_escape_char_pattern() -> LexPattern {
ChoicePattern {
left: CharPattern { char: 34 },
Expand Down Expand Up @@ -519,6 +543,7 @@ fn dag_lex_rules() -> LexRuleSet {
dag_whitespace_trivia_rule(),
dag_string_literal_lex_rule(),
dag_line_comment_annotation_rule(),
dag_float_literal_lex_rule(),
dag_int_literal_lex_rule(),
dag_ident_lex_rule(),
dag_keyword_lex_rule(token_class: ^dag_token_kw_module, text: "module"),
Expand Down Expand Up @@ -1273,6 +1298,8 @@ fn dag_grammar_primary_expr_core() -> GrammarExpr {
right: dag_grammar_choice(
left: dag_grammar_terminal(token_class: ^dag_token_int_literal),
right: dag_grammar_choice(
left: dag_grammar_terminal(token_class: ^dag_token_float_literal),
right: dag_grammar_choice(
left: dag_grammar_terminal(token_class: ^dag_token_string_literal),
right: dag_grammar_choice(
left: dag_grammar_terminal(token_class: ^dag_token_kw_true),
Expand Down Expand Up @@ -1315,6 +1342,7 @@ fn dag_grammar_primary_expr_core() -> GrammarExpr {
)
)
)
)
)
)
}
Expand Down Expand Up @@ -1424,7 +1452,10 @@ fn dag_grammar_pattern_expr() -> GrammarExpr {
left: dag_grammar_terminal(token_class: ^dag_token_kw_false),
right: dag_grammar_choice(
left: dag_grammar_terminal(token_class: ^dag_token_int_literal),
right: dag_grammar_terminal(token_class: ^dag_token_string_literal)
right: dag_grammar_choice(
left: dag_grammar_terminal(token_class: ^dag_token_float_literal),
right: dag_grammar_terminal(token_class: ^dag_token_string_literal)
)
)
)
)
Expand Down Expand Up @@ -3026,6 +3057,13 @@ fn dag_node_is_int_literal_atom(node: Node) -> Bool {
}
}

fn dag_node_is_float_literal_atom(node: Node) -> Bool {
match node.kind {
TypeNode { connective: Atom { identity: id } } => id == ^dag_token_float_literal
_ => false
}
}

// Single authority for eval literal-vs-binding dispatch (v2_eval_is_literal_shaped_node projects
// this). Fixture pick literals admit both token_class (^dag_pick_lit_*) and branded binding
// (^dag_binding_pick_lit_*) rows — same ingest surface, two spellings. Complement bool literals
Expand Down Expand Up @@ -3113,6 +3151,32 @@ fn dag_int_literal_node_from_lexeme(lexeme: String, occurrence_id: NodeOccurrenc
}
}

// Lexeme capture, not IEEE decode. Identity stays `dag_token_float_literal` so 1.8 and 2.5
// are not the same class-stamped atom. Magnitude home is the dissolve-on below, not this string.
fn dag_float_literal_node_from_lexeme(lexeme: String, occurrence_id: NodeOccurrenceIdentity) -> Node {
Node {
kind: TypeNode { connective: Atom { identity: ^dag_token_float_literal } },
children: [
Edge {
label: Named { name: ^float_literal_lexeme_field },
target: Node {
kind: TypeNode { connective: Atom { identity: symbol_intern_lexeme(lexeme: lexeme) } },
children: [],
occurrence_id: OccurrenceSynthetic
}
}
],
occurrence_id: occurrence_id
}
}

data dag_float_literal_magnitude_home_dissolve_on: DissolutionCondition = retires_dissolution(
ref: decl_ref(
module_path: "v2.extdeps.languages.dag",
decl_name: "dag_float_literal_node_from_lexeme"
)
)

fn dag_int_literal_magnitude_edge_target_optional(edge: Edge) -> Optional<Node> {
match edge {
Edge { label: Named { name: field }, target: magnitude } =>
Expand Down
29 changes: 29 additions & 0 deletions src/v2/std/lens_verdict.dag
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ module v2.std.lens_verdict

import v2.std.compilers.lexing { symbol_intern_lexeme, symbol_lexeme }
import v2.std.diagnostic {
ByteRange,
CorrectionNotModeled,
Diagnostic,
Extent,
NodeLocus,
InvariantPortLocus,
SourcePortLocus,
Textual,
Unavailable,
WholeFile,
textual_whole_file_diagnostic,
}
import v2.std.integer { integer_int_to_decimal_string }
import v2.std.node { Symbol }
import v2.std.text { String }

Expand Down Expand Up @@ -62,3 +66,28 @@ fn lens_verdict_diagnostic_locus_module(d: Diagnostic) -> String {
InvariantPortLocus { anchor: _, invariant: _ } => ""
}
}

// File + extent, for a reader who has to act. The module-name fold above remains the identity
// projection callers already consume; this sibling is the same Locus match with extent kept.
fn lens_verdict_extent_text(e: Extent) -> String {
match e {
WholeFile => "(whole file)"
ByteRange { start: st, end: en } =>
concat(
concat("bytes ", integer_int_to_decimal_string(value: st)),
concat("..", integer_int_to_decimal_string(value: en))
)
}
}

fn lens_verdict_diagnostic_locus_text(d: Diagnostic) -> String {
match d.at {
Textual { file: f, extent: e } =>
concat(concat(symbol_lexeme(sym: f), " "), lens_verdict_extent_text(e: e))
SourcePortLocus { anchor: _, file: f, extent: e } =>
concat(concat(symbol_lexeme(sym: f), " "), lens_verdict_extent_text(e: e))
NodeLocus { anchor: _ } => "<node locus, no file>"
InvariantPortLocus { anchor: _, invariant: inv } =>
concat("<invariant ", concat(symbol_lexeme(sym: inv), ">"))
}
}
Loading
Loading