chronlang_engine/
project.rs

1use std::collections::HashMap;
2
3use chronlang_parser::ast;
4
5#[derive(Clone, Debug, PartialEq)]
6pub struct Location {
7    pub source_name: String,
8    pub span: ast::Span,
9}
10
11#[derive(Clone, Debug, PartialEq)]
12pub struct Symbol {
13    pub name: String,
14    pub loc: Location,
15    pub value: Entity,
16    pub dependencies: Vec<String>,
17}
18
19#[derive(Clone, Debug, PartialEq)]
20pub enum Entity {
21    Language {
22        id: ast::Spanned<String>,
23        name: Option<ast::Spanned<String>>,
24        parent: Option<ast::Spanned<String>>,
25    },
26    Word {
27        gloss: ast::Spanned<String>,
28        pronunciation: ast::Spanned<Vec<String>>,
29        definitions: Vec<ast::Definition>,
30        tag: Tag,
31    },
32    Class {
33        label: ast::Spanned<String>,
34        encodes: Vec<ast::Spanned<String>>,
35        annotates: Vec<ast::Spanned<String>>,
36        phonemes: Vec<String>,
37    },
38    Phoneme {
39        class: ast::Spanned<String>,
40        label: ast::Spanned<String>,
41        traits: Vec<ast::Spanned<String>>,
42    },
43    Series {
44        label: ast::Spanned<String>,
45        series: ast::Spanned<ast::Series>,
46    },
47    Trait {
48        label: ast::Spanned<String>,
49        members: Vec<ast::TraitMember>,
50    },
51    TraitMember {
52        label: ast::Spanned<String>,
53        aliases: Vec<ast::Spanned<String>>,
54        default: bool,
55        notation: Option<ast::Spanned<String>>,
56    }
57}
58
59#[derive(Debug, PartialEq)]
60pub enum ImportError {
61    NoSuchSymbol(String),
62    FailedDependency(String),
63    NameCollision(Symbol),
64}
65
66#[derive(Debug, PartialEq)]
67pub struct Project {
68    pub milestones: Vec<i64>,
69    pub symbols: HashMap<String, Symbol>,
70    pub sound_changes: Vec<SoundChange>,
71}
72
73impl Project {
74    pub fn new() -> Self {
75        Self {
76            symbols: HashMap::new(),
77            milestones: Vec::new(),
78            sound_changes: Vec::new(),
79        }
80    }
81
82    pub fn add_symbol(&mut self, symbol: Symbol) -> Result<(), Symbol> {
83        let id = symbol.name.clone();
84        match self.symbols.get(&id) {
85            Some(clash) => Err(clash.clone()),
86            _ => {
87                self.symbols.insert(id, symbol);
88                Ok(())
89            }
90        }
91    }
92
93    pub fn add_all_symbols(&mut self, symbols: impl Iterator<Item = Symbol>) -> Result<(), Vec<Symbol>> {
94        let clashes = symbols
95            .flat_map(|sym| match self.add_symbol(sym) {
96                Ok(_) => vec![],
97                Err(symbol) => vec![symbol]
98            })
99            .collect::<Vec<_>>();
100
101        if clashes.is_empty() {
102            Ok(())
103        } else {
104            Err(clashes)
105        }
106    }
107
108    pub fn import(&mut self, names: &[&str], other: &Self) -> Result<(), Vec<ImportError>> {
109        let mut imports = HashMap::new();
110        let mut errors = Vec::new();
111        let mut deps = Vec::new();
112
113        names.iter().for_each(|name| match other.symbols.get(&name.to_string()) {
114            Some(_) => deps.push(name.to_string()),
115            None => errors.push(ImportError::NoSuchSymbol(name.to_string())),
116        });
117
118        while deps.len() > 0 {
119            let current = deps.pop().unwrap();
120            if imports.contains_key(&current) { continue; }
121            match other.symbols.get(&current) {
122                Some(symbol) => {
123                    imports.insert(current.to_string(), symbol.clone());
124                    // println!("Adding deps {:?} to search", symbol.dependencies);
125                    deps.append(&mut symbol.dependencies.clone());
126                    // println!("Will import: {:?}", imports.keys().collect::<Vec<_>>());
127                    // println!("Left to search: {:?}", deps);
128                },
129                None => errors.push(ImportError::FailedDependency(current.to_string())),
130            }
131        }
132
133        // println!("\n\nImporting {:?}, depends on {:?}\n\n", name, imports.keys().collect::<Vec<_>>());
134
135        let maybe_clashes = self.add_all_symbols(imports.values().map(|s| s.clone()));
136        if let Err(clashes) = maybe_clashes {
137            errors.append(&mut clashes.iter().map(|symbol| ImportError::NameCollision(symbol.clone())).collect())
138        }
139
140        if errors.len() > 0 {
141            Err(errors)
142        } else {
143            Ok(())
144        }
145    }
146
147    pub fn import_all_from(&mut self, other: &Project) -> Result<(), Vec<ImportError>> {
148        self.import(other.symbols.keys().map(|k| k.as_str()).collect::<Vec<_>>().as_slice(), other)
149    }
150}
151
152#[derive(Clone, Debug, PartialEq)]
153pub struct Tag {
154    pub language: String,
155    pub lang_set_span: ast::Span,
156    pub time: ast::Time,
157    pub time_set_span: ast::Span,
158}
159
160impl Tag {
161    pub fn new(lang: &ast::Spanned<String>, time: &ast::Spanned<ast::Time>) -> Self {
162        Self {
163            language: lang.1.clone(),
164            lang_set_span: lang.0.clone(),
165            time: time.1.clone(),
166            time_set_span: time.0.clone(),
167        }
168    }
169}
170
171#[derive(Clone, Debug, PartialEq)]
172pub struct SoundChange {
173    pub source_name: String,
174    pub source: ast::Spanned<ast::Source>,
175    pub target: ast::Spanned<ast::Target>,
176    pub environment: Option<ast::Spanned<ast::Environment>>,
177    pub description: Option<ast::Spanned<String>>,
178    pub tag: Tag,
179}