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