stof/stof/
env.rs

1//
2// Copyright 2024 Formata, Inc. All rights reserved.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//    http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17use std::collections::HashMap;
18use crate::{lang::SError, IntoNodeRef, SData, SDataRef, SDoc, SField, SFunc, SNodeRef, SType, SVal};
19
20
21/// Stof parse environment.
22/// Used in controling the parsing of a stof document.
23#[derive(Clone, Debug)]
24pub struct StofEnv {
25    /// The "root" node we are parsing Stof source into.
26    pub main: SNodeRef,
27
28    /// Process ID in which we are compiling.
29    pub pid: String,
30
31    /// Relative import path. Gets added to the import path when starting with '.'
32    pub relative_import_path: String,
33
34    /// Assign type stack (keep track of variable types for casting when declared with type).
35    pub assign_type_stack: Vec<HashMap<String, SType>>,
36
37    /// Init functions to execute (in order) after parse is complete.
38    pub init_funcs: Vec<(SDataRef, Vec<SVal>)>,
39
40    /// "Compile" time field names per node.
41    /// Used for collision handling.
42    /// NodeRef->(FieldName->FieldDataRef)
43    node_field_collisions: HashMap<String, HashMap<String, SDataRef>>,
44}
45impl StofEnv {
46    /// Construct a new Stof env from a document.
47    pub fn new(pid: &str, doc: &mut SDoc) -> Self {
48        let main;
49        if doc.graph.roots.len() < 1 {
50            main = doc.graph.insert_root("root");
51        } else {
52            main = doc.graph.main_root().unwrap();
53        }
54        Self {
55            main,
56            pid: pid.to_owned(),
57            assign_type_stack: vec![Default::default()],
58            init_funcs: Default::default(),
59            node_field_collisions: Default::default(),
60            relative_import_path: String::default(),
61        }
62    }
63
64    /// Construct a new Stof env from a document and main node.
65    pub fn new_at_node(pid: &str, doc: &mut SDoc, node: impl IntoNodeRef) -> Option<Self> {
66        let nref = node.node_ref();
67        if nref.exists(&doc.graph) {
68            return Some(Self {
69                main: nref,
70                pid: pid.to_owned(),
71                init_funcs: Default::default(),
72                assign_type_stack: vec![Default::default()],
73                node_field_collisions: Default::default(),
74                relative_import_path: String::default(),
75            });
76        }
77        None
78    }
79
80    /// Insert a field onto a node.
81    /// Check for field collisions on the node, merging fields if necessary.
82    pub(crate) fn insert_field(&mut self, doc: &mut SDoc, node: impl IntoNodeRef, field: SField) -> Result<(), SError> {
83        let node_ref = node.node_ref();
84        if !self.node_field_collisions.contains_key(&node_ref.id) {
85            let mut map = HashMap::new();
86            for existing_ref in SField::field_refs(&doc.graph, &node_ref) {
87                if let Some(field) = SData::get::<SField>(&doc.graph, &existing_ref) {
88                    map.insert(field.name.clone(), existing_ref);
89                }
90            }
91            self.node_field_collisions.insert(node_ref.id.clone(), map);
92        }
93        if let Some(existing) = self.node_field_collisions.get_mut(&node_ref.id) {
94            if existing.contains_key(&field.name) {
95                // This field collides with an existing one on this node!
96                // Union the existing field with the new field, and set the existing back into the graph
97                if let Some(existing_field) = SData::get_mut::<SField>(&mut doc.graph, existing.get(&field.name).unwrap()) {
98                    existing_field.merge(&field)?;
99                }
100            } else {
101                // We have not collided with any field names on this node, so insert the field into the collisions
102                let name = field.name.clone();
103                if let Some(dref) = SData::insert_new(&mut doc.graph, &node_ref, Box::new(field)) {
104                    existing.insert(name, dref);
105                }
106            }
107        }
108        Ok(())
109    }
110
111    /// Before parse.
112    pub fn before_parse(&mut self, doc: &mut SDoc) {
113        doc.push_self(&self.pid, self.main.clone());
114    }
115
116    /// After parse.
117    pub fn after_parse(&mut self, doc: &mut SDoc) {
118        self.call_init_functions(doc);
119        doc.clean(&self.pid);
120    }
121
122    /// Already compiled this file?
123    pub fn compiled_path(&self, path: &str, doc: &SDoc) -> bool {
124        doc.env_compiled_paths.contains(path)
125    }
126
127    /// Add file path to compiled files.
128    pub fn add_compiled_path(&mut self, path: &str, doc: &mut SDoc) {
129        doc.env_compiled_paths.insert(path.to_owned());
130    }
131
132    /// Current scope.
133    pub fn scope(&self, doc: &SDoc) -> SNodeRef {
134        if let Some(nref) = doc.self_ptr(&self.pid) {
135            nref
136        } else {
137            self.main.clone()
138        }
139    }
140
141    /// Set scope of this graph!
142    /// Adds every node in the path if needed and sets the current scope.
143    pub fn push_scope(&mut self, doc: &mut SDoc, path: &str, sep: char, fields: bool) -> SNodeRef {
144        let nref = doc.graph.ensure_nodes(path, sep, fields, None);
145        self.push_scope_ref(doc, nref.clone());
146        nref
147    }
148
149    /// Push scope ref.
150    pub fn push_scope_ref(&mut self, doc: &mut SDoc, nref: SNodeRef) {
151        doc.push_self(&self.pid, nref);
152        self.assign_type_stack.push(HashMap::default());
153    }
154
155    /// Pop scope.
156    pub fn pop_scope(&mut self, doc: &mut SDoc) {
157        doc.pop_self(&self.pid);
158        self.assign_type_stack.pop();
159    }
160
161    /// Call init functions with the document.
162    pub fn call_init_functions(&self, doc: &mut SDoc) {
163        for (dref, params) in &self.init_funcs {
164            SFunc::call(dref, &self.pid, doc, params.clone(), true).expect(&format!("Failed to call init function: {:?}", SData::get::<SFunc>(&doc.graph, dref)));
165        }
166    }
167}