mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-22 18:19:54 -04:00
FIX: Restore the env variable functionality
This commit is contained in:
parent
5a20012fcb
commit
a8164a1f06
@ -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() {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
};
|
||||
|
30
src/main.rs
30
src/main.rs
@ -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);
|
||||
|
@ -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")),
|
||||
|
Loading…
x
Reference in New Issue
Block a user