DEV: More of the ucg commandline works now.

This commit is contained in:
Jeremy Wall 2019-09-02 17:22:58 -05:00
parent b3fd37a6b5
commit d092fba5e8
7 changed files with 150 additions and 136 deletions

View File

@ -88,10 +88,10 @@ impl AssertCollector {
/// Builder handles building ucg code for a single file.
pub struct FileBuilder<'a, Stdout, Stderr>
where
Stdout: std::io::Write,
Stderr: std::io::Write,
Stdout: std::io::Write + Clone,
Stderr: std::io::Write + Clone,
{
environment: Rc<RefCell<Environment<Stdout, Stderr>>>,
pub environment: Rc<RefCell<Environment<Stdout, Stderr>>>,
working_dir: PathBuf,
// FIXME(jwall): These need to be compiled and added to the op cache.
// specifically in the environment.
@ -104,8 +104,8 @@ where
impl<'a, Stdout, Stderr> FileBuilder<'a, Stdout, Stderr>
where
Stdout: std::io::Write,
Stderr: std::io::Write,
Stdout: std::io::Write + Clone,
Stderr: std::io::Write + Clone,
{
/// Constructs a new Builder.
pub fn new<P: Into<PathBuf>>(
@ -148,7 +148,7 @@ where
f.read_to_string(&mut s)?;
let input = OffsetStrIter::new(&s).with_src_file(file.clone());
// TODO(jwall): Pass in the file name?
let eval_result = self.eval_input(input);
let eval_result = self.eval_input(input, Some(file.clone()));
match eval_result {
Ok(v) => {
self.last = Some(v);
@ -187,10 +187,13 @@ where
}
/// Builds a list of parsed UCG Statements.
pub fn eval_stmts(&mut self, ast: Vec<Statement>) -> BuildResult {
pub fn eval_stmts(&mut self, ast: Vec<Statement>, path: Option<PathBuf>) -> BuildResult {
// We should probably stash this in an op_cache somewhere?
let ops = translate::AST::translate(ast, &self.working_dir);
let mut vm = VM::new(Rc::new(ops), self.environment.clone(), &self.working_dir);
if path.is_some() {
vm.set_path(path.unwrap());
}
if self.validate_mode {
vm.enable_validate_mode();
}
@ -199,10 +202,14 @@ where
Ok(())
}
pub fn eval_input(&mut self, input: OffsetStrIter) -> Result<Rc<Val>, Box<dyn Error>> {
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)?;
self.eval_stmts(stmts, path)?;
if let Some(v) = self.out.clone() {
return Ok(v);
}
@ -214,7 +221,7 @@ where
/// Evaluate an input string as UCG.
pub fn eval_string(&mut self, input: &str) -> Result<Rc<Val>, Box<dyn Error>> {
self.eval_input(OffsetStrIter::new(input))
self.eval_input(OffsetStrIter::new(input), None)
}
pub fn eval_expr(&mut self, expr: Expression) -> Result<Rc<Val>, Box<dyn Error>> {

View File

@ -55,6 +55,8 @@ impl<'a> Entry<'a> {
v
}
};
Ok(OpPointer::new(cached).with_path(path.into()))
let mut ptr = OpPointer::new(cached);
ptr.set_path(path.into());
Ok(ptr)
}
}

View File

@ -22,6 +22,7 @@ use super::pointer::OpPointer;
use super::Error;
use super::Value;
use crate::build::AssertCollector;
use crate::build::Val;
use crate::convert::{ConverterRegistry, ImporterRegistry};
use crate::iter::OffsetStrIter;
use crate::parse::parse;
@ -29,8 +30,8 @@ use crate::parse::parse;
// Shared Environmental between VM's for runtime usage.
pub struct Environment<Stdout, Stderr>
where
Stdout: Write,
Stderr: Write,
Stdout: Write + Clone,
Stderr: Write + Clone,
{
pub val_cache: BTreeMap<String, Rc<Value>>,
pub op_cache: cache::Ops,
@ -43,7 +44,7 @@ where
pub out_lock: BTreeSet<PathBuf>,
}
impl<Stdout: Write, Stderr: Write> Environment<Stdout, Stderr> {
impl<Stdout: Write + Clone, Stderr: Write + Clone> Environment<Stdout, Stderr> {
pub fn new(out: Stdout, err: Stderr) -> Self {
Self::new_with_vars(out, err, BTreeMap::new())
}
@ -111,4 +112,32 @@ impl<Stdout: Write, Stderr: Write> Environment<Stdout, Stderr> {
pub fn reset_out_lock_for_path<P: AsRef<Path>>(&mut self, path: P) {
self.out_lock.remove(path.as_ref());
}
pub fn stdout(&self) -> Stdout {
self.stdout.clone()
}
pub fn stderr(&self) -> Stderr {
self.stderr.clone()
}
pub fn convert_val(&mut self, typ: &str, writer: &mut dyn Write, val: Rc<Val>) -> bool {
match self.converter_registry.get_converter(typ) {
Some(c) => {
if let Err(e) = c.convert(val, writer) {
writeln!(&mut self.stderr, "{}", e).unwrap();
return false;
}
}
None => {
writeln!(
&mut self.stderr,
"No such format {}\nrun `ucg converters` to see available formats.",
typ
)
.unwrap();
return false;
}
}
return true;
}
}

View File

@ -36,9 +36,8 @@ impl OpPointer {
}
}
pub fn with_path(mut self, path: PathBuf) -> Self {
pub fn set_path(&mut self, path: PathBuf) {
self.path = Some(path);
self
}
pub fn next(&mut self) -> Option<&Op> {

View File

@ -65,10 +65,10 @@ impl Builtins {
pos: Position,
) -> Result<(), Error>
where
P: AsRef<Path>,
P: AsRef<Path> + Debug,
WP: Into<PathBuf> + Clone + Debug,
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
{
match h {
Hook::Import => self.import(working_dir, stack, env, import_stack, pos),
@ -168,8 +168,8 @@ impl Builtins {
pos: Position,
) -> Result<(), Error>
where
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
P: Into<PathBuf> + Clone + Debug,
{
let path = stack.pop();
@ -219,8 +219,8 @@ impl Builtins {
pos: Position,
) -> Result<(), Error>
where
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
P: Into<PathBuf> + Clone + Debug,
{
let path = stack.pop();
@ -282,8 +282,8 @@ impl Builtins {
env: Rc<RefCell<Environment<O, E>>>,
) -> Result<(), Error>
where
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
{
if let Some((tuple, tpl_pos)) = stack.pop() {
if let &Value::C(Tuple(ref tuple_flds, _)) = tuple.as_ref() {
@ -334,7 +334,7 @@ impl Builtins {
return Ok(());
}
fn out<P: AsRef<Path>, O, E>(
fn out<P, O, E>(
&self,
path: Option<P>,
stack: &mut Vec<(Rc<Value>, Position)>,
@ -342,17 +342,19 @@ impl Builtins {
pos: Position,
) -> Result<(), Error>
where
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
P: AsRef<Path> + Debug,
{
let mut writer: Box<dyn std::io::Write> = if let Some(path) = path {
if env.borrow().get_out_lock_for_path(path.as_ref()) {
let write_path: Option<PathBuf> = if let Some(path) = path {
let write_path = path.as_ref().to_path_buf();
if env.borrow().get_out_lock_for_path(&path) {
return Err(Error::new(
format!("You can only have one output per file"),
pos));
}
env.borrow_mut().set_out_lock_for_path(path.as_ref());
Box::new(File::create(path)?)
Some(write_path)
} else {
if env.borrow().get_out_lock_for_path("/dev/stdout") {
return Err(Error::new(
@ -360,7 +362,7 @@ impl Builtins {
pos));
}
env.borrow_mut().set_out_lock_for_path("/dev/stdout");
Box::new(std::io::stdout())
None
};
let val = stack.pop();
if let Some((val, val_pos)) = val {
@ -368,7 +370,15 @@ impl Builtins {
let c_type = stack.pop();
if let Some((c_type_val, c_type_pos)) = c_type {
if let &Value::P(Primitive::Str(ref c_type)) = c_type_val.as_ref() {
let stdout = env.borrow().stdout();
if let Some(c) = env.borrow().converter_registry.get_converter(c_type) {
let mut writer: Box<dyn std::io::Write> = match write_path {
Some(p) => {
let p = p.with_extension(c.file_ext());
Box::new(File::create(&p)?)
},
None => Box::new(stdout),
};
if let Err(e) = c.convert(Rc::new(val), &mut writer) {
return Err(Error::new(format!("{}", e), pos.clone()));
}
@ -396,8 +406,8 @@ impl Builtins {
pos: Position,
) -> Result<(), Error>
where
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
{
let val = stack.pop();
if let Some((val, val_pos)) = val {
@ -442,8 +452,8 @@ impl Builtins {
pos: Position,
) -> Result<(), Error>
where
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
{
// get the list from the stack
let (list, list_pos) = if let Some(list) = stack.pop() {
@ -554,8 +564,8 @@ impl Builtins {
pos: Position,
) -> Result<(), Error>
where
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
{
// get the list from the stack
let (list, list_pos) = if let Some(list) = stack.pop() {
@ -700,8 +710,8 @@ impl Builtins {
pos: Position,
) -> Result<(), Error>
where
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
{
// get the list from the stack
let (list, list_pos) = if let Some(list) = stack.pop() {
@ -839,8 +849,8 @@ impl Builtins {
env: Rc<RefCell<Environment<O, E>>>,
) -> Result<(), Error>
where
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
{
let (val, val_pos) = if let Some(val) = stack.pop() {
val

View File

@ -43,8 +43,8 @@ fn construct_reserved_word_set() -> BTreeSet<&'static str> {
pub struct VM<O, E>
where
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
{
working_dir: PathBuf,
stack: Vec<(Rc<Value>, Position)>,
@ -60,8 +60,8 @@ where
impl<'a, O, E> VM<O, E>
where
O: std::io::Write,
E: std::io::Write,
O: std::io::Write + Clone,
E: std::io::Write + Clone,
{
pub fn new<P: Into<PathBuf>>(
ops: Rc<PositionMap>,
@ -90,6 +90,10 @@ where
}
}
pub fn set_path(&mut self, path: PathBuf) {
self.ops.set_path(path);
}
pub fn to_new_pointer(mut self, ops: OpPointer) -> Self {
self.ops = ops;
self

View File

@ -43,12 +43,6 @@ fn do_flags<'a, 'b>() -> clap::App<'a, 'b> {
(author: crate_authors!())
(about: "Universal Configuration Grammar compiler.")
(@arg nostrict: --("no-strict") "Turn off strict checking.")
(@subcommand eval =>
(about: "Evaluate an expression with an optional ucg file as context.")
(@arg expr: --expr -e +takes_value +required "Expression to evaluate.")
(@arg target: --format +takes_value "Output type. (flags, json, env, exec) defaults to json.")
(@arg INPUT: "ucg file to use as context for the expression.")
)
(@subcommand repl =>
(about: "Start the ucg repl for interactive evaluation.")
)
@ -82,33 +76,67 @@ fn do_flags<'a, 'b>() -> clap::App<'a, 'b> {
)
}
fn run_converter(c: &dyn traits::Converter, v: Rc<Val>, f: Option<&str>) -> traits::ConvertResult {
let mut file: Box<dyn std::io::Write> = match f {
Some(f) => {
let mut path_buf = PathBuf::from(f);
path_buf.set_extension(c.file_ext());
let new_path = path_buf.to_str().unwrap();
Box::new(File::create(&new_path)?)
}
None => Box::new(io::stdout()),
};
let result = c.convert(v, file.as_mut());
file.flush()?;
result
struct StdoutWrapper(io::Stdout);
impl StdoutWrapper {
fn new() -> Self {
Self(io::stdout())
}
}
impl io::Write for StdoutWrapper {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl Clone for StdoutWrapper {
fn clone(&self) -> Self {
Self(io::stdout())
}
}
struct StderrWrapper(io::Stderr);
impl StderrWrapper {
fn new() -> Self {
Self(io::stderr())
}
}
impl io::Write for StderrWrapper {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl Clone for StderrWrapper {
fn clone(&self) -> Self {
Self(io::stderr())
}
}
// TODO(jwall): Build sharable stdout stderr providers.
fn build_file<'a>(
file: &'a str,
validate: bool,
strict: bool,
import_paths: &'a Vec<PathBuf>,
) -> Result<build::FileBuilder<'a, Stdout, Stderr>, Box<dyn Error>> {
) -> Result<build::FileBuilder<'a, StdoutWrapper, StderrWrapper>, Box<dyn Error>> {
let mut file_path_buf = PathBuf::from(file);
if file_path_buf.is_relative() {
file_path_buf = std::env::current_dir()?.join(file_path_buf);
}
let out = std::io::stdout();
let err = std::io::stderr();
let out = StdoutWrapper::new();
let err = StderrWrapper::new();
let mut builder = build::FileBuilder::new(std::env::current_dir()?, import_paths, out, err);
// FIXME(jwall): builder.set_strict(strict);
if validate {
@ -217,69 +245,6 @@ fn visit_ucg_files(
Ok(result)
}
fn inspect_command(matches: &clap::ArgMatches, import_paths: &Vec<PathBuf>, strict: bool) {
let file = matches.value_of("INPUT");
let sym = matches.value_of("expr");
let target = matches.value_of("target").unwrap_or("json");
let mut builder = build::FileBuilder::new(
std::env::current_dir().unwrap(),
import_paths,
io::stdout(),
io::stderr(),
);
// FIXME(jwall): builder.set_strict(strict);
// FIXME(jwall): Converting a value should be built into our builder?
//match registry.get_converter(target) {
// Some(converter) => {
// if let Some(file) = file {
// if let Err(e) = builder.build(file) {
// eprintln!("{:?}", e);
// process::exit(1);
// }
// }
// let val = match sym {
// Some(sym_name) => {
// let normalized = if !sym_name.ends_with(";") {
// let mut temp = sym_name.to_owned();
// temp.push_str(";");
// temp
// } else {
// sym_name.to_owned()
// };
// let mut builder = builder.clone_builder();
// match builder.eval_string(&normalized) {
// Ok(v) => Some(v.clone()),
// Err(e) => {
// eprintln!("{}", e);
// process::exit(1);
// }
// }
// }
// None => builder.last,
// };
// match val {
// Some(value) => {
// // We use None here because we always output to stdout for an inspect.
// run_converter(converter, value, None).unwrap();
// println!("");
// process::exit(0);
// }
// None => {
// eprintln!("No value.");
// process::exit(1);
// }
// }
// }
// None => {
// eprintln!(
// "No such format {}\nrun `ucg converters` to see available formats.",
// target
// );
// process::exit(1);
// }
//}
}
fn build_command(matches: &clap::ArgMatches, import_paths: &Vec<PathBuf>, strict: bool) {
let files = matches.values_of("INPUT");
let recurse = matches.is_present("recurse");
@ -474,8 +439,8 @@ fn do_repl(import_paths: &Vec<PathBuf>) -> std::result::Result<(), Box<dyn Error
let mut builder = build::FileBuilder::new(
std::env::current_dir()?,
import_paths,
io::stdout(),
io::stderr(),
StdoutWrapper::new(),
StderrWrapper::new(),
);
// loop
let mut lines = ucglib::io::StatementAccumulator::new();
@ -575,9 +540,7 @@ fn main() {
} else {
true
};
if let Some(matches) = app_matches.subcommand_matches("eval") {
inspect_command(matches, &import_paths, strict);
} else if let Some(matches) = app_matches.subcommand_matches("build") {
if let Some(matches) = app_matches.subcommand_matches("build") {
build_command(matches, &import_paths, strict);
} else if let Some(matches) = app_matches.subcommand_matches("test") {
test_command(matches, &import_paths, strict);