difftreelog
refactor(evaluator) extract json to module
in: master
4 files changed
crates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs
@@ -0,0 +1,150 @@
+use crate::error::Error::*;
+use crate::error::Result;
+use crate::{throw, Val};
+
+#[derive(PartialEq)]
+pub enum ManifestType {
+ // Applied in manifestification
+ Manifest,
+ /// Used for std.manifestJson
+ /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest
+ Std,
+ // No line breaks, used in `obj+''`
+ ToString,
+}
+
+pub struct ManifestJsonOptions<'s> {
+ pub padding: &'s str,
+ pub mtype: ManifestType,
+}
+
+pub(crate) fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {
+ let mut out = String::new();
+ manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;
+ Ok(out)
+}
+fn manifest_json_ex_buf(
+ val: &Val,
+ buf: &mut String,
+ cur_padding: &mut String,
+ options: &ManifestJsonOptions<'_>,
+) -> Result<()> {
+ use std::fmt::Write;
+ match val.unwrap_if_lazy()? {
+ Val::Bool(v) => {
+ if v {
+ buf.push_str("true");
+ } else {
+ buf.push_str("false");
+ }
+ }
+ Val::Null => buf.push_str("null"),
+ Val::Str(s) => buf.push_str(&escape_string_json(&s)),
+ Val::Num(n) => write!(buf, "{}", n).unwrap(),
+ Val::Arr(items) => {
+ buf.push('[');
+ if !items.is_empty() {
+ if options.mtype != ManifestType::ToString {
+ buf.push('\n');
+ }
+
+ let old_len = cur_padding.len();
+ cur_padding.push_str(options.padding);
+ for (i, item) in items.iter().enumerate() {
+ if i != 0 {
+ buf.push(',');
+ if options.mtype == ManifestType::ToString {
+ buf.push(' ');
+ } else {
+ buf.push('\n');
+ }
+ }
+ buf.push_str(cur_padding);
+ manifest_json_ex_buf(item, buf, cur_padding, options)?;
+ }
+ cur_padding.truncate(old_len);
+
+ if options.mtype != ManifestType::ToString {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ }
+ } else if options.mtype == ManifestType::Std {
+ buf.push_str("\n\n");
+ buf.push_str(cur_padding);
+ } else if options.mtype == ManifestType::ToString {
+ buf.push(' ');
+ }
+ buf.push(']');
+ }
+ Val::Obj(obj) => {
+ buf.push('{');
+ let fields = obj.visible_fields();
+ if !fields.is_empty() {
+ if options.mtype != ManifestType::ToString {
+ buf.push('\n');
+ }
+
+ let old_len = cur_padding.len();
+ cur_padding.push_str(options.padding);
+ for (i, field) in fields.into_iter().enumerate() {
+ if i != 0 {
+ buf.push(',');
+ if options.mtype == ManifestType::ToString {
+ buf.push(' ');
+ } else {
+ buf.push('\n');
+ }
+ }
+ buf.push_str(cur_padding);
+ buf.push_str(&escape_string_json(&field));
+ buf.push_str(": ");
+ manifest_json_ex_buf(&obj.get(field)?.unwrap(), buf, cur_padding, options)?;
+ }
+ cur_padding.truncate(old_len);
+
+ if options.mtype != ManifestType::ToString {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ }
+ } else if options.mtype == ManifestType::Std {
+ buf.push_str("\n\n");
+ buf.push_str(cur_padding);
+ } else if options.mtype == ManifestType::ToString {
+ buf.push(' ');
+ }
+ buf.push('}');
+ }
+ Val::Func(_) | Val::Intristic(_, _) => {
+ throw!(RuntimeError("tried to manifest function".into()))
+ }
+ Val::Lazy(_) => unreachable!(),
+ };
+ Ok(())
+}
+pub fn escape_string_json(s: &str) -> String {
+ use std::fmt::Write;
+ let mut out = String::new();
+ out.push('"');
+ for c in s.chars() {
+ match c {
+ '"' => out.push_str("\\\""),
+ '\\' => out.push_str("\\\\"),
+ '\u{0008}' => out.push_str("\\b"),
+ '\u{000c}' => out.push_str("\\f"),
+ '\n' => out.push_str("\\n"),
+ '\r' => out.push_str("\\r"),
+ '\t' => out.push_str("\\t"),
+ c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {
+ write!(out, "\\u{:04x}", c as u32).unwrap()
+ }
+ c => out.push(c),
+ }
+ }
+ out.push('"');
+ out
+}
+
+#[test]
+fn json_test() {
+ assert_eq!(escape_string_json("\u{001f}"), "\"\\u001f\"")
+}
crates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -2,3 +2,4 @@
pub use stdlib::*;
pub mod format;
+pub mod manifest;
crates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate.rs
@@ -1,12 +1,13 @@
use crate::{
- builtin::format::{format_arr, format_obj},
+ builtin::{
+ format::{format_arr, format_obj},
+ manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType},
+ },
context_creator, equals,
error::Error::*,
- escape_string_json, future_wrapper, lazy_val, manifest_json_ex, parse_args, primitive_equals,
- push, throw,
- val::ManifestJsonOptions,
- with_state, Context, ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError, ManifestType,
- ObjMember, ObjValue, Result, Val, ValType,
+ future_wrapper, lazy_val, parse_args, primitive_equals, push, throw, with_state, Context,
+ ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError, ObjMember, ObjValue, Result, Val,
+ ValType,
};
use closure::closure;
use jrsonnet_parser::{
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use crate::{2 error::Error::*,3 evaluate,4 function::{parse_function_call, parse_function_call_map, place_args},5 throw, with_state, Context, ObjValue, Result,6};7use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParamsDesc};8use std::{9 cell::RefCell,10 collections::HashMap,11 fmt::{Debug, Display},12 rc::Rc,13};1415enum LazyValInternals {16 Computed(Val),17 Waiting(Box<dyn Fn() -> Result<Val>>),18}19#[derive(Clone)]20pub struct LazyVal(Rc<RefCell<LazyValInternals>>);21impl LazyVal {22 pub fn new(f: Box<dyn Fn() -> Result<Val>>) -> Self {23 LazyVal(Rc::new(RefCell::new(LazyValInternals::Waiting(f))))24 }25 pub fn new_resolved(val: Val) -> Self {26 LazyVal(Rc::new(RefCell::new(LazyValInternals::Computed(val))))27 }28 pub fn evaluate(&self) -> Result<Val> {29 let new_value = match &*self.0.borrow() {30 LazyValInternals::Computed(v) => return Ok(v.clone()),31 LazyValInternals::Waiting(f) => f()?,32 };33 *self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());34 Ok(new_value)35 }36}3738#[macro_export]39macro_rules! lazy_val {40 ($f: expr) => {41 $crate::LazyVal::new(Box::new($f))42 };43}44#[macro_export]45macro_rules! resolved_lazy_val {46 ($f: expr) => {47 $crate::LazyVal::new_resolved($f)48 };49}50impl Debug for LazyVal {51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {52 write!(f, "Lazy")53 }54}55impl PartialEq for LazyVal {56 fn eq(&self, other: &Self) -> bool {57 Rc::ptr_eq(&self.0, &other.0)58 }59}6061#[derive(Debug, PartialEq)]62pub struct FuncDesc {63 pub name: Rc<str>,64 pub ctx: Context,65 pub params: ParamsDesc,66 pub body: LocExpr,67}68impl FuncDesc {69 /// This function is always inlined to make tailstrict work70 pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result<Val> {71 let ctx = parse_function_call(72 call_ctx,73 Some(self.ctx.clone()),74 &self.params,75 args,76 tailstrict,77 )?;78 evaluate(ctx, &self.body)79 }8081 pub fn evaluate_map(82 &self,83 call_ctx: Context,84 args: &HashMap<Rc<str>, Val>,85 tailstrict: bool,86 ) -> Result<Val> {87 let ctx = parse_function_call_map(88 call_ctx,89 Some(self.ctx.clone()),90 &self.params,91 args,92 tailstrict,93 )?;94 evaluate(ctx, &self.body)95 }9697 pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result<Val> {98 let ctx = place_args(call_ctx, Some(self.ctx.clone()), &self.params, args)?;99 evaluate(ctx, &self.body)100 }101}102103#[derive(Debug, Clone, Copy, PartialEq)]104pub enum ValType {105 Bool,106 Null,107 Str,108 Num,109 Arr,110 Obj,111 Func,112}113impl ValType {114 pub fn name(&self) -> &'static str {115 use ValType::*;116 match self {117 Bool => "boolean",118 Null => "null",119 Str => "string",120 Num => "number",121 Arr => "array",122 Obj => "object",123 Func => "function",124 }125 }126}127impl Display for ValType {128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {129 write!(f, "{}", self.name())130 }131}132133#[derive(Debug, Clone)]134pub enum Val {135 Bool(bool),136 Null,137 Str(Rc<str>),138 Num(f64),139 Lazy(LazyVal),140 Arr(Rc<Vec<Val>>),141 Obj(ObjValue),142 Func(Rc<FuncDesc>),143144 // Library functions implemented in native145 Intristic(Rc<str>, Rc<str>),146}147macro_rules! matches_unwrap {148 ($e: expr, $p: pat, $r: expr) => {149 match $e {150 $p => $r,151 _ => panic!("no match"),152 }153 };154}155impl Val {156 /// Creates Val::Num after checking for overflow. As numbers are f64, we can just check for finity157 pub fn new_checked_num(num: f64) -> Result<Val> {158 if num.is_finite() {159 Ok(Val::Num(num))160 } else {161 throw!(RuntimeError("overflow".into()))162 }163 }164165 pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {166 let this_type = self.value_type()?;167 if this_type != val_type {168 throw!(TypeMismatch(context, vec![val_type], this_type))169 } else {170 Ok(())171 }172 }173 pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {174 self.assert_type(context, ValType::Bool)?;175 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))176 }177 pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {178 self.assert_type(context, ValType::Str)?;179 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))180 }181 pub fn try_cast_num(self, context: &'static str) -> Result<f64> {182 self.assert_type(context, ValType::Num)?;183 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))184 }185 pub fn unwrap_if_lazy(&self) -> Result<Self> {186 Ok(if let Val::Lazy(v) = self {187 v.evaluate()?.unwrap_if_lazy()?188 } else {189 self.clone()190 })191 }192 pub fn value_type(&self) -> Result<ValType> {193 Ok(match self {194 Val::Str(..) => ValType::Str,195 Val::Num(..) => ValType::Num,196 Val::Arr(..) => ValType::Arr,197 Val::Obj(..) => ValType::Obj,198 Val::Func(..) => ValType::Func,199 Val::Bool(_) => ValType::Bool,200 Val::Null => ValType::Null,201 Val::Intristic(_, _) => ValType::Func,202 Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,203 })204 }205206 pub fn into_string(self) -> Result<Rc<str>> {207 Ok(match self.unwrap_if_lazy()? {208 Val::Bool(true) => "true".into(),209 Val::Bool(false) => "false".into(),210 Val::Null => "null".into(),211 Val::Str(s) => s,212 v => manifest_json_ex(213 &v,214 &ManifestJsonOptions {215 padding: &"",216 mtype: ManifestType::ToString,217 },218 )?219 .into(),220 })221 }222223 /// For manifestification224 pub fn into_json(self, padding: usize) -> Result<Rc<str>> {225 manifest_json_ex(226 &self,227 &ManifestJsonOptions {228 padding: &" ".repeat(padding),229 mtype: ManifestType::Manifest,230 },231 )232 .map(|s| s.into())233 }234235 /// Calls std.manifestJson236 #[cfg(feature = "faster")]237 pub fn into_std_json(self, padding: usize) -> Result<Rc<str>> {238 manifest_json_ex(239 &self,240 &ManifestJsonOptions {241 padding: &" ".repeat(padding),242 mtype: ManifestType::Std,243 },244 )245 .map(|s| s.into())246 }247248 /// Calls std.manifestJson249 #[cfg(not(feature = "faster"))]250 pub fn into_std_json(self, padding: usize) -> Result<Rc<str>> {251 with_state(|s| {252 let ctx = s253 .create_default_context()?254 .with_var("__tmp__to_json__".into(), self)?;255 Ok(evaluate(256 ctx,257 &el!(Expr::Apply(258 el!(Expr::Index(259 el!(Expr::Var("std".into())),260 el!(Expr::Str("manifestJsonEx".into()))261 )),262 ArgsDesc(vec![263 Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),264 Arg(None, el!(Expr::Str(" ".repeat(padding).into())))265 ]),266 false267 )),268 )?269 .try_cast_str("to json")?)270 })271 }272 pub fn into_yaml(self, padding: usize) -> Result<Rc<str>> {273 with_state(|s| {274 let ctx = s275 .create_default_context()?276 .with_var("__tmp__to_json__".into(), self);277 Ok(evaluate(278 ctx,279 &el!(Expr::Apply(280 el!(Expr::Index(281 el!(Expr::Var("std".into())),282 el!(Expr::Str("manifestYamlDoc".into()))283 )),284 ArgsDesc(vec![285 Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),286 Arg(None, el!(Expr::Str(" ".repeat(padding).into())))287 ]),288 false289 )),290 )?291 .try_cast_str("to json")?)292 })293 }294}295296fn is_function_like(val: &Val) -> bool {297 matches!(val, Val::Func(_) | Val::Intristic(_, _))298}299300/// Implements std.primitiveEquals builtin301pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {302 Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {303 (Val::Bool(a), Val::Bool(b)) => a == b,304 (Val::Null, Val::Null) => true,305 (Val::Str(a), Val::Str(b)) => a == b,306 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,307 (Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(308 "primitiveEquals operates on primitive types, got array".into(),309 )),310 (Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(311 "primitiveEquals operates on primitive types, got object".into(),312 )),313 (a, b) if is_function_like(&a) && is_function_like(&b) => {314 throw!(RuntimeError("cannot test equality of functions".into()))315 }316 (_, _) => false,317 })318}319320/// Native implementation of std.equals321pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {322 let val_a = val_a.unwrap_if_lazy()?;323 let val_b = val_b.unwrap_if_lazy()?;324325 if val_a.value_type()? != val_b.value_type()? {326 return Ok(false);327 }328 match (val_a, val_b) {329 // Cant test for ptr equality, because all fields needs to be evaluated330 (Val::Arr(a), Val::Arr(b)) => {331 if a.len() != b.len() {332 return Ok(false);333 }334 for (a, b) in a.iter().zip(b.iter()) {335 if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {336 return Ok(false);337 }338 }339 Ok(true)340 }341 (Val::Obj(a), Val::Obj(b)) => {342 let fields = a.visible_fields();343 if fields != b.visible_fields() {344 return Ok(false);345 }346 for field in fields {347 if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {348 return Ok(false);349 }350 }351 Ok(true)352 }353 (a, b) => Ok(primitive_equals(&a, &b)?),354 }355}356357#[derive(PartialEq)]358pub enum ManifestType {359 // Applied in manifestification360 Manifest,361 /// Used for std.manifestJson362 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest363 Std,364 // No line breaks, used in `obj+''`365 ToString,366}367368pub struct ManifestJsonOptions<'s> {369 pub padding: &'s str,370 pub mtype: ManifestType,371}372373pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {374 let mut out = String::new();375 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;376 Ok(out)377}378fn manifest_json_ex_buf(379 val: &Val,380 buf: &mut String,381 cur_padding: &mut String,382 options: &ManifestJsonOptions<'_>,383) -> Result<()> {384 use std::fmt::Write;385 match val.unwrap_if_lazy()? {386 Val::Bool(v) => {387 if v {388 buf.push_str("true");389 } else {390 buf.push_str("false");391 }392 }393 Val::Null => buf.push_str("null"),394 Val::Str(s) => buf.push_str(&escape_string_json(&s)),395 Val::Num(n) => write!(buf, "{}", n).unwrap(),396 Val::Arr(items) => {397 buf.push('[');398 if !items.is_empty() {399 if options.mtype != ManifestType::ToString {400 buf.push('\n');401 }402403 let old_len = cur_padding.len();404 cur_padding.push_str(options.padding);405 for (i, item) in items.iter().enumerate() {406 if i != 0 {407 buf.push(',');408 if options.mtype == ManifestType::ToString {409 buf.push(' ');410 } else {411 buf.push('\n');412 }413 }414 buf.push_str(cur_padding);415 manifest_json_ex_buf(item, buf, cur_padding, options)?;416 }417 cur_padding.truncate(old_len);418419 if options.mtype != ManifestType::ToString {420 buf.push('\n');421 buf.push_str(cur_padding);422 }423 } else if options.mtype == ManifestType::Std {424 buf.push_str("\n\n");425 buf.push_str(cur_padding);426 } else if options.mtype == ManifestType::ToString {427 buf.push(' ');428 }429 buf.push(']');430 }431 Val::Obj(obj) => {432 buf.push('{');433 let fields = obj.visible_fields();434 if !fields.is_empty() {435 if options.mtype != ManifestType::ToString {436 buf.push('\n');437 }438439 let old_len = cur_padding.len();440 cur_padding.push_str(options.padding);441 for (i, field) in fields.into_iter().enumerate() {442 if i != 0 {443 buf.push(',');444 if options.mtype == ManifestType::ToString {445 buf.push(' ');446 } else {447 buf.push('\n');448 }449 }450 buf.push_str(cur_padding);451 buf.push_str(&escape_string_json(&field));452 buf.push_str(": ");453 manifest_json_ex_buf(&obj.get(field)?.unwrap(), buf, cur_padding, options)?;454 }455 cur_padding.truncate(old_len);456457 if options.mtype != ManifestType::ToString {458 buf.push('\n');459 buf.push_str(cur_padding);460 }461 } else if options.mtype == ManifestType::Std {462 buf.push_str("\n\n");463 buf.push_str(cur_padding);464 } else if options.mtype == ManifestType::ToString {465 buf.push(' ');466 }467 buf.push('}');468 }469 Val::Func(_) | Val::Intristic(_, _) => {470 throw!(RuntimeError("tried to manifest function".into()))471 }472 Val::Lazy(_) => unreachable!(),473 };474 Ok(())475}476pub fn escape_string_json(s: &str) -> String {477 use std::fmt::Write;478 let mut out = String::new();479 out.push('"');480 for c in s.chars() {481 match c {482 '"' => out.push_str("\\\""),483 '\\' => out.push_str("\\\\"),484 '\u{0008}' => out.push_str("\\b"),485 '\u{000c}' => out.push_str("\\f"),486 '\n' => out.push_str("\\n"),487 '\r' => out.push_str("\\r"),488 '\t' => out.push_str("\\t"),489 c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {490 write!(out, "\\u{:04x}", c as u32).unwrap()491 }492 c => out.push(c),493 }494 }495 out.push('"');496 out497}498499#[test]500fn json_test() {501 assert_eq!(escape_string_json("\u{001f}"), "\"\\u001f\"")502}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 throw, with_state, Context, ObjValue, Result,7};8use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParamsDesc};9use std::{10 cell::RefCell,11 collections::HashMap,12 fmt::{Debug, Display},13 rc::Rc,14};1516enum LazyValInternals {17 Computed(Val),18 Waiting(Box<dyn Fn() -> Result<Val>>),19}20#[derive(Clone)]21pub struct LazyVal(Rc<RefCell<LazyValInternals>>);22impl LazyVal {23 pub fn new(f: Box<dyn Fn() -> Result<Val>>) -> Self {24 LazyVal(Rc::new(RefCell::new(LazyValInternals::Waiting(f))))25 }26 pub fn new_resolved(val: Val) -> Self {27 LazyVal(Rc::new(RefCell::new(LazyValInternals::Computed(val))))28 }29 pub fn evaluate(&self) -> Result<Val> {30 let new_value = match &*self.0.borrow() {31 LazyValInternals::Computed(v) => return Ok(v.clone()),32 LazyValInternals::Waiting(f) => f()?,33 };34 *self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());35 Ok(new_value)36 }37}3839#[macro_export]40macro_rules! lazy_val {41 ($f: expr) => {42 $crate::LazyVal::new(Box::new($f))43 };44}45#[macro_export]46macro_rules! resolved_lazy_val {47 ($f: expr) => {48 $crate::LazyVal::new_resolved($f)49 };50}51impl Debug for LazyVal {52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {53 write!(f, "Lazy")54 }55}56impl PartialEq for LazyVal {57 fn eq(&self, other: &Self) -> bool {58 Rc::ptr_eq(&self.0, &other.0)59 }60}6162#[derive(Debug, PartialEq)]63pub struct FuncDesc {64 pub name: Rc<str>,65 pub ctx: Context,66 pub params: ParamsDesc,67 pub body: LocExpr,68}69impl FuncDesc {70 /// This function is always inlined to make tailstrict work71 pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result<Val> {72 let ctx = parse_function_call(73 call_ctx,74 Some(self.ctx.clone()),75 &self.params,76 args,77 tailstrict,78 )?;79 evaluate(ctx, &self.body)80 }8182 pub fn evaluate_map(83 &self,84 call_ctx: Context,85 args: &HashMap<Rc<str>, Val>,86 tailstrict: bool,87 ) -> Result<Val> {88 let ctx = parse_function_call_map(89 call_ctx,90 Some(self.ctx.clone()),91 &self.params,92 args,93 tailstrict,94 )?;95 evaluate(ctx, &self.body)96 }9798 pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result<Val> {99 let ctx = place_args(call_ctx, Some(self.ctx.clone()), &self.params, args)?;100 evaluate(ctx, &self.body)101 }102}103104#[derive(Debug, Clone, Copy, PartialEq)]105pub enum ValType {106 Bool,107 Null,108 Str,109 Num,110 Arr,111 Obj,112 Func,113}114impl ValType {115 pub fn name(&self) -> &'static str {116 use ValType::*;117 match self {118 Bool => "boolean",119 Null => "null",120 Str => "string",121 Num => "number",122 Arr => "array",123 Obj => "object",124 Func => "function",125 }126 }127}128impl Display for ValType {129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {130 write!(f, "{}", self.name())131 }132}133134#[derive(Debug, Clone)]135pub enum Val {136 Bool(bool),137 Null,138 Str(Rc<str>),139 Num(f64),140 Lazy(LazyVal),141 Arr(Rc<Vec<Val>>),142 Obj(ObjValue),143 Func(Rc<FuncDesc>),144145 // Library functions implemented in native146 Intristic(Rc<str>, Rc<str>),147}148macro_rules! matches_unwrap {149 ($e: expr, $p: pat, $r: expr) => {150 match $e {151 $p => $r,152 _ => panic!("no match"),153 }154 };155}156impl Val {157 /// Creates Val::Num after checking for overflow. As numbers are f64, we can just check for finity158 pub fn new_checked_num(num: f64) -> Result<Val> {159 if num.is_finite() {160 Ok(Val::Num(num))161 } else {162 throw!(RuntimeError("overflow".into()))163 }164 }165166 pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {167 let this_type = self.value_type()?;168 if this_type != val_type {169 throw!(TypeMismatch(context, vec![val_type], this_type))170 } else {171 Ok(())172 }173 }174 pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {175 self.assert_type(context, ValType::Bool)?;176 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))177 }178 pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {179 self.assert_type(context, ValType::Str)?;180 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))181 }182 pub fn try_cast_num(self, context: &'static str) -> Result<f64> {183 self.assert_type(context, ValType::Num)?;184 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))185 }186 pub fn unwrap_if_lazy(&self) -> Result<Self> {187 Ok(if let Val::Lazy(v) = self {188 v.evaluate()?.unwrap_if_lazy()?189 } else {190 self.clone()191 })192 }193 pub fn value_type(&self) -> Result<ValType> {194 Ok(match self {195 Val::Str(..) => ValType::Str,196 Val::Num(..) => ValType::Num,197 Val::Arr(..) => ValType::Arr,198 Val::Obj(..) => ValType::Obj,199 Val::Func(..) => ValType::Func,200 Val::Bool(_) => ValType::Bool,201 Val::Null => ValType::Null,202 Val::Intristic(_, _) => ValType::Func,203 Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,204 })205 }206207 pub fn into_string(self) -> Result<Rc<str>> {208 Ok(match self.unwrap_if_lazy()? {209 Val::Bool(true) => "true".into(),210 Val::Bool(false) => "false".into(),211 Val::Null => "null".into(),212 Val::Str(s) => s,213 v => manifest_json_ex(214 &v,215 &ManifestJsonOptions {216 padding: &"",217 mtype: ManifestType::ToString,218 },219 )?220 .into(),221 })222 }223224 /// For manifestification225 pub fn into_json(self, padding: usize) -> Result<Rc<str>> {226 manifest_json_ex(227 &self,228 &ManifestJsonOptions {229 padding: &" ".repeat(padding),230 mtype: ManifestType::Manifest,231 },232 )233 .map(|s| s.into())234 }235236 /// Calls std.manifestJson237 #[cfg(feature = "faster")]238 pub fn into_std_json(self, padding: usize) -> Result<Rc<str>> {239 manifest_json_ex(240 &self,241 &ManifestJsonOptions {242 padding: &" ".repeat(padding),243 mtype: ManifestType::Std,244 },245 )246 .map(|s| s.into())247 }248249 /// Calls std.manifestJson250 #[cfg(not(feature = "faster"))]251 pub fn into_std_json(self, padding: usize) -> Result<Rc<str>> {252 with_state(|s| {253 let ctx = s254 .create_default_context()?255 .with_var("__tmp__to_json__".into(), self)?;256 Ok(evaluate(257 ctx,258 &el!(Expr::Apply(259 el!(Expr::Index(260 el!(Expr::Var("std".into())),261 el!(Expr::Str("manifestJsonEx".into()))262 )),263 ArgsDesc(vec![264 Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),265 Arg(None, el!(Expr::Str(" ".repeat(padding).into())))266 ]),267 false268 )),269 )?270 .try_cast_str("to json")?)271 })272 }273 pub fn into_yaml(self, padding: usize) -> Result<Rc<str>> {274 with_state(|s| {275 let ctx = s276 .create_default_context()?277 .with_var("__tmp__to_json__".into(), self);278 Ok(evaluate(279 ctx,280 &el!(Expr::Apply(281 el!(Expr::Index(282 el!(Expr::Var("std".into())),283 el!(Expr::Str("manifestYamlDoc".into()))284 )),285 ArgsDesc(vec![286 Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),287 Arg(None, el!(Expr::Str(" ".repeat(padding).into())))288 ]),289 false290 )),291 )?292 .try_cast_str("to json")?)293 })294 }295}296297fn is_function_like(val: &Val) -> bool {298 matches!(val, Val::Func(_) | Val::Intristic(_, _))299}300301/// Implements std.primitiveEquals builtin302pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {303 Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {304 (Val::Bool(a), Val::Bool(b)) => a == b,305 (Val::Null, Val::Null) => true,306 (Val::Str(a), Val::Str(b)) => a == b,307 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,308 (Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(309 "primitiveEquals operates on primitive types, got array".into(),310 )),311 (Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(312 "primitiveEquals operates on primitive types, got object".into(),313 )),314 (a, b) if is_function_like(&a) && is_function_like(&b) => {315 throw!(RuntimeError("cannot test equality of functions".into()))316 }317 (_, _) => false,318 })319}320321/// Native implementation of std.equals322pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {323 let val_a = val_a.unwrap_if_lazy()?;324 let val_b = val_b.unwrap_if_lazy()?;325326 if val_a.value_type()? != val_b.value_type()? {327 return Ok(false);328 }329 match (val_a, val_b) {330 // Cant test for ptr equality, because all fields needs to be evaluated331 (Val::Arr(a), Val::Arr(b)) => {332 if a.len() != b.len() {333 return Ok(false);334 }335 for (a, b) in a.iter().zip(b.iter()) {336 if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {337 return Ok(false);338 }339 }340 Ok(true)341 }342 (Val::Obj(a), Val::Obj(b)) => {343 let fields = a.visible_fields();344 if fields != b.visible_fields() {345 return Ok(false);346 }347 for field in fields {348 if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {349 return Ok(false);350 }351 }352 Ok(true)353 }354 (a, b) => Ok(primitive_equals(&a, &b)?),355 }356}