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