span-1-and-2 #14
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "span-1-and-2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
things landed: What shipped Layer 1 — frontend carries spans - plg-shared: Span { file, lo, hi }, Spanned<T>, FileId — value types, never embedded in Term (so its PartialEq is untouched). - Tokenizer: every Token now carries byte offsets (lo/hi), stamped at one point in next_token. - ParseError { message, span } replaces the Result<_, String> parser API; the … at line N col M trailer is gone. All ~20 parser methods + every error site flipped; SourceMap (byte offset → line/col, and UTF-16 for LSP) resolves position at format time. - Parser records atom-functor occurrences (CallSite { functor, arity, span }), exposed via parse_program_with_spans. Layer 2 — consumers use spans directly - LSP: parse_at_line_col and call_site_ranges both deleted. Parse errors map span → Range; undefined-call warnings filter the parser's recorded occurrences by the lint's (name, arity) set — so squiggles land on real calls and a name in a comment is invisible (it's never parsed). - plgc: format_parse_error resolves file:line:col from the span + SourceMap (scraper extract_line_col deleted). - repl: renders line/col from the span. Tests (all 23 binaries green, clippy -D warnings clean): updated the parser/LSP assertions off the old string; added span-byte-range pins (unexpected token, EOF) and the checkpoint-2 comment-vs-call test. Two scope notes, as agreed - Option A held: plgc and the repl took only mechanical error-rendering edits; codegen/runtime untouched. - For the lint squiggle you chose the occurrence-list approach over a fully spanned AST. One known over-approximation, logged in SPANS.md: a data term sharing a functor/arity with an undefined-called predicate would also squiggle (rare; the doc's checkpoint doesn't exercise it). SPANS.md now reads "Layers 1–2 implemented; Layer 3 pending" — Layer 3 (runtime error provenance: the @plg_srcmap side-table + site_id ABI) is the remaining piece and stays out of this session's scope.Review — Spans, Layers 1 & 2
The shape of this is right. The "
Spannever inTerm" decision is the load-bearing one —Term'sPartialEqstays untouched, the AST stays the same value type the codegen and tests already work against, and spans annotate from the outside viaSpanned<T>/CallSite. TheSourceMapresolved on demand from a&strthe consumer already holds keeps positions off the parser's return signatures. Single-point byte-offset stamping inTokenizer::next_tokenkeeps the per-kind helpers ignorant of offsets. These are all the calls I'd have wanted made.Issues below, ordered by impact.
Real bugs
1. Lexer-level errors lose their span.
Tokenizer::tokenizestill returnsResult<Vec<Token>, String>, and every parser entry point doeslet tokens = Tokenizer::tokenize(input)?— the?runsFrom<String> for ParseError, which discards position and stampsSpan::point(0, 0). Consequences:parse_error_to_diagnosticunderlines bytes 0..0 (start of file) for any lexer error — invalid character, unterminated quote — instead of the actual offending byte.plgc:format_parse_errorprintsfile.pl:1:1: Unexpected character 'X' at line N col M— the file-prefix line/col is wrong, AND the old trailer (preserved in the message) duplicates the position from the real location. Two positions, one wrong.The fix wants
Tokenizer::tokenizeto returnResult<_, ParseError>directly, with the lexer building spans at the offending byte (it haspos/line/colin scope at every error site —tokenizer/mod.rs:167-170). TheFrom<String>impl onParseErrorthen becomes the temporary scaffold to delete once that flips. As-is, the SPANS.md claim that the "... at line N col M" trailer is gone is true only for parser errors — the tokenizer still emits it and the parser propagates it verbatim.2. Tokenizer still emits the old trailer.
tokenizer/mod.rs:167-170:This is the source of the duplication in #1. The grep for
"at line"in error strings should be empty after this PR — it isn't.Design observations
3.
Spanned<T>is defined, exported, and unused.git grep Spannedreturns four hits: the definition, its doc comment, the re-export, and a doc reference. Per the commit's scope note ("occurrence-list approach over a fully spanned AST"), this is anticipatory infrastructure for Layer 3 — but as it stands, a future contributor will see a pub type with no consumer and ask the same question. Either:Spanned<T>is reserved for the Layer 3 runtime-provenance path (link the design doc), orI lean toward the first — the doc on the type already explains the design rationale; one extra line "no in-tree consumer yet — see
docs/design/SPANS.mdLayer 3" would prevent the confusion.4.
CallSiteover-approximation is broader than the SPANS.md note suggests. The doc note says "a data term sharing a functor/arity with an undefined-called predicate would also squiggle." True. But the parser records every atom-functor occurrence inparse_primary— including atoms used as constants (X = foo), atoms inside data terms (assertz(p(foo, bar))), atoms in operator specs (dynamic(foo/1)recordsdynamic,foo, and/), even[]recorded as[]/0. The lint filter (must match a(name, arity)reported as undefined) keeps the false-positive surface small in practice, but the over-approximation is wider than the doc reads. Worth a one-line widening of the note, or arecord_call_sitecallsite annotation noting which paths intentionally over-record vs. which could be filtered (e.g. don't record atoms indynamicspecs).Small observations
5.
SourceMap::line_colcounts characters,utf16_positioncounts UTF-16 units. Both are correct for their consumers (humans / LSP). Worth a one-line doc note online_colthat it's char-based — emacs/CI tooling that expects byte columns will mismatch on emoji/multibyte source. The split (human display vs LSP wire format) is right; the discoverability isn't.6. LSP undefined-call loop is O(callees × call_sites).
diagnostics.rs:60-75— for each undefined callee, scan all recorded call_sites. Editor-buffer sized; fine. AHashMap<(AtomId, usize), Vec<usize>>index over call_sites would flatten it to O(N + K). Not a near-term concern; flag for when a 5k-line buffer makes this visible.7.
From<String> for ParseErroris a temporary bridge. Right shape for landing without rewriting the tokenizer in the same PR — but it's the mechanism by which #1 happens. Once the tokenizer returnsParseErrordirectly, this impl should go too. The doc comment ("Lexer-level errors surface as plainStrings (rare)") undersells it: every parse path that uses the tokenizer hits this. "Rare" is misleading — the path is hit every lexer error.What's good
Spannever inTerm" keepsPartialEqstable and avoids codegen ripple. The file header makes the decision explicit; future contributors will see the constraint.SourceMapis built on demand at format time from the source string a consumer already has. No positions threaded through return types; the parser API stays small.Tokenizer::next_token. Per-kind helpers don't have to track offsets. The construction site is one place to break it; no scattered drift.line_colandutf16_position. LSP needs UTF-16 code units; humans need char columns. The emoji test (utf16_columns_count_code_units) pins the case most LSPs get wrong.Display for ParseErroronly renders the message. Position is the caller's concern viaSourceMap. Discipline pays off:format_parse_errorinplgccollapses to two lines.comment_mention_does_not_squiggle_only_the_real_callis exactly the test the design promises, and it asserts both severity and the precise character range.parse_error_span_covers_unexpected_token,parse_error_span_points_at_end_of_input) catch off-by-one regressions cheaply.(name, arity)is the right narrowing — call_sites by themselves are too broad, but combined with the lint they squiggle the right cells.Suggested order
#1 + #2 (lexer span loss + leftover trailer) are the only items I'd want fixed before merge — together they undermine the SPANS.md claim that line/col is fully positional. Both are local fixes; the rest are observations and follow-up surface.