FIX: Restore the env variable functionality

This commit is contained in:
Jeremy Wall 2021-03-17 20:53:17 -04:00
parent 5a20012fcb
commit a8164a1f06
5 changed files with 136 additions and 18 deletions

View File

@ -13,6 +13,7 @@
// limitations under the License.
use std::cell::RefCell;
use std::collections::BTreeMap;
use regex::Regex;
@ -24,7 +25,9 @@ fn assert_build(input: &str) {
let i_paths = Vec::new();
let out_buffer: Vec<u8> = Vec::new();
let err_buffer: Vec<u8> = Vec::new();
let env = RefCell::new(Environment::new(out_buffer, err_buffer));
let mut env_vars = BTreeMap::new();
env_vars.insert("FOO".to_owned(), "bar".to_owned());
let env = RefCell::new(Environment::new_with_vars(out_buffer, err_buffer, env_vars));
let mut b = FileBuilder::new("<Eval>", &i_paths, &env);
b.enable_validate_mode();
b.eval_string(input).unwrap();
@ -148,6 +151,47 @@ fn test_type_checks() {
assert_build(include_str!("../../integration_tests/types_test.ucg"));
}
#[test]
fn test_environment_variable_exists() {
assert_build("assert { ok = env.FOO == \"bar\", desc = \"env var $FOO is bar\"};");
}
// TODO(jwall): that shadowing the env variable is not allowed?
#[test]
fn test_env_as_field_name_works() {
assert_build(
"let tpl = { env = \"quux\" };
assert {
ok = env.FOO == \"bar\",
desc = \"env var $FOO is bar\",
};
assert {
ok = tpl.env == \"quux\",
desc = \"tpl.env is quux\",
};
let tpl2 = { env = { bar = 2 } };
assert {
ok = tpl2.env.bar == 2,
desc = \"tpl2.env.bar is 2\",
};
",
);
}
// TODO(jwall): tests for missing values.
#[test]
fn test_env_is_not_a_valid_binding() {
assert_build_failure(
"let env = 1;",
vec![
Regex::new("Cannot use binding env").unwrap(),
Regex::new("It is a reserved word").unwrap(),
Regex::new("at line: 1 column: 5").unwrap(),
],
);
}
#[test]
#[should_panic(expected = "UserDefined: I am a failure!")]
fn test_declarative_failures_are_caused_by_msg() {

View File

@ -17,16 +17,16 @@ use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use super::cache;
use super::pointer::OpPointer;
use super::Error;
use super::Value;
use crate::build::stdlib;
use super::{cache, Primitive};
use super::{Composite, Value};
use crate::build::AssertCollector;
use crate::build::Val;
use crate::convert::{ConverterRegistry, ImporterRegistry};
use crate::iter::OffsetStrIter;
use crate::parse::parse;
use crate::{ast::Position, build::stdlib};
// Shared Environmental between VM's for runtime usage.
pub struct Environment<Stdout, Stderr>
@ -47,6 +47,7 @@ where
impl<Stdout: Write + Clone, Stderr: Write + Clone> Environment<Stdout, Stderr> {
pub fn new(out: Stdout, err: Stderr) -> Self {
// TODO(jwall): populate this with environment variables?
Self::new_with_vars(out, err, BTreeMap::new())
}
@ -66,6 +67,16 @@ impl<Stdout: Write + Clone, Stderr: Write + Clone> Environment<Stdout, Stderr> {
return me;
}
pub fn get_env_vars_tuple(&self) -> Value {
let mut fields = Vec::new();
let mut positions = Vec::new();
for (key, val) in self.env_vars.iter() {
fields.push((key.clone(), Rc::new(Value::P(Primitive::Str(val.clone())))));
positions.push((Position::new(0, 0, 0), Position::new(0, 0, 0)));
}
Value::C(Composite::Tuple(fields, positions))
}
pub fn get_cached_path_val(&self, path: &String) -> Option<Rc<Value>> {
self.val_cache.get(path).cloned()
}

View File

@ -144,7 +144,7 @@ impl VM {
Op::Val(p) => self.push(Rc::new(P(p.clone())), pos)?,
Op::Cast(t) => self.op_cast(t)?,
Op::Sym(s) => self.push(Rc::new(S(s.clone())), pos)?,
Op::DeRef(s) => self.op_deref(s.clone(), &pos)?,
Op::DeRef(s) => self.op_deref(s.clone(), env, &pos)?,
Op::Add => self.op_add(pos)?,
Op::Mod => self.op_mod(pos)?,
Op::Sub => self.op_sub(pos)?,
@ -242,8 +242,17 @@ impl VM {
Ok(())
}
fn op_deref(&mut self, name: String, pos: &Position) -> Result<(), Error> {
let (val, _) = self.get_binding(&name, pos)?.clone();
fn op_deref<O, E>(
&mut self,
name: String,
env: &RefCell<Environment<O, E>>,
pos: &Position,
) -> Result<(), Error>
where
O: std::io::Write + Clone,
E: std::io::Write + Clone,
{
let (val, _) = self.get_binding(&name, env, pos)?.clone();
self.push(val, pos.clone())
}
@ -1078,9 +1087,29 @@ impl VM {
Ok(())
}
pub fn get_binding(&self, name: &str, pos: &Position) -> Result<(Rc<Value>, Position), Error> {
pub fn get_binding<O, E>(
&self,
name: &str,
env: &RefCell<Environment<O, E>>,
pos: &Position,
) -> Result<(Rc<Value>, Position), Error>
where
O: std::io::Write + Clone,
E: std::io::Write + Clone,
{
// TODO(jwall): Handle environment variables here?
let tpl = if name == "self" {
self.self_stack.last().cloned()
} else if name == "env" {
let candidate = self.symbols.get(name);
if candidate.is_some() {
candidate
} else {
Some((
Rc::new(env.borrow().get_env_vars_tuple()),
Position::new(0, 0, 0),
))
}
} else {
self.symbols.get(name)
};

View File

@ -426,7 +426,11 @@ fn env_help() {
);
}
fn do_repl(import_paths: &Vec<PathBuf>, strict: bool) -> std::result::Result<(), Box<dyn Error>> {
fn do_repl(
import_paths: &Vec<PathBuf>,
strict: bool,
env: &RefCell<Environment<StdoutWrapper, StderrWrapper>>,
) -> std::result::Result<(), Box<dyn Error>> {
let config = rustyline::Config::builder();
let mut editor = rustyline::Editor::<()>::with_config(
config
@ -458,10 +462,6 @@ fn do_repl(import_paths: &Vec<PathBuf>, strict: bool) -> std::result::Result<(),
}
}
}
let env = std::cell::RefCell::new(build::opcode::Environment::new(
StdoutWrapper::new(),
StderrWrapper::new(),
));
let mut builder = build::FileBuilder::new(std::env::current_dir()?, import_paths, &env);
builder.set_strict(strict);
@ -469,8 +469,12 @@ fn do_repl(import_paths: &Vec<PathBuf>, strict: bool) -> std::result::Result<(),
Ok(())
}
fn repl(import_paths: &Vec<PathBuf>, strict: bool) {
if let Err(e) = do_repl(import_paths, strict) {
fn repl(
import_paths: &Vec<PathBuf>,
strict: bool,
env: &RefCell<Environment<StdoutWrapper, StderrWrapper>>,
) {
if let Err(e) = do_repl(import_paths, strict, env) {
eprintln!("{}", e);
process::exit(1);
}
@ -482,7 +486,15 @@ fn main() {
// FIXME(jwall): Do we want these to be shared or not?
let registry = ConverterRegistry::make_registry();
let mut import_paths = Vec::new();
let env = RefCell::new(Environment::new(StdoutWrapper::new(), StderrWrapper::new()));
let mut env_vars = BTreeMap::new();
for (var, val) in std::env::vars() {
env_vars.insert(var, val);
}
let env = RefCell::new(Environment::new_with_vars(
StdoutWrapper::new(),
StderrWrapper::new(),
env_vars,
));
if let Some(mut p) = dirs::home_dir() {
p.push(".ucg");
// Attempt to create directory if it doesn't exist.
@ -518,7 +530,7 @@ fn main() {
} else if let Some(_) = app_matches.subcommand_matches("env") {
env_help()
} else if let Some(_) = app_matches.subcommand_matches("repl") {
repl(&import_paths, strict)
repl(&import_paths, strict, &env)
} else if let Some(matches) = app_matches.subcommand_matches("fmt") {
if let Err(e) = fmt_command(matches) {
eprintln!("{}", e);

View File

@ -805,11 +805,33 @@ make_fn!(
)
);
/// Verify binding is not a reserved word.
macro_rules! match_binding_name {
($i:expr,) => {{
use abortable_parser::{Error, Result};
let mut _i = $i.clone();
match match_type!(_i, BAREWORD) {
Result::Complete(i, t) => {
if t.fragment == "env" {
return Result::Abort(Error::new(
format!("Cannot use binding {}. It is a reserved word.", t.fragment),
Box::new($i.clone()),
));
}
Result::Complete(i, t)
}
Result::Incomplete(i) => Result::Incomplete(i),
Result::Fail(e) => Result::Fail(e),
Result::Abort(e) => Result::Abort(e),
}
}};
}
make_fn!(
let_stmt_body<SliceIter<Token>, Statement>,
do_each!(
pos => pos,
name => wrap_err!(match_type!(BAREWORD), "Expected name for binding"),
name => wrap_err!(match_binding_name!(), "Expected name for binding"),
_ => optional!(trace_parse!(shape_suffix)),
_ => punct!("="),
val => trace_parse!(wrap_err!(expression, "Expected Expression to bind")),