Skip to main content

lex_syntax/
lib.rs

1//! M1: lexer, parser, syntax tree, pretty-printer for Lex.
2//!
3//! See spec §3 for the grammar.
4
5pub mod token;
6pub mod syntax;
7pub mod parser;
8pub mod printer;
9pub mod loader;
10
11pub use loader::{load_program, load_program_from_str, LoadError};
12pub use parser::{parse, ParseError};
13pub use printer::print_program;
14pub use syntax::*;
15pub use token::{lex, LexError, Token, TokenKind};
16
17/// Convenience: lex + parse a source string.
18pub fn parse_source(src: &str) -> Result<Program, SyntaxError> {
19    let toks = lex(src).map_err(SyntaxError::Lex)?;
20    parse(toks).map_err(SyntaxError::Parse)
21}
22
23/// Byte-offset start position of each `fn` declaration, keyed by
24/// function name (#306 slice 1). Used by `lex_types::Position`
25/// renderers to map a type error back to its `fn` location.
26pub type FnPositions = std::collections::BTreeMap<String, usize>;
27
28/// Variant of [`parse_source`] that also returns the byte-offset
29/// position of each top-level `fn` declaration in `src`. Used by
30/// the `lex check` CLI (and any other LLM-facing tooling) to stamp
31/// source positions onto `lex_types::PositionedError`s.
32pub fn parse_source_with_positions(src: &str) -> Result<(Program, FnPositions), SyntaxError> {
33    let toks = lex(src).map_err(SyntaxError::Lex)?;
34    // Capture `fn`-token byte offsets *before* parse consumes them.
35    // The token stream preserves source order, so a single linear
36    // scan recovering `Fn` → next `Ident` pairs is sufficient. Names
37    // collide → last wins; the type checker rejects duplicates
38    // upstream so a collision here is structurally impossible.
39    let mut fn_positions = FnPositions::new();
40    let mut i = 0;
41    while i < toks.len() {
42        if matches!(toks[i].kind, TokenKind::Fn) {
43            let fn_start = toks[i].span.start;
44            // Walk forward to the first Ident (skipping newlines).
45            let mut j = i + 1;
46            while j < toks.len() {
47                match &toks[j].kind {
48                    TokenKind::Ident(name) => {
49                        fn_positions.insert(name.clone(), fn_start);
50                        break;
51                    }
52                    TokenKind::Newline => { j += 1; }
53                    _ => break,
54                }
55            }
56        }
57        i += 1;
58    }
59    let program = parse(toks).map_err(SyntaxError::Parse)?;
60    Ok((program, fn_positions))
61}
62
63#[derive(Debug, thiserror::Error)]
64pub enum SyntaxError {
65    #[error(transparent)]
66    Lex(#[from] LexError),
67    #[error(transparent)]
68    Parse(#[from] ParseError),
69}