use std::collections::HashMap;
use std::sync::Arc;
use chumsky::{
error::Rich,
extra,
input::IterInput,
prelude::*,
recursive::{self, Direct},
};
use crate::ast::{
AST, ArtifactField, ArtifactMethodTarget, AssignmentOp, ConditionalAssignment, LineInfo, Type,
};
use super::SimpleSpan;
use super::helpers::LineMap;
use super::tokens::{SpannedToken, Token};
type ParserInput<'src> = IterInput<std::vec::IntoIter<SpannedToken>, SimpleSpan<usize>>;
type ParserError<'src> = Rich<'src, Token, SimpleSpan<usize>>;
type ParserExtra<'src> = extra::Full<ParserError<'src>, (), ()>;
type BoxedParser<'src, T> = chumsky::Boxed<'src, 'src, ParserInput<'src>, T, ParserExtra<'src>>;
type RecursiveParser<'src, T> =
recursive::Recursive<Direct<'src, 'src, ParserInput<'src>, T, ParserExtra<'src>>>;
type SpannedAst = (AST, SimpleSpan<usize>);
type IndexedTarget = (SpannedAst, SpannedAst);
#[derive(Clone)]
struct ParserContext {
map: Arc<LineMap>,
}
impl ParserContext {
fn info(&self, span: SimpleSpan<usize>) -> Option<LineInfo> {
self.map.line_info(span)
}
fn wrap_statement(&self, ast: AST, span: SimpleSpan<usize>) -> AST {
AST::Statement(Box::new(ast), self.info(span))
}
}
fn merge_span(a: SimpleSpan<usize>, b: SimpleSpan<usize>) -> SimpleSpan<usize> {
SimpleSpan::new(a.start().min(b.start()), a.end().max(b.end()))
}
pub fn build_parser<'src>(map: Arc<LineMap>) -> BoxedParser<'src, Vec<AST>> {
let ctx = ParserContext { map };
let ctx_for_recursive = ctx.clone();
let statement = recursive(|statement| {
let ctx = ctx_for_recursive.clone();
let block = block_parser(ctx.clone(), statement.clone());
let expression = expression_parser(ctx.clone(), block.clone());
let body = statement_body_parser(ctx.clone(), expression.clone(), block.clone());
let ctx_for_stmt = ctx.clone();
body.then(just(Token::Semicolon).map_with(|_, extra| {
let span: SimpleSpan<usize> = extra.span();
span
}))
.map(move |((ast, body_span), semi_span)| {
let total_span = merge_span(body_span, semi_span);
ctx_for_stmt.wrap_statement(ast, total_span)
})
.boxed()
})
.boxed();
statement
.clone()
.repeated()
.collect::<Vec<AST>>()
.then_ignore(end())
.boxed()
}
fn block_parser<'src>(
ctx: ParserContext,
statement: RecursiveParser<'src, AST>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
just(Token::OpenBrace)
.map_with(|_, extra| extra.span())
.then(statement.clone().repeated().collect::<Vec<AST>>())
.then(just(Token::CloseBrace).map_with(|_, extra| extra.span()))
.map(move |((open_span, statements), close_span)| {
let span = SimpleSpan::new(open_span.start(), close_span.end());
let info = ctx_for_map.info(span);
(AST::Block(statements, info), span)
})
.boxed()
}
fn expression_parser<'src>(
ctx: ParserContext,
block: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
recursive(|expression: RecursiveParser<'src, SpannedAst>| {
let ctx = ctx.clone();
let block = block.clone();
let expr = expression.clone().boxed();
let oracle = oracle_expr_parser(ctx.clone(), expr.clone(), block.clone());
let logical = or_expr_parser(ctx, expr);
oracle.or(logical).boxed()
})
.boxed()
}
fn statement_body_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
block: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
choice((
artifact_def_parser(ctx.clone()),
forge_parser(ctx.clone(), expression.clone()),
engrave_parser(ctx.clone(), block.clone()),
reveal_parser(ctx.clone(), expression.clone()),
orbit_parser(ctx.clone(), expression.clone(), block.clone()),
orbit_flow_parser(ctx.clone()),
index_assignment_parser(ctx.clone(), expression.clone()),
field_assignment_parser(ctx.clone(), expression.clone()),
assignment_parser(ctx.clone(), expression.clone()),
expression,
))
.boxed()
}
fn artifact_def_parser<'src>(ctx: ParserContext) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let ident =
select! { Token::Identifier(name) => name }.map_with(|name, extra| (name, extra.span()));
just(Token::Artifact)
.map_with(|_, extra| extra.span())
.then(ident)
.then(artifact_fields_parser(ctx.clone()))
.map(move |((artifact_span, (name, _)), (fields, body_span))| {
let span = SimpleSpan::new(artifact_span.start(), body_span.end());
let info = ctx_for_map.info(span);
(
AST::ArtifactDef {
name,
fields,
line_info: info.clone(),
},
span,
)
})
.boxed()
}
fn artifact_fields_parser<'src>(
ctx: ParserContext,
) -> BoxedParser<'src, (Vec<ArtifactField>, SimpleSpan<usize>)> {
let field = select! { Token::Identifier(name) => name }
.map_with(|name, extra| (name, extra.span()))
.then_ignore(just(Token::Colon))
.then(type_parser())
.then_ignore(just(Token::Semicolon));
just(Token::OpenBrace)
.map_with(|_, extra| extra.span())
.then(field.repeated().collect::<Vec<_>>())
.then(just(Token::CloseBrace).map_with(|_, extra| extra.span()))
.try_map(move |((open_span, raw_fields), close_span), _extra| {
let span = SimpleSpan::new(open_span.start(), close_span.end());
let mut seen: HashMap<String, SimpleSpan<usize>> = HashMap::new();
let mut fields = Vec::with_capacity(raw_fields.len());
for ((name, name_span), (field_type, type_span)) in raw_fields {
if let Some(previous_span) = seen.insert(name.clone(), name_span) {
let dup_span = SimpleSpan::new(previous_span.start(), name_span.end());
return Err(Rich::custom(
dup_span,
format!("Duplicate field `{name}` in artifact definition"),
));
}
let field_span = SimpleSpan::new(name_span.start(), type_span.end());
let info = ctx.info(field_span);
fields.push(ArtifactField {
name,
field_type,
line_info: info,
});
}
Ok((fields, span))
})
.boxed()
}
fn forge_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let morph_flag = just(Token::Morph)
.to(true)
.or_not()
.map(|flag| flag.unwrap_or(false));
just(Token::Forge)
.map_with(|_, extra| extra.span())
.then(morph_flag)
.then(
select! { Token::Identifier(name) => name }
.map_with(|name, extra| (name, extra.span())),
)
.then_ignore(just(Token::Colon))
.then(type_parser())
.then_ignore(just(Token::Assign))
.then(expression)
.map(
move |(
(((forge_span, is_morph), (name, _name_span)), (ty, _ty_span)),
(value_ast, value_span),
)| {
let span = SimpleSpan::new(forge_span.start(), value_span.end());
let info = ctx_for_map.info(span);
(
AST::VarAssign {
name,
value: Box::new(value_ast),
var_type: ty,
is_morph,
line_info: info.clone(),
},
span,
)
},
)
.boxed()
}
#[derive(Clone)]
enum RawEngraveParam {
Receiver {
is_morph: bool,
span: SimpleSpan<usize>,
},
Param(AST),
}
enum EngraveTarget {
Function {
name: String,
},
Method {
artifact: String,
method: String,
span: SimpleSpan<usize>,
},
}
fn method_receiver_parser<'src>() -> BoxedParser<'src, RawEngraveParam> {
just(Token::Morph)
.map_with(|_, extra| extra.span())
.or_not()
.then(core_keyword_span())
.map(
|(maybe_morph, core_span): (Option<SimpleSpan<usize>>, SimpleSpan<usize>)| {
let span = if let Some(morph_span) = maybe_morph {
SimpleSpan::new(morph_span.start(), core_span.end())
} else {
core_span
};
RawEngraveParam::Receiver {
is_morph: maybe_morph.is_some(),
span,
}
},
)
.boxed()
}
fn core_keyword_span<'src>() -> BoxedParser<'src, SimpleSpan<usize>> {
just(Token::Core).map_with(|_, extra| extra.span()).boxed()
}
fn engrave_parser<'src>(
ctx: ParserContext,
block: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let params = choice((
method_receiver_parser(),
engrave_param_parser(ctx.clone()).map(RawEngraveParam::Param),
))
.separated_by(just(Token::Comma))
.collect::<Vec<_>>()
.or_not();
let ident_with_span: BoxedParser<'src, (String, SimpleSpan<usize>)> =
select! { Token::Identifier(name) => name }
.map_with(|name, extra| (name, extra.span()))
.boxed();
let target = ident_with_span
.clone()
.then(
just(Token::DoubleColon)
.ignore_then(ident_with_span.clone())
.or_not(),
)
.map(|((name, name_span), maybe_method)| {
if let Some((method, method_span)) = maybe_method {
let span = SimpleSpan::new(name_span.start(), method_span.end());
EngraveTarget::Method {
artifact: name,
method,
span,
}
} else {
EngraveTarget::Function { name }
}
});
just(Token::Engrave)
.map_with(|_, extra| extra.span())
.then(target)
.then_ignore(just(Token::OpenParen))
.then(params)
.then_ignore(just(Token::CloseParen))
.then(just(Token::Arrow).ignore_then(type_parser()).or_not())
.then(block)
.try_map(
move |((((engrave_span, target), params_opt), ret_opt), (body_ast, body_span)), _extra| {
let span = SimpleSpan::new(engrave_span.start(), body_span.end());
let info = ctx_for_map.info(span);
let raw_params = params_opt.unwrap_or_default();
let return_type = ret_opt.map(|(ty, _)| ty).unwrap_or(Type::Abyss);
match target {
EngraveTarget::Function { name } => {
let mut params = Vec::with_capacity(raw_params.len());
for entry in raw_params {
match entry {
RawEngraveParam::Param(node) => params.push(node),
RawEngraveParam::Receiver { span: recv_span, .. } => {
return Err(Rich::custom(
recv_span,
"The `core` parameter is reserved for artifact methods. Use a regular parameter name for standalone functions.",
));
}
}
}
Ok((
AST::Engrave {
name,
params,
return_type,
body: Box::new(body_ast),
method_target: None,
line_info: info,
},
span,
))
}
EngraveTarget::Method {
artifact,
method,
span: target_span,
} => {
let mut params = Vec::with_capacity(raw_params.len());
let mut target_meta = ArtifactMethodTarget {
artifact,
requires_morph: false,
};
let mut receiver_seen = false;
for (idx, entry) in raw_params.into_iter().enumerate() {
match entry {
RawEngraveParam::Receiver {
is_morph,
span: recv_span,
} => {
if receiver_seen {
return Err(Rich::custom(
recv_span,
"The `core` receiver can only appear once in the parameter list",
));
}
if idx != 0 {
return Err(Rich::custom(
recv_span,
"`core` receiver must be the first parameter",
));
}
receiver_seen = true;
target_meta.requires_morph = is_morph;
let info = ctx_for_map.info(recv_span);
params.push(AST::EngraveParam {
name: "core".to_string(),
param_type: Type::Artifact(target_meta.artifact.clone()),
is_morph,
line_info: info,
});
}
RawEngraveParam::Param(node) => params.push(node),
}
}
if !receiver_seen {
return Err(Rich::custom(
target_span,
"Artifact methods must declare `core` (or `morph core`) as the first parameter",
));
}
Ok((
AST::Engrave {
name: method,
params,
return_type,
body: Box::new(body_ast),
method_target: Some(target_meta),
line_info: info,
},
span,
))
}
}
},
)
.boxed()
}
fn reveal_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
just(Token::Reveal)
.map_with(|_, extra| extra.span())
.then(expression.or_not())
.map(move |(reveal_span, maybe_expr)| {
let span = maybe_expr
.as_ref()
.map(|(_, expr_span)| SimpleSpan::new(reveal_span.start(), expr_span.end()))
.unwrap_or(reveal_span);
let info = ctx_for_map.info(span);
let value = maybe_expr
.map(|(ast, _)| Box::new(ast))
.unwrap_or_else(|| Box::new(AST::Abyss(info.clone())));
(AST::Reveal(value, info.clone()), span)
})
.boxed()
}
fn orbit_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
block: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let params = orbit_param_parser(ctx.clone(), expression.clone())
.separated_by(just(Token::Comma))
.at_least(1)
.collect::<Vec<_>>()
.delimited_by(just(Token::OpenParen), just(Token::CloseParen))
.or_not();
just(Token::Orbit)
.map_with(|_, extra| extra.span())
.then(params)
.then(block)
.map(move |((orbit_span, params_opt), (body_ast, body_span))| {
let span = SimpleSpan::new(orbit_span.start(), body_span.end());
let info = ctx_for_map.info(span);
(
AST::Orbit {
params: params_opt.unwrap_or_default(),
body: Box::new(body_ast),
line_info: info.clone(),
},
span,
)
})
.boxed()
}
fn orbit_flow_parser<'src>(ctx: ParserContext) -> BoxedParser<'src, SpannedAst> {
let ctx_resume = ctx.clone();
let ident: BoxedParser<'src, (String, SimpleSpan<usize>)> = select! {
Token::Identifier(name) => name
}
.map_with(|name, extra| (name, extra.span()))
.boxed();
let resume = just(Token::Resume)
.map_with(|_, extra| extra.span())
.then(ident.clone().or_not())
.map(
move |(resume_span, maybe_ident): (
SimpleSpan<usize>,
Option<(String, SimpleSpan<usize>)>,
)| {
let span = maybe_ident
.as_ref()
.map(|(_, id_span)| SimpleSpan::new(resume_span.start(), id_span.end()))
.unwrap_or(resume_span);
let info = ctx_resume.info(span);
(
AST::Resume(maybe_ident.map(|(name, _)| name), info.clone()),
span,
)
},
);
let ctx_eject = ctx;
let eject = just(Token::Eject)
.map_with(|_, extra| extra.span())
.then(ident.or_not())
.map(
move |(eject_span, maybe_ident): (
SimpleSpan<usize>,
Option<(String, SimpleSpan<usize>)>,
)| {
let span = maybe_ident
.as_ref()
.map(|(_, id_span)| SimpleSpan::new(eject_span.start(), id_span.end()))
.unwrap_or(eject_span);
let info = ctx_eject.info(span);
(
AST::Eject(maybe_ident.map(|(name, _)| name), info.clone()),
span,
)
},
);
resume.or(eject).boxed()
}
fn assignment_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let ident =
select! { Token::Identifier(name) => name }.map_with(|name, extra| (name, extra.span()));
let op = choice((
just(Token::Assign),
just(Token::AddAssign),
just(Token::SubAssign),
just(Token::MulAssign),
just(Token::DivAssign),
just(Token::ModAssign),
just(Token::PowArcanaAssign),
just(Token::PowAetherAssign),
))
.map_with(|token, extra| (token, extra.span()));
ident
.then(op)
.then(expression)
.map(
move |(((name, name_span), (token, _op_span)), (value_ast, value_span))| {
let span = SimpleSpan::new(name_span.start(), value_span.end());
let info = ctx_for_map.info(span);
(
AST::Assignment {
name,
value: Box::new(value_ast),
op: assignment_op_from_token(token),
line_info: info.clone(),
},
span,
)
},
)
.boxed()
}
fn field_assignment_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
expression
.clone()
.then_ignore(just(Token::Assign))
.then(expression)
.try_map(
move |((target_ast, target_span), (value_ast, value_span)), _extra| {
if let AST::FieldAccess { target, field, .. } = target_ast {
let span = SimpleSpan::new(target_span.start(), value_span.end());
let info = ctx_for_map.info(span);
Ok((
AST::FieldAssignment {
target,
field,
value: Box::new(value_ast),
line_info: info.clone(),
},
span,
))
} else {
Err(Rich::custom(
target_span,
"Field assignment requires an artifact field target".to_string(),
))
}
},
)
.boxed()
}
fn assignment_op_from_token(token: Token) -> AssignmentOp {
match token {
Token::Assign => AssignmentOp::Assign,
Token::AddAssign => AssignmentOp::AddAssign,
Token::SubAssign => AssignmentOp::SubAssign,
Token::MulAssign => AssignmentOp::MulAssign,
Token::DivAssign => AssignmentOp::DivAssign,
Token::ModAssign => AssignmentOp::ModAssign,
Token::PowArcanaAssign => AssignmentOp::PowArcanaAssign,
Token::PowAetherAssign => AssignmentOp::PowAetherAssign,
other => unreachable!("Unhandled assignment operator: {other:?}"),
}
}
fn index_assignment_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
indexed_target_parser(ctx, expression.clone())
.then_ignore(just(Token::Assign))
.then(expression)
.map(
move |(
((target_ast, target_span), (index_ast, index_span)),
(value_ast, value_span),
)| {
let lhs_span = SimpleSpan::new(target_span.start(), index_span.end());
let span = SimpleSpan::new(lhs_span.start(), value_span.end());
let info = ctx_for_map.info(span);
(
AST::IndexAssignment {
target: Box::new(target_ast),
index: Box::new(index_ast),
value: Box::new(value_ast),
line_info: info.clone(),
},
span,
)
},
)
.boxed()
}
fn indexed_target_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, IndexedTarget> {
let ctx_for_map = ctx.clone();
primary_expr_parser(ctx.clone(), expression.clone())
.then(
index_suffix_parser(ctx.clone(), expression)
.repeated()
.at_least(1)
.collect::<Vec<_>>(),
)
.map(move |(base, mut suffixes)| {
let last = suffixes
.pop()
.expect("Parser guaranteed at least one index suffix via .at_least(1)");
let target = suffixes.into_iter().fold(base, |acc, suffix| {
create_index_access(ctx_for_map.clone(), acc, suffix)
});
(target, last)
})
.boxed()
}
fn oracle_expr_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
block: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_cond = ctx.clone();
let match_scrutinee = expression
.clone()
.separated_by(just(Token::Comma))
.at_least(1)
.collect::<Vec<_>>()
.map(|exprs| {
exprs
.into_iter()
.enumerate()
.map(|(idx, (ast, _))| ConditionalAssignment {
variable: format!("__match_{}", idx),
expression: Box::new(ast),
line_info: None,
})
.collect::<Vec<_>>()
});
let conditional = just(Token::OpenParen)
.ignore_then(match_scrutinee)
.then_ignore(just(Token::CloseParen))
.or_not();
let branch = oracle_branch_parser(ctx.clone(), expression.clone(), block.clone());
just(Token::Oracle)
.map_with(|_, extra| extra.span())
.then(conditional)
.then_ignore(just(Token::OpenBrace))
.then(branch.repeated().collect::<Vec<SpannedAst>>())
.then(just(Token::CloseBrace).map_with(|_, extra| extra.span()))
.map(
move |(((oracle_span, conditional_opt), branches), close_span)| {
let span = SimpleSpan::new(oracle_span.start(), close_span.end());
let info = ctx_for_cond.info(span);
let (is_match, mut conditionals) = if let Some(conds) = conditional_opt {
(true, conds)
} else {
(false, Vec::new())
};
for cond in &mut conditionals {
cond.line_info = info.clone();
}
let mut branch_asts = Vec::with_capacity(branches.len());
for (branch_ast, _branch_span) in branches {
branch_asts.push(branch_ast);
}
(
AST::Oracle {
is_match,
conditionals,
branches: branch_asts,
line_info: info.clone(),
},
span,
)
},
)
.boxed()
}
fn oracle_branch_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
block: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_stmt = ctx.clone();
let single_statement = statement_body_parser(ctx.clone(), expression.clone(), block.clone())
.then(just(Token::Semicolon).map_with(|_, extra| extra.span()))
.map(move |((ast, body_span), semi_span)| {
let span = merge_span(body_span, semi_span);
let stmt = ctx_for_stmt.wrap_statement(ast, span);
(stmt, span)
});
let body = block.clone().or(single_statement);
let guard = just(Token::Ward)
.ignore_then(expression.clone())
.map(|(ast, _)| Box::new(ast))
.or_not();
let pattern_element = pattern_element_parser(ctx.clone(), expression.clone());
let pattern = pattern_parser(ctx.clone(), expression.clone(), pattern_element);
pattern
.then(guard)
.then_ignore(just(Token::FatArrow))
.then(body)
.map(
move |(((pattern, pattern_span), guard_ast), (body_ast, body_span))| {
let span = merge_span(pattern_span, body_span);
let info = ctx.info(span);
(
AST::OracleBranch {
pattern,
guard: guard_ast,
body: Box::new(body_ast),
line_info: info.clone(),
},
span,
)
},
)
.boxed()
}
fn pattern_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
pattern_element: BoxedParser<'src, AST>,
) -> BoxedParser<'src, (Vec<AST>, SimpleSpan<usize>)> {
let wildcard = select! { Token::Identifier(name) if name == "_" => () }
.map_with(|_, extra| (Vec::new(), extra.span()));
let list = just(Token::OpenParen)
.map_with(|_, extra| extra.span())
.then(
pattern_element
.clone()
.separated_by(just(Token::Comma))
.at_least(1)
.collect::<Vec<_>>(),
)
.then(just(Token::CloseParen).map_with(|_, extra| extra.span()))
.map(|((open_span, elements), close_span)| {
let span = SimpleSpan::new(open_span.start(), close_span.end());
(elements, span)
});
let scroll = scroll_pattern_parser(ctx.clone(), pattern_element.clone(), expression.clone())
.map_with(|ast, extra| {
let span: SimpleSpan<usize> = SimpleSpan::new(extra.span().start(), extra.span().end());
(vec![ast], span)
});
let artifact =
artifact_pattern_parser(ctx.clone(), pattern_element.clone(), expression.clone()).map_with(
|ast, extra| {
let span: SimpleSpan<usize> =
SimpleSpan::new(extra.span().start(), extra.span().end());
(vec![ast], span)
},
);
let lexicon =
lexicon_pattern_parser(ctx, pattern_element, expression).map_with(|ast, extra| {
let span: SimpleSpan<usize> = SimpleSpan::new(extra.span().start(), extra.span().end());
(vec![ast], span)
});
wildcard
.or(list)
.or(scroll)
.or(artifact)
.or(lexicon)
.boxed()
}
fn pattern_element_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, AST> {
recursive(|pattern_elem: RecursiveParser<'src, AST>| {
let ctx_for_wild = ctx.clone();
let dont_care =
select! { Token::Identifier(name) if name == "_" => () }.map_with(move |_, extra| {
let span = extra.span();
AST::OracleDontCareItem(ctx_for_wild.info(span))
});
let pattern_elem_boxed = pattern_elem.clone().boxed();
let scroll =
scroll_pattern_parser(ctx.clone(), pattern_elem_boxed.clone(), expression.clone());
let artifact =
artifact_pattern_parser(ctx.clone(), pattern_elem_boxed.clone(), expression.clone());
let lexicon = lexicon_pattern_parser(ctx.clone(), pattern_elem_boxed, expression.clone());
let expr = expression.clone().map(|(ast, _)| ast);
dont_care
.or(scroll)
.or(artifact)
.or(lexicon)
.or(expr)
.boxed()
})
.boxed()
}
fn artifact_pattern_parser<'src>(
ctx: ParserContext,
pattern_element: BoxedParser<'src, AST>,
_expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, AST> {
let ctx_for_field = ctx.clone();
let field = select! { Token::Identifier(name) => name }
.map_with(move |name, extra| (name, extra.span()))
.then(just(Token::Colon).ignore_then(pattern_element).or_not())
.map(move |((name, name_span), value)| {
let pattern = value.unwrap_or_else(|| {
AST::Var(
name.clone(),
ctx_for_field.info(SimpleSpan::new(name_span.start(), name_span.end())),
)
});
(name, pattern)
});
let ctx_for_outer = ctx;
select! { Token::Identifier(name) => name }
.map_with(|name, extra| (name, extra.span()))
.then_ignore(just(Token::OpenBrace))
.then(
field
.separated_by(just(Token::Comma))
.allow_trailing()
.collect::<Vec<_>>(),
)
.then(just(Token::CloseBrace).map_with(|_, extra| extra.span()))
.map(move |(((type_name, type_span), fields), close_span)| {
let span = SimpleSpan::new(type_span.start(), close_span.end());
AST::OracleArtifactPattern {
type_name,
fields,
line_info: ctx_for_outer.info(span),
}
})
.boxed()
}
fn lexicon_pattern_parser<'src>(
ctx: ParserContext,
pattern_element: BoxedParser<'src, AST>,
_expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, AST> {
let entry = select! { Token::Rune(key) => key }
.map_with(|key, extra| (key, extra.span()))
.then_ignore(just(Token::Colon))
.then(pattern_element)
.map(|((key, _), value)| (key, value));
let ctx_for_outer = ctx;
just(Token::OpenBrace)
.map_with(|_, extra| extra.span())
.then(
entry
.separated_by(just(Token::Comma))
.allow_trailing()
.collect::<Vec<_>>(),
)
.then(just(Token::CloseBrace).map_with(|_, extra| extra.span()))
.map(move |((open_span, entries), close_span)| {
let span = SimpleSpan::new(open_span.start(), close_span.end());
AST::OracleLexiconPattern {
entries,
line_info: ctx_for_outer.info(span),
}
})
.boxed()
}
fn scroll_pattern_parser<'src>(
ctx: ParserContext,
pattern_element: BoxedParser<'src, AST>,
_expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, AST> {
let ctx_for_rest = ctx.clone();
let rest = just(Token::RangeExclusive)
.map_with(|_, extra| extra.span())
.then(
select! { Token::Identifier(name) => name }
.map_with(|name, extra| (name, extra.span()))
.or_not(),
)
.map(move |(open_span, named)| {
let close_span: SimpleSpan<usize> =
named.as_ref().map(|(_, span)| *span).unwrap_or(open_span);
let span = SimpleSpan::new(open_span.start(), close_span.end());
AST::OracleScrollRest {
name: named.map(|(name, _)| name),
line_info: ctx_for_rest.info(span),
}
});
let element = rest.or(pattern_element);
let ctx_for_outer = ctx;
just(Token::OpenBracket)
.map_with(|_, extra| extra.span())
.then(
element
.separated_by(just(Token::Comma))
.allow_trailing()
.collect::<Vec<_>>(),
)
.then(just(Token::CloseBracket).map_with(|_, extra| extra.span()))
.map(move |((open_span, elements), close_span)| {
let span = SimpleSpan::new(open_span.start(), close_span.end());
AST::OracleScrollPattern {
elements,
line_info: ctx_for_outer.info(span),
}
})
.boxed()
}
fn orbit_param_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, AST> {
let ctx_for_map = ctx.clone();
select! { Token::Identifier(name) => name }
.map_with(|name, extra| (name, extra.span()))
.then_ignore(just(Token::Assign))
.then(range_expr_parser(ctx.clone(), expression))
.map(
move |((name, name_span), (start_ast, end_ast, op, range_span))| {
let span = merge_span(name_span, range_span);
let info = ctx_for_map.info(span);
AST::OrbitParam {
name,
start: Box::new(start_ast),
end: Box::new(end_ast),
op,
line_info: info,
}
},
)
.boxed()
}
fn range_expr_parser<'src>(
_ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, (AST, AST, String, SimpleSpan<usize>)> {
let op = choice((
just(Token::RangeInclusive).to(String::from("..=")),
just(Token::RangeExclusive).to(String::from("..")),
));
expression
.clone()
.then(op)
.then(expression)
.map(|((start, op_str), end)| {
let span = SimpleSpan::new(start.1.start(), end.1.end());
(start.0, end.0, op_str, span)
})
.boxed()
}
fn or_expr_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let and_expr = and_expr_parser(ctx, expression);
and_expr
.clone()
.then(
just(Token::DoublePipe)
.ignore_then(and_expr.clone())
.repeated()
.collect::<Vec<_>>(),
)
.map(move |(first, rest)| {
rest.into_iter().fold(first, |left, right| {
let span = merge_span(left.1, right.1);
let info = ctx_for_map.info(span);
(
AST::LogicalOr(Box::new(left.0), Box::new(right.0), info.clone()),
span,
)
})
})
.boxed()
}
fn and_expr_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let not_expr = not_expr_parser(ctx, expression);
not_expr
.clone()
.then(
just(Token::DoubleAmpersand)
.ignore_then(not_expr.clone())
.repeated()
.collect::<Vec<_>>(),
)
.map(move |(first, rest)| {
rest.into_iter().fold(first, |left, right| {
let span = merge_span(left.1, right.1);
let info = ctx_for_map.info(span);
(
AST::LogicalAnd(Box::new(left.0), Box::new(right.0), info.clone()),
span,
)
})
})
.boxed()
}
fn not_expr_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
just(Token::Bang)
.map_with(|_, extra| extra.span())
.repeated()
.collect::<Vec<_>>()
.then(comp_expr_parser(ctx, expression))
.map(move |(nots, mut current)| {
for span in nots.into_iter().rev() {
let total_span = SimpleSpan::new(span.start(), current.1.end());
let info = ctx_for_map.info(total_span);
current = (
AST::LogicalNot(Box::new(current.0), info.clone()),
total_span,
);
}
current
})
.boxed()
}
fn comp_expr_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let add_expr = add_expr_parser(ctx, expression);
let comparator = choice((
just(Token::Equal),
just(Token::NotEqual),
just(Token::LessThanOrEqual),
just(Token::GreaterThanOrEqual),
just(Token::LessThan),
just(Token::GreaterThan),
));
add_expr
.clone()
.then(comparator.then(add_expr.clone()).or_not())
.map(move |(left, maybe)| {
if let Some((op_token, right)) = maybe {
let span = merge_span(left.1, right.1);
let info = ctx_for_map.info(span);
let ast = match op_token {
Token::Equal => AST::Equal(Box::new(left.0), Box::new(right.0), info.clone()),
Token::NotEqual => {
AST::NotEqual(Box::new(left.0), Box::new(right.0), info.clone())
}
Token::LessThan => {
AST::LessThan(Box::new(left.0), Box::new(right.0), info.clone())
}
Token::LessThanOrEqual => {
AST::LessThanOrEqual(Box::new(left.0), Box::new(right.0), info.clone())
}
Token::GreaterThan => {
AST::GreaterThan(Box::new(left.0), Box::new(right.0), info.clone())
}
Token::GreaterThanOrEqual => {
AST::GreaterThanOrEqual(Box::new(left.0), Box::new(right.0), info.clone())
}
_ => unreachable!(),
};
(ast, span)
} else {
left
}
})
.boxed()
}
fn add_expr_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let mul_expr = mul_expr_parser(ctx, expression);
mul_expr
.clone()
.then(
choice((just(Token::Plus), just(Token::Minus)))
.then(mul_expr.clone())
.repeated()
.collect::<Vec<_>>(),
)
.map(move |(first, rest)| {
rest.into_iter().fold(first, |left, (op_token, right)| {
let span = merge_span(left.1, right.1);
let info = ctx_for_map.info(span);
let ast = match op_token {
Token::Plus => AST::Add(Box::new(left.0), Box::new(right.0), info.clone()),
Token::Minus => AST::Sub(Box::new(left.0), Box::new(right.0), info.clone()),
_ => unreachable!(),
};
(ast, span)
})
})
.boxed()
}
fn mul_expr_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let pow_expr = pow_expr_parser(ctx, expression);
pow_expr
.clone()
.then(
choice((just(Token::Star), just(Token::Slash), just(Token::Percent)))
.then(pow_expr.clone())
.repeated()
.collect::<Vec<_>>(),
)
.map(move |(first, rest)| {
rest.into_iter().fold(first, |left, (op_token, right)| {
let span = merge_span(left.1, right.1);
let info = ctx_for_map.info(span);
let ast = match op_token {
Token::Star => AST::Mul(Box::new(left.0), Box::new(right.0), info.clone()),
Token::Slash => AST::Div(Box::new(left.0), Box::new(right.0), info.clone()),
Token::Percent => AST::Mod(Box::new(left.0), Box::new(right.0), info.clone()),
_ => unreachable!(),
};
(ast, span)
})
})
.boxed()
}
fn pow_expr_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let factor = factor_parser(ctx, expression);
factor
.clone()
.then(
choice((just(Token::DoubleStar), just(Token::Caret)))
.then(factor.clone())
.repeated()
.collect::<Vec<_>>(),
)
.map(move |(first, rest)| {
rest.into_iter().fold(first, |left, (op_token, right)| {
let span = merge_span(left.1, right.1);
let info = ctx_for_map.info(span);
let ast = match op_token {
Token::DoubleStar => {
AST::PowAether(Box::new(left.0), Box::new(right.0), info.clone())
}
Token::Caret => {
AST::PowArcana(Box::new(left.0), Box::new(right.0), info.clone())
}
_ => unreachable!(),
};
(ast, span)
})
})
.boxed()
}
fn factor_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let primary = primary_expr_parser(ctx.clone(), expression.clone());
apply_postfix_suffixes(ctx, expression, primary).boxed()
}
fn primary_expr_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
choice((
list_literal_parser(ctx.clone(), expression.clone()),
map_literal_parser(ctx.clone(), expression.clone()),
artifact_literal_parser(ctx.clone(), expression.clone()),
literal_parser(ctx.clone()),
func_call_parser(ctx.clone(), expression.clone()),
identifier_node(ctx.clone()),
expression.delimited_by(just(Token::OpenParen), just(Token::CloseParen)),
))
.boxed()
}
enum PostfixSuffix {
Index((AST, SimpleSpan<usize>)),
Field((String, SimpleSpan<usize>)),
Method(MethodSuffix),
}
struct MethodSuffix {
name: String,
args: Vec<AST>,
span: SimpleSpan<usize>,
}
fn apply_postfix_suffixes<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
base: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
base.then(
postfix_suffix_parser(ctx.clone(), expression)
.repeated()
.collect::<Vec<_>>(),
)
.map(move |(current, suffixes)| {
suffixes
.into_iter()
.fold(current, |acc, suffix| match suffix {
PostfixSuffix::Index(index_suffix) => {
create_index_access(ctx_for_map.clone(), acc, index_suffix)
}
PostfixSuffix::Field(field_suffix) => {
create_field_access(ctx_for_map.clone(), acc, field_suffix)
}
PostfixSuffix::Method(method_suffix) => {
create_method_call(ctx_for_map.clone(), acc, method_suffix)
}
})
})
.boxed()
}
fn postfix_suffix_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, PostfixSuffix> {
let index = index_suffix_parser(ctx.clone(), expression.clone()).map(PostfixSuffix::Index);
let method = just(Token::Dot)
.ignore_then(
select! { Token::Identifier(name) => name }
.map_with(|name, extra| (name, extra.span())),
)
.then(
just(Token::OpenParen)
.map_with(|_, extra| extra.span())
.then(
expression
.separated_by(just(Token::Comma))
.collect::<Vec<_>>()
.or_not(),
)
.then(just(Token::CloseParen).map_with(|_, extra| extra.span()))
.map(|((open_span, maybe_args), close_span)| {
let span = SimpleSpan::new(open_span.start(), close_span.end());
let args = maybe_args
.unwrap_or_default()
.into_iter()
.map(|(ast, _)| ast)
.collect::<Vec<_>>();
(args, span)
})
.or_not(),
)
.map(|((name, name_span), maybe_call)| {
if let Some((args, call_span)) = maybe_call {
let suffix_span = SimpleSpan::new(name_span.start(), call_span.end());
PostfixSuffix::Method(MethodSuffix {
name,
args,
span: suffix_span,
})
} else {
PostfixSuffix::Field((name, name_span))
}
});
choice((index, method)).boxed()
}
fn index_suffix_parser<'src>(
_ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, (AST, SimpleSpan<usize>)> {
just(Token::OpenBracket)
.map_with(|_, extra| extra.span())
.then(expression)
.then(just(Token::CloseBracket).map_with(|_, extra| extra.span()))
.map(|((open_span, (index_ast, _)), close_span)| {
let span = SimpleSpan::new(open_span.start(), close_span.end());
(index_ast, span)
})
.boxed()
}
fn create_field_access(
ctx: ParserContext,
current: SpannedAst,
field_suffix: (String, SimpleSpan<usize>),
) -> SpannedAst {
let span = SimpleSpan::new(current.1.start(), field_suffix.1.end());
let info = ctx.info(span);
(
AST::FieldAccess {
target: Box::new(current.0),
field: field_suffix.0,
line_info: info,
},
span,
)
}
fn create_method_call(
ctx: ParserContext,
current: SpannedAst,
method_suffix: MethodSuffix,
) -> SpannedAst {
let span = SimpleSpan::new(current.1.start(), method_suffix.span.end());
let info = ctx.info(span);
(
AST::MethodCall {
receiver: Box::new(current.0),
method: method_suffix.name,
args: method_suffix.args,
line_info: info,
},
span,
)
}
fn create_index_access(
ctx: ParserContext,
current: SpannedAst,
index_suffix: (AST, SimpleSpan<usize>),
) -> SpannedAst {
let span = SimpleSpan::new(current.1.start(), index_suffix.1.end());
let info = ctx.info(span);
(
AST::IndexAccess {
target: Box::new(current.0),
index: Box::new(index_suffix.0),
line_info: info,
},
span,
)
}
fn func_call_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
select! { Token::Identifier(name) => name }
.map_with(|name, extra| (name, extra.span()))
.then(just(Token::OpenParen).map_with(|_, extra| extra.span()))
.then(
expression
.separated_by(just(Token::Comma))
.collect::<Vec<_>>()
.or_not(),
)
.then(just(Token::CloseParen).map_with(|_, extra| extra.span()))
.map(
move |((((name, name_span), _open_span), args_opt), close_span)| {
let span = SimpleSpan::new(name_span.start(), close_span.end());
let info = ctx_for_map.info(span);
let args = args_opt
.unwrap_or_default()
.into_iter()
.map(|(ast, _)| ast)
.collect();
(
AST::FuncCall {
name,
args,
line_info: info.clone(),
},
span,
)
},
)
.boxed()
}
fn list_literal_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
just(Token::OpenBracket)
.map_with(|_, extra| extra.span())
.then(
expression
.clone()
.separated_by(just(Token::Comma))
.collect::<Vec<_>>()
.or_not(),
)
.then(just(Token::CloseBracket).map_with(|_, extra| extra.span()))
.map(move |((open_span, maybe_items), close_span)| {
let span = SimpleSpan::new(open_span.start(), close_span.end());
let info = ctx_for_map.info(span);
let elements = maybe_items
.unwrap_or_default()
.into_iter()
.map(|(ast, _)| ast)
.collect();
(
AST::ListLiteral {
elements,
line_info: info.clone(),
},
span,
)
})
.boxed()
}
fn map_literal_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let entry = select! { Token::Rune(key) => key }
.map_with(|key, extra| (key, extra.span()))
.then_ignore(just(Token::Colon))
.then(expression.clone());
just(Token::OpenBrace)
.map_with(|_, extra| extra.span())
.then(
entry
.separated_by(just(Token::Comma))
.collect::<Vec<_>>()
.or_not(),
)
.then(just(Token::CloseBrace).map_with(|_, extra| extra.span()))
.map(move |((open_span, maybe_entries), close_span)| {
let span = SimpleSpan::new(open_span.start(), close_span.end());
let info = ctx_for_map.info(span);
let entries = maybe_entries
.unwrap_or_default()
.into_iter()
.map(|((key, _), (value_ast, _))| (key, value_ast))
.collect();
(
AST::MapLiteral {
entries,
line_info: info.clone(),
},
span,
)
})
.boxed()
}
fn artifact_literal_parser<'src>(
ctx: ParserContext,
expression: BoxedParser<'src, SpannedAst>,
) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
let entry = select! { Token::Identifier(name) => name }
.map_with(|name, extra| (name, extra.span()))
.then_ignore(just(Token::Colon))
.then(expression.clone());
select! { Token::Identifier(type_name) => type_name }
.map_with(|type_name, extra| (type_name, extra.span()))
.then(just(Token::OpenBrace).map_with(|_, extra| extra.span()))
.then(
entry
.separated_by(just(Token::Comma))
.collect::<Vec<_>>()
.or_not(),
)
.then(just(Token::CloseBrace).map_with(|_, extra| extra.span()))
.map(
move |((((type_name, type_span), _open_span), maybe_fields), close_span)| {
let span = SimpleSpan::new(type_span.start(), close_span.end());
let info = ctx_for_map.info(span);
let fields = maybe_fields
.unwrap_or_default()
.into_iter()
.map(|((field, _), (value_ast, _))| (field, value_ast))
.collect();
(
AST::ArtifactLiteral {
type_name,
fields,
line_info: info.clone(),
},
span,
)
},
)
.boxed()
}
fn literal_parser<'src>(ctx: ParserContext) -> BoxedParser<'src, SpannedAst> {
let ctx_omen = ctx.clone();
let omen = select! { Token::OmenLiteral(value) => value }.map_with(move |value, extra| {
let span = extra.span();
let info = ctx_omen.info(span);
(AST::Omen(value, info), span)
});
let ctx_arcana = ctx.clone();
let arcana = select! { Token::Arcana(value) => value }.map_with(move |value, extra| {
let span = extra.span();
let info = ctx_arcana.info(span);
(AST::Arcana(value, info), span)
});
let ctx_aether = ctx.clone();
let aether = select! { Token::Aether(value) => value }.map_with(move |value, extra| {
let span = extra.span();
let info = ctx_aether.info(span);
(AST::Aether(value.into_inner(), info), span)
});
let ctx_rune = ctx;
let rune = select! { Token::Rune(value) => value }.map_with(move |value, extra| {
let span = extra.span();
let info = ctx_rune.info(span);
(AST::Rune(value, info), span)
});
choice((omen, arcana, aether, rune)).boxed()
}
fn identifier_node<'src>(ctx: ParserContext) -> BoxedParser<'src, SpannedAst> {
let ctx_for_map = ctx.clone();
select! { Token::Identifier(name) => name, Token::Core => "core".to_string(), Token::Type(ty) => type_keyword_name(&ty) }
.map_with(move |name, extra| {
let span = extra.span();
let info = ctx_for_map.info(span);
(AST::Var(name, info), span)
})
.boxed()
}
fn engrave_param_parser<'src>(ctx: ParserContext) -> BoxedParser<'src, AST> {
let ctx_for_map = ctx.clone();
select! { Token::Identifier(name) => name }
.map_with(|name, extra| (name, extra.span()))
.then_ignore(just(Token::Colon))
.then(type_parser())
.map(move |((name, name_span), (ty, ty_span))| {
let span = merge_span(name_span, ty_span);
let info = ctx_for_map.info(span);
AST::EngraveParam {
name,
param_type: ty,
is_morph: false,
line_info: info,
}
})
.boxed()
}
fn type_parser<'src>() -> BoxedParser<'src, (Type, SimpleSpan<usize>)> {
choice((
select! { Token::Type(ty) => ty }.map_with(|ty, extra| (ty, extra.span())),
select! { Token::Identifier(name) => name }
.map_with(|name, extra| (Type::Artifact(name), extra.span())),
))
.boxed()
}
fn type_keyword_name(ty: &Type) -> String {
match ty {
Type::Arcana => "arcana",
Type::Aether => "aether",
Type::Rune => "rune",
Type::Omen => "omen",
Type::Abyss => "abyss",
Type::Scroll => "scroll",
Type::Lexicon => "lexicon",
Type::Materia => "materia",
Type::Glyph => "glyph",
Type::Artifact(name) => name,
}
.to_string()
}