use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap};
use std::error::Error;
use std::path::PathBuf;
use std::process;
use std::rc::Rc;
use atty;
use atty::Stream;
use rustyline;
use rustyline::error::ReadlineError;
use simple_error;
use crate::ast::*;
use crate::build::opcode::pointer::OpPointer;
use crate::build::opcode::translate;
use crate::build::opcode::translate::OpsMap;
use crate::build::opcode::Environment;
use crate::build::opcode::VM;
use crate::iter::OffsetStrIter;
use crate::parse::parse;
pub mod format;
pub mod ir;
pub mod opcode;
pub mod scope;
pub mod stdlib;
pub use self::ir::Val;
type BuildResult = Result<(), Box<dyn Error>>;
pub struct AssertCollector {
pub counter: i32,
pub success: bool,
pub summary: String,
pub failures: String,
}
impl AssertCollector {
pub fn new() -> Self {
Self {
counter: 0,
success: true,
summary: String::new(),
failures: String::new(),
}
}
fn record_assert_result(&mut self, msg: &str, is_success: bool) {
if !is_success {
let msg = format!("{} - NOT OK: {}\n", self.counter, msg);
self.summary.push_str(&msg);
self.failures.push_str(&msg);
self.success = false;
} else {
let msg = format!("{} - OK: {}\n", self.counter, msg);
self.summary.push_str(&msg);
}
self.counter += 1;
}
}
pub struct FileBuilder<'a, Stdout, Stderr>
where
Stdout: std::io::Write + Clone,
Stderr: std::io::Write + Clone,
{
pub environment: &'a RefCell<Environment<Stdout, Stderr>>,
working_dir: PathBuf,
strict: bool,
std: Rc<HashMap<String, &'static str>>,
import_path: &'a Vec<PathBuf>,
pub last: Option<Rc<Val>>,
pub out: Option<Rc<Val>>,
validate_mode: bool,
}
impl<'a, Stdout, Stderr> FileBuilder<'a, Stdout, Stderr>
where
Stdout: std::io::Write + Clone,
Stderr: std::io::Write + Clone,
{
pub fn new<P: Into<PathBuf>>(
working_dir: P,
import_paths: &'a Vec<PathBuf>,
environment: &'a RefCell<Environment<Stdout, Stderr>>,
) -> Self {
FileBuilder {
environment: environment,
strict: false,
working_dir: working_dir.into(),
std: Rc::new(stdlib::get_libs()),
import_path: import_paths,
out: None,
last: None,
validate_mode: false,
}
}
pub fn clone_builder(&self) -> Self {
FileBuilder {
environment: self.environment,
strict: self.strict,
working_dir: self.working_dir.clone(),
std: self.std.clone(),
import_path: self.import_path,
out: None,
last: None,
validate_mode: self.validate_mode,
}
}
pub fn set_strict(&mut self, strict: bool) {
self.strict = strict;
}
pub fn build<P: Into<PathBuf>>(&mut self, file: P) -> BuildResult {
let file = file.into();
self.working_dir = file.parent().unwrap().to_path_buf();
let ptr = self.environment.borrow_mut().get_ops_for_path(&file)?;
let eval_result = self.eval_ops(ptr, Some(file.clone()));
match eval_result {
Ok(_) => {
self.last = self.out.clone();
Ok(())
}
Err(e) => {
let err = simple_error::SimpleError::new(&format!(
"Error building file: {}\n{}",
file.to_string_lossy(),
e.as_ref()
));
Err(Box::new(err))
}
}
}
pub fn enable_validate_mode(&mut self) {
self.validate_mode = true;
}
fn link_ops(&self, ops: &OpPointer) -> BuildResult {
let mut links = Vec::new();
for (link, pos) in &ops.pos_map.links {
links.push((link.clone(), pos.clone()));
}
let mut found = BTreeSet::new();
loop {
let (link, path_pos) = match links.pop() {
Some(t) => t,
None => break,
};
if found.contains(&link) {
continue;
}
let ops = match self
.environment
.borrow_mut()
.get_ops_for_path(link.as_ref())
{
Ok(ops) => ops,
Err(e) => return Err(Box::new(e.with_pos(path_pos))),
};
found.insert(link);
for (link, pos) in &ops.pos_map.links {
links.push((link.clone(), pos.clone()));
}
}
Ok(())
}
fn eval_ops(&mut self, ops: OpPointer, path: Option<PathBuf>) -> BuildResult {
self.link_ops(&ops)?;
let mut vm = VM::with_pointer(self.strict, ops, &self.working_dir);
if path.is_some() {
vm.set_path(path.unwrap());
}
if self.validate_mode {
vm.enable_validate_mode();
}
vm.run(self.environment)?;
self.out = Some(Rc::new(vm.symbols_to_tuple(false).into()));
Ok(())
}
pub fn eval_stmts(&mut self, ast: Vec<Statement>, path: Option<PathBuf>) -> BuildResult {
let ops = translate::AST::translate(ast, &self.working_dir);
self.eval_ops(OpPointer::new(Rc::new(ops)), path)
}
pub fn repl(&mut self, mut editor: rustyline::Editor<()>, config_home: PathBuf) -> BuildResult {
let mut lines = crate::io::StatementAccumulator::new();
if atty::is(Stream::Stdin) {
println!("Welcome to the UCG repl. Ctrl-D to exit, Ctrl-C to abort expression.");
println!("Type '#help' for help.");
println!("");
}
let mut vm = VM::new(self.strict, Rc::new(OpsMap::new()), &self.working_dir);
loop {
let line = match editor.readline(&format!("{}> ", lines.next_line())) {
Ok(l) => l,
Err(e) => {
if let ReadlineError::Eof = e {
eprintln!("Recieved EOF Exiting...");
process::exit(0);
}
if let ReadlineError::Interrupted = e {
eprintln!("Interrupted!");
lines.reset();
continue;
}
eprintln!("Error: {}", e);
process::exit(1);
}
};
let trimmed = line.trim();
if trimmed.starts_with("#") {
if trimmed.starts_with("#help") {
println!(include_str!("../help/repl.txt"));
} else if trimmed.starts_with("#del") {
let args: Vec<&str> = trimmed.split(" ").skip(1).collect();
if args.len() != 1 {
eprintln!("The '#del' command expects a single argument specifying \nthe binding to delete.");
} else {
let key = args[0].to_string();
if let None = vm.remove_symbol(&key) {
eprintln!("No such binding {}", key);
}
}
} else if trimmed.starts_with("#exit") {
process::exit(0);
} else {
eprintln!("Invalid repl command...");
eprintln!("");
println!(include_str!("../help/repl.txt"));
}
continue;
}
lines.push(line);
loop {
if let Some(stmt) = lines.get_statement() {
let stmts = parse(OffsetStrIter::new(&stmt), None)?;
let ops = translate::AST::translate(stmts, &self.working_dir);
vm = vm.to_new_pointer(OpPointer::new(Rc::new(ops)));
match vm.run(self.environment) {
Err(e) => eprintln!("{}", e),
Ok(_) => {
match vm.last {
Some((ref val, _)) => {
println!("{}", val);
vm.last = None;
}
None => {}
}
editor.history_mut().add(stmt);
editor.save_history(&config_home)?;
}
}
break;
}
lines.push(editor.readline(&format!("{}> ", lines.next_line()))?);
}
}
}
pub fn eval_input(
&mut self,
input: OffsetStrIter,
path: Option<PathBuf>,
) -> Result<Rc<Val>, Box<dyn Error>> {
match parse(input.clone(), None) {
Ok(stmts) => {
self.eval_stmts(stmts, path)?;
if let Some(v) = self.out.clone() {
return Ok(v);
}
unreachable!();
}
Err(err) => Err(Box::new(err)),
}
}
pub fn eval_string(&mut self, input: &str) -> Result<Rc<Val>, Box<dyn Error>> {
self.eval_input(OffsetStrIter::new(input), None)
}
pub fn eval_expr(&mut self, expr: Expression) -> Result<Rc<Val>, Box<dyn Error>> {
let ops_map =
translate::AST::translate(vec![Statement::Expression(expr)], &self.working_dir);
self.eval_ops(
OpPointer::new(Rc::new(ops_map)),
Some(self.working_dir.clone()),
)?;
if let Some(val) = &self.last {
return Ok(val.clone());
}
unreachable!();
}
pub fn get_out_by_name(&self, name: &str) -> Option<Rc<Val>> {
if let Some(val) = self.out.clone() {
if let &Val::Tuple(ref flds) = val.as_ref() {
for (k, v) in flds.iter() {
if k.as_ref() == name {
return Some(v.clone());
}
}
}
}
return None;
}
pub fn assert_results(&self) -> bool {
self.environment.borrow().assert_results.success
}
pub fn assert_summary(&self) -> String {
self.environment.borrow().assert_results.summary.clone()
}
}
#[cfg(test)]
mod compile_test;
#[cfg(test)]
mod test;