1use crate::{2 builtin::manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType},3 error::Error::*,4 evaluate,5 function::{parse_function_call, parse_function_call_map, place_args},6 native::NativeCallback,7 throw, with_state, Context, ObjValue, Result,8};9use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParamsDesc};10use std::{11 cell::RefCell,12 collections::HashMap,13 fmt::{Debug, Display},14 rc::Rc,15};1617enum LazyValInternals {18 Computed(Val),19 Waiting(Box<dyn Fn() -> Result<Val>>),20}21#[derive(Clone)]22pub struct LazyVal(Rc<RefCell<LazyValInternals>>);23impl LazyVal {24 pub fn new(f: Box<dyn Fn() -> Result<Val>>) -> Self {25 LazyVal(Rc::new(RefCell::new(LazyValInternals::Waiting(f))))26 }27 pub fn new_resolved(val: Val) -> Self {28 LazyVal(Rc::new(RefCell::new(LazyValInternals::Computed(val))))29 }30 pub fn evaluate(&self) -> Result<Val> {31 let new_value = match &*self.0.borrow() {32 LazyValInternals::Computed(v) => return Ok(v.clone()),33 LazyValInternals::Waiting(f) => f()?,34 };35 *self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());36 Ok(new_value)37 }38}3940#[macro_export]41macro_rules! lazy_val {42 ($f: expr) => {43 $crate::LazyVal::new(Box::new($f))44 };45}46#[macro_export]47macro_rules! resolved_lazy_val {48 ($f: expr) => {49 $crate::LazyVal::new_resolved($f)50 };51}52impl Debug for LazyVal {53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {54 write!(f, "Lazy")55 }56}57impl PartialEq for LazyVal {58 fn eq(&self, other: &Self) -> bool {59 Rc::ptr_eq(&self.0, &other.0)60 }61}6263#[derive(Debug, PartialEq)]64pub struct FuncDesc {65 pub name: Rc<str>,66 pub ctx: Context,67 pub params: ParamsDesc,68 pub body: LocExpr,69}70impl FuncDesc {71 72 pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result<Val> {73 let ctx = parse_function_call(74 call_ctx,75 Some(self.ctx.clone()),76 &self.params,77 args,78 tailstrict,79 )?;80 evaluate(ctx, &self.body)81 }8283 pub fn evaluate_map(84 &self,85 call_ctx: Context,86 args: &HashMap<Rc<str>, Val>,87 tailstrict: bool,88 ) -> Result<Val> {89 let ctx = parse_function_call_map(90 call_ctx,91 Some(self.ctx.clone()),92 &self.params,93 args,94 tailstrict,95 )?;96 evaluate(ctx, &self.body)97 }9899 pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result<Val> {100 let ctx = place_args(call_ctx, Some(self.ctx.clone()), &self.params, args)?;101 evaluate(ctx, &self.body)102 }103}104105#[derive(Debug, Clone, Copy, PartialEq)]106pub enum ValType {107 Bool,108 Null,109 Str,110 Num,111 Arr,112 Obj,113 Func,114}115impl ValType {116 pub fn name(&self) -> &'static str {117 use ValType::*;118 match self {119 Bool => "boolean",120 Null => "null",121 Str => "string",122 Num => "number",123 Arr => "array",124 Obj => "object",125 Func => "function",126 }127 }128}129impl Display for ValType {130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {131 write!(f, "{}", self.name())132 }133}134135#[derive(Clone)]136pub enum ManifestFormat {137 YamlStream(Box<ManifestFormat>),138 Yaml(usize),139 Json(usize),140 String,141}142143#[derive(Debug, Clone)]144pub enum Val {145 Bool(bool),146 Null,147 Str(Rc<str>),148 Num(f64),149 Lazy(LazyVal),150 Arr(Rc<Vec<Val>>),151 Obj(ObjValue),152 Func(Rc<FuncDesc>),153154 155 Intristic(Rc<str>, Rc<str>),156 NativeExt(Rc<str>, Rc<NativeCallback>),157}158macro_rules! matches_unwrap {159 ($e: expr, $p: pat, $r: expr) => {160 match $e {161 $p => $r,162 _ => panic!("no match"),163 }164 };165}166impl Val {167 168 pub fn new_checked_num(num: f64) -> Result<Val> {169 if num.is_finite() {170 Ok(Val::Num(num))171 } else {172 throw!(RuntimeError("overflow".into()))173 }174 }175176 pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {177 let this_type = self.value_type()?;178 if this_type != val_type {179 throw!(TypeMismatch(context, vec![val_type], this_type))180 } else {181 Ok(())182 }183 }184 pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {185 self.assert_type(context, ValType::Bool)?;186 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))187 }188 pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {189 self.assert_type(context, ValType::Str)?;190 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))191 }192 pub fn try_cast_num(self, context: &'static str) -> Result<f64> {193 self.assert_type(context, ValType::Num)?;194 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))195 }196 pub fn unwrap_if_lazy(&self) -> Result<Self> {197 Ok(if let Val::Lazy(v) = self {198 v.evaluate()?.unwrap_if_lazy()?199 } else {200 self.clone()201 })202 }203 pub fn value_type(&self) -> Result<ValType> {204 Ok(match self {205 Val::Str(..) => ValType::Str,206 Val::Num(..) => ValType::Num,207 Val::Arr(..) => ValType::Arr,208 Val::Obj(..) => ValType::Obj,209 Val::Bool(_) => ValType::Bool,210 Val::Null => ValType::Null,211 Val::Func(..) | Val::Intristic(_, _) | Val::NativeExt(_, _) => ValType::Func,212 Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,213 })214 }215216 pub fn to_string(&self) -> Result<Rc<str>> {217 Ok(match self.unwrap_if_lazy()? {218 Val::Bool(true) => "true".into(),219 Val::Bool(false) => "false".into(),220 Val::Null => "null".into(),221 Val::Str(s) => s,222 v => manifest_json_ex(223 &v,224 &ManifestJsonOptions {225 padding: &"",226 mtype: ManifestType::ToString,227 },228 )?229 .into(),230 })231 }232233 234 pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result<Vec<(Rc<str>, Rc<str>)>> {235 let obj = match self {236 Val::Obj(obj) => obj,237 _ => throw!(MultiManifestOutputIsNotAObject),238 };239 let keys = obj.visible_fields();240 let mut out = Vec::with_capacity(keys.len());241 for key in keys {242 let value = obj243 .get(key.clone())?244 .expect("item in object")245 .manifest(ty)?;246 out.push((key, value));247 }248 Ok(out)249 }250251 252 pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result<Vec<Rc<str>>> {253 let arr = match self {254 Val::Arr(a) => a,255 _ => throw!(StreamManifestOutputIsNotAArray),256 };257 let mut out = Vec::with_capacity(arr.len());258 for i in arr.iter() {259 out.push(i.manifest(ty)?);260 }261 Ok(out)262 }263264 pub fn manifest(&self, ty: &ManifestFormat) -> Result<Rc<str>> {265 Ok(match ty {266 ManifestFormat::YamlStream(format) => {267 let arr = match self {268 Val::Arr(a) => a,269 _ => throw!(StreamManifestOutputIsNotAArray),270 };271 let mut out = String::new();272273 match format as &ManifestFormat {274 ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),275 ManifestFormat::String => throw!(StreamManifestCannotNestString),276 _ => {}277 };278279 if !arr.is_empty() {280 for v in arr.iter() {281 out.push_str("---\n");282 out.push_str(&v.manifest(format)?);283 out.push_str("\n");284 }285 out.push_str("...");286 }287288 out.into()289 }290 ManifestFormat::Yaml(padding) => self.to_yaml(*padding)?,291 ManifestFormat::Json(padding) => self.to_json(*padding)?,292 ManifestFormat::String => match self {293 Val::Str(s) => s.clone(),294 _ => throw!(StringManifestOutputIsNotAString),295 },296 })297 }298299 300 pub fn to_json(&self, padding: usize) -> Result<Rc<str>> {301 manifest_json_ex(302 self,303 &ManifestJsonOptions {304 padding: &" ".repeat(padding),305 mtype: if padding == 0 {306 ManifestType::Minify307 } else {308 ManifestType::Manifest309 },310 },311 )312 .map(|s| s.into())313 }314315 316 #[cfg(feature = "faster")]317 pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {318 manifest_json_ex(319 &self,320 &ManifestJsonOptions {321 padding: &" ".repeat(padding),322 mtype: ManifestType::Std,323 },324 )325 .map(|s| s.into())326 }327328 329 #[cfg(not(feature = "faster"))]330 pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {331 with_state(|s| {332 let ctx = s333 .create_default_context()?334 .with_var("__tmp__to_json__".into(), self.clone())?;335 Ok(evaluate(336 ctx,337 &el!(Expr::Apply(338 el!(Expr::Index(339 el!(Expr::Var("std".into())),340 el!(Expr::Str("manifestJsonEx".into()))341 )),342 ArgsDesc(vec![343 Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),344 Arg(None, el!(Expr::Str(" ".repeat(padding).into())))345 ]),346 false347 )),348 )?349 .try_cast_str("to json")?)350 })351 }352 pub fn to_yaml(&self, padding: usize) -> Result<Rc<str>> {353 with_state(|s| {354 let ctx = s355 .create_default_context()?356 .with_var("__tmp__to_json__".into(), self.clone());357 Ok(evaluate(358 ctx,359 &el!(Expr::Apply(360 el!(Expr::Index(361 el!(Expr::Var("std".into())),362 el!(Expr::Str("manifestYamlDoc".into()))363 )),364 ArgsDesc(vec![365 Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),366 Arg(None, el!(Expr::Str(" ".repeat(padding).into())))367 ]),368 false369 )),370 )?371 .try_cast_str("to json")?)372 })373 }374}375376fn is_function_like(val: &Val) -> bool {377 matches!(val, Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _))378}379380381pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {382 Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {383 (Val::Bool(a), Val::Bool(b)) => a == b,384 (Val::Null, Val::Null) => true,385 (Val::Str(a), Val::Str(b)) => a == b,386 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,387 (Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(388 "primitiveEquals operates on primitive types, got array".into(),389 )),390 (Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(391 "primitiveEquals operates on primitive types, got object".into(),392 )),393 (a, b) if is_function_like(&a) && is_function_like(&b) => {394 throw!(RuntimeError("cannot test equality of functions".into()))395 }396 (_, _) => false,397 })398}399400401pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {402 let val_a = val_a.unwrap_if_lazy()?;403 let val_b = val_b.unwrap_if_lazy()?;404405 if val_a.value_type()? != val_b.value_type()? {406 return Ok(false);407 }408 match (val_a, val_b) {409 410 (Val::Arr(a), Val::Arr(b)) => {411 if a.len() != b.len() {412 return Ok(false);413 }414 for (a, b) in a.iter().zip(b.iter()) {415 if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {416 return Ok(false);417 }418 }419 Ok(true)420 }421 (Val::Obj(a), Val::Obj(b)) => {422 let fields = a.visible_fields();423 if fields != b.visible_fields() {424 return Ok(false);425 }426 for field in fields {427 if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {428 return Ok(false);429 }430 }431 Ok(true)432 }433 (a, b) => Ok(primitive_equals(&a, &b)?),434 }435}