diff --git a/src/build/mod.rs b/src/build/mod.rs index 628fa39..9981a02 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -111,7 +111,7 @@ impl AssertCollector { 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); @@ -124,7 +124,6 @@ impl AssertCollector { } self.counter += 1; } - } /// Builder handles building ucg code for a single file. diff --git a/src/build/opcode/error.rs b/src/build/opcode/error.rs new file mode 100644 index 0000000..05476c0 --- /dev/null +++ b/src/build/opcode/error.rs @@ -0,0 +1,27 @@ +// Copyright 2019 Jeremy Wall +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use std::convert::From; + +#[derive(Debug)] +pub struct Error {} + +impl From for Error +where + E: std::error::Error + Sized, +{ + fn from(_e: E) -> Error { + // FIXME(jwall): This should really have more information for debugging + Error {} + } +} diff --git a/src/build/opcode/mod.rs b/src/build/opcode/mod.rs index 73e6ada..98d060c 100644 --- a/src/build/opcode/mod.rs +++ b/src/build/opcode/mod.rs @@ -11,14 +11,17 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +use std::convert::{TryFrom, TryInto}; use std::rc::Rc; mod cache; +mod error; pub mod pointer; mod runtime; pub mod scope; mod vm; +pub use error::Error; pub use vm::VM; use pointer::OpPointer; @@ -34,12 +37,16 @@ pub enum Primitive { Empty, } +use Primitive::{Bool, Empty, Float, Int, Str}; + #[derive(Debug, PartialEq, Clone)] pub enum Composite { List(Vec>), Tuple(Vec<(String, Rc)>), } +use Composite::{List, Tuple}; + #[derive(Debug, PartialEq, Clone)] pub struct Func { ptr: OpPointer, @@ -70,6 +77,8 @@ pub enum Value { M(Module), } +use Value::{C, F, M, P, S, T}; + #[derive(Debug, PartialEq, Clone)] pub enum Hook { Map, @@ -132,10 +141,101 @@ pub enum Op { FCall, // Runtime hooks Runtime(Hook), + // TODO(jwall): TRACE instruction } -#[derive(Debug)] -pub struct Error {} +use super::ir::Val; + +impl TryFrom> for Val { + type Error = Error; + + fn try_from(val: Rc) -> Result { + val.as_ref().try_into() + } +} + +impl TryFrom<&Value> for Val { + type Error = Error; + + fn try_from(val: &Value) -> Result { + Ok(match val { + P(Int(i)) => Val::Int(*i), + P(Float(f)) => Val::Float(*f), + P(Str(s)) => Val::Str(s.clone()), + P(Bool(b)) => Val::Boolean(*b), + P(Empty) => Val::Empty, + C(Tuple(fs)) => { + let mut flds = Vec::new(); + for &(ref k, ref v) in fs.iter() { + let v = v.clone(); + // TODO(jwall): The RC for a Val should no longer be required. + flds.push((k.clone(), Rc::new(v.try_into()?))); + } + Val::Tuple(flds) + } + C(List(elems)) => { + let mut els = Vec::new(); + for e in elems.iter() { + let e = e.clone(); + // TODO + els.push(Rc::new(e.try_into()?)); + } + Val::List(els) + } + S(_) | F(_) | M(_) | T(_) => { + return Err(dbg!(Error {})); + } + }) + } +} + +impl TryFrom> for Value { + type Error = Error; + + fn try_from(val: Rc) -> Result { + val.as_ref().try_into() + } +} + +impl TryFrom<&Val> for Value { + type Error = Error; + + fn try_from(val: &Val) -> Result { + Ok(match val { + Val::Int(i) => P(Int(*i)), + Val::Float(f) => P(Float(*f)), + Val::Boolean(b) => P(Bool(*b)), + Val::Str(s) => P(Str(s.clone())), + Val::Empty => P(Empty), + Val::List(els) => { + let mut lst = Vec::new(); + for e in els.iter() { + let e = e.clone(); + lst.push(Rc::new(e.try_into()?)); + } + C(List(lst)) + } + Val::Tuple(flds) => { + let mut field_list = Vec::new(); + for &(ref key, ref val) in flds.iter() { + let val = val.clone(); + field_list.push((key.clone(), Rc::new(val.try_into()?))); + } + C(Tuple(field_list)) + } + Val::Env(flds) => { + let mut field_list = Vec::new(); + for &(ref key, ref val) in flds.iter() { + field_list.push((key.clone(), Rc::new(P(Str(val.clone()))))); + } + C(Tuple(field_list)) + } + // TODO(jwall): These can go away eventually when we replace the tree + // walking interpreter. + Val::Module(_) | Val::Func(_) => return Err(dbg!(Error {})), + }) + } +} #[cfg(test)] mod test; diff --git a/src/build/opcode/runtime.rs b/src/build/opcode/runtime.rs index 476caff..e190ae7 100644 --- a/src/build/opcode/runtime.rs +++ b/src/build/opcode/runtime.rs @@ -12,45 +12,116 @@ // See the License for the specific language governing permissions and // limitations under the License. use std::collections::BTreeMap; +use std::convert::TryInto; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; use std::rc::Rc; use super::cache; +use super::Value::{P, C, F}; use super::VM; use super::{Composite, Error, Hook, Primitive, Value}; use crate::build::AssertCollector; -use Composite::Tuple; -use Primitive::{Bool, Str}; +use crate::convert::{ConverterRegistry, ImporterRegistry}; +use Composite::{List, Tuple}; +use Primitive::{Bool, Empty, Str}; pub struct Builtins { op_cache: cache::Ops, val_cache: BTreeMap>, assert_results: AssertCollector, + converter_registry: ConverterRegistry, + importer_registry: ImporterRegistry, + working_dir: PathBuf, + import_path: Vec, // TODO(jwall): IO sink for stderr // TODO(jwall): IO sink for stdout } impl Builtins { pub fn new() -> Self { + Self::with_working_dir(std::env::current_dir().unwrap()) + } + + pub fn with_working_dir>(path: P) -> Self { Self { op_cache: cache::Ops::new(), val_cache: BTreeMap::new(), assert_results: AssertCollector::new(), + converter_registry: ConverterRegistry::make_registry(), + importer_registry: ImporterRegistry::make_registry(), + // TODO(jwall): This should move into the VM and not in the Runtime. + working_dir: path.into(), + import_path: Vec::new(), } } - pub fn handle(&mut self, h: Hook, stack: &mut Vec>) -> Result<(), Error> { + pub fn handle>( + &mut self, + path: P, + h: Hook, + stack: &mut Vec>, + ) -> Result<(), Error> { match h { Hook::Import => self.import(stack), Hook::Include => self.include(stack), Hook::Assert => self.assert(stack), Hook::Convert => self.convert(stack), - Hook::Out => self.out(stack), - Hook::Map => self.map(stack), - Hook::Filter => self.filter(stack), - Hook::Reduce => self.reduce(stack), + Hook::Out => self.out(path, stack), + Hook::Map => self.map(path, stack), + Hook::Filter => self.filter(path, stack), + Hook::Reduce => self.reduce(path, stack), } } + fn find_file>( + &self, + path: P, + use_import_path: bool, + ) -> Result { + // Try a relative path first. + let path = path.into(); + let mut normalized = self.working_dir.clone(); + if path.is_relative() { + normalized.push(&path); + // First see if the normalized file exists or not. + if !normalized.exists() && use_import_path { + // TODO(jwall): Support importing from a zip file in this + // import_path? + // If it does not then look for it in the list of import_paths + for mut p in self.import_path.iter().cloned() { + p.push(&path); + if p.exists() { + normalized = p; + break; + } + } + } + } else { + normalized = path; + } + match normalized.canonicalize() { + Ok(p) => Ok(p), + Err(_e) => Err(dbg!(Error {})), + } + } + + fn get_file_as_string(&self, path: &str) -> Result { + let sep = format!("{}", std::path::MAIN_SEPARATOR); + let raw_path = path.replace("/", &sep); + let normalized = match self.find_file(raw_path, false) { + Ok(p) => p, + Err(_e) => { + return Err(dbg!(Error {})); + } + }; + let mut f = File::open(normalized).unwrap(); + let mut contents = String::new(); + f.read_to_string(&mut contents).unwrap(); + Ok(contents) + } + fn import(&mut self, stack: &mut Vec>) -> Result<(), Error> { let path = stack.pop(); if let Some(val) = path { @@ -59,10 +130,10 @@ impl Builtins { stack.push(self.val_cache[path].clone()); } else { let op_pointer = self.op_cache.entry(path).get_pointer_or_else(|| { - // TODO(jwall): import + // FIXME(jwall): import unimplemented!("Compiling paths are not implemented yet"); }); - let mut vm = VM::with_pointer(op_pointer); + let mut vm = VM::with_pointer(path, op_pointer); vm.run()?; let result = Rc::new(vm.symbols_to_tuple(true)); self.val_cache.insert(path.clone(), result.clone()); @@ -77,15 +148,48 @@ impl Builtins { fn include(&self, stack: &mut Vec>) -> Result<(), Error> { // TODO(jwall): include let path = stack.pop(); - if let Some(val) = path { - if let &Value::P(Str(ref path)) = val.as_ref() {} - unimplemented!("TODO(jwall): Includes are not implemented yet") + let typ = stack.pop(); + let path = if let Some(val) = path { + if let &Value::P(Str(ref path)) = val.as_ref() { + path.clone() + } else { + return dbg!(Err(Error {})); + } + } else { + return dbg!(Err(Error {})); + }; + let typ = if let Some(val) = typ.as_ref() { + if let &Value::P(Str(ref typ)) = val.as_ref() { + typ.clone() + } else { + return dbg!(Err(Error {})); + } + } else { + return dbg!(Err(Error {})); + }; + if typ == "str" { + stack.push(Rc::new(P(Str(self.get_file_as_string(&path)?)))); + } else { + stack.push(Rc::new(match self.importer_registry.get_importer(&typ) { + Some(importer) => { + let contents = self.get_file_as_string(&path)?; + if contents.len() == 0 { + eprintln!("including an empty file. Use NULL as the result"); + P(Empty) + } else { + match importer.import(contents.as_bytes()) { + Ok(v) => v.try_into()?, + Err(_e) => return dbg!(Err(Error {})), + } + } + } + None => return dbg!(Err(Error {})), + })); } return Err(Error {}); } fn assert(&mut self, stack: &mut Vec>) -> Result<(), Error> { - // TODO(jwall): assert let tuple = stack.pop(); if let Some(val) = tuple.clone() { if let &Value::C(Tuple(ref tuple)) = val.as_ref() { @@ -119,38 +223,177 @@ impl Builtins { return Ok(()); } + fn out>(&self, path: P, stack: &mut Vec>) -> Result<(), Error> { + let val = stack.pop(); + if let Some(val) = val { + let val = val.try_into()?; + if let Some(c_type_val) = stack.pop() { + if let &Value::S(ref c_type) = c_type_val.as_ref() { + if let Some(c) = self.converter_registry.get_converter(c_type) { + match c.convert(Rc::new(val), &mut File::create(path)?) { + Ok(_) => { + // noop + } + Err(_e) => return Err(Error {}), + } + return Ok(()); + } + } + } + } + return Err(Error {}); + } + fn convert(&self, stack: &mut Vec>) -> Result<(), Error> { - // TODO(jwall): convert let val = stack.pop(); if let Some(val) = val { - unimplemented!("TODO(jwall): Conversions are not implemented yet") - } else { - Err(Error {}) + let val = val.try_into()?; + if let Some(c_type_val) = stack.pop() { + if let &Value::S(ref c_type) = c_type_val.as_ref() { + if let Some(c) = self.converter_registry.get_converter(c_type) { + let mut buf: Vec = Vec::new(); + match c.convert(Rc::new(val), &mut buf) { + Ok(_) => { + stack + .push(Rc::new(P(Str( + String::from_utf8_lossy(buf.as_slice()).to_string() + )))); + } + Err(_e) => return Err(Error {}), + } + return Ok(()); + } + } + } } + return Err(Error {}); } - fn out(&self, stack: &mut Vec>) -> Result<(), Error> { - // TODO(jwall): out - let val = stack.pop(); - if let Some(val) = val { - unimplemented!("TODO(jwall): Out expressions are not implemented yet") + fn map>(&self, path: P, stack: &mut Vec>) -> Result<(), Error> { + // get the list from the stack + let list = if let Some(list) = stack.pop() { + list } else { - Err(Error {}) + return dbg!(Err(Error {})); + }; + let elems = if let &C(List(ref elems)) = list.as_ref() { + elems + } else { + return dbg!(Err(Error {})); + }; + + // get the func ptr from the stack + let fptr = if let Some(ptr) = stack.pop() { + ptr + } else { + return dbg!(Err(Error {})); + }; + + let f = if let &F(ref f) = fptr.as_ref() { + f + } else { + return dbg!(Err(Error {})); + }; + + let mut result_elems = Vec::new(); + for e in elems.iter() { + // push function argument on the stack. + stack.push(e.clone()); + // call function and push it's result on the stack. + result_elems.push(VM::fcall_impl(path.as_ref().to_owned(), f, stack)?); } + stack.push(Rc::new(C(List(result_elems)))); + Ok(()) } - fn map(&self, stack: &mut Vec>) -> Result<(), Error> { - // TODO(jwall): map (combine these into one?) - unimplemented!("TODO(jwall): Map expressions are not implemented yet") + fn filter>(&self, path: P, stack: &mut Vec>) -> Result<(), Error> { + // get the list from the stack + let list = if let Some(list) = stack.pop() { + list + } else { + return dbg!(Err(Error {})); + }; + let elems = if let &C(List(ref elems)) = list.as_ref() { + elems + } else { + return dbg!(Err(Error {})); + }; + + // get the func ptr from the stack + let fptr = if let Some(ptr) = stack.pop() { + ptr + } else { + return dbg!(Err(Error {})); + }; + + let f = if let &F(ref f) = fptr.as_ref() { + f + } else { + return dbg!(Err(Error {})); + }; + + let mut result_elems = Vec::new(); + for e in elems.iter() { + // push function argument on the stack. + stack.push(e.clone()); + // call function and push it's result on the stack. + let condition = VM::fcall_impl(path.as_ref().to_owned(), f, stack)?; + // Check for empty or boolean results and only push e back in + // if they are non empty and true + match condition.as_ref() { + &P(Empty) | &P(Bool(false)) => { + continue; + } + _ => result_elems.push(e.clone()), + } + } + stack.push(Rc::new(C(List(result_elems)))); + Ok(()) } - fn filter(&self, stack: &mut Vec>) -> Result<(), Error> { - // TODO(jwall): filter - unimplemented!("TODO(jwall): Filter expressions are not implemented yet") - } + fn reduce>(&self, path: P, stack: &mut Vec>) -> Result<(), Error> { + // get the list from the stack + let list = if let Some(list) = stack.pop() { + list + } else { + return dbg!(Err(Error {})); + }; + let elems = if let &C(List(ref elems)) = list.as_ref() { + elems + } else { + return dbg!(Err(Error {})); + }; - fn reduce(&self, stack: &mut Vec>) -> Result<(), Error> { - // TODO(jwall): reduce - unimplemented!("TODO(jwall): Reduce expressions are not implemented yet") + // Get the accumulator from the stack + let mut acc = if let Some(acc) = stack.pop() { + acc + } else { + return dbg!(Err(Error {})); + }; + // get the func ptr from the stack + let fptr = if let Some(ptr) = stack.pop() { + ptr + } else { + return dbg!(Err(Error {})); + }; + + let f = if let &F(ref f) = fptr.as_ref() { + f + } else { + return dbg!(Err(Error {})); + }; + + for e in elems.iter() { + // push function arguments on the stack. + stack.push(e.clone()); + stack.push(acc.clone()); + // call function and push it's result on the stack. + acc = VM::fcall_impl(path.as_ref().to_owned(), f, stack)?; + // Check for empty or boolean results and only push e back in + // if they are non empty and true + } + // push the acc on the stack as our result + stack.push(acc); + Ok(()) } } diff --git a/src/build/opcode/test.rs b/src/build/opcode/test.rs index 13b94dc..653aa44 100644 --- a/src/build/opcode/test.rs +++ b/src/build/opcode/test.rs @@ -27,7 +27,7 @@ use super::VM; macro_rules! assert_cases { (__impl__ $cases:expr) => { for case in $cases.drain(0..) { - let mut vm = VM::new(Rc::new(case.0)); + let mut vm = VM::new("foo.ucg", Rc::new(case.0)); vm.run().unwrap(); assert_eq!(dbg!(vm.pop()).unwrap(), Rc::new(case.1)); } @@ -88,7 +88,7 @@ fn test_bind_op() { )]; for case in cases.drain(0..) { - let mut vm = VM::new(Rc::new(case.0)); + let mut vm = VM::new("bar.ucg", Rc::new(case.0)); vm.run().unwrap(); let (name, result) = case.1; let v = vm.get_binding(name).unwrap(); diff --git a/src/build/opcode/vm.rs b/src/build/opcode/vm.rs index 235e81b..62d5290 100644 --- a/src/build/opcode/vm.rs +++ b/src/build/opcode/vm.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use std::cell::RefCell; +use std::path::PathBuf; use std::rc::Rc; use super::pointer::OpPointer; @@ -30,19 +31,21 @@ pub struct VM { symbols: Stack, runtime: Rc>, ops: OpPointer, + path: PathBuf, } impl<'a> VM { - pub fn new(ops: Rc>) -> Self { - Self::with_pointer(OpPointer::new(ops)) + pub fn new>(path: P, ops: Rc>) -> Self { + Self::with_pointer(path, OpPointer::new(ops)) } - pub fn with_pointer(ops: OpPointer) -> Self { + pub fn with_pointer>(path: P, ops: OpPointer) -> Self { Self { stack: Vec::new(), symbols: Stack::new(), runtime: Rc::new(RefCell::new(runtime::Builtins::new())), ops: ops, + path: path.into(), } } @@ -52,6 +55,7 @@ impl<'a> VM { symbols: symbols, runtime: self.runtime.clone(), ops: self.ops.clone(), + path: self.path.clone(), } } @@ -226,26 +230,35 @@ impl<'a> VM { self.ops.jump(jptr) } - fn op_fcall(&mut self) -> Result<(), Error> { - let f = self.pop()?; - if let &F(Func { + pub fn fcall_impl>( + path: P, + f: &Func, + stack: &mut Vec>, + ) -> Result, Error> { + let Func { ref ptr, ref bindings, ref snapshot, - }) = f.as_ref() - { - // use the captured scope snapshot for the function. - let mut vm = Self::with_pointer(ptr.clone()).to_scoped(snapshot.clone()); - for nm in bindings.iter() { - // now put each argument on our scope stack as a binding. - let val = self.pop()?; - vm.binding_push(nm.clone(), val)?; - } - // proceed to the function body - vm.run()?; - self.push(vm.pop()?)?; - } else { - return dbg!(Err(Error {})); + } = f; + // use the captured scope snapshot for the function. + let mut vm = Self::with_pointer(path, ptr.clone()).to_scoped(snapshot.clone()); + for nm in bindings.iter() { + // now put each argument on our scope stack as a binding. + // TODO(jwall): This should do a better error if there is + // nothing on the stack. + let val = stack.pop().unwrap(); + vm.binding_push(nm.clone(), val)?; + } + // proceed to the function body + vm.run()?; + return vm.pop(); + } + + fn op_fcall(&mut self) -> Result<(), Error> { + let f = self.pop()?; + if let &F(ref f) = f.as_ref() { + let val = Self::fcall_impl(&self.path, f, &mut self.stack)?; + self.push(val)?; } Ok(()) } @@ -428,7 +441,11 @@ impl<'a> VM { } } - fn find_in_flds(&self, index: &Value, flds: &Vec<(String, Rc)>) -> Result, Error> { + fn find_in_flds( + &self, + index: &Value, + flds: &Vec<(String, Rc)>, + ) -> Result, Error> { let idx = match index { S(p) => p, P(Str(p)) => p, @@ -508,7 +525,7 @@ impl<'a> VM { } // FIXME(jwall): We need to populate the pkg key for modules. //self.merge_field_into_tuple(&mut flds, "this".to_owned(), this)?; - let mut vm = Self::with_pointer(ptr.clone()); + let mut vm = Self::with_pointer(self.path.clone(), ptr.clone()); vm.push(Rc::new(S("mod".to_owned())))?; vm.push(Rc::new(C(Tuple(dbg!(flds)))))?; vm.run()?; @@ -548,7 +565,7 @@ impl<'a> VM { Ok(()) } - fn binding_push(&mut self, name: String, val: Rc) -> Result<(), Error> { + pub fn binding_push(&mut self, name: String, val: Rc) -> Result<(), Error> { if self.symbols.is_bound(&name) { return Err(Error {}); } @@ -609,6 +626,8 @@ impl<'a> VM { } fn op_runtime(&mut self, h: Hook) -> Result<(), Error> { - self.runtime.borrow_mut().handle(h, &mut self.stack) + self.runtime + .borrow_mut() + .handle(&self.path, h, &mut self.stack) } }