use std::num::NonZeroU32;
use std::sync::Mutex;
use bumpalo::Bump;
use crate::ast::{AtomicKind, ParserAst, SkipPolicy, TemplatePart, WsPolicy};
#[derive(Debug)]
pub enum PlanNode {
Atomic { kind: AtomicKind },
Lines { child: u32 },
Sections { child: u32 },
SectionsNamed {
fields: &'static [SectionItemNode],
repeated_tail: Option<(&'static str, u32)>,
field_order: &'static [&'static str],
},
Block {
items: &'static [BlockItemNode],
field_order: &'static [&'static str],
},
Choice {
cases: &'static [(&'static str, u32)],
},
Optional { child: u32 },
Scan { child: u32 },
OneOf { chars_index: u32 },
Characters { child: u32, skip: SkipPolicy },
Matrix { child: u32 },
GridRagged { child: u32, fill_index: u32 },
Csv { child: u32 },
Ws { child: u32 },
Sep { separator_index: u32, child: u32 },
Grid { child: u32 },
Template {
parts: &'static [TemplatePartNode],
field_order: &'static [&'static str],
},
}
#[derive(Debug)]
pub enum TemplatePartNode {
Literal { text: &'static str, ws: WsPolicy },
Capture {
child: u32,
field_index: Option<u16>,
name: Option<&'static str>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TemplateShape {
Unit,
Scalar { child: u32 },
Record,
Tuple,
}
impl TemplateShape {
pub fn of(parts: &[TemplatePartNode]) -> TemplateShape {
let mut captures = 0usize;
let mut any_named = false;
let mut sole_anonymous: Option<u32> = None;
for part in parts {
if let TemplatePartNode::Capture { child, name, .. } = part {
captures += 1;
match name {
Some(_) => any_named = true,
None => sole_anonymous = Some(*child),
}
}
}
match (any_named, captures) {
(true, _) => TemplateShape::Record,
(false, 0) => TemplateShape::Unit,
(false, 1) => match sole_anonymous {
Some(child) => TemplateShape::Scalar { child },
None => TemplateShape::Unit,
},
(false, _) => TemplateShape::Tuple,
}
}
}
#[derive(Debug)]
pub enum SectionItemNode {
One { name: &'static str, child: u32 },
Counted {
name: &'static str,
child: u32,
count: u32,
},
}
impl SectionItemNode {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
SectionItemNode::One { name, .. } | SectionItemNode::Counted { name, .. } => name,
}
}
#[must_use]
pub fn sections_wanted(&self) -> usize {
match self {
SectionItemNode::One { .. } => 1,
SectionItemNode::Counted { count, .. } => *count as usize,
}
}
}
#[derive(Debug)]
pub enum BlockItemNode {
Positional { child: u32 },
Named { name: &'static str, child: u32 },
}
pub struct ParserPlan {
pub nodes: &'static [PlanNode],
pub template_parts: &'static [TemplatePartNode],
pub literals: &'static [&'static str],
pub root: u32,
}
impl std::fmt::Debug for ParserPlan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ParserPlan")
.field("nodes", &self.nodes)
.field("template_parts_len", &self.template_parts.len())
.field("literals", &self.literals)
.field("root", &self.root)
.finish()
}
}
struct PlanBuilder<'a> {
arena: &'a Bump,
nodes: Vec<PlanNode>,
template_parts: Vec<TemplatePartNode>,
literals: Vec<&'static str>,
order: &'a mut dyn FieldOrder,
}
impl<'a> PlanBuilder<'a> {
fn new(arena: &'a Bump, order: &'a mut dyn FieldOrder) -> Self {
PlanBuilder {
arena,
nodes: Vec::new(),
template_parts: Vec::new(),
literals: Vec::new(),
order,
}
}
fn canonical_order(&mut self, names: &[&str]) -> &'static [&'static str] {
let canonical = self.order.canonical(names);
let entries: Vec<&'static str> = canonical.iter().map(|n| self.alloc_str(n)).collect();
self.alloc_slice(entries)
}
fn push_node(&mut self, node: PlanNode) -> u32 {
let idx = self.nodes.len() as u32;
self.nodes.push(node);
idx
}
fn intern_literal(&mut self, s: &'static str) -> u32 {
let idx = self.literals.len() as u32;
self.literals.push(s);
idx
}
fn alloc_str(&self, s: &str) -> &'static str {
alloc_str(self.arena, s)
}
fn alloc_slice<T>(&self, v: Vec<T>) -> &'static [T] {
alloc_slice(self.arena, v)
}
fn finish(self, root: u32) -> &'static ParserPlan {
let PlanBuilder {
arena,
nodes,
template_parts,
literals,
order: _,
} = self;
let plan: &ParserPlan = arena.alloc(ParserPlan {
nodes: alloc_slice(arena, nodes),
template_parts: alloc_slice(arena, template_parts),
literals: alloc_slice(arena, literals),
root,
});
unsafe { &*(plan as *const ParserPlan) }
}
}
fn alloc_str(arena: &Bump, s: &str) -> &'static str {
let stored: &str = arena.alloc_str(s);
unsafe { &*(stored as *const str) }
}
fn alloc_slice<T>(arena: &Bump, v: Vec<T>) -> &'static [T] {
let stored: &[T] = arena.alloc_slice_fill_iter(v);
unsafe { &*(stored as *const [T]) }
}
pub struct CompiledPlan {
#[allow(dead_code)]
arena: Bump,
plan: *const ParserPlan,
}
impl CompiledPlan {
pub fn plan(&self) -> &ParserPlan {
unsafe { &*self.plan }
}
}
impl std::fmt::Debug for CompiledPlan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.plan().fmt(f)
}
}
unsafe impl Send for CompiledPlan {}
pub fn lower_to_plan(ast: &ParserAst, order: &mut dyn FieldOrder) -> CompiledPlan {
let arena = Bump::new();
let plan = {
let mut b = PlanBuilder::new(&arena, order);
let root = lower_node(&mut b, ast);
b.finish(root) as *const ParserPlan
};
CompiledPlan { arena, plan }
}
pub trait FieldOrder {
fn canonical(&mut self, names: &[&str]) -> Vec<String>;
}
pub struct SourceOrder;
impl FieldOrder for SourceOrder {
fn canonical(&mut self, names: &[&str]) -> Vec<String> {
names.iter().map(|n| (*n).to_string()).collect()
}
}
impl FieldOrder for praxis_typeck::TypeDb {
fn canonical(&mut self, names: &[&str]) -> Vec<String> {
self.canonical_field_order(names).to_vec()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct PlanId(NonZeroU32);
impl PlanId {
pub fn get(self) -> u32 {
self.0.get()
}
pub fn from_raw(raw: u32) -> Option<PlanId> {
NonZeroU32::new(raw).map(PlanId)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct TooManyPlans {
pub limit: usize,
}
impl std::fmt::Display for TooManyPlans {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"too many parser plans registered in one process (limit {})",
self.limit
)
}
}
impl std::error::Error for TooManyPlans {}
pub const MAX_PLANS: usize = 1 << 20;
const _: () = assert!(MAX_PLANS < u32::MAX as usize);
static PLAN_ARENA: Mutex<Vec<CompiledPlan>> = Mutex::new(Vec::new());
pub fn register_plan(plan: CompiledPlan) -> Result<PlanId, TooManyPlans> {
register_with_limit(plan, MAX_PLANS)
}
fn register_with_limit(plan: CompiledPlan, limit: usize) -> Result<PlanId, TooManyPlans> {
let mut arena = PLAN_ARENA
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if arena.len() >= limit {
return Err(TooManyPlans { limit });
}
arena.push(plan);
let raw = u32::try_from(arena.len()).map_err(|_| TooManyPlans { limit })?;
Ok(PlanId(
NonZeroU32::new(raw).expect("len is at least 1 after the push"),
))
}
pub fn get_plan(id: PlanId) -> Option<&'static ParserPlan> {
let arena = PLAN_ARENA
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let plan: *const ParserPlan = arena.get(id.get() as usize - 1)?.plan;
Some(unsafe { &*plan })
}
pub fn plan_count() -> usize {
PLAN_ARENA
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.len()
}
pub unsafe fn retire_all_plans() {
PLAN_ARENA
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clear();
}
fn lower_node(b: &mut PlanBuilder<'_>, ast: &ParserAst) -> u32 {
macro_rules! unary {
($node:ident, $child:expr_2021) => {{
let c = lower_node(b, $child);
b.push_node(PlanNode::$node { child: c })
}};
}
match ast {
ParserAst::Atomic { kind, .. } => b.push_node(PlanNode::Atomic { kind: *kind }),
ParserAst::Lines { child, .. } => unary!(Lines, child),
ParserAst::Sections { child, .. } => unary!(Sections, child),
ParserAst::SectionsNamed {
fields,
repeated_tail,
..
} => {
let field_entries: Vec<SectionItemNode> = fields
.iter()
.map(|item| {
let name = b.alloc_str(item.name());
let child = lower_node(b, item.parser());
match item {
crate::ast::SectionItem::One { .. } => SectionItemNode::One { name, child },
crate::ast::SectionItem::Counted { count, .. } => {
SectionItemNode::Counted {
name,
child,
count: count.get(),
}
}
}
})
.collect();
let tail_entry = repeated_tail.as_ref().map(|(name, p)| {
let n = b.alloc_str(name);
let c = lower_node(b, p);
(n, c)
});
let mut names: Vec<&str> = field_entries.iter().map(|f| f.name()).collect();
if let Some((tail_name, _)) = tail_entry {
names.push(tail_name);
}
let field_order = b.canonical_order(&names);
let field_slice = b.alloc_slice(field_entries);
b.push_node(PlanNode::SectionsNamed {
fields: field_slice,
repeated_tail: tail_entry,
field_order,
})
}
ParserAst::Csv { child, .. } => unary!(Csv, child),
ParserAst::Ws { child, .. } => unary!(Ws, child),
ParserAst::Sep {
separator, child, ..
} => {
let sep_static: &'static str = b.alloc_str(separator.as_str());
let sep_idx = b.intern_literal(sep_static);
let c = lower_node(b, child);
b.push_node(PlanNode::Sep {
separator_index: sep_idx,
child: c,
})
}
ParserAst::Grid { child, .. } => unary!(Grid, child),
ParserAst::Block { items, .. } => {
let mut names: Vec<&str> = Vec::new();
for item in items {
match item {
crate::ast::BlockItem::Positional(p) => names.extend(template_field_names(p)),
crate::ast::BlockItem::Named { name, .. } => names.push(name),
}
}
let field_order = b.canonical_order(&names);
let item_nodes: Vec<BlockItemNode> = items
.iter()
.map(|item| match item {
crate::ast::BlockItem::Positional(p) => BlockItemNode::Positional {
child: lower_node(b, p),
},
crate::ast::BlockItem::Named { name, parser } => BlockItemNode::Named {
name: b.alloc_str(name),
child: lower_node(b, parser),
},
})
.collect();
let items_slice = b.alloc_slice(item_nodes);
b.push_node(PlanNode::Block {
items: items_slice,
field_order,
})
}
ParserAst::Choice { cases, .. } => {
let case_entries: Vec<(&'static str, u32)> = cases
.iter()
.map(|(name, p)| {
let n = b.alloc_str(name);
let c = lower_node(b, p);
(n, c)
})
.collect();
let cases_slice = b.alloc_slice(case_entries);
b.push_node(PlanNode::Choice { cases: cases_slice })
}
ParserAst::Optional { child, .. } => unary!(Optional, child),
ParserAst::Scan { child, .. } => unary!(Scan, child),
ParserAst::OneOf { chars, .. } => {
let chars_static = b.alloc_str(chars);
let idx = b.intern_literal(chars_static);
b.push_node(PlanNode::OneOf { chars_index: idx })
}
ParserAst::Characters { child, skip, .. } => {
let c = lower_node(b, child);
b.push_node(PlanNode::Characters {
child: c,
skip: *skip,
})
}
ParserAst::Matrix { child, .. } => unary!(Matrix, child),
ParserAst::GridRagged { child, fill, .. } => {
let c = lower_node(b, child);
let fill_static = b.alloc_str(fill);
let fill_idx = b.intern_literal(fill_static);
b.push_node(PlanNode::GridRagged {
child: c,
fill_index: fill_idx,
})
}
ParserAst::Template { parts, .. } => lower_template(b, parts),
}
}
fn lower_template(b: &mut PlanBuilder<'_>, parts: &[TemplatePart]) -> u32 {
let captures: Vec<(usize, &TemplatePart)> = parts
.iter()
.enumerate()
.filter(|(_, p)| matches!(p, TemplatePart::Capture { .. }))
.collect();
let names = template_field_names_of(parts);
let field_order = if names.is_empty() {
&[][..]
} else {
b.canonical_order(&names)
};
let part_indices = lower_template_parts(b, parts, &captures);
b.push_node(PlanNode::Template {
parts: part_indices,
field_order,
})
}
fn template_field_names_of(parts: &[TemplatePart]) -> Vec<&str> {
let names: Vec<&str> = parts
.iter()
.filter_map(|p| match p {
TemplatePart::Capture { name, .. } => name.as_ref().map(|n| n.as_str()),
TemplatePart::Literal { .. } => None,
})
.collect();
names
}
fn template_field_names(ast: &ParserAst) -> Vec<&str> {
match ast {
ParserAst::Template { parts, .. } => template_field_names_of(parts),
_ => Vec::new(),
}
}
fn lower_template_parts(
b: &mut PlanBuilder<'_>,
parts: &[TemplatePart],
captures: &[(usize, &TemplatePart)],
) -> &'static [TemplatePartNode] {
let mut nodes = Vec::new();
for part in parts {
match part {
TemplatePart::Literal { text, ws, .. } => {
let text_static = b.alloc_str(text);
nodes.push(TemplatePartNode::Literal {
text: text_static,
ws: *ws,
});
}
TemplatePart::Capture { name, parser, .. } => {
let child = lower_node(b, parser);
let field_index = captures
.iter()
.position(|(_, p)| std::ptr::eq(*p, part))
.map(|i| i as u16);
let name_static = name.as_ref().map(|n| b.alloc_str(n.as_str()));
nodes.push(TemplatePartNode::Capture {
child,
field_index,
name: name_static,
});
}
}
}
b.alloc_slice(nodes)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{AtomicKind, Separator, TemplatePart, WsPolicy};
use praxis_source::Span;
#[test]
fn atomic_lower_to_plan() {
let ast = ParserAst::Atomic {
kind: AtomicKind::Int,
span: Span::at(0),
};
let compiled = lower_to_plan(&ast, &mut SourceOrder);
let plan = compiled.plan();
assert_eq!(plan.root, 0);
assert!(matches!(
plan.nodes[0],
PlanNode::Atomic {
kind: AtomicKind::Int
}
));
}
#[test]
fn lines_of_int_lower_to_plan() {
let ast = ParserAst::Lines {
child: Box::new(ParserAst::Atomic {
kind: AtomicKind::Int,
span: Span::at(0),
}),
span: Span::at(0),
};
let compiled = lower_to_plan(&ast, &mut SourceOrder);
let plan = compiled.plan();
assert!(matches!(
plan.nodes[0],
PlanNode::Atomic {
kind: AtomicKind::Int
}
));
assert!(matches!(
plan.nodes[plan.root as usize],
PlanNode::Lines { child: 0 }
));
}
#[test]
fn sep_lower_interns_separator() {
let ast = ParserAst::Sep {
separator: Separator::new(" -> ").expect("a non-empty separator"),
child: Box::new(ParserAst::Atomic {
kind: AtomicKind::Word,
span: Span::at(0),
}),
span: Span::at(0),
};
let compiled = lower_to_plan(&ast, &mut SourceOrder);
let plan = compiled.plan();
match plan.nodes[plan.root as usize] {
PlanNode::Sep {
separator_index, ..
} => {
assert_eq!(plan.literals[separator_index as usize], " -> ");
}
_ => panic!("expected Sep at root"),
}
}
#[test]
fn zero_is_not_a_plan_id() {
assert!(PlanId::from_raw(0).is_none());
assert_eq!(PlanId::from_raw(1).map(PlanId::get), Some(1));
}
#[test]
fn registered_plans_round_trip_through_their_raw_id() {
let first = register_plan(lower_to_plan(
&ParserAst::Atomic {
kind: AtomicKind::Int,
span: Span::at(0),
},
&mut SourceOrder,
))
.expect("the arena is far from full");
let second = register_plan(lower_to_plan(
&ParserAst::Atomic {
kind: AtomicKind::Word,
span: Span::at(0),
},
&mut SourceOrder,
))
.expect("the arena is far from full");
assert_ne!(first, second);
for (id, expected) in [(first, AtomicKind::Int), (second, AtomicKind::Word)] {
let raw = id.get();
assert!(raw > 0, "a plan id is never zero");
let recovered = PlanId::from_raw(raw).expect("a registered id is non-zero");
let plan = get_plan(recovered).expect("a registered plan resolves");
assert!(
matches!(plan.nodes[plan.root as usize], PlanNode::Atomic { kind } if kind == expected)
);
}
}
#[test]
fn registration_past_the_bound_is_refused() {
let atom = || ParserAst::Atomic {
kind: AtomicKind::Int,
span: Span::at(0),
};
let refused = register_with_limit(lower_to_plan(&atom(), &mut SourceOrder), 0)
.expect_err("a zero limit admits no plans at all");
assert_eq!(refused.limit, 0);
assert!(refused.to_string().contains("too many parser plans"));
let accepted = register_plan(lower_to_plan(&atom(), &mut SourceOrder))
.expect("the real arena has room");
assert!(get_plan(accepted).is_some());
}
#[test]
fn an_unregistered_id_resolves_to_nothing() {
let beyond = PlanId::from_raw(u32::MAX).expect("non-zero");
assert!(get_plan(beyond).is_none());
}
#[test]
fn a_compiled_plan_owns_its_interned_strings() {
let compiled = lower_to_plan(
&ParserAst::Sep {
separator: Separator::new(" -> ").expect("a non-empty separator"),
child: Box::new(ParserAst::Atomic {
kind: AtomicKind::Word,
span: Span::at(0),
}),
span: Span::at(0),
},
&mut SourceOrder,
);
assert_eq!(compiled.plan().literals, &[" -> "]);
}
#[test]
fn a_named_template_carries_the_canonical_field_order_and_not_its_own() {
struct WThenH;
impl FieldOrder for WThenH {
fn canonical(&mut self, _names: &[&str]) -> Vec<String> {
vec!["w".to_string(), "h".to_string()]
}
}
let named = |name: &str| TemplatePart::Capture {
name: Some(crate::ast::CaptureName::parse(name).expect("a legal name")),
parser: Box::new(ParserAst::Atomic {
kind: AtomicKind::Int,
span: Span::at(0),
}),
span: Span::at(0),
name_span: None,
};
let ast = ParserAst::Template {
parts: vec![
named("h"),
TemplatePart::Literal {
text: "x".to_string(),
ws: WsPolicy::SpaceRun,
span: Span::at(0),
},
named("w"),
],
span: Span::at(0),
};
let compiled = lower_to_plan(&ast, &mut WThenH);
let plan = compiled.plan();
let PlanNode::Template { parts, field_order } = &plan.nodes[plan.root as usize] else {
panic!("a named-capture template lowers to a Template node");
};
assert_eq!(TemplateShape::of(parts), TemplateShape::Record);
assert_eq!(*field_order, &["w", "h"]);
let compiled = lower_to_plan(&ast, &mut SourceOrder);
let PlanNode::Template { field_order, .. } =
&compiled.plan().nodes[compiled.plan().root as usize]
else {
panic!("a named-capture template lowers to a Template node");
};
assert_eq!(*field_order, &["h", "w"]);
}
#[test]
fn a_tuple_template_carries_no_field_order() {
let anonymous = || TemplatePart::Capture {
name: None,
parser: Box::new(ParserAst::Atomic {
kind: AtomicKind::Int,
span: Span::at(0),
}),
span: Span::at(0),
name_span: None,
};
let ast = ParserAst::Template {
parts: vec![
anonymous(),
TemplatePart::Literal {
text: ",".to_string(),
ws: WsPolicy::SpaceRun,
span: Span::at(0),
},
anonymous(),
],
span: Span::at(0),
};
let compiled = lower_to_plan(&ast, &mut SourceOrder);
let PlanNode::Template { field_order, .. } =
&compiled.plan().nodes[compiled.plan().root as usize]
else {
panic!("a template lowers to a Template node");
};
assert!(field_order.is_empty(), "a tuple has no fields to order");
}
#[test]
fn template_literal_lower_to_plan() {
let ast = ParserAst::Template {
parts: vec![
TemplatePart::Capture {
name: None,
parser: Box::new(ParserAst::Atomic {
kind: AtomicKind::Int,
span: Span::at(0),
}),
span: Span::at(0),
name_span: None,
},
TemplatePart::Literal {
text: ",".to_string(),
ws: WsPolicy::SpaceRun,
span: Span::at(0),
},
TemplatePart::Capture {
name: None,
parser: Box::new(ParserAst::Atomic {
kind: AtomicKind::Int,
span: Span::at(0),
}),
span: Span::at(0),
name_span: None,
},
],
span: Span::at(0),
};
let compiled = lower_to_plan(&ast, &mut SourceOrder);
let plan = compiled.plan();
let PlanNode::Template { parts, .. } = &plan.nodes[plan.root as usize] else {
panic!("a two-anonymous-capture template lowers to a Template node");
};
assert_eq!(TemplateShape::of(parts), TemplateShape::Tuple);
}
#[test]
fn a_counted_item_keeps_its_count_and_its_position_in_the_plan() {
use crate::ast::{RepeatCount, SectionItem};
let ast = ParserAst::SectionsNamed {
fields: vec![
SectionItem::Counted {
name: "shapes".to_string(),
count: RepeatCount::new(6).expect("six sections"),
parser: ParserAst::Lines {
child: Box::new(ParserAst::Atomic {
kind: AtomicKind::Int,
span: Span::at(0),
}),
span: Span::at(0),
},
},
SectionItem::One {
name: "regions".to_string(),
parser: ParserAst::Atomic {
kind: AtomicKind::Char,
span: Span::at(0),
},
},
],
repeated_tail: None,
span: Span::at(0),
};
let compiled = lower_to_plan(&ast, &mut SourceOrder);
let plan = compiled.plan();
let PlanNode::SectionsNamed {
fields,
repeated_tail,
..
} = &plan.nodes[plan.root as usize]
else {
panic!("a named `sections` lowers to a SectionsNamed node");
};
assert!(repeated_tail.is_none(), "a counted group is not the tail");
assert_eq!(fields.len(), 2);
match &fields[0] {
SectionItemNode::Counted { name, child, count } => {
assert_eq!(*name, "shapes");
assert_eq!(*count, 6);
assert!(matches!(
plan.nodes[*child as usize],
PlanNode::Lines { .. }
));
}
other => panic!("the first field is the counted group, got {other:?}"),
}
match &fields[1] {
SectionItemNode::One { name, .. } => assert_eq!(*name, "regions"),
other => panic!("the second field follows the counted group, got {other:?}"),
}
assert_eq!(fields[0].sections_wanted(), 6);
assert_eq!(fields[1].sections_wanted(), 1);
}
}