Replies: 3 comments
-
One possibility would be:
nickel export config.ncl --field=outputs -- 'inputs.machine=(let machines = import "machines.ncl" in machines.A)' |
Beta Was this translation helpful? Give feedback.
-
In general, I think it's better to have an explicit dependency on the machine parameter, as in @suimong 's solution (or it could be a function parameter, although record fields are more flexible). It shouldn't be too hard to do the same without resorting to the CLI, if you're embedding Nickel in your Rust application: the logic of extracting one specific field and applying overrides is entirely implemented inside That being said, if for your application some functions make sense as a custom standard library because they are really pervasive, then you'll have to use pub fn eval_in_env<T, S>(
source: T,
source_name: S,
trace: impl Write + 'static,
// Put your additional stuff in this environment
env: Environment,
) -> Result<RichTerm, Error>
where
T: Read,
S: Into<OsString> + Clone,
{
// Mostly Program::new_from_source
let mut cache = Cache::new(ErrorTolerance::Strict);
let path = PathBuf::from(source_name.into());
// you probably don't want unwrap here
let main_id = cache.add_source(SourcePath::Path(path), source).unwrap();
let mut vm = VirtualMachine::new(cache, trace);
// Mostly Program::prepare_eval()
let prepared = vm.prepare_eval(main_id)?;
// If you want to evaluate deeply and substitute variables
// Mostly Program::eval_full
Ok(vm.eval_full_closure(Closure { body: prepared, env})?.body)
} However, there's a subtle issue here: the One simple work-around for now is to simply eschew typechecking altogether: pub fn eval_in_env<T, S>(
source: T,
source_name: S,
trace: impl Write + 'static,
extension: Environment,
) -> Result<RichTerm, Error>
where
T: Read,
S: Into<OsString> + Clone,
{
// Mostly Program::new_from_source
let mut cache = Cache::new(ErrorTolerance::Strict);
let path = PathBuf::from(source_name.into());
// you probably don't want unwrap here
let main_id = cache.add_source(SourcePath::Path(path), source).unwrap();
let mut vm = VirtualMachine::new(cache, trace);
// Custom preparation phase: prepare does three steps. Parsing, typechecking and program
// transformations. We just eschew the typechecking.
vm.import_resolver_mut().parse(main_id).unwrap();
vm.import_resolver_mut().transform(main_id).unwrap();
let prepared = vm.import_resolver_mut().get_owned(main_id).unwrap();
// If you want to evaluate deeply and substitute variables
// Mostly Program::eval_full
Ok(vm.eval_full_closure(Closure { body: prepared, env: extension})?.body)
} I think this is the simplest approach. Doing typechecking with a custom stdlib is technically possible, but more tedious, because the current infrastructure wasn't built with this use-case in mind. So the initial typing environment is mostly automatically generated from a hard-coded list of stdlib module. By using directly the cache ( |
Beta Was this translation helpful? Give feedback.
-
Thank you both for the detailed replies and pointers. I'll take your suggestions and see what I can work out. |
Beta Was this translation helpful? Give feedback.
-
If I'm using Nickel as a library in Rust code how would I go about evaluating a program with a module/import consisting of dynamic information already present in the environment akin to how
std
is loaded in mk_eval_env?For example if I had a configuration file like:
When evaluated it would produce something like this (although it would be deserialised into Rust types instead of generating JSON):
I'd like to be able to pro-populate
machine
with data read at runtime by the program usingnickel-lang-core
.Alternatively if the is a better way to achieve the same thing I'd be happy to have suggestions.
Beta Was this translation helpful? Give feedback.
All reactions