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