Skip to main content

teksilo_parse/
parse.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Parser for the `teksu!` DSL.
5//!
6//! Entry point is [`parse_root`]. The parser is hand-written recursive
7//! descent over `syn::parse::ParseStream`. Positional arguments, property
8//! values, and `rust { }` blocks delegate to `syn::Expr` / `syn::Block`
9//! so Rust's own grammar handles nested brackets and expressions.
10
11use proc_macro2::TokenStream as TokenStream2;
12use syn::Token;
13use syn::parse::{ParseStream, Parser, Result};
14
15use crate::ir::TeksiRoot;
16
17mod body;
18mod cursor;
19mod element;
20mod property;
21mod structural;
22
23pub fn parse_root(tokens: TokenStream2) -> Result<TeksiRoot> {
24    Parser::parse2(parse_root_impl, tokens)
25}
26
27fn parse_root_impl(input: ParseStream) -> Result<TeksiRoot> {
28    // Disambiguate the two invocation forms by two-token lookahead:
29    //   teksu!(ctx => <element>)   — ident followed by `=>`
30    //   teksu!(<element>)          — anything else
31    let ctx = if input.peek(syn::Ident) && input.peek2(Token![=>]) {
32        let ident: syn::Ident = input.parse()?;
33        let _arrow: Token![=>] = input.parse()?;
34        Some(ident)
35    } else {
36        None
37    };
38
39    let root = element::parse_element(input)?;
40
41    if !input.is_empty() {
42        return Err(input.error(
43            "expected end of teksu! input — the root element must be the only top-level element",
44        ));
45    }
46
47    Ok(TeksiRoot { ctx, root })
48}
49
50// Re-export the submodule parse functions for sibling modules.
51pub(crate) use body::parse_body;
52#[allow(unused_imports)]
53pub(crate) use cursor::{peek_binding, peek_element_start, peek_escape};
54pub(crate) use element::parse_element;
55pub(crate) use property::parse_property_args;
56pub(crate) use structural::{parse_for, parse_if, parse_match, parse_spread, peek_spread};