use chumsky::prelude::*;
use itertools::Itertools;
use prqlc_ast::TokenKind;
use super::common::into_expr;
use crate::ast::expr::*;
use crate::ast::Literal;
use crate::ast::{string_stream, Span};
use crate::err::parse_error::{ChumError, PError};
pub fn parse(string: String, span_base: Span) -> Result<Vec<InterpolateItem>, Vec<PError>> {
let prepped_stream = string_stream(string, span_base);
let res = interpolated_parser().parse(prepped_stream);
match res {
Ok(items) => {
log::trace!("interpolated string ok: {:?}", items);
Ok(items)
}
Err(errors) => Err(errors
.into_iter()
.map(|err| {
log::debug!("interpolated string error (lex inside parse): {:?}", err);
err.map(|c| TokenKind::Literal(Literal::String(c.to_string())))
})
.collect_vec()),
}
}
fn interpolated_parser() -> impl Parser<char, Vec<InterpolateItem>, Error = ChumError<char>> {
let expr = interpolate_ident_part()
.map_with_span(move |name, s| (name, s))
.separated_by(just('.'))
.at_least(1)
.map(|ident_parts| {
let mut parts = ident_parts.into_iter();
let (first, first_span) = parts.next().unwrap();
let mut base = Box::new(into_expr(ExprKind::Ident(first), first_span));
for (part, span) in parts {
let field = IndirectionKind::Name(part);
base = Box::new(into_expr(ExprKind::Indirection { base, field }, span));
}
base
})
.labelled("interpolated string variable")
.then(
just(':')
.ignore_then(filter(|c| *c != '}').repeated().collect::<String>())
.or_not(),
)
.delimited_by(just('{'), just('}'))
.map(|(expr, format)| InterpolateItem::Expr { expr, format });
let string = just("{{")
.to('{')
.or(just("}}").to('}'))
.or(none_of("{}"))
.repeated()
.at_least(1)
.collect::<String>()
.map(InterpolateItem::String);
expr.or(string).repeated().then_ignore(end())
}
pub fn interpolate_ident_part() -> impl Parser<char, String, Error = ChumError<char>> + Clone {
let plain = filter(|c: &char| c.is_alphabetic() || *c == '_')
.chain(filter(|c: &char| c.is_alphanumeric() || *c == '_').repeated())
.labelled("interpolated string");
let backticks = none_of('`').repeated().delimited_by(just('`'), just('`'));
plain.or(backticks.labelled("interp:backticks")).collect()
}
#[test]
fn parse_interpolate() {
use insta::assert_debug_snapshot;
let span_base = Span::new(0, 0..0);
assert_debug_snapshot!(
parse("concat({a})".to_string(), span_base).unwrap(),
@r###"
[
String(
"concat(",
),
Expr {
expr: Expr {
kind: Ident(
"a",
),
span: Some(
0:8-9,
),
alias: None,
},
format: None,
},
String(
")",
),
]
"###);
assert_debug_snapshot!(
parse("print('{{hello}}')".to_string(), span_base).unwrap(),
@r###"
[
String(
"print('{hello}')",
),
]
"###);
assert_debug_snapshot!(
parse("concat('{{', a, '}}')".to_string(), span_base).unwrap(),
@r###"
[
String(
"concat('{', a, '}')",
),
]
"###);
assert_debug_snapshot!(
parse("concat('{{', {a}, '}}')".to_string(), span_base).unwrap(),
@r###"
[
String(
"concat('{', ",
),
Expr {
expr: Expr {
kind: Ident(
"a",
),
span: Some(
0:14-15,
),
alias: None,
},
format: None,
},
String(
", '}')",
),
]
"###);
}