--- a/cmds/jrsonnet/src/main.rs +++ b/cmds/jrsonnet/src/main.rs @@ -93,7 +93,7 @@ enum Error { // Handled differently #[error("evaluation error")] - Evaluation(jrsonnet_evaluator::error::LocError), + Evaluation(LocError), #[error("io error")] Io(#[from] std::io::Error), #[error("input is not utf8 encoded")] @@ -106,6 +106,11 @@ Self::Evaluation(e) } } +impl From for Error { + fn from(e: jrsonnet_evaluator::error::Error) -> Self { + Self::from(LocError::from(e)) + } +} fn main_catch(opts: Opts) -> bool { let s = State::default(); @@ -144,9 +149,17 @@ dir.pop(); create_dir_all(dir)?; } - for (file, data) in s.manifest_multi(val)?.iter() { + let Val::Obj(obj) = val else { + throw!("value should be object for --multi manifest, got {}", val.value_type()) + }; + for (field, data) in obj.iter( + #[cfg(feature = "exp-preserve-order")] + opts.manifest.preserve_order, + ) { + let data = data.with_description(|| format!("getting field {field} for manifest"))?; + let mut path = multi.clone(); - path.push(file as &str); + path.push(&field as &str); if opts.output.create_output_dirs { let mut dir = path.clone(); dir.pop(); @@ -154,7 +167,12 @@ } println!("{}", path.to_str().expect("path")); let mut file = File::create(path)?; - writeln!(file, "{}", data)?; + writeln!( + file, + "{}", + data.manifest(&manifest_format) + .with_description(|| format!("manifesting {field}"))? + )?; } } else if let Some(path) = opts.output.output_file { if opts.output.create_output_dirs { @@ -163,9 +181,9 @@ create_dir_all(dir)?; } let mut file = File::create(path)?; - writeln!(file, "{}", s.manifest(val)?)?; + writeln!(file, "{}", val.manifest(manifest_format)?)?; } else { - let output = s.manifest(val)?; + let output = val.manifest(manifest_format)?; if !output.is_empty() { println!("{}", output); } --- a/crates/jrsonnet-cli/src/manifest.rs +++ b/crates/jrsonnet-cli/src/manifest.rs @@ -1,7 +1,11 @@ use std::{path::PathBuf, str::FromStr}; use clap::{Parser, ValueEnum}; -use jrsonnet_evaluator::{error::Result, ManifestFormat, State}; +use jrsonnet_evaluator::{ + error::Result, + stdlib::manifest::{JsonFormat, StringFormat, ToStringFormat, YamlFormat, YamlStreamFormat}, + ManifestFormat, State, +}; use crate::ConfigureState; @@ -36,7 +40,7 @@ #[clap(long, short = 'S', conflicts_with = "format")] string: bool, /// Write output as YAML stream, can be used with --format json/yaml - #[clap(long, short = 'y')] + #[clap(long, short = 'y', conflicts_with = "string")] yaml_stream: bool, /// Number of spaces to pad output manifest with. /// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml] @@ -45,34 +49,35 @@ /// Preserve order in object manifestification #[cfg(feature = "exp-preserve-order")] #[clap(long)] - exp_preserve_order: bool, + preserve_order: bool, } impl ConfigureState for ManifestOpts { - type Guards = (); - fn configure(&self, s: &State) -> Result<()> { - if self.string { - s.set_manifest_format(ManifestFormat::String); + type Guards = Box; + fn configure(&self, _s: &State) -> Result { + let format: Box = if self.string { + Box::new(StringFormat) } else { #[cfg(feature = "exp-preserve-order")] - let preserve_order = self.exp_preserve_order; + let preserve_order = self.preserve_order; match self.format { - ManifestFormatName::String => s.set_manifest_format(ManifestFormat::String), - ManifestFormatName::Json => s.set_manifest_format(ManifestFormat::Json { - padding: self.line_padding.unwrap_or(3), + ManifestFormatName::String => Box::new(ToStringFormat), + ManifestFormatName::Json => Box::new(JsonFormat::cli( + self.line_padding.unwrap_or(3), #[cfg(feature = "exp-preserve-order")] preserve_order, - }), - ManifestFormatName::Yaml => s.set_manifest_format(ManifestFormat::Yaml { - padding: self.line_padding.unwrap_or(2), + )), + ManifestFormatName::Yaml => Box::new(YamlFormat::cli( + self.line_padding.unwrap_or(2), #[cfg(feature = "exp-preserve-order")] preserve_order, - }), + )), } - } - if self.yaml_stream { - s.set_manifest_format(ManifestFormat::YamlStream(Box::new(s.manifest_format()))) - } - Ok(()) + }; + Ok(if self.yaml_stream { + Box::new(YamlStreamFormat(format)) + } else { + format + }) } } --- a/crates/jrsonnet-cli/src/stdlib.rs +++ b/crates/jrsonnet-cli/src/stdlib.rs @@ -60,8 +60,6 @@ pub struct StdOpts { /// Disable standard library. /// By default standard library will be available via global `std` variable. - /// Note that standard library will still be loaded - /// if chosen manifestification method is not `none`. #[clap(long)] no_stdlib: bool, /// Add string external variable. --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -149,10 +149,6 @@ pub context_initializer: TraceBox, /// Used to resolve file locations/contents pub import_resolver: TraceBox, - /// Used in manifestification functions - pub manifest_format: ManifestFormat, - /// Used for bindings - pub trace_format: TraceBox, } impl Default for EvaluationSettings { fn default() -> Self { @@ -447,26 +443,5 @@ } pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> { Ref::map(self.settings(), |s| &*s.context_initializer) - } - - pub fn manifest_format(&self) -> ManifestFormat { - self.settings().manifest_format.clone() - } - pub fn set_manifest_format(&self, format: ManifestFormat) { - self.settings_mut().manifest_format = format; - } - - pub fn trace_format(&self) -> Ref<'_, dyn TraceFormat> { - Ref::map(self.settings(), |s| &*s.trace_format) - } - pub fn set_trace_format(&self, format: impl TraceFormat) { - self.settings_mut().trace_format = tb!(format); - } - - pub fn max_trace(&self) -> usize { - self.settings().max_trace - } - pub fn set_max_trace(&self, trace: usize) { - self.settings_mut().max_trace = trace; } } --- a/crates/jrsonnet-evaluator/src/stdlib/manifest.rs +++ b/crates/jrsonnet-evaluator/src/stdlib/manifest.rs @@ -1,6 +1,8 @@ +use std::{borrow::Cow, fmt::Write}; + use crate::{ error::{Error::*, Result}, - throw, State, Val, + throw, ManifestFormat, State, Val, }; #[derive(PartialEq, Eq, Clone, Copy)] @@ -16,16 +18,88 @@ Minify, } -pub struct ManifestJsonOptions<'s> { - pub padding: &'s str, - pub mtype: ManifestType, - pub newline: &'s str, - pub key_val_sep: &'s str, +pub struct JsonFormat<'s> { + padding: Cow<'s, str>, + mtype: ManifestType, + newline: &'s str, + key_val_sep: &'s str, #[cfg(feature = "exp-preserve-order")] - pub preserve_order: bool, + preserve_order: bool, } -pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result { +impl<'s> JsonFormat<'s> { + // Minifying format + pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self { + Self { + padding: Cow::Borrowed(""), + mtype: ManifestType::Minify, + newline: "\n", + key_val_sep: ":", + #[cfg(feature = "exp-preserve-order")] + preserve_order, + } + } + // Same format as std.toString + pub fn std_to_string() -> Self { + Self { + padding: Cow::Borrowed(""), + mtype: ManifestType::ToString, + newline: "\n", + key_val_sep: ": ", + #[cfg(feature = "exp-preserve-order")] + preserve_order: false, + } + } + pub fn std_to_json( + padding: String, + newline: &'s str, + key_val_sep: &'s str, + #[cfg(feature = "exp-preserve-order")] preserve_order: bool, + ) -> Self { + Self { + padding: Cow::Owned(padding), + mtype: ManifestType::Std, + newline, + key_val_sep, + #[cfg(feature = "exp-preserve-order")] + preserve_order, + } + } + // Same format as CLI manifestification + pub fn cli( + padding: usize, + #[cfg(feature = "exp-preserve-order")] preserve_order: bool, + ) -> Self { + if padding == 0 { + return Self::minify( + #[cfg(feature = "exp-preserve-order")] + preserve_order, + ); + } + Self { + padding: Cow::Owned(" ".repeat(padding)), + mtype: ManifestType::Manifest, + newline: "\n", + key_val_sep: ": ", + #[cfg(feature = "exp-preserve-order")] + preserve_order, + } + } +} +impl Default for JsonFormat<'static> { + fn default() -> Self { + Self { + padding: Cow::Borrowed(" "), + mtype: ManifestType::Manifest, + newline: "\n", + key_val_sep: ": ", + #[cfg(feature = "exp-preserve-order")] + preserve_order: false, + } + } +} + +pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result { let mut out = String::new(); manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?; Ok(out) @@ -34,9 +108,8 @@ val: &Val, buf: &mut String, cur_padding: &mut String, - options: &ManifestJsonOptions<'_>, + options: &JsonFormat<'_>, ) -> Result<()> { - use std::fmt::Write; let mtype = options.mtype; match val { Val::Bool(v) => { @@ -57,7 +130,7 @@ } let old_len = cur_padding.len(); - cur_padding.push_str(options.padding); + cur_padding.push_str(&options.padding); for (i, item) in items.iter().enumerate() { if i != 0 { buf.push(','); @@ -97,7 +170,7 @@ } let old_len = cur_padding.len(); - cur_padding.push_str(options.padding); + cur_padding.push_str(&options.padding); for (i, field) in fields.into_iter().enumerate() { if i != 0 { buf.push(','); @@ -138,6 +211,48 @@ Ok(()) } +impl ManifestFormat for JsonFormat<'_> { + fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> { + manifest_json_ex_buf(&val, buf, &mut String::new(), &self) + } +} + +pub struct ToStringFormat; +impl ManifestFormat for ToStringFormat { + fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> { + JsonFormat::std_to_string().manifest_buf(val, out) + } +} +pub struct StringFormat; +impl ManifestFormat for StringFormat { + fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> { + let Val::Str(s) = val else { + throw!("output should be string for string manifest format, got {}", val.value_type()) + }; + out.write_str(&s).unwrap(); + Ok(()) + } +} + +pub struct YamlStreamFormat(pub I); +impl ManifestFormat for YamlStreamFormat { + fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> { + let Val::Arr(arr) = val else { + throw!("output should be array for yaml stream format, got {}", val.value_type()) + }; + if !arr.is_empty() { + for v in arr.iter() { + let v = v?; + out.push_str("---\n"); + self.0.manifest_buf(v, out)?; + out.push('\n'); + } + out.push_str("..."); + } + Ok(()) + } +} + pub fn escape_string_json(s: &str) -> String { let mut buf = String::new(); escape_string_json_buf(s, &mut buf); @@ -145,7 +260,6 @@ } fn escape_string_json_buf(s: &str, buf: &mut String) { - use std::fmt::Write; buf.push('"'); for c in s.chars() { match c { @@ -165,33 +279,66 @@ buf.push('"'); } -pub struct ManifestYamlOptions<'s> { +pub struct YamlFormat<'s> { /// Padding before fields, i.e /// ```yaml /// a: /// b: /// ## <- this /// ``` - pub padding: &'s str, + padding: Cow<'s, str>, /// Padding before array elements in objects /// ```yaml /// a: /// - 1 /// ## <- this /// ``` - pub arr_element_padding: &'s str, + arr_element_padding: Cow<'s, str>, /// Should yaml keys appear unescaped, when possible /// ```yaml /// "safe_key": 1 /// # vs /// safe_key: 1 /// ``` - pub quote_keys: bool, + quote_keys: bool, /// If true - then order of fields is preserved as written, /// instead of sorting alphabetically #[cfg(feature = "exp-preserve-order")] - pub preserve_order: bool, + preserve_order: bool, } +impl YamlFormat<'_> { + pub fn cli( + padding: usize, + #[cfg(feature = "exp-preserve-order")] preserve_order: bool, + ) -> Self { + let padding = " ".repeat(padding); + Self { + padding: Cow::Owned(padding.clone()), + arr_element_padding: Cow::Owned(padding), + quote_keys: false, + #[cfg(feature = "exp-preserve-order")] + preserve_order, + } + } + pub fn std_to_yaml( + indent_array_in_object: bool, + quote_keys: bool, + #[cfg(feature = "exp-preserve-order")] preserve_order: bool, + ) -> Self { + Self { + padding: Cow::Borrowed(" "), + arr_element_padding: Cow::Borrowed(if indent_array_in_object { " " } else { "" }), + quote_keys, + #[cfg(feature = "exp-preserve-order")] + preserve_order, + } + } +} +impl ManifestFormat for YamlFormat<'_> { + fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> { + manifest_yaml_ex_buf(&val, buf, &mut String::new(), self) + } +} /// From /// With added date check @@ -221,7 +368,7 @@ || string.parse::().is_ok() } -pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result { +pub fn manifest_yaml_ex(val: &Val, options: &YamlFormat<'_>) -> Result { let mut out = String::new(); manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?; Ok(out) @@ -232,9 +379,8 @@ val: &Val, buf: &mut String, cur_padding: &mut String, - options: &ManifestYamlOptions<'_>, + options: &YamlFormat<'_>, ) -> Result<()> { - use std::fmt::Write; match val { Val::Bool(v) => { if *v { @@ -252,7 +398,7 @@ for line in s.split('\n') { buf.push('\n'); buf.push_str(cur_padding); - buf.push_str(options.padding); + buf.push_str(&options.padding); buf.push_str(line); } } else if !options.quote_keys && !yaml_needs_quotes(s) { @@ -277,7 +423,7 @@ Val::Arr(a) if !a.is_empty() => { buf.push('\n'); buf.push_str(cur_padding); - buf.push_str(options.padding); + buf.push_str(&options.padding); } _ => buf.push(' '), } @@ -288,7 +434,7 @@ }; let prev_len = cur_padding.len(); if extra_padding { - cur_padding.push_str(options.padding); + cur_padding.push_str(&options.padding); } manifest_yaml_ex_buf(&item, buf, cur_padding, options)?; cur_padding.truncate(prev_len); @@ -323,14 +469,14 @@ Val::Arr(a) if !a.is_empty() => { buf.push('\n'); buf.push_str(cur_padding); - buf.push_str(options.arr_element_padding); - cur_padding.push_str(options.arr_element_padding); + buf.push_str(&options.arr_element_padding); + cur_padding.push_str(&options.arr_element_padding); } Val::Obj(o) if !o.is_empty() => { buf.push('\n'); buf.push_str(cur_padding); - buf.push_str(options.padding); - cur_padding.push_str(options.padding); + buf.push_str(&options.padding); + cur_padding.push_str(&options.padding); } _ => buf.push(' '), } --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, fmt::Debug, rc::Rc}; +use std::{cell::RefCell, fmt::Debug}; use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::{IBytes, IStr}; @@ -8,9 +8,6 @@ error::{Error::*, LocError}, function::FuncVal, gc::{GcHashMap, TraceBox}, - stdlib::manifest::{ - manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions, - }, throw, typed::BoundedUsize, ObjValue, Result, Unbound, WeakObjValue, @@ -122,34 +119,32 @@ } } -#[derive(Clone, Trace)] -pub enum ManifestFormat { - YamlStream(Box), - Yaml { - padding: usize, - #[cfg(feature = "exp-preserve-order")] - preserve_order: bool, - }, - Json { - padding: usize, - #[cfg(feature = "exp-preserve-order")] - preserve_order: bool, - }, - ToString, - String, +pub trait ManifestFormat { + fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>; + fn manifest(&self, val: Val) -> Result { + let mut out = String::new(); + self.manifest_buf(val, &mut out)?; + Ok(out) + } } -impl ManifestFormat { - #[cfg(feature = "exp-preserve-order")] - fn preserve_order(&self) -> bool { - match self { - ManifestFormat::YamlStream(s) => s.preserve_order(), - ManifestFormat::Yaml { preserve_order, .. } => *preserve_order, - ManifestFormat::Json { preserve_order, .. } => *preserve_order, - ManifestFormat::ToString => false, - ManifestFormat::String => false, - } +impl ManifestFormat for Box +where + T: ManifestFormat + ?Sized, +{ + fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> { + let inner = &**self; + inner.manifest_buf(val, buf) } } +impl ManifestFormat for &'_ T +where + T: ManifestFormat + ?Sized, +{ + fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> { + let inner = &**self; + inner.manifest_buf(val, buf) + } +} #[derive(Debug, Clone, Trace)] pub struct Slice { @@ -641,172 +636,25 @@ } } + pub fn manifest(&self, format: impl ManifestFormat) -> Result { + fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result { + manifest.manifest(val.clone()) + } + manifest_dyn(self, &format) + } + pub fn to_string(&self) -> Result { Ok(match self { Self::Bool(true) => "true".into(), Self::Bool(false) => "false".into(), Self::Null => "null".into(), Self::Str(s) => s.clone(), - v => manifest_json_ex( - v, - &ManifestJsonOptions { - padding: "", - mtype: ManifestType::ToString, - newline: "\n", - key_val_sep: ": ", - #[cfg(feature = "exp-preserve-order")] - preserve_order: false, - }, - )? - .into(), - }) - } - - /// Expects value to be object, outputs (key, manifested value) pairs - pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result> { - let Self::Obj(obj) = self else { - throw!(MultiManifestOutputIsNotAObject); - }; - let keys = obj.fields( - #[cfg(feature = "exp-preserve-order")] - ty.preserve_order(), - ); - let mut out = Vec::with_capacity(keys.len()); - for key in keys { - let value = obj - .get(key.clone())? - .expect("item in object") - .manifest(ty)?; - out.push((key, value)); - } - Ok(out) - } - - /// Expects value to be array, outputs manifested values - pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result> { - let Self::Arr(arr) = self else { - throw!(StreamManifestOutputIsNotAArray); - }; - let mut out = Vec::with_capacity(arr.len()); - for i in arr.iter() { - out.push(i?.manifest(ty)?); - } - Ok(out) - } - - pub fn manifest(&self, ty: &ManifestFormat) -> Result { - Ok(match ty { - ManifestFormat::YamlStream(format) => { - let Self::Arr(arr) = self else { - throw!(StreamManifestOutputIsNotAArray) - }; - let mut out = String::new(); - - match format as &ManifestFormat { - ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed), - ManifestFormat::String => throw!(StreamManifestCannotNestString), - _ => {} - }; - - if !arr.is_empty() { - for v in arr.iter() { - out.push_str("---\n"); - out.push_str(&v?.manifest(format)?); - out.push('\n'); - } - out.push_str("..."); - } - - out.into() - } - ManifestFormat::Yaml { - padding, - #[cfg(feature = "exp-preserve-order")] - preserve_order, - } => self.to_yaml( - *padding, - #[cfg(feature = "exp-preserve-order")] - *preserve_order, - )?, - ManifestFormat::Json { - padding, - #[cfg(feature = "exp-preserve-order")] - preserve_order, - } => self.to_json( - *padding, - #[cfg(feature = "exp-preserve-order")] - *preserve_order, - )?, - ManifestFormat::ToString => self.to_string()?, - ManifestFormat::String => match self { - Self::Str(s) => s.clone(), - _ => throw!(StringManifestOutputIsNotAString), - }, + _ => self + .manifest(crate::stdlib::manifest::ToStringFormat) + .map(IStr::from)?, }) - } - - /// For manifestification - pub fn to_json( - &self, - padding: usize, - #[cfg(feature = "exp-preserve-order")] preserve_order: bool, - ) -> Result { - manifest_json_ex( - self, - &ManifestJsonOptions { - padding: &" ".repeat(padding), - mtype: if padding == 0 { - ManifestType::Minify - } else { - ManifestType::Manifest - }, - newline: "\n", - key_val_sep: ": ", - #[cfg(feature = "exp-preserve-order")] - preserve_order, - }, - ) - .map(Into::into) } - /// Calls `std.manifestJson` - pub fn to_std_json( - &self, - padding: usize, - #[cfg(feature = "exp-preserve-order")] preserve_order: bool, - ) -> Result> { - manifest_json_ex( - self, - &ManifestJsonOptions { - padding: &" ".repeat(padding), - mtype: ManifestType::Std, - newline: "\n", - key_val_sep: ": ", - #[cfg(feature = "exp-preserve-order")] - preserve_order, - }, - ) - .map(Into::into) - } - - pub fn to_yaml( - &self, - padding: usize, - #[cfg(feature = "exp-preserve-order")] preserve_order: bool, - ) -> Result { - let padding = &" ".repeat(padding); - manifest_yaml_ex( - self, - &ManifestYamlOptions { - padding, - arr_element_padding: padding, - quote_keys: false, - #[cfg(feature = "exp-preserve-order")] - preserve_order, - }, - ) - .map(Into::into) - } pub fn into_indexable(self) -> Result { Ok(match self { Val::Str(s) => IndexableVal::Str(s), --- a/crates/jrsonnet-stdlib/src/manifest.rs +++ b/crates/jrsonnet-stdlib/src/manifest.rs @@ -1,10 +1,7 @@ use jrsonnet_evaluator::{ error::Result, function::builtin, - stdlib::manifest::{ - escape_string_json, manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, - ManifestYamlOptions, - }, + stdlib::manifest::{escape_string_json, JsonFormat, YamlFormat}, typed::Any, IStr, }; @@ -24,17 +21,13 @@ ) -> Result { let newline = newline.as_deref().unwrap_or("\n"); let key_val_sep = key_val_sep.as_deref().unwrap_or(": "); - manifest_json_ex( - &value.0, - &ManifestJsonOptions { - padding: &indent, - mtype: ManifestType::Std, - newline, - key_val_sep, - #[cfg(feature = "exp-preserve-order")] - preserve_order: preserve_order.unwrap_or(false), - }, - ) + value.0.manifest(JsonFormat::std_to_json( + indent.to_string(), + newline, + key_val_sep, + #[cfg(feature = "exp-preserve-order")] + preserve_order.unwrap_or(false), + )) } #[builtin] @@ -44,18 +37,10 @@ quote_keys: Option, #[cfg(feature = "exp-preserve-order")] preserve_order: Option, ) -> Result { - manifest_yaml_ex( - &value.0, - &ManifestYamlOptions { - padding: " ", - arr_element_padding: if indent_array_in_object.unwrap_or(false) { - " " - } else { - "" - }, - quote_keys: quote_keys.unwrap_or(true), - #[cfg(feature = "exp-preserve-order")] - preserve_order: preserve_order.unwrap_or(false), - }, - ) + value.0.manifest(YamlFormat::std_to_yaml( + indent_array_in_object.unwrap_or(false), + quote_keys.unwrap_or(true), + #[cfg(feature = "exp-preserve-order")] + preserve_order.unwrap_or(false), + )) }