use std::collections::HashMap;
use std::path::PathBuf;
use crate::phonetic::common::Position;
use crate::phonetic::regex::ast::{Regex, RegexFlags};
#[derive(Debug, Clone)]
pub struct LLreFile {
pub metadata: FileMetadata,
pub imports: Vec<ImportDirective>,
pub global_flags: LLreFlags,
pub pattern: Regex,
pub pattern_source: String,
pub pattern_position: Position,
pub source_file: Option<PathBuf>,
pub resolved_imports: Vec<ResolvedImport>,
pub symbol_table: SymbolTable,
}
impl LLreFile {
pub fn new(pattern: Regex, pattern_source: String, pattern_position: Position) -> Self {
Self {
metadata: FileMetadata::default(),
imports: Vec::new(),
global_flags: LLreFlags::default(),
pattern,
pattern_source,
pattern_position,
source_file: None,
resolved_imports: Vec::new(),
symbol_table: SymbolTable::default(),
}
}
pub fn with_source_file(mut self, path: impl Into<PathBuf>) -> Self {
self.source_file = Some(path.into());
self
}
pub fn effective_flags(&self) -> RegexFlags {
RegexFlags {
case_insensitive: self.global_flags.case_insensitive,
multiline: self.global_flags.multiline,
dotall: self.global_flags.dotall,
..Default::default()
}
}
pub fn is_multiline(&self) -> bool {
self.global_flags.multiline.unwrap_or(false)
}
pub fn is_dotall(&self) -> bool {
self.global_flags.dotall.unwrap_or(false)
}
}
#[derive(Debug, Clone, Default)]
pub struct FileMetadata {
pub name: Option<String>,
pub version: Option<String>,
pub author: Option<String>,
pub description: Option<String>,
}
impl FileMetadata {
pub fn with_name(name: impl Into<String>) -> Self {
Self {
name: Some(name.into()),
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub struct ImportDirective {
pub path: String,
pub alias: Option<String>,
pub position: Position,
}
impl ImportDirective {
pub fn new(path: impl Into<String>, position: Position) -> Self {
Self {
path: path.into(),
alias: None,
position,
}
}
pub fn with_alias(
path: impl Into<String>,
alias: impl Into<String>,
position: Position,
) -> Self {
Self {
path: path.into(),
alias: Some(alias.into()),
position,
}
}
}
#[derive(Debug, Clone)]
pub struct ResolvedImport {
pub directive: ImportDirective,
pub resolved_path: PathBuf,
pub symbols: Vec<String>,
pub rules: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct LLreFlags {
pub multiline: Option<bool>,
pub dotall: Option<bool>,
pub case_insensitive: Option<bool>,
pub unicode: Option<bool>,
}
impl LLreFlags {
pub fn multiline() -> Self {
Self {
multiline: Some(true),
..Default::default()
}
}
pub fn dotall() -> Self {
Self {
dotall: Some(true),
..Default::default()
}
}
pub fn merge(&self, other: &Self) -> Self {
Self {
multiline: other.multiline.or(self.multiline),
dotall: other.dotall.or(self.dotall),
case_insensitive: other.case_insensitive.or(self.case_insensitive),
unicode: other.unicode.or(self.unicode),
}
}
pub fn is_empty(&self) -> bool {
self.multiline.is_none()
&& self.dotall.is_none()
&& self.case_insensitive.is_none()
&& self.unicode.is_none()
}
}
#[derive(Debug, Clone, Default)]
pub struct SymbolTable {
pub char_classes: HashMap<String, Vec<char>>,
pub patterns: HashMap<String, Regex>,
pub symbol_sources: HashMap<String, PathBuf>,
}
impl SymbolTable {
pub fn new() -> Self {
Self::default()
}
pub fn add_char_class(
&mut self,
name: impl Into<String>,
chars: Vec<char>,
source: Option<PathBuf>,
) {
let name = name.into();
if let Some(src) = source {
self.symbol_sources.insert(name.clone(), src);
}
self.char_classes.insert(name, chars);
}
pub fn add_pattern(
&mut self,
name: impl Into<String>,
pattern: Regex,
source: Option<PathBuf>,
) {
let name = name.into();
if let Some(src) = source {
self.symbol_sources.insert(name.clone(), src);
}
self.patterns.insert(name, pattern);
}
pub fn get_char_class(&self, name: &str) -> Option<&Vec<char>> {
self.char_classes.get(name)
}
pub fn get_pattern(&self, name: &str) -> Option<&Regex> {
self.patterns.get(name)
}
pub fn contains(&self, name: &str) -> bool {
self.char_classes.contains_key(name) || self.patterns.contains_key(name)
}
pub fn symbol_names(&self) -> Vec<String> {
let mut names: Vec<_> = self.char_classes.keys().cloned().collect();
names.extend(self.patterns.keys().cloned());
names.sort();
names.dedup();
names
}
pub fn merge(&mut self, other: &Self) {
for (name, chars) in &other.char_classes {
self.char_classes.insert(name.clone(), chars.clone());
}
for (name, pattern) in &other.patterns {
self.patterns.insert(name.clone(), pattern.clone());
}
for (name, source) in &other.symbol_sources {
self.symbol_sources.insert(name.clone(), source.clone());
}
}
pub fn get_source(&self, name: &str) -> Option<&PathBuf> {
self.symbol_sources.get(name)
}
}
#[derive(Debug, Clone)]
pub enum Directive {
Name(String, Position),
Version(String, Position),
Author(String, Position),
Description(String, Position),
Import(ImportDirective),
Flags(LLreFlags, Position),
}
impl Directive {
pub fn position(&self) -> Position {
match self {
Directive::Name(_, pos) => *pos,
Directive::Version(_, pos) => *pos,
Directive::Author(_, pos) => *pos,
Directive::Description(_, pos) => *pos,
Directive::Import(import) => import.position,
Directive::Flags(_, pos) => *pos,
}
}
pub fn name(&self) -> &'static str {
match self {
Directive::Name(..) => "name",
Directive::Version(..) => "version",
Directive::Author(..) => "author",
Directive::Description(..) => "description",
Directive::Import(..) => "import",
Directive::Flags(..) => "flags",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_metadata() {
let meta = FileMetadata::with_name("Test Pattern");
assert_eq!(meta.name, Some("Test Pattern".to_string()));
assert!(meta.version.is_none());
}
#[test]
fn test_import_directive() {
let import = ImportDirective::new("symbols.llev", Position::new(1, 1, 0));
assert_eq!(import.path, "symbols.llev");
assert!(import.alias.is_none());
let aliased = ImportDirective::with_alias("english.llev", "en", Position::new(2, 1, 20));
assert_eq!(aliased.path, "english.llev");
assert_eq!(aliased.alias, Some("en".to_string()));
}
#[test]
fn test_llre_flags() {
let flags = LLreFlags::multiline();
assert_eq!(flags.multiline, Some(true));
assert!(flags.dotall.is_none());
let dotall = LLreFlags::dotall();
let merged = flags.merge(&dotall);
assert_eq!(merged.multiline, Some(true));
assert_eq!(merged.dotall, Some(true));
}
#[test]
fn test_symbol_table() {
let mut table = SymbolTable::new();
table.add_char_class("VOWEL", vec!['a', 'e', 'i', 'o', 'u'], None);
assert!(table.contains("VOWEL"));
assert!(!table.contains("CONSONANT"));
let vowels = table
.get_char_class("VOWEL")
.expect("expected Some VOWEL class in test");
assert_eq!(vowels.len(), 5);
assert!(vowels.contains(&'a'));
}
#[test]
fn test_symbol_table_merge() {
let mut table1 = SymbolTable::new();
table1.add_char_class("A", vec!['a'], None);
let mut table2 = SymbolTable::new();
table2.add_char_class("B", vec!['b'], None);
table2.add_char_class("A", vec!['x'], None);
table1.merge(&table2);
assert!(table1.contains("A"));
assert!(table1.contains("B"));
assert_eq!(
table1
.get_char_class("A")
.expect("expected Some A class in test"),
&vec!['x']
);
}
}