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