difftreelog
feat --exp-apply argument
in: master
3 files changed
cmds/jrsonnet/Cargo.tomldiffbeforeafterboth--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -8,7 +8,7 @@
edition = "2021"
[features]
-experimental = ["exp-preserve-order", "exp-destruct"]
+experimental = ["exp-preserve-order", "exp-destruct", "exp-null-coaelse", "exp-object-iteration", "exp-bigint", "exp-apply"]
# Use mimalloc as allocator
mimalloc = ["mimallocator"]
# Experimental feature, which allows to preserve order of object fields
@@ -22,6 +22,10 @@
exp-object-iteration = ["jrsonnet-evaluator/exp-object-iteration"]
# Bigint type
exp-bigint = ["jrsonnet-evaluator/exp-bigint", "jrsonnet-cli/exp-bigint"]
+# obj?.field, obj?.['field']
+exp-null-coaelse = ["jrsonnet-evaluator/exp-null-coaelse", "jrsonnet-parser/exp-null-coaelse"]
+# --exp-apply
+exp-apply = []
# std.thisFile support
legacy-this-file = ["jrsonnet-cli/legacy-this-file"]
cmds/jrsonnet/src/main.rsdiffbeforeafterboth1use std::{2 fs::{create_dir_all, File},3 io::{Read, Write},4};56use clap::{CommandFactory, Parser};7use clap_complete::Shell;8use jrsonnet_cli::{GcOpts, ManifestOpts, MiscOpts, OutputOpts, StdOpts, TlaOpts, TraceOpts};9use jrsonnet_evaluator::{10 apply_tla,11 error::{Error as JrError, ErrorKind},12 throw, ResultExt, State, Val,13};1415#[cfg(feature = "mimalloc")]16#[global_allocator]17static GLOBAL: mimallocator::Mimalloc = mimallocator::Mimalloc;1819#[derive(Parser)]20enum SubOpts {21 /// Generate completions for specified shell22 Generate {23 /// Target shell name24 shell: Shell,25 },26}2728#[derive(Parser)]29#[clap(next_help_heading = "DEBUG")]30struct DebugOpts {31 /// Required OS stack size.32 /// This shouldn't be changed unless jrsonnet is failing with stack overflow error.33 #[clap(long, name = "size")]34 pub os_stack: Option<usize>,35}3637#[derive(Parser)]38#[clap(next_help_heading = "INPUT")]39struct InputOpts {40 /// Treat input as code, evaluate them instead of reading file41 #[clap(long, short = 'e')]42 pub exec: bool,4344 /// Path to the file to be compiled if `--evaluate` is unset, otherwise code itself45 pub input: Option<String>,46}4748/// Jsonnet commandline interpreter (Rust implementation)49#[derive(Parser)]50#[clap(51 args_conflicts_with_subcommands = true,52 disable_version_flag = true,53 version,54 author55)]56struct Opts {57 #[clap(subcommand)]58 sub: Option<SubOpts>,59 /// Print version60 #[clap(long)]61 version: bool,6263 #[clap(flatten)]64 input: InputOpts,65 #[clap(flatten)]66 misc: MiscOpts,67 #[clap(flatten)]68 tla: TlaOpts,69 #[clap(flatten)]70 std: StdOpts,71 #[clap(flatten)]72 gc: GcOpts,7374 #[clap(flatten)]75 trace: TraceOpts,76 #[clap(flatten)]77 manifest: ManifestOpts,78 #[clap(flatten)]79 output: OutputOpts,80 #[clap(flatten)]81 debug: DebugOpts,82}8384fn main() {85 let opts: Opts = Opts::parse();8687 if opts.version {88 print!("{}", Opts::command().render_version());89 std::process::exit(0)90 }9192 if let Some(sub) = opts.sub {93 match sub {94 SubOpts::Generate { shell } => {95 use clap_complete::generate;96 let app = &mut Opts::command();97 let buf = &mut std::io::stdout();98 generate(shell, app, "jrsonnet", buf);99 std::process::exit(0)100 }101 }102 }103104 let success = if let Some(size) = opts.debug.os_stack {105 std::thread::Builder::new()106 .stack_size(size * 1024 * 1024)107 .spawn(|| main_catch(opts))108 .expect("new thread spawned")109 .join()110 .expect("thread finished successfully")111 } else {112 main_catch(opts)113 };114 if !success {115 std::process::exit(1);116 }117}118119#[derive(thiserror::Error, Debug)]120enum Error {121 // Handled differently122 #[error("evaluation error")]123 Evaluation(JrError),124 #[error("io error")]125 Io(#[from] std::io::Error),126 #[error("input is not utf8 encoded")]127 Utf8(#[from] std::str::Utf8Error),128 #[error("missing input argument")]129 MissingInputArgument,130}131impl From<JrError> for Error {132 fn from(e: JrError) -> Self {133 Self::Evaluation(e)134 }135}136impl From<ErrorKind> for Error {137 fn from(e: ErrorKind) -> Self {138 Self::from(JrError::from(e))139 }140}141142fn main_catch(opts: Opts) -> bool {143 let s = State::default();144 let trace = opts.trace.trace_format();145 if let Err(e) = main_real(&s, opts) {146 if let Error::Evaluation(e) = e {147 let mut out = String::new();148 trace.write_trace(&mut out, &e).expect("format error");149 eprintln!("{out}")150 } else {151 eprintln!("{e}");152 }153 return false;154 }155 true156}157158fn main_real(s: &State, opts: Opts) -> Result<(), Error> {159 let _gc_leak_guard = opts.gc.leak_on_exit();160 let _gc_print_stats = opts.gc.stats_printer();161 let _stack_depth_override = opts.misc.stack_size_override();162163 let import_resolver = opts.misc.import_resolver();164 s.set_import_resolver(import_resolver);165166 let std = opts.std.context_initializer(s)?;167 if let Some(std) = std {168 s.set_context_initializer(std);169 }170171 let input = opts.input.input.ok_or(Error::MissingInputArgument)?;172 let val = if opts.input.exec {173 s.evaluate_snippet("<cmdline>".to_owned(), &input as &str)?174 } else if input == "-" {175 let mut input = Vec::new();176 std::io::stdin().read_to_end(&mut input)?;177 let input_str = std::str::from_utf8(&input)?;178 s.evaluate_snippet("<stdin>".to_owned(), input_str)?179 } else {180 s.import(&input)?181 };182183 let tla = opts.tla.tla_opts()?;184 let val = apply_tla(s.clone(), &tla, val)?;185186 let manifest_format = opts.manifest.manifest_format();187 if let Some(multi) = opts.output.multi {188 if opts.output.create_output_dirs {189 let mut dir = multi.clone();190 dir.pop();191 create_dir_all(dir)?;192 }193 let Val::Obj(obj) = val else {194 throw!("value should be object for --multi manifest, got {}", val.value_type())195 };196 for (field, data) in obj.iter(197 #[cfg(feature = "exp-preserve-order")]198 opts.manifest.preserve_order,199 ) {200 let data = data.with_description(|| format!("getting field {field} for manifest"))?;201202 let mut path = multi.clone();203 path.push(&field as &str);204 if opts.output.create_output_dirs {205 let mut dir = path.clone();206 dir.pop();207 create_dir_all(dir)?;208 }209 println!("{}", path.to_str().expect("path"));210 let mut file = File::create(path)?;211 write!(212 file,213 "{}",214 data.manifest(&manifest_format)215 .with_description(|| format!("manifesting {field}"))?,216 )?;217 if manifest_format.file_trailing_newline() {218 writeln!(file)?;219 }220 file.flush()?;221 }222 } else if let Some(path) = opts.output.output_file {223 if opts.output.create_output_dirs {224 let mut dir = path.clone();225 dir.pop();226 create_dir_all(dir)?;227 }228 let mut file = File::create(path)?;229 writeln!(file, "{}", val.manifest(manifest_format)?)?;230 } else {231 let output = val.manifest(manifest_format)?;232 if !output.is_empty() {233 println!("{output}");234 }235 }236237 Ok(())238}1use std::{2 fs::{create_dir_all, File},3 io::{Read, Write},4};56use clap::{CommandFactory, Parser};7use clap_complete::Shell;8use jrsonnet_cli::{GcOpts, ManifestOpts, MiscOpts, OutputOpts, StdOpts, TlaOpts, TraceOpts};9use jrsonnet_evaluator::{10 apply_tla,11 error::{Error as JrError, ErrorKind},12 throw, ResultExt, State, Val,13};1415#[cfg(feature = "mimalloc")]16#[global_allocator]17static GLOBAL: mimallocator::Mimalloc = mimallocator::Mimalloc;1819#[derive(Parser)]20enum SubOpts {21 /// Generate completions for specified shell22 Generate {23 /// Target shell name24 shell: Shell,25 },26}2728#[derive(Parser)]29#[clap(next_help_heading = "DEBUG")]30struct DebugOpts {31 /// Required OS stack size.32 /// This shouldn't be changed unless jrsonnet is failing with stack overflow error.33 #[clap(long, name = "size")]34 pub os_stack: Option<usize>,35}3637#[derive(Parser)]38#[clap(next_help_heading = "INPUT")]39struct InputOpts {40 /// Treat input as code, evaluate them instead of reading file41 #[clap(long, short = 'e')]42 pub exec: bool,4344 /// Path to the file to be compiled if `--evaluate` is unset, otherwise code itself45 pub input: Option<String>,4647 /// After executing input, apply specified code.48 /// Output of the initial input will be accessible using `$`49 #[cfg(feature = "exp-apply")]50 #[clap(long)]51 pub exp_apply: Vec<String>,52}5354/// Jsonnet commandline interpreter (Rust implementation)55#[derive(Parser)]56#[clap(57 args_conflicts_with_subcommands = true,58 disable_version_flag = true,59 version,60 author61)]62struct Opts {63 #[clap(subcommand)]64 sub: Option<SubOpts>,65 /// Print version66 #[clap(long)]67 version: bool,6869 #[clap(flatten)]70 input: InputOpts,71 #[clap(flatten)]72 misc: MiscOpts,73 #[clap(flatten)]74 tla: TlaOpts,75 #[clap(flatten)]76 std: StdOpts,77 #[clap(flatten)]78 gc: GcOpts,7980 #[clap(flatten)]81 trace: TraceOpts,82 #[clap(flatten)]83 manifest: ManifestOpts,84 #[clap(flatten)]85 output: OutputOpts,86 #[clap(flatten)]87 debug: DebugOpts,88}8990fn main() {91 let opts: Opts = Opts::parse();9293 if opts.version {94 print!("{}", Opts::command().render_version());95 std::process::exit(0)96 }9798 if let Some(sub) = opts.sub {99 match sub {100 SubOpts::Generate { shell } => {101 use clap_complete::generate;102 let app = &mut Opts::command();103 let buf = &mut std::io::stdout();104 generate(shell, app, "jrsonnet", buf);105 std::process::exit(0)106 }107 }108 }109110 let success = if let Some(size) = opts.debug.os_stack {111 std::thread::Builder::new()112 .stack_size(size * 1024 * 1024)113 .spawn(|| main_catch(opts))114 .expect("new thread spawned")115 .join()116 .expect("thread finished successfully")117 } else {118 main_catch(opts)119 };120 if !success {121 std::process::exit(1);122 }123}124125#[derive(thiserror::Error, Debug)]126enum Error {127 // Handled differently128 #[error("evaluation error")]129 Evaluation(JrError),130 #[error("io error")]131 Io(#[from] std::io::Error),132 #[error("input is not utf8 encoded")]133 Utf8(#[from] std::str::Utf8Error),134 #[error("missing input argument")]135 MissingInputArgument,136}137impl From<JrError> for Error {138 fn from(e: JrError) -> Self {139 Self::Evaluation(e)140 }141}142impl From<ErrorKind> for Error {143 fn from(e: ErrorKind) -> Self {144 Self::from(JrError::from(e))145 }146}147148fn main_catch(opts: Opts) -> bool {149 let s = State::default();150 let trace = opts.trace.trace_format();151 if let Err(e) = main_real(&s, opts) {152 if let Error::Evaluation(e) = e {153 let mut out = String::new();154 trace.write_trace(&mut out, &e).expect("format error");155 eprintln!("{out}")156 } else {157 eprintln!("{e}");158 }159 return false;160 }161 true162}163164fn main_real(s: &State, opts: Opts) -> Result<(), Error> {165 let _gc_leak_guard = opts.gc.leak_on_exit();166 let _gc_print_stats = opts.gc.stats_printer();167 let _stack_depth_override = opts.misc.stack_size_override();168169 let import_resolver = opts.misc.import_resolver();170 s.set_import_resolver(import_resolver);171172 let std = opts.std.context_initializer(s)?;173 if let Some(std) = std {174 s.set_context_initializer(std);175 }176177 let input = opts.input.input.ok_or(Error::MissingInputArgument)?;178 let val = if opts.input.exec {179 s.evaluate_snippet("<cmdline>".to_owned(), &input as &str)?180 } else if input == "-" {181 let mut input = Vec::new();182 std::io::stdin().read_to_end(&mut input)?;183 let input_str = std::str::from_utf8(&input)?;184 s.evaluate_snippet("<stdin>".to_owned(), input_str)?185 } else {186 s.import(&input)?187 };188189 let tla = opts.tla.tla_opts()?;190 #[allow(unused_mut)]191 let mut val = apply_tla(s.clone(), &tla, val)?;192193 #[cfg(feature = "exp-apply")]194 for apply in opts.input.exp_apply {195 use jrsonnet_evaluator::{InitialUnderscore, Thunk};196 val = s.evaluate_snippet_with(197 "<exp_apply>".to_owned(),198 &apply,199 InitialUnderscore(Thunk::evaluated(val)),200 )?;201 }202203 let manifest_format = opts.manifest.manifest_format();204 if let Some(multi) = opts.output.multi {205 if opts.output.create_output_dirs {206 let mut dir = multi.clone();207 dir.pop();208 create_dir_all(dir)?;209 }210 let Val::Obj(obj) = val else {211 throw!("value should be object for --multi manifest, got {}", val.value_type())212 };213 for (field, data) in obj.iter(214 #[cfg(feature = "exp-preserve-order")]215 opts.manifest.preserve_order,216 ) {217 let data = data.with_description(|| format!("getting field {field} for manifest"))?;218219 let mut path = multi.clone();220 path.push(&field as &str);221 if opts.output.create_output_dirs {222 let mut dir = path.clone();223 dir.pop();224 create_dir_all(dir)?;225 }226 println!("{}", path.to_str().expect("path"));227 let mut file = File::create(path)?;228 write!(229 file,230 "{}",231 data.manifest(&manifest_format)232 .with_description(|| format!("manifesting {field}"))?,233 )?;234 if manifest_format.file_trailing_newline() {235 writeln!(file)?;236 }237 file.flush()?;238 }239 } else if let Some(path) = opts.output.output_file {240 if opts.output.create_output_dirs {241 let mut dir = path.clone();242 dir.pop();243 create_dir_all(dir)?;244 }245 let mut file = File::create(path)?;246 writeln!(file, "{}", val.manifest(manifest_format)?)?;247 } else {248 let output = val.manifest(manifest_format)?;249 if !output.is_empty() {250 println!("{output}");251 }252 }253254 Ok(())255}crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -154,6 +154,37 @@
}
}
+macro_rules! impl_context_initializer {
+ ($($gen:ident)*) => {
+ #[allow(non_snake_case)]
+ impl<$($gen: ContextInitializer + Trace,)*> ContextInitializer for ($($gen,)*) {
+ fn reserve_vars(&self) -> usize {
+ let mut out = 0;
+ let ($($gen,)*) = self;
+ $(out += $gen.reserve_vars();)*
+ out
+ }
+ fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {
+ let ($($gen,)*) = self;
+ $($gen.populate(for_file.clone(), builder);)*
+ }
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+ }
+ };
+ ($($cur:ident)* @ $c:ident $($rest:ident)*) => {
+ impl_context_initializer!($($cur)*);
+ impl_context_initializer!($($cur)* $c @ $($rest)*);
+ };
+ ($($cur:ident)* @) => {
+ impl_context_initializer!($($cur)*);
+ }
+}
+impl_context_initializer! {
+ A B @ C D E
+}
+
/// Dynamically reconfigurable evaluation settings
#[derive(Trace)]
pub struct EvaluationSettings {
@@ -361,6 +392,23 @@
context_initializer.initialize(self.clone(), source)
}
+ /// Creates context with all passed global variables, calling custom modifier
+ pub fn create_default_context_with(
+ &self,
+ source: Source,
+ context_initializer: impl ContextInitializer,
+ ) -> Context {
+ let default_initializer = &self.settings().context_initializer;
+ let mut builder = ContextBuilder::with_capacity(
+ self.clone(),
+ default_initializer.reserve_vars() + context_initializer.reserve_vars(),
+ );
+ default_initializer.populate(source.clone(), &mut builder);
+ context_initializer.populate(source, &mut builder);
+
+ builder.build()
+ }
+
/// Executes code creating a new stack frame
pub fn push<T>(
e: CallLocation<'_>,
@@ -428,25 +476,34 @@
}
let mut settings = self.settings_mut();
let initializer = &mut settings.context_initializer;
- match initializer.as_any().downcast_ref::<GlobalsCtx>() {
- Some(glob) => {
- glob.globals.borrow_mut().insert(name, value);
- }
- None => {
- let inner = std::mem::replace(&mut settings.context_initializer, tb!(()));
- settings.context_initializer = tb!(GlobalsCtx {
- globals: {
- let mut out = GcHashMap::with_capacity(1);
- out.insert(name, value);
- RefCell::new(out)
- },
- inner
- })
- }
+ if let Some(global) = initializer.as_any().downcast_ref::<GlobalsCtx>() {
+ global.globals.borrow_mut().insert(name, value);
+ } else {
+ let inner = std::mem::replace(&mut settings.context_initializer, tb!(()));
+ settings.context_initializer = tb!(GlobalsCtx {
+ globals: {
+ let mut out = GcHashMap::with_capacity(1);
+ out.insert(name, value);
+ RefCell::new(out)
+ },
+ inner
+ });
}
}
}
+#[derive(Trace)]
+pub struct InitialUnderscore(pub Thunk<Val>);
+impl ContextInitializer for InitialUnderscore {
+ fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {
+ builder.bind("_".into(), self.0.clone());
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
/// Raw methods evaluate passed values but don't perform TLA execution
impl State {
/// Parses and evaluates the given snippet
@@ -465,6 +522,30 @@
})?;
evaluate(self.create_default_context(source), &parsed)
}
+ /// Parses and evaluates the given snippet with custom context modifier
+ pub fn evaluate_snippet_with(
+ &self,
+ name: impl Into<IStr>,
+ code: impl Into<IStr>,
+ context_initializer: impl ContextInitializer,
+ ) -> Result<Val> {
+ let code = code.into();
+ let source = Source::new_virtual(name.into(), code.clone());
+ let parsed = jrsonnet_parser::parse(
+ &code,
+ &ParserSettings {
+ source: source.clone(),
+ },
+ )
+ .map_err(|e| ImportSyntaxError {
+ path: source.clone(),
+ error: Box::new(e),
+ })?;
+ evaluate(
+ self.create_default_context_with(source, context_initializer),
+ &parsed,
+ )
+ }
}
/// Settings utilities