pink_runtime/parser/
mod.rs

1mod resolvers;
2mod standalone;
3#[cfg(test)]
4mod test;
5
6pub use standalone::expression;
7
8use std::{collections::BTreeMap, error::Error, fmt::Display, io, path::PathBuf};
9
10// use regex::Regex;
11use regex_macro::regex;
12
13use crate::engine::{Runtime, Structure, StructureError};
14
15use self::standalone::{definition, domain, get_domain, get_reserved, parse_use, reserve};
16
17/// TODO: Maybe make a type for inputs with comments and without. To further ensure safety.
18/// Then we can add a trait and make it super generic! But that might be unecessary.
19/// It would be better to just rewrite the parser without nom in a nicer way tailored to the project.
20fn strip_comments(input: String) -> String {
21    input.replace(regex!("#.*"), "")
22}
23
24pub fn parse(name: &str, resolver: impl Fn(&str) -> Option<String>) -> Result<Runtime, ParseError> {
25    let mut partial_runtime =
26        BTreeMap::from([("intrinsic".to_string(), Some(Structure::intrinsic()))]);
27
28    let input = resolver(name).unwrap();
29
30    parse_into_runtime(&input, name, &resolver, &mut partial_runtime)?;
31
32    let runtime = partial_runtime
33        .into_iter()
34        .filter_map(|(name, structure)| structure.map(|structure| (name, structure)))
35        .collect();
36
37    Ok(Runtime::new(runtime))
38}
39
40// The `Option` is `None` if the file has not been parsed yet. This is used to prevent circular dependencies.
41type PartialRuntime = BTreeMap<String, Option<Structure>>;
42
43fn parse_into_runtime(
44    input: &str,
45    name: &str,
46    resolver: &impl Fn(&str) -> Option<String>,
47    runtime: &mut PartialRuntime,
48) -> Result<(), ParseError> {
49    let input = strip_comments(input.to_string());
50
51    let (input, domain) = domain(&input)?;
52    let (input, reserved) = reserve(input)?;
53    let (input, dependencies) = parse_use(input)?;
54
55    for dependency in dependencies {
56        match runtime.get(&dependency) {
57            // Already parsed
58            Some(Some(_)) => continue,
59
60            // Circular dependency
61            Some(None) => {
62                return Err(ParseError::CircularDependency {
63                    cycle: vec![dependency.clone(), name.to_string()],
64                })
65            }
66
67            // Not parsed yet
68            None => (),
69        }
70
71        runtime.insert(dependency.clone(), None);
72
73        let dependecy_program = resolver(&dependency)
74            .expect("Hande failures of resolver (basically not finding files)");
75
76        match parse_into_runtime(&dependecy_program, &dependency, resolver, runtime) {
77            Ok(()) => (),
78
79            // Bubble up circular dependency error
80            Err(ParseError::CircularDependency { mut cycle }) => {
81                cycle.push(dependency);
82                return Err(ParseError::CircularDependency { cycle });
83            }
84
85            Err(err) => return Err(err),
86        };
87    }
88
89    let full_domain = domain.iter().chain(get_domain(runtime)).collect();
90    let full_reserved = reserved.iter().chain(get_reserved(runtime)).collect();
91
92    let mut definitions = Vec::new();
93    let mut input = input;
94    while let Ok((rest, mut parsed_definitions)) = definition(input, &full_domain, &full_reserved) {
95        input = rest;
96        definitions.append(&mut parsed_definitions);
97    }
98
99    let structure = match Structure::create(domain, reserved, definitions) {
100        Ok(structure) => structure,
101        Err(StructureError::DomainAndReservedOverlap { culprit }) => {
102            return Err(ParseError::DomainAndReservedOverlap { culprit })
103        }
104    };
105
106    runtime.insert(name.to_string(), Some(structure));
107
108    Ok(())
109}
110
111pub fn parse_file(path: PathBuf) -> Result<Runtime, ParseError> {
112    let (root, name) = resolvers::get_root_and_name(path).unwrap();
113    let resolver = resolvers::file_resolver(root);
114
115    parse(name.as_str(), resolver)
116}
117
118#[derive(Debug)]
119pub enum ParseError {
120    Expected { expected: String, found: String },
121    DomainAndReservedOverlap { culprit: String },
122    CircularDependency { cycle: Vec<String> },
123    UknownToken(String),
124
125    // For file handling shenaningans
126    Io(io::Error),
127}
128
129impl Display for ParseError {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        let message = match self {
132            ParseError::Expected { expected, found } => {
133                format!("Expected {}, found {}", expected, found)
134            }
135            ParseError::DomainAndReservedOverlap { culprit } => {
136                format!("Domain and reserved overlap: {}", culprit)
137            }
138            ParseError::CircularDependency { cycle } => {
139                format!("Found circular dependency: {}", cycle.join(" -> "))
140            }
141            ParseError::UknownToken(token) => format!("Unknown token in: \"{}\"", token),
142            ParseError::Io(e) => format!("IO error: {}", e),
143        };
144
145        write!(f, "{}", message)
146    }
147}
148
149impl Error for ParseError {}