use crate::ast::{
BlockItem, Constructor, InvalidRepeatCount, ParserAst, RepeatCount, SectionItem, Separator,
SkipPolicy,
};
use crate::validate::{ArgKind, ValidationError, check_call};
use praxis_source::{DiagCode, Span};
#[derive(Clone, Debug)]
pub enum CallArg {
Parser(ParserAst),
String(String),
Int(i64),
Flag(String),
Named { name: String, parser: ParserAst },
Keyword { name: String, value: String },
RepeatedTail {
name: String,
parser: ParserAst,
count: Option<RepeatCount>,
},
}
impl CallArg {
#[must_use]
pub fn kind(&self) -> ArgKind {
match self {
CallArg::Parser(_) => ArgKind::Parser,
CallArg::String(_) => ArgKind::String,
CallArg::Int(_) => ArgKind::Int,
CallArg::Flag(f) => ArgKind::Flag(f.clone()),
CallArg::Named { name, .. } => ArgKind::Named(name.clone()),
CallArg::Keyword { name, .. } => ArgKind::Keyword(name.clone()),
CallArg::RepeatedTail { name, .. } => ArgKind::RepeatedTail(name.clone()),
}
}
}
pub fn build_call(
ctor: Constructor,
args: Vec<CallArg>,
span: Span,
) -> Result<ParserAst, Vec<ValidationError>> {
if ctor == Constructor::Repeated {
return Err(vec![ValidationError {
span,
code: DiagCode::MisplacedRepeatedTail,
message: "`repeated(...)` is only a named argument of a `sections` call".to_string(),
}]);
}
let kinds: Vec<ArgKind> = args.iter().map(CallArg::kind).collect();
let shape_errors = check_call(ctor, &kinds, span);
if !shape_errors.is_empty() {
return Err(shape_errors);
}
let internal = |what: &str| {
vec![ValidationError {
span,
code: DiagCode::InvalidConstructorArgument,
message: format!("`{}` {what}", ctor.keyword()),
}]
};
let unexpected = |arg: &CallArg| {
vec![ValidationError {
span,
code: DiagCode::InvalidConstructorArgument,
message: format!(
"`{}` does not take {}",
ctor.keyword(),
arg.kind().describe()
),
}]
};
match ctor {
Constructor::Lines
| Constructor::Csv
| Constructor::Ws
| Constructor::Matrix
| Constructor::Optional
| Constructor::Scan => {
let child = Box::new(sole_parser(args).ok_or_else(|| internal("needs one parser"))?);
Ok(match ctor {
Constructor::Lines => ParserAst::Lines { child, span },
Constructor::Csv => ParserAst::Csv { child, span },
Constructor::Ws => ParserAst::Ws { child, span },
Constructor::Matrix => ParserAst::Matrix { child, span },
Constructor::Optional => ParserAst::Optional { child, span },
_ => ParserAst::Scan { child, span },
})
}
Constructor::Sections => {
if kinds
.iter()
.any(|k| matches!(k, ArgKind::Named(_) | ArgKind::RepeatedTail(_)))
{
build_sections_named(args, span)
} else {
Ok(ParserAst::Sections {
child: Box::new(sole_parser(args).ok_or_else(|| internal("needs one parser"))?),
span,
})
}
}
Constructor::Sep => {
let mut separator = None;
let mut child = None;
for arg in args {
match arg {
CallArg::String(s) => separator = Some(s),
CallArg::Parser(p) => child = Some(p),
other => return Err(unexpected(&other)),
}
}
let separator = Separator::new(separator.as_deref().unwrap_or("")).map_err(|_| {
vec![ValidationError {
span,
code: DiagCode::EmptySeparator,
message: "`sep` needs a non-empty separator: an empty one never advances"
.to_string(),
}]
})?;
Ok(ParserAst::Sep {
separator,
child: Box::new(child.ok_or_else(|| internal("needs an element parser"))?),
span,
})
}
Constructor::OneOf => {
let mut chars = None;
for arg in args {
match arg {
CallArg::String(s) => chars = Some(s),
other => return Err(unexpected(&other)),
}
}
Ok(ParserAst::OneOf {
chars: chars.ok_or_else(|| internal("needs a character set"))?,
span,
})
}
Constructor::Chars => {
let mut child = None;
let mut skip = SkipPolicy::Whitespace;
for arg in args {
match arg {
CallArg::Parser(p) => child = Some(p),
CallArg::Keyword { name, value }
if Some(name.as_str()) == ctor.keyword_arg() =>
{
skip = SkipPolicy::from_keyword(&value).ok_or_else(|| {
vec![ValidationError {
span,
code: DiagCode::InvalidConstructorArgument,
message: format!(
"`skip: {value}` is not a skip policy — `none` (skips {}), \
`whitespace` (skips {}) or `newlines` (skips {})",
SkipPolicy::None.skips(),
SkipPolicy::Whitespace.skips(),
SkipPolicy::Newlines.skips(),
),
}]
})?;
}
other => return Err(unexpected(&other)),
}
}
Ok(ParserAst::Characters {
child: Box::new(child.ok_or_else(|| internal("needs a character parser"))?),
skip,
span,
})
}
Constructor::Grid => {
let mut child = None;
let mut fill = None;
for arg in args {
match arg {
CallArg::Parser(p) => child = Some(p),
CallArg::Keyword { name, value }
if Some(name.as_str()) == ctor.keyword_arg() =>
{
let decoded = praxis_syntax::literal::unquote_text(&value);
if decoded.is_empty() {
return Err(vec![ValidationError {
span,
code: DiagCode::InvalidConstructorArgument,
message: "`fill:` needs a value to pad a short row with — an \
empty one fills nothing"
.to_string(),
}]);
}
fill = Some(decoded);
}
CallArg::Flag(f) if Some(f.as_str()) == ctor.flag_arg() => {}
other => return Err(unexpected(&other)),
}
}
let child = Box::new(child.ok_or_else(|| internal("needs a cell parser"))?);
Ok(match fill {
Some(fill) => ParserAst::GridRagged { child, fill, span },
None => ParserAst::Grid { child, span },
})
}
Constructor::Block => {
let mut items = Vec::with_capacity(args.len());
for arg in args {
match arg {
CallArg::Parser(p) => items.push(BlockItem::Positional(p)),
CallArg::Named { name, parser } => {
items.push(BlockItem::Named { name, parser })
}
other => return Err(unexpected(&other)),
}
}
Ok(ParserAst::Block { items, span })
}
Constructor::Choice => {
let mut cases = Vec::with_capacity(args.len());
for arg in args {
match arg {
CallArg::Named { name, parser } => cases.push((name, parser)),
other => return Err(unexpected(&other)),
}
}
Ok(ParserAst::Choice { cases, span })
}
Constructor::Repeated => Err(internal("is not a parser")),
}
}
pub fn build_repeated_tail(
name: String,
args: Vec<CallArg>,
span: Span,
) -> Result<CallArg, Vec<ValidationError>> {
let kinds: Vec<ArgKind> = args.iter().map(CallArg::kind).collect();
let shape_errors = check_call(Constructor::Repeated, &kinds, span);
if !shape_errors.is_empty() {
return Err(shape_errors);
}
let bad = |message: String| {
vec![ValidationError {
span,
code: DiagCode::InvalidConstructorArgument,
message,
}]
};
let mut args = args.into_iter();
let parser = match args.next() {
Some(CallArg::Parser(parser)) => parser,
Some(other) => {
return Err(bad(format!(
"`repeated`'s first argument must be a parser, but it is {}",
other.kind().describe()
)));
}
None => return Err(bad("`repeated` needs a parser".to_string())),
};
let count = match args.next() {
None => None,
Some(CallArg::Int(n)) => Some(RepeatCount::new(n).map_err(|why| {
bad(match why {
InvalidRepeatCount::NotPositive => "`repeated`'s count must be at least 1 — a \
group of no sections parses nothing"
.to_string(),
InvalidRepeatCount::TooLarge => {
"`repeated`'s count must fit in 32 bits".to_string()
}
})
})?),
Some(other) => {
return Err(bad(format!(
"`repeated`'s count must be a whole-number literal, but it is {} — the parser \
plan is built when the program is compiled, so the count cannot be a parser or \
a variable",
other.kind().describe()
)));
}
};
Ok(CallArg::RepeatedTail {
name,
parser,
count,
})
}
fn sole_parser(args: Vec<CallArg>) -> Option<ParserAst> {
match args.into_iter().next() {
Some(CallArg::Parser(p)) => Some(p),
_ => None,
}
}
fn build_sections_named(args: Vec<CallArg>, span: Span) -> Result<ParserAst, Vec<ValidationError>> {
let unbounded: Vec<usize> = args
.iter()
.enumerate()
.filter(|(_, a)| matches!(a, CallArg::RepeatedTail { count: None, .. }))
.map(|(i, _)| i)
.collect();
if unbounded.len() > 1 {
return Err(vec![ValidationError {
span,
code: DiagCode::MisplacedRepeatedTail,
message: "`sections` takes at most one unbounded `repeated(...)` tail".to_string(),
}]);
}
if let Some(&at) = unbounded.first()
&& at != args.len() - 1
{
return Err(vec![ValidationError {
span,
code: DiagCode::MisplacedRepeatedTail,
message: "an unbounded `repeated(...)` tail may appear only as the final named \
argument: it consumes every remaining section, so nothing can \
follow it — write `repeated(P, N)` for a group of N sections, which can"
.to_string(),
}]);
}
let mut fields: Vec<SectionItem> = Vec::new();
let mut repeated_tail: Option<(String, Box<ParserAst>)> = None;
for arg in args {
match arg {
CallArg::Named { name, parser } => fields.push(SectionItem::One { name, parser }),
CallArg::RepeatedTail {
name,
parser,
count: Some(count),
} => fields.push(SectionItem::Counted {
name,
count,
parser,
}),
CallArg::RepeatedTail {
name,
parser,
count: None,
} => {
repeated_tail = Some((name, Box::new(parser)));
}
other => {
return Err(vec![ValidationError {
span,
code: DiagCode::InvalidConstructorArgument,
message: format!("`sections` does not take {}", other.kind().describe()),
}]);
}
}
}
Ok(ParserAst::SectionsNamed {
fields,
repeated_tail,
span,
})
}