Skip to main content

busbar_sf_agentscript/parser/
primitives.rs

1//! Primitive parsers for basic tokens.
2//!
3//! This module contains parsers for identifiers, strings, numbers,
4//! newlines, indentation, and noise-skipping utilities.
5
6use crate::ast::Spanned;
7use crate::lexer::Token;
8use chumsky::input::MappedInput;
9use chumsky::prelude::*;
10
11/// Token span type (from lexer).
12pub type Span = SimpleSpan<usize>;
13
14/// Spanned token type.
15pub type SpannedToken<'src> = (Token<'src>, Span);
16
17/// Parser input type - a slice of spanned tokens mapped into chumsky format.
18/// Created by calling `tokens.split_token_span(eoi_span)` on a token slice.
19pub type ParserInput<'tokens, 'src> =
20    MappedInput<'tokens, Token<'src>, Span, &'tokens [SpannedToken<'src>]>;
21
22/// Convert a chumsky SimpleSpan to our AST Span (Range<usize>).
23pub fn to_ast_span(span: Span) -> std::ops::Range<usize> {
24    span.start..span.end
25}
26
27/// Parse an identifier token.
28pub fn ident<'tokens, 'src: 'tokens>() -> impl Parser<
29    'tokens,
30    ParserInput<'tokens, 'src>,
31    &'src str,
32    extra::Err<Rich<'tokens, Token<'src>, Span>>,
33> + Clone {
34    select! {
35        Token::Ident(s) => s,
36    }
37}
38
39/// Parse an identifier as a spanned string.
40pub fn spanned_ident<'tokens, 'src: 'tokens>() -> impl Parser<
41    'tokens,
42    ParserInput<'tokens, 'src>,
43    Spanned<String>,
44    extra::Err<Rich<'tokens, Token<'src>, Span>>,
45> + Clone {
46    ident().map_with(|s, e| Spanned::new(s.to_string(), to_ast_span(e.span())))
47}
48
49/// Parse a string literal.
50pub fn string_lit<'tokens, 'src: 'tokens>() -> impl Parser<
51    'tokens,
52    ParserInput<'tokens, 'src>,
53    &'src str,
54    extra::Err<Rich<'tokens, Token<'src>, Span>>,
55> + Clone {
56    select! {
57        Token::StringLit(s) => s,
58    }
59}
60
61/// Parse a spanned string literal.
62pub fn spanned_string<'tokens, 'src: 'tokens>() -> impl Parser<
63    'tokens,
64    ParserInput<'tokens, 'src>,
65    Spanned<String>,
66    extra::Err<Rich<'tokens, Token<'src>, Span>>,
67> + Clone {
68    string_lit().map_with(|s, e| Spanned::new(s.to_string(), to_ast_span(e.span())))
69}
70
71/// Parse a number literal.
72pub fn number_lit<'tokens, 'src: 'tokens>() -> impl Parser<
73    'tokens,
74    ParserInput<'tokens, 'src>,
75    f64,
76    extra::Err<Rich<'tokens, Token<'src>, Span>>,
77> + Clone {
78    select! {
79        Token::NumberLit(n) => n,
80    }
81}
82
83/// Parse a newline token (for line tracking).
84pub fn newline<'tokens, 'src: 'tokens>(
85) -> impl Parser<'tokens, ParserInput<'tokens, 'src>, (), extra::Err<Rich<'tokens, Token<'src>, Span>>>
86       + Clone {
87    just(Token::Newline).ignored()
88}
89
90/// Skip optional newlines.
91#[allow(dead_code)]
92pub fn skip_newlines<'tokens, 'src: 'tokens>(
93) -> impl Parser<'tokens, ParserInput<'tokens, 'src>, (), extra::Err<Rich<'tokens, Token<'src>, Span>>>
94       + Clone {
95    newline().repeated().ignored()
96}
97
98/// Parse an INDENT token.
99pub fn indent<'tokens, 'src: 'tokens>(
100) -> impl Parser<'tokens, ParserInput<'tokens, 'src>, (), extra::Err<Rich<'tokens, Token<'src>, Span>>>
101       + Clone {
102    just(Token::Indent).ignored()
103}
104
105/// Parse a DEDENT token.
106pub fn dedent<'tokens, 'src: 'tokens>(
107) -> impl Parser<'tokens, ParserInput<'tokens, 'src>, (), extra::Err<Rich<'tokens, Token<'src>, Span>>>
108       + Clone {
109    just(Token::Dedent).ignored()
110}
111
112/// Skip noise tokens (newlines, indents, dedents, comments) between blocks.
113#[allow(dead_code)]
114pub fn skip_noise<'tokens, 'src: 'tokens>(
115) -> impl Parser<'tokens, ParserInput<'tokens, 'src>, (), extra::Err<Rich<'tokens, Token<'src>, Span>>>
116       + Clone {
117    choice((newline(), indent(), dedent(), select! { Token::Comment(_) => () }))
118        .repeated()
119        .ignored()
120}
121
122/// Skip block noise (newlines and comments only - not indent/dedent).
123pub fn skip_block_noise<'tokens, 'src: 'tokens>(
124) -> impl Parser<'tokens, ParserInput<'tokens, 'src>, (), extra::Err<Rich<'tokens, Token<'src>, Span>>>
125       + Clone {
126    choice((newline(), select! { Token::Comment(_) => () }))
127        .repeated()
128        .ignored()
129}
130
131/// Skip noise between top-level blocks (newlines, comments, AND dedents).
132/// DEDENTs appear between blocks when exiting nested indented blocks.
133pub fn skip_toplevel_noise<'tokens, 'src: 'tokens>(
134) -> impl Parser<'tokens, ParserInput<'tokens, 'src>, (), extra::Err<Rich<'tokens, Token<'src>, Span>>>
135       + Clone {
136    choice((newline(), dedent(), select! { Token::Comment(_) => () }))
137        .repeated()
138        .ignored()
139}
140
141/// Skip comments only (not newlines).
142#[allow(dead_code)]
143pub fn skip_comments<'tokens, 'src: 'tokens>(
144) -> impl Parser<'tokens, ParserInput<'tokens, 'src>, (), extra::Err<Rich<'tokens, Token<'src>, Span>>>
145       + Clone {
146    select! { Token::Comment(_) => () }.repeated().ignored()
147}
148
149/// Parse a description entry: `description: "..."`
150pub fn description_entry<'tokens, 'src: 'tokens>() -> impl Parser<
151    'tokens,
152    ParserInput<'tokens, 'src>,
153    Spanned<String>,
154    extra::Err<Rich<'tokens, Token<'src>, Span>>,
155> + Clone {
156    just(Token::Description)
157        .ignore_then(just(Token::Colon))
158        .ignore_then(spanned_string())
159}