use sim_codec::{DecodeBudget, DecodeLimits};
use sim_kernel::{
CodecId, Error, Expr, Fixity, LocatedExprTree, Origin, PrattOperator, PrattTable,
PrattToken as Token, Result, SourceId, Span, Symbol, Trivia,
parse_pratt_symbol as parse_symbol,
};
use crate::{PrattTokenSource, SpannedPrattToken};
pub fn raw_number_tag() -> Symbol {
Symbol::qualified("codec", "algol-number-literal")
}
pub fn raw_number_expr(raw: String) -> Expr {
Expr::Extension {
tag: raw_number_tag(),
payload: Box::new(Expr::String(raw)),
}
}
pub struct PrattCodecParser<S> {
operators: PrattTable,
token_source: S,
surface_name: &'static str,
}
impl<S: PrattTokenSource> PrattCodecParser<S> {
pub fn new(operators: PrattTable, token_source: S) -> Self {
Self {
operators,
token_source,
surface_name: "pratt",
}
}
pub fn with_surface_name(mut self, surface_name: &'static str) -> Self {
self.surface_name = surface_name;
self
}
pub fn operators(&self) -> &PrattTable {
&self.operators
}
pub fn token_source(&self) -> &S {
&self.token_source
}
pub fn parse_tree(&self, codec: CodecId, source: &str) -> Result<LocatedExprTree> {
let mut budget = DecodeBudget::new(DecodeLimits::default());
self.parse_tree_with_budget(codec, source, &mut budget)
}
pub fn parse_tree_with_budget(
&self,
codec: CodecId,
source: &str,
budget: &mut DecodeBudget,
) -> Result<LocatedExprTree> {
self.parse_tree_with_source_and_budget(
codec,
SourceId(format!("<{}>", self.surface_name)),
source,
budget,
)
}
pub fn parse_tree_with_source(
&self,
codec: CodecId,
source_id: impl Into<String>,
source: &str,
) -> Result<LocatedExprTree> {
let mut budget = DecodeBudget::new(DecodeLimits::default());
self.parse_tree_with_source_and_budget(
codec,
SourceId(source_id.into()),
source,
&mut budget,
)
}
pub fn parse_tree_with_source_and_budget(
&self,
codec: CodecId,
source_id: SourceId,
source: &str,
budget: &mut DecodeBudget,
) -> Result<LocatedExprTree> {
let tokens = self.token_source.tokenize_pratt(codec, source, budget)?;
budget.check_tokens(codec, tokens.len())?;
let mut cx = ParseCx::new(tokens, self.surface_name);
let expr = self.parse_expr_tree(&mut cx, codec, &source_id, source, 0, budget, 0)?;
if !cx.is_empty() {
return Err(Error::Eval(format!(
"trailing {} tokens",
self.surface_name
)));
}
Ok(expr)
}
#[allow(clippy::too_many_arguments)]
pub(super) fn parse_expr_tree(
&self,
cx: &mut ParseCx,
codec: CodecId,
source_id: &SourceId,
source: &str,
min_bp: u16,
budget: &mut DecodeBudget,
depth: usize,
) -> Result<LocatedExprTree> {
budget.enter_node(codec, depth)?;
let mut left = self.parse_nud_tree(cx, codec, source_id, source, budget, depth)?;
loop {
if matches!(
cx.peek(),
Some(SpannedPrattToken {
token: Token::OpenParen,
..
})
) {
if 110 < min_bp {
break;
}
let open = cx.next_required()?;
left = self.parse_call_tree(
cx, codec, source_id, source, left, open.start, budget, depth,
)?;
continue;
}
let Some(token) = cx.peek().cloned() else {
break;
};
let Some(op) = self.operators.lookup_led(&token.token) else {
break;
};
if op.fixity == Fixity::Postfix {
if op.left_bp < min_bp {
break;
}
cx.advance();
left = self.parse_postfix_tree(
codec, source_id, source, left, op, token.end, budget, depth,
)?;
continue;
}
if op.left_bp < min_bp {
break;
}
cx.advance();
left = self.parse_led_tree(cx, codec, source_id, source, left, op, budget, depth)?;
}
Ok(left)
}
fn parse_nud_tree(
&self,
cx: &mut ParseCx,
codec: CodecId,
source_id: &SourceId,
source: &str,
budget: &mut DecodeBudget,
depth: usize,
) -> Result<LocatedExprTree> {
let token = cx.next_required()?;
if let Some(op) = self.operators.lookup_nud(&token.token) {
let right =
self.parse_expr_tree(cx, codec, source_id, source, op.right_bp, budget, depth + 1)?;
return self.build_prefix_tree(
codec,
source_id,
source,
op,
token.start,
right,
budget,
depth,
);
}
match token.token {
Token::Ident(name) => {
budget.enter_node(codec, depth)?;
let expr = match name.as_str() {
"nil" => Expr::Nil,
"true" => Expr::Bool(true),
"false" => Expr::Bool(false),
_ => Expr::Symbol(parse_symbol(&name)),
};
Ok(LocatedExprTree::without_children(
expr,
Some(tree_origin(
codec,
source_id.clone(),
source,
token.start,
token.end,
token.leading_trivia,
)),
))
}
Token::Number(number) => {
budget.enter_node(codec, depth)?;
Ok(LocatedExprTree::without_children(
raw_number_expr(number),
Some(tree_origin(
codec,
source_id.clone(),
source,
token.start,
token.end,
token.leading_trivia,
)),
))
}
Token::String(value) => {
budget.enter_node(codec, depth)?;
budget.check_string_bytes(codec, value.len())?;
Ok(LocatedExprTree::without_children(
Expr::String(value),
Some(tree_origin(
codec,
source_id.clone(),
source,
token.start,
token.end,
token.leading_trivia,
)),
))
}
Token::OpenParen => {
let mut expr =
self.parse_expr_tree(cx, codec, source_id, source, 0, budget, depth + 1)?;
let close = cx.next_required()?;
if close.token != Token::CloseParen {
return Err(Error::Eval(format!(
"expected ')' in {} input, found {:?}",
self.surface_name, close
)));
}
extend_tree_trivia(&mut expr, close.leading_trivia.clone());
Ok(with_origin_span(
expr,
tree_origin(codec, source_id.clone(), source, token.start, close.end, {
let mut trivia = token.leading_trivia;
trivia.extend(close.leading_trivia);
trivia
}),
))
}
other => Err(Error::Eval(format!(
"unexpected {} token in nud {:?}",
self.surface_name, other
))),
}
}
#[allow(clippy::too_many_arguments)]
fn parse_led_tree(
&self,
cx: &mut ParseCx,
codec: CodecId,
source_id: &SourceId,
source: &str,
left: LocatedExprTree,
op: PrattOperator,
budget: &mut DecodeBudget,
depth: usize,
) -> Result<LocatedExprTree> {
match op.fixity {
Fixity::InfixLeft | Fixity::InfixRight => {
let right = self.parse_expr_tree(
cx,
codec,
source_id,
source,
op.right_bp,
budget,
depth + 1,
)?;
self.build_infix_tree(codec, source_id, source, op, left, right, budget, depth)
}
_ => Err(Error::Eval("operator cannot be used here".to_owned())),
}
}
}
pub(super) struct ParseCx {
tokens: Vec<SpannedPrattToken>,
index: usize,
surface_name: &'static str,
}
impl ParseCx {
pub(super) fn new(tokens: Vec<SpannedPrattToken>, surface_name: &'static str) -> Self {
Self {
tokens,
index: 0,
surface_name,
}
}
pub(super) fn peek(&self) -> Option<&SpannedPrattToken> {
self.tokens.get(self.index)
}
pub(super) fn advance(&mut self) -> Option<SpannedPrattToken> {
let token = self.tokens.get(self.index).cloned()?;
self.index += 1;
Some(token)
}
pub(super) fn next_required(&mut self) -> Result<SpannedPrattToken> {
self.advance()
.ok_or_else(|| Error::Eval(format!("unexpected end of {} input", self.surface_name)))
}
pub(super) fn is_empty(&self) -> bool {
self.index >= self.tokens.len()
}
}
pub(super) fn with_origin_span(mut tree: LocatedExprTree, origin: Origin) -> LocatedExprTree {
tree.origin = Some(origin);
tree
}
pub(super) fn extend_tree_trivia(tree: &mut LocatedExprTree, trivia: Vec<Trivia>) {
if trivia.is_empty() {
return;
}
if let Some(origin) = &mut tree.origin {
origin.trivia.extend(trivia);
}
}
pub(super) fn tree_origin(
codec: CodecId,
source: SourceId,
_raw: &str,
start: usize,
end: usize,
trivia: Vec<Trivia>,
) -> Origin {
Origin {
codec,
source,
span: Span { start, end },
trivia,
}
}