use chumsky::{error::Rich, prelude::*, span::SimpleSpan};
use crate::{
Engine, SpannedValue, Value,
engine::{BinaryOperator, EngineSyntax, Expression, SpannedBinaryOperator, SpannedExpr},
};
impl Engine {
pub fn parser<'src>(
syntax: &'src EngineSyntax,
) -> impl Parser<'src, &'src str, Vec<Box<SpannedExpr>>, extra::Err<Rich<'src, char>>> {
recursive(|expr| {
let numeric_key_prefixed = just('_').ignore_then(text::int(10));
let dotted_ident = text::ident()
.or(numeric_key_prefixed)
.map_with(|_, e| e.span())
.separated_by(just('.').padded())
.at_least(1)
.collect::<Vec<SimpleSpan>>()
.map_with(|spans, e| {
Box::new(SpannedExpr {
expr: Expression::Access { keywords: spans },
span: e.span(),
})
});
let plain_ident = text::ident().map(|s: &str| Value::Ident(s.to_string()));
let escape = just('\\').ignore_then(just('"').or(just('\\')));
let inner = escape
.or(none_of("\"\\"))
.repeated()
.collect::<String>()
.map(Value::Ident);
let quoted_ident = inner.delimited_by(just('"'), just('"'));
let ident = quoted_ident.or(plain_ident);
let sign = just('-').or(just('+')).or_not();
let int = sign
.then(text::int(10))
.map(|(sign, digits): (Option<char>, &str)| {
let number = format!("{}{}", sign.unwrap_or('+'), digits);
Value::Int(number.parse::<i64>().unwrap())
});
let float = sign
.then(text::int(10))
.then_ignore(just('.'))
.then(text::digits(10).to_slice())
.map(
|((sign, int_part), frac_part): ((Option<char>, &str), &str)| {
let number = format!("{}{}.{}", sign.unwrap_or('+'), int_part, frac_part);
Value::Float(number.parse::<f64>().unwrap())
},
);
let range = int
.then_ignore(just(".."))
.then(int)
.map_with(|(start, end), e| {
Box::new(SpannedExpr {
expr: Expression::Range {
start: start.get_int().expect("Failed to get int from range"),
end: end.get_int().expect("Failed to get int from range"),
},
span: e.span(),
})
});
let boolean = just("true")
.to(Value::Bool(true))
.or(just("false").to(Value::Bool(false)));
let spanned_ident = ident.map_with(|value, e| SpannedValue::new(value, e.span()));
let literal = float.or(int).or(ident).or(boolean).map_with(|value, e| {
Box::new(SpannedExpr {
expr: Expression::LiteralValue {
value: SpannedValue {
value,
span: e.span(),
},
},
span: e.span(),
})
});
let op = just('+')
.to(BinaryOperator::Add)
.or(just('-').to(BinaryOperator::Sub))
.or(just('*').to(BinaryOperator::Mul))
.or(just('/').to(BinaryOperator::Div))
.map_with(|op, e| SpannedBinaryOperator { op, span: e.span() });
let arg = literal
.clone()
.or(expr.clone())
.padded()
.clone()
.then(op.padded())
.then(literal.clone().or(expr.clone()).padded())
.map_with(|((a, b), c), e| {
Box::new(SpannedExpr {
expr: Expression::BinaryOp {
lhs: a,
op: b,
rhs: c,
},
span: e.span(),
})
});
let filter = text::ident()
.map_with(|_, e| e.span())
.then(
just(':')
.padded()
.ignore_then(
literal
.clone()
.or(arg.clone())
.or(expr.clone())
.padded()
.separated_by(just(',').padded())
.collect::<Vec<Box<SpannedExpr>>>(),
)
.or_not(),
)
.map_with(
|(name, args), e: &mut chumsky::input::MapExtra<'_, '_, _, _>| SpannedExpr {
expr: Expression::Filter {
name,
args: args.unwrap_or_default(),
},
span: e.span(),
},
);
let filters = just('|')
.padded()
.ignore_then(filter.padded())
.repeated()
.collect::<Vec<_>>();
let full_expr = dotted_ident
.or(literal.clone())
.or(arg)
.or(expr.clone())
.padded()
.then(filters)
.map(|(access, filters)| {
let keyword = SpannedExpr {
span: access.span.clone(),
expr: Expression::Keyword { keywords: access },
};
if filters.is_empty() {
keyword
} else {
let span = SimpleSpan::new(
(),
keyword.span.start
..filters
.last()
.map(|f| f.span.end)
.unwrap_or(keyword.span.end),
);
SpannedExpr {
expr: Expression::KeywordWithFilters {
keyword: Box::new(keyword),
filters,
},
span,
}
}
});
let keyword_full = full_expr
.padded()
.delimited_by(
just(syntax.keyword_left.as_str()),
just(syntax.keyword_right.as_str()),
)
.map_with(|expr, e| {
Box::new(SpannedExpr {
expr: expr.expr,
span: e.span(),
})
});
let escaped_raw = just("\\").ignore_then(
just(syntax.keyword_left.as_str())
.or(just(syntax.block_left.as_str()))
.or(just(syntax.keyword_right.as_str()))
.or(just(syntax.block_right.as_str()))
.or(just("\\"))
.map_with(|_, e| e.span()),
);
let generic_raw = any()
.and_is(
just(syntax.keyword_left.as_str())
.map(|_| ())
.or(just(syntax.block_left.as_str()).map(|_| ()))
.or(escaped_raw.map(|_| ()))
.not(),
)
.repeated()
.at_least(1)
.map_with(|_, e| e.span());
let raw = choice((escaped_raw, generic_raw)).map(|span| {
Box::new(SpannedExpr {
expr: Expression::Raw { value: span },
span,
})
});
let include = just("include")
.padded()
.ignore_then(spanned_ident.padded())
.delimited_by(
just(syntax.block_left.as_str()),
just(syntax.block_right.as_str()),
)
.map_with(|name, e| {
Box::new(SpannedExpr {
expr: Expression::Include { name },
span: e.span(),
})
});
let negation = just("not")
.padded()
.to(true)
.or_not()
.map(|n| n.unwrap_or(false));
let if_statement = just("if")
.padded()
.ignore_then(negation)
.then(keyword_full.clone().padded())
.then_ignore(just(syntax.block_right.as_str()).padded())
.then(expr.clone().repeated().collect())
.then(
just(syntax.block_left.as_str())
.padded()
.ignore_then(just("else").padded())
.ignore_then(just(syntax.block_right.as_str()).padded())
.ignore_then(expr.clone().repeated().collect())
.or_not(),
)
.delimited_by(
just(syntax.block_left.as_str()),
just("endif").padded().delimited_by(
just(syntax.block_left.as_str()),
just(syntax.block_right.as_str()),
),
)
.map_with(|(((negated, condition), then_branch), else_branch), e| {
Box::new(SpannedExpr {
expr: Expression::If {
condition: condition,
then_branch: then_branch,
else_branch: else_branch,
negated: negated,
},
span: e.span(),
})
});
let for_loop = just("for")
.padded()
.ignore_then(
spanned_ident
.separated_by(just(',').padded())
.at_least(1)
.collect::<Vec<SpannedValue>>(),
)
.padded()
.then_ignore(just("in").padded())
.then(dotted_ident.or(range).padded())
.then_ignore(just(syntax.block_right.as_str()))
.then(expr.repeated().collect())
.delimited_by(
just(syntax.block_left.as_str()),
just("endfor").padded().delimited_by(
just(syntax.block_left.as_str()),
just(syntax.block_right.as_str()),
),
)
.map_with(|((var, iter), body), e| {
Box::new(SpannedExpr {
expr: Expression::ForLoop { var, iter, body },
span: e.span(),
})
});
choice((raw, keyword_full, for_loop, if_statement, include))
})
.repeated()
.collect::<Vec<Box<SpannedExpr>>>()
}
}