use std::fs;
use clap::ValueEnum;
use rustemo::LOG;
use yansi::Paint;
use crate::table::TableType;
use crate::{Error, Result};
use std::path::{Path, PathBuf};
use crate::generator::generate_parser;
#[derive(Debug, Default, Clone, ValueEnum)]
pub enum ParserAlgo {
#[default]
LR,
GLR,
}
#[derive(Debug, Default, Clone, ValueEnum)]
pub enum LexerType {
#[default]
Default,
Custom,
}
#[derive(Debug, Default, Clone, ValueEnum)]
pub enum BuilderType {
#[default]
Default,
Generic,
Custom,
}
#[derive(Debug, Default, Clone, ValueEnum)]
pub enum GeneratorTableType {
Arrays,
#[default]
Functions,
}
#[derive(Debug, Clone)]
pub struct Settings {
pub(crate) out_dir_root: Option<PathBuf>,
pub(crate) out_dir_actions_root: Option<PathBuf>,
pub(crate) root_dir: Option<PathBuf>,
pub(crate) prefer_shifts: bool,
pub(crate) prefer_shifts_over_empty: bool,
pub(crate) table_type: TableType,
pub(crate) parser_algo: ParserAlgo,
pub(crate) print_table: bool,
pub(crate) exclude: Vec<String>,
pub(crate) actions: bool,
pub(crate) trace: bool,
pub(crate) lexer_type: LexerType,
pub(crate) builder_type: BuilderType,
pub(crate) builder_loc_info: bool,
pub(crate) generator_table_type: GeneratorTableType,
pub(crate) input_type: String,
pub(crate) lexical_disamb_most_specific: bool,
pub(crate) lexical_disamb_longest_match: bool,
pub(crate) lexical_disamb_grammar_order: bool,
pub(crate) partial_parse: bool,
pub(crate) skip_ws: bool,
pub(crate) force: bool,
force_explicit: bool,
pub(crate) dot: bool,
pub(crate) fancy_regex: bool,
}
impl Default for Settings {
fn default() -> Self {
let out_dir_root = std::env::var("OUT_DIR").map_or(None, |d| Some(PathBuf::from(d)));
let root_dir =
std::env::var("CARGO_MANIFEST_DIR").map_or(None, |d| Some(PathBuf::from(d)));
Self {
root_dir,
out_dir_root: out_dir_root.clone(),
out_dir_actions_root: out_dir_root,
prefer_shifts: false,
prefer_shifts_over_empty: true,
table_type: Default::default(),
parser_algo: Default::default(),
print_table: false,
actions: true,
trace: false,
lexer_type: Default::default(),
builder_type: Default::default(),
builder_loc_info: false,
generator_table_type: Default::default(),
input_type: "str".into(),
lexical_disamb_most_specific: true,
lexical_disamb_longest_match: true,
lexical_disamb_grammar_order: true,
partial_parse: false,
skip_ws: true,
force: true, force_explicit: false,
exclude: vec![],
dot: false,
fancy_regex: false,
}
}
}
impl Settings {
pub fn new() -> Self {
Settings::default()
}
pub fn root_dir(mut self, root_dir: PathBuf) -> Self {
self.root_dir = Some(root_dir);
self
}
pub fn out_dir_root(mut self, out_dir: PathBuf) -> Self {
self.out_dir_root = Some(out_dir);
self
}
pub fn out_dir_actions_root(mut self, out_dir: PathBuf) -> Self {
self.out_dir_actions_root = Some(out_dir);
self
}
pub fn in_source_tree(mut self) -> Self {
self.out_dir_root = None;
if matches!(self.builder_type, BuilderType::Default) {
self.actions_in_source_tree()
} else {
self
}
}
pub fn actions_in_source_tree(mut self) -> Self {
if !matches!(self.builder_type, BuilderType::Default) {
panic!("Settings 'actions_in_source_tree' is only available for the default builder type!");
}
self.out_dir_actions_root = None;
if !self.force_explicit {
self.force = false;
}
self
}
pub fn exclude(mut self, exclude: Vec<String>) -> Self {
self.exclude = exclude;
self
}
pub fn prefer_shifts(mut self, prefer: bool) -> Self {
self.prefer_shifts = prefer;
self
}
pub fn prefer_shifts_over_empty(mut self, prefer: bool) -> Self {
self.prefer_shifts_over_empty = prefer;
self
}
pub fn table_type(mut self, table_type: TableType) -> Self {
self.table_type = table_type;
self
}
pub fn parser_algo(mut self, parser_algo: ParserAlgo) -> Self {
match parser_algo {
ParserAlgo::LR => {}
ParserAlgo::GLR => {
self.table_type = TableType::LALR_RN;
self.prefer_shifts = false;
self.prefer_shifts_over_empty = false;
self.lexical_disamb_grammar_order = false;
}
}
self.parser_algo = parser_algo;
self
}
pub fn lexer_type(mut self, lexer_type: LexerType) -> Self {
self.lexer_type = lexer_type;
self
}
pub fn builder_type(mut self, builder_type: BuilderType) -> Self {
self.builder_type = builder_type;
self
}
pub fn builder_loc_info(mut self, builder_loc_info: bool) -> Self {
self.builder_loc_info = builder_loc_info;
self
}
pub fn generator_table_type(mut self, generator_table_type: GeneratorTableType) -> Self {
self.generator_table_type = generator_table_type;
self
}
pub fn input_type(mut self, input_type: String) -> Self {
self.input_type = input_type;
self
}
pub fn lexical_disamb_most_specific(mut self, most_specific: bool) -> Self {
self.lexical_disamb_most_specific = most_specific;
self
}
pub fn lexical_disamb_longest_match(mut self, longest_match: bool) -> Self {
self.lexical_disamb_longest_match = longest_match;
self
}
pub fn lexical_disamb_grammar_order(mut self, grammar_order: bool) -> Self {
if let ParserAlgo::LR = self.parser_algo {
if !grammar_order {
panic!("Can't disable grammar order strategy for LR.")
}
}
self.lexical_disamb_grammar_order = grammar_order;
self
}
pub fn fancy_regex(mut self, fancy_regex: bool) -> Self {
self.fancy_regex = fancy_regex;
self
}
pub fn print_table(mut self, print_table: bool) -> Self {
self.print_table = print_table;
self
}
pub fn partial_parse(mut self, partial_parse: bool) -> Self {
self.partial_parse = partial_parse;
self
}
pub fn skip_ws(mut self, skip_ws: bool) -> Self {
self.skip_ws = skip_ws;
self
}
pub fn actions(mut self, actions: bool) -> Self {
self.actions = actions;
self
}
pub fn trace(mut self, trace: bool) -> Self {
let trace = if !trace {
std::env::var("RUSTEMO_TRACE").is_ok()
} else {
std::env::set_var("RUSTEMO_TRACE", "1");
true
};
self.trace = trace;
self
}
pub fn force(mut self, force: bool) -> Self {
self.force = force;
self.force_explicit = true;
self
}
pub fn dot(mut self, dot: bool) -> Self {
self.dot = dot;
self
}
pub fn process_dir(&self) -> Result<()> {
if let Some(root_dir) = &self.root_dir {
if !root_dir.exists() {
return Err(Error::Error(format!(
"Directory/File {root_dir:?} doesn't exist."
)));
}
let visitor = |grammar: &Path| -> Result<()> {
self.process_grammar(grammar)?;
Ok(())
};
self.visit_dirs(root_dir, &visitor)
} else {
Err(Error::Error("Root dir must be set!".to_string()))
}
}
pub fn process_grammar(&self, grammar_path: &Path) -> Result<()> {
println!(
"{} {:?}",
"Generating parser for grammar".paint(LOG),
grammar_path.paint(LOG)
);
let relative_outdir = |p: &Path| -> Result<PathBuf> {
Ok(p.join(
grammar_path
.parent()
.ok_or(Error::Error(format!(
"Cannot find parent of '{grammar_path:?}' file."
)))?
.strip_prefix(self.root_dir.as_ref().expect("'root_dir' must be set!"))
.unwrap_or(grammar_path),
))
};
let out_dir = self
.out_dir_root
.as_ref()
.map(|p| relative_outdir(p))
.transpose()?;
let out_dir_actions = self
.out_dir_actions_root
.as_ref()
.map(|p| relative_outdir(p))
.transpose()?;
if let Some(ref dir) = out_dir {
println!("Parser out dir: {dir:?}");
}
if let Some(ref dir) = out_dir_actions {
println!("Actions out dir: {dir:?}");
}
generate_parser(
grammar_path,
out_dir.as_deref(),
out_dir_actions.as_deref(),
self,
)
}
fn visit_dirs(&self, dir: &Path, visitor: &dyn Fn(&Path) -> Result<()>) -> Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let path_name = path.to_string_lossy();
if self.exclude.iter().any(|e| path_name.contains(e)) {
println!("Excluding path: {path_name:?}");
continue;
}
if path.is_dir() {
self.visit_dirs(&path, visitor)?;
} else if matches!(path.extension(), Some(ext) if ext == "rustemo") {
visitor(&path)?
}
}
}
Ok(())
}
}
pub fn process_dir<P: AsRef<Path>>(dir: P) -> Result<()> {
Settings::new()
.root_dir(PathBuf::from(dir.as_ref()))
.process_dir()?;
Ok(())
}
pub fn process_crate_dir() -> Result<()> {
Settings::new().process_dir()?;
Ok(())
}
pub fn process_grammar<P: AsRef<Path>>(grammar: P) -> Result<()> {
Settings::new().process_grammar(grammar.as_ref())?;
Ok(())
}