pub mod ast;
mod engine;
mod common;
pub mod error;
pub mod grammar;
pub mod lexer;
pub mod load_grammar;
mod macros;
pub mod parser;
pub mod parser_frontends;
pub mod preclude;
mod transform;
use crate::ast::Ast;
pub use crate::engine::ParserEngine;
use crate::grammar::Algorithm;
use crate::load_grammar::load_grammar;
use crate::parser_frontends::GrammarRuntime;
use error::SwiftletError;
use std::fmt::{Display, Formatter};
use std::sync::Arc;
#[derive(Clone, Debug)]
pub enum Ambiguity {
Resolve,
Explicit,
}
impl Display for Ambiguity {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Ambiguity::Resolve => write!(f, "resolve"),
Ambiguity::Explicit => write!(f, "explicit"),
}
}
}
#[derive(Debug, Clone)]
pub struct ParserConfig {
pub start: String,
pub algorithm: Algorithm,
pub ambiguity: Ambiguity,
pub debug: bool,
}
impl Default for ParserConfig {
fn default() -> Self {
Self {
start: "start".to_string(),
algorithm: Algorithm::Earley,
ambiguity: Ambiguity::Resolve,
debug: false,
}
}
}
impl Display for ParserConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"ParserConfig {{ start: {}, algorithm: {}, ambiguity: {}, debug: {} }}",
self.start, self.algorithm, self.ambiguity, self.debug
)
}
}
fn normalize_grammar(grammar: &str) -> String {
format!(
r#"{}
"#,
grammar.trim()
)
}
pub struct Swiftlet {
frontend: Arc<GrammarRuntime>,
}
impl Swiftlet {
pub fn from_str(grammar: &str) -> Result<Self, SwiftletError> {
let grammar = normalize_grammar(grammar);
let frontend = load_grammar(&grammar)?;
Ok(Self { frontend })
}
pub fn from_file(path: &str) -> Result<Self, SwiftletError> {
let content = std::fs::read_to_string(path).map_err(|source| {
SwiftletError::GrammarFileReadError {
path: path.to_string(),
source,
}
})?;
Self::from_str(&content)
}
pub fn parser(&self, config: impl Into<Arc<ParserConfig>>) -> Parser {
let config = config.into();
#[cfg(feature = "debug")]
if config.debug {
println!("\nBNF Grammar");
println!("===========");
let rules = self.frontend.get_parser().rules.clone();
for (_, v) in &rules {
for rule in v {
println!("{}", rule);
}
}
}
Parser {
parser_engine: ParserEngine::new(self.frontend.clone(), config),
}
}
}
pub struct Parser {
parser_engine: ParserEngine,
}
impl Parser {
pub fn parse(&self, text: &str) -> Result<Ast, SwiftletError> {
self.parser_engine.parse(text)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ambiguity_display_works_for_both_variants() {
assert_eq!(format!("{}", Ambiguity::Resolve), "resolve");
assert_eq!(format!("{}", Ambiguity::Explicit), "explicit");
}
#[test]
fn parser_config_display_contains_all_fields() {
let config = ParserConfig::default();
let s = format!("{}", config);
assert!(s.contains("start"));
assert!(s.contains("Earley"));
assert!(s.contains("resolve"));
assert!(s.contains("false"));
}
#[test]
fn parser_config_debug_is_derivable() {
let config = ParserConfig::default();
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("ParserConfig"));
}
#[test]
fn from_file_returns_error_for_missing_path() {
let path = std::env::temp_dir()
.join("swiftlet_nonexistent_test_grammar.lark")
.to_string_lossy()
.into_owned();
let err = match Swiftlet::from_file(&path) {
Ok(_) => panic!("expected error for missing file"),
Err(e) => e,
};
assert!(matches!(
err,
error::SwiftletError::GrammarFileReadError { .. }
));
}
#[test]
fn from_file_loads_valid_grammar_file() {
use std::io::Write;
let grammar = "start: WORD\nWORD: /\\w+/\n";
let path = std::env::temp_dir().join("swiftlet_test_grammar_valid.lark");
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(grammar.as_bytes()).unwrap();
let swiftlet = Swiftlet::from_file(path.to_str().unwrap())
.expect("valid grammar file should load");
let result = swiftlet
.parser(ParserConfig::default())
.parse("hello");
assert!(result.is_ok());
std::fs::remove_file(path).ok();
}
}