use in_str::in_str;
use whitehole::{action::Action, contextual, parser::Parser};
pub struct MyState {
pub nested: usize,
}
contextual!(MyState, ());
pub fn build_lexer(s: &str) -> Parser<impl Action<Text = str, State = MyState, Heap = ()>> {
let body_optional = || {
let escape = {
let simple = next(in_str!("0'\"\\nrvtbf\u{000a}\u{000d}\u{2028}\u{2029}"));
let hex = eat('x') + next(|c| c.is_ascii_hexdigit()) * 2;
let unicode = eat('u') + next(|c| c.is_ascii_hexdigit()) * 4;
let code_point = eat('u') + '{' + next(|c| c.is_ascii_hexdigit()) * (1..) + '}';
eat('\\') + (simple | hex | unicode | code_point | '$')
};
let non_close = next(|c| c != '`' && c != '$') * (1..);
let non_close_dollar = eat("$") + !eat('{');
escape | non_close | non_close_dollar
} * (..);
let whole_or_left = {
let whole_end = eat('`');
let left_end = eat("${").then(|input| input.state.nested += 1);
eat('`') + body_optional() + (whole_end | left_end)
};
let middle_or_right = {
let middle_end = eat("${");
let right_end = eat('`').then(|input| input.state.nested -= 1);
eat('}') + body_optional() + (right_end | middle_end)
}
.prevent(|input| input.state.nested == 0);
let others = {
let outside = (next(|c| c != '`') * (1..)).when(|input| input.state.nested == 0);
let inside = (next(|c| c != '}' && c != '`') * (1..)).when(|input| input.state.nested != 0);
outside | inside
};
Parser::builder()
.state(MyState { nested: 0 })
.entry(others | whole_or_left | middle_or_right)
.build(s)
}
fn main() {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_js_template_str_lexer() {
let mut lexer = build_lexer("`begin${ `${ `123` }` }end`");
while lexer.next().is_some() {}
assert_eq!(lexer.instant.rest(), "");
let mut lexer = build_lexer("`begin${ 123 }middle${ 456 }end`");
while lexer.next().is_some() {}
assert_eq!(lexer.instant.rest(), "");
}
}