Skip to main content

ucglib/build/
mod.rs

1// Copyright 2017 Jeremy Wall <jeremy@marzhillstudios.com>
2//
3//  Licensed under the Apache License, Version 2.0 (the "License");
4//  you may not use this file except in compliance with the License.
5//  You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14
15//! The build stage of the ucg compiler.
16use std::cell::RefCell;
17use std::collections::{BTreeSet, HashMap};
18use std::error::Error;
19use std::path::PathBuf;
20use std::process;
21use std::rc::Rc;
22
23use atty;
24use atty::Stream;
25use rustyline;
26use rustyline::error::ReadlineError;
27use simple_error;
28
29use crate::ast::*;
30use crate::build::opcode::pointer::OpPointer;
31use crate::build::opcode::translate;
32use crate::build::opcode::translate::OpsMap;
33use crate::build::opcode::Environment;
34use crate::build::opcode::VM;
35use crate::iter::OffsetStrIter;
36use crate::parse::parse;
37
38pub mod format;
39pub mod ir;
40pub mod opcode;
41pub mod scope;
42
43pub mod stdlib;
44
45pub use self::ir::Val;
46
47/// The result of a build.
48type BuildResult = Result<(), Box<dyn Error>>;
49
50/// AssertCollector collects the results of assertions in the UCG AST.
51pub struct AssertCollector {
52    pub counter: i32,
53    pub success: bool,
54    pub summary: String,
55    pub failures: String,
56}
57
58impl AssertCollector {
59    pub fn new() -> Self {
60        Self {
61            counter: 0,
62            success: true,
63            summary: String::new(),
64            failures: String::new(),
65        }
66    }
67
68    fn record_assert_result(&mut self, msg: &str, is_success: bool) {
69        if !is_success {
70            let msg = format!("{} - NOT OK: {}\n", self.counter, msg);
71            self.summary.push_str(&msg);
72            self.failures.push_str(&msg);
73            self.success = false;
74        } else {
75            let msg = format!("{} - OK: {}\n", self.counter, msg);
76            self.summary.push_str(&msg);
77        }
78        self.counter += 1;
79    }
80}
81
82// TODO(jwall): I think the Rc<Val> is no longer necessary.
83/// Builder handles building ucg code for a single file.
84pub struct FileBuilder<'a, Stdout, Stderr>
85where
86    Stdout: std::io::Write + Clone,
87    Stderr: std::io::Write + Clone,
88{
89    pub environment: &'a RefCell<Environment<Stdout, Stderr>>,
90    working_dir: PathBuf,
91    strict: bool,
92    std: Rc<HashMap<String, &'static str>>,
93    import_path: &'a Vec<PathBuf>,
94    pub last: Option<Rc<Val>>,
95    pub out: Option<Rc<Val>>,
96    validate_mode: bool,
97}
98
99impl<'a, Stdout, Stderr> FileBuilder<'a, Stdout, Stderr>
100where
101    Stdout: std::io::Write + Clone,
102    Stderr: std::io::Write + Clone,
103{
104    /// Constructs a new Builder.
105    pub fn new<P: Into<PathBuf>>(
106        working_dir: P,
107        import_paths: &'a Vec<PathBuf>,
108        environment: &'a RefCell<Environment<Stdout, Stderr>>,
109    ) -> Self {
110        FileBuilder {
111            environment: environment,
112            strict: false,
113            // Our import stack is initialized with ourself.
114            working_dir: working_dir.into(),
115            std: Rc::new(stdlib::get_libs()),
116            import_path: import_paths,
117            out: None,
118            last: None,
119            validate_mode: false,
120        }
121    }
122
123    pub fn clone_builder(&self) -> Self {
124        FileBuilder {
125            environment: self.environment,
126            strict: self.strict,
127            working_dir: self.working_dir.clone(),
128            std: self.std.clone(),
129            import_path: self.import_path,
130            out: None,
131            last: None,
132            validate_mode: self.validate_mode,
133        }
134    }
135
136    pub fn set_strict(&mut self, strict: bool) {
137        self.strict = strict;
138    }
139
140    /// Builds a ucg file at the named path.
141    pub fn build<P: Into<PathBuf>>(&mut self, file: P) -> BuildResult {
142        let file = file.into();
143        self.working_dir = file.parent().unwrap().to_path_buf();
144        let ptr = self.environment.borrow_mut().get_ops_for_path(&file)?;
145        let eval_result = self.eval_ops(ptr, Some(file.clone()));
146        match eval_result {
147            Ok(_) => {
148                self.last = self.out.clone();
149                Ok(())
150            }
151            Err(e) => {
152                let err = simple_error::SimpleError::new(&format!(
153                    "Error building file: {}\n{}",
154                    file.to_string_lossy(),
155                    e.as_ref()
156                ));
157                Err(Box::new(err))
158            }
159        }
160    }
161
162    /// Puts the builder in validation mode.
163    ///
164    /// Among other things this means that assertions will be evaluated and their results
165    /// will be saved in a report for later output.
166    pub fn enable_validate_mode(&mut self) {
167        self.validate_mode = true;
168    }
169
170    fn link_ops(&self, ops: &OpPointer) -> BuildResult {
171        let mut links = Vec::new();
172        for (link, pos) in &ops.pos_map.links {
173            links.push((link.clone(), pos.clone()));
174        }
175        let mut found = BTreeSet::new();
176        loop {
177            let (link, path_pos) = match links.pop() {
178                Some(t) => t,
179                None => break,
180            };
181            if found.contains(&link) {
182                continue;
183            }
184            let ops = match self
185                .environment
186                .borrow_mut()
187                .get_ops_for_path(link.as_ref())
188            {
189                Ok(ops) => ops,
190                Err(e) => return Err(Box::new(e.with_pos(path_pos))),
191            };
192            found.insert(link);
193            for (link, pos) in &ops.pos_map.links {
194                links.push((link.clone(), pos.clone()));
195            }
196        }
197        Ok(())
198    }
199
200    fn eval_ops(&mut self, ops: OpPointer, path: Option<PathBuf>) -> BuildResult {
201        self.link_ops(&ops)?;
202        let mut vm = VM::with_pointer(self.strict, ops, &self.working_dir);
203        if path.is_some() {
204            vm.set_path(path.unwrap());
205        }
206        if self.validate_mode {
207            vm.enable_validate_mode();
208        }
209        vm.run(self.environment)?;
210        self.out = Some(Rc::new(vm.symbols_to_tuple(false).into()));
211        Ok(())
212    }
213
214    /// Builds a list of parsed UCG Statements.
215    pub fn eval_stmts(&mut self, ast: Vec<Statement>, path: Option<PathBuf>) -> BuildResult {
216        // We should probably stash this in an op_cache somewhere?
217        let ops = translate::AST::translate(ast, &self.working_dir);
218        self.eval_ops(OpPointer::new(Rc::new(ops)), path)
219    }
220
221    pub fn repl(&mut self, mut editor: rustyline::Editor<()>, config_home: PathBuf) -> BuildResult {
222        // loop
223        let mut lines = crate::io::StatementAccumulator::new();
224        if atty::is(Stream::Stdin) {
225            println!("Welcome to the UCG repl. Ctrl-D to exit, Ctrl-C to abort expression.");
226            println!("Type '#help' for help.");
227            println!("");
228        }
229        // Initialize VM with an empty OpPointer
230        let mut vm = VM::new(self.strict, Rc::new(OpsMap::new()), &self.working_dir);
231        loop {
232            // print prompt
233            let line = match editor.readline(&format!("{}> ", lines.next_line())) {
234                Ok(l) => l,
235                Err(e) => {
236                    if let ReadlineError::Eof = e {
237                        eprintln!("Recieved EOF Exiting...");
238                        process::exit(0);
239                    }
240                    if let ReadlineError::Interrupted = e {
241                        // Reset our lines and start over again
242                        eprintln!("Interrupted!");
243                        lines.reset();
244                        continue;
245                    }
246                    eprintln!("Error: {}", e);
247                    process::exit(1);
248                }
249            };
250            // repl commands are only valid while not accumulating a statement;
251            let trimmed = line.trim();
252            if trimmed.starts_with("#") {
253                // handle the various commands.
254                if trimmed.starts_with("#help") {
255                    println!(include_str!("../help/repl.txt"));
256                } else if trimmed.starts_with("#del") {
257                    // remove a named binding from the builder output.
258                    let args: Vec<&str> = trimmed.split(" ").skip(1).collect();
259                    if args.len() != 1 {
260                        // print usage of the #del command
261                        eprintln!("The '#del' command expects a single argument specifying \nthe binding to delete.");
262                    } else {
263                        let key = args[0].to_string();
264                        if let None = vm.remove_symbol(&key) {
265                            eprintln!("No such binding {}", key);
266                        }
267                    }
268                } else if trimmed.starts_with("#exit") {
269                    process::exit(0);
270                } else {
271                    eprintln!("Invalid repl command...");
272                    eprintln!("");
273                    println!(include_str!("../help/repl.txt"));
274                }
275                continue;
276            }
277            lines.push(line);
278            // check to see if that line is a statement
279            loop {
280                // read a statement
281                if let Some(stmt) = lines.get_statement() {
282                    // if it is then
283                    // eval statement
284                    let stmts = parse(OffsetStrIter::new(&stmt), None)?;
285                    let ops = translate::AST::translate(stmts, &self.working_dir);
286                    vm = vm.to_new_pointer(OpPointer::new(Rc::new(ops)));
287                    match vm.run(self.environment) {
288                        // print the result
289                        Err(e) => eprintln!("{}", e),
290                        Ok(_) => {
291                            match vm.last {
292                                Some((ref val, _)) => {
293                                    println!("{}", val);
294                                    vm.last = None;
295                                }
296                                None => {}
297                            }
298                            editor.history_mut().add(stmt);
299                            editor.save_history(&config_home)?;
300                        }
301                    }
302                    // start loop over at prompt.
303                    break;
304                }
305                // if not then keep accumulating lines without a prompt
306                lines.push(editor.readline(&format!("{}> ", lines.next_line()))?);
307            }
308        }
309    }
310
311    pub fn eval_input(
312        &mut self,
313        input: OffsetStrIter,
314        path: Option<PathBuf>,
315    ) -> Result<Rc<Val>, Box<dyn Error>> {
316        match parse(input.clone(), None) {
317            Ok(stmts) => {
318                self.eval_stmts(stmts, path)?;
319                if let Some(v) = self.out.clone() {
320                    return Ok(v);
321                }
322                unreachable!();
323            }
324            Err(err) => Err(Box::new(err)),
325        }
326    }
327
328    /// Evaluate an input string as UCG.
329    pub fn eval_string(&mut self, input: &str) -> Result<Rc<Val>, Box<dyn Error>> {
330        self.eval_input(OffsetStrIter::new(input), None)
331    }
332
333    pub fn eval_expr(&mut self, expr: Expression) -> Result<Rc<Val>, Box<dyn Error>> {
334        let ops_map =
335            translate::AST::translate(vec![Statement::Expression(expr)], &self.working_dir);
336        self.eval_ops(
337            OpPointer::new(Rc::new(ops_map)),
338            Some(self.working_dir.clone()),
339        )?;
340        if let Some(val) = &self.last {
341            return Ok(val.clone());
342        }
343        unreachable!();
344    }
345
346    pub fn get_out_by_name(&self, name: &str) -> Option<Rc<Val>> {
347        if let Some(val) = self.out.clone() {
348            if let &Val::Tuple(ref flds) = val.as_ref() {
349                for (k, v) in flds.iter() {
350                    if k.as_ref() == name {
351                        return Some(v.clone());
352                    }
353                }
354            }
355        }
356        return None;
357    }
358
359    pub fn assert_results(&self) -> bool {
360        self.environment.borrow().assert_results.success
361    }
362
363    pub fn assert_summary(&self) -> String {
364        self.environment.borrow().assert_results.summary.clone()
365    }
366}
367
368#[cfg(test)]
369mod compile_test;
370
371#[cfg(test)]
372mod test;