#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![no_std]
#![forbid(unsafe_code)]
extern crate alloc;
mod analyze;
mod chars;
pub mod code;
pub mod cst;
mod error;
mod lower;
mod norm;
mod parser;
mod serialize;
mod validate;
use alloc::borrow::Cow;
use alloc::vec::Vec;
use mf2_model::{Diagnostic, Diagnostics, Frontend, Message, Parsed, Pattern, PatternMessage};
pub use analyze::{Analysis, Name, analyze};
pub use cst::{Cst, CstRef, Node, SyntaxKind, SyntaxNode};
pub use error::{Error, NameRole};
pub use serialize::serialize;
pub use validate::validate;
pub fn parse_cst(source: &str) -> Cst<'_> {
let mut nodes = Vec::new();
let mut diagnostics = Vec::new();
parser::parse_into(source, &mut nodes, &mut diagnostics, true);
Cst {
src: source,
nodes,
diagnostics,
}
}
pub fn parse_model(source: &str) -> Parsed<'_> {
if let Some(parsed) = simple_text(source) {
return parsed;
}
Parser::new().parse_model(source)
}
#[derive(Clone, Debug, Default)]
pub struct Parser {
nodes: Vec<Node>,
diagnostics: Vec<Diagnostic>,
}
impl Parser {
pub const fn new() -> Self {
Parser {
nodes: Vec::new(),
diagnostics: Vec::new(),
}
}
pub fn with_capacity(nodes: usize) -> Self {
Parser {
nodes: Vec::with_capacity(nodes),
diagnostics: Vec::new(),
}
}
pub fn parse_cst<'p, 'src>(&'p mut self, source: &'src str) -> CstRef<'p, 'src> {
parser::parse_into(source, &mut self.nodes, &mut self.diagnostics, true);
CstRef {
src: source,
nodes: &self.nodes,
diagnostics: &self.diagnostics,
}
}
pub fn parse_model<'src>(&mut self, source: &'src str) -> Parsed<'src> {
if let Some(parsed) = simple_text(source) {
return parsed;
}
parser::parse_into(source, &mut self.nodes, &mut self.diagnostics, false);
if !self.diagnostics.is_empty() || self.nodes.is_empty() {
return Parsed {
message: None,
diagnostics: Diagnostics::from(core::mem::take(&mut self.diagnostics)),
};
}
model_from_arena(source, &self.nodes)
}
}
impl Frontend for Parser {
fn parse<'src>(&mut self, source: &'src str) -> Parsed<'src> {
self.parse_model(source)
}
}
fn simple_text(source: &str) -> Option<Parsed<'_>> {
let b = source.as_bytes();
if chars::find_text_end(b, 0) != b.len() {
return None;
}
let mut i = 0;
while let Some((n, _)) = chars::trivia_at(b, i) {
i += n;
}
if b.get(i) == Some(&b'.') {
return None;
}
Some(Parsed {
message: Some(Message::Pattern(PatternMessage {
declarations: Vec::new(),
pattern: Pattern::from_text(Cow::Borrowed(source)),
})),
diagnostics: Diagnostics::new(),
})
}
pub(crate) fn model_from_arena<'src>(source: &'src str, nodes: &[Node]) -> Parsed<'src> {
let arena = lower::Arena { src: source, nodes };
let message = arena.message();
let mut found = Vec::new();
validate::check(&message, &mut |kind, code, loc| {
found.push((kind, code, loc));
});
if found.is_empty() {
return Parsed {
message: Some(message),
diagnostics: Diagnostics::new(),
};
}
let mut locator = lower::Locator::new(&arena);
let mut diagnostics: Vec<Diagnostic> = found
.iter()
.map(|(kind, code, loc)| Diagnostic::new(*kind, *code, locator.resolve(loc)))
.collect();
diagnostics.sort_by_key(|d| d.span.map_or(0, |s| s.start));
Parsed {
message: Some(message),
diagnostics: Diagnostics::from(diagnostics),
}
}