From a98d285aef91f62ff3d5499c8ead081725278f3d Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Mon, 25 Jan 2021 02:56:45 +0000 Subject: [PATCH] Merge pull request #29 from CertainLach/type-safety Jsonnet type system --- --- a/Cargo.lock +++ b/Cargo.lock @@ -167,26 +167,34 @@ "bincode", "closure", "indexmap", + "jrsonnet-interner", "jrsonnet-parser", "jrsonnet-stdlib", + "jrsonnet-types", "md5", "pathdiff", "rustc-hash", "serde", "serde_json", - "structdump", "thiserror", ] [[package]] +name = "jrsonnet-interner" +version = "0.3.3" +dependencies = [ + "rustc-hash", + "serde", +] + +[[package]] name = "jrsonnet-parser" version = "0.3.3" dependencies = [ + "jrsonnet-interner", "jrsonnet-stdlib", "peg", "serde", - "structdump", - "structdump-derive", "unescape", ] @@ -195,10 +203,18 @@ version = "0.3.3" [[package]] +name = "jrsonnet-types" +version = "0.3.3" +dependencies = [ + "peg", +] + +[[package]] name = "jsonnet" version = "0.3.3" dependencies = [ "jrsonnet-evaluator", + "jrsonnet-interner", "jrsonnet-parser", ] @@ -370,23 +386,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "structdump" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e16ec33a0342fdb67d13913b4ffae6527ebccfa04b5d7da174bdc7a31db29b8" - -[[package]] -name = "structdump-derive" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c337fdc077e02ccbfcc62af0090564a4af342975c3b7be09705efab90c1888" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] [[package]] name = "syn" --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,13 @@ [workspace] members = [ + "crates/jrsonnet-interner", "crates/jrsonnet-parser", "crates/jrsonnet-evaluator", "crates/jrsonnet-stdlib", "crates/jrsonnet-cli", + "crates/jrsonnet-types", "bindings/jsonnet", - "cmds/jrsonnet" + "cmds/jrsonnet", ] [profile.test] --- a/bindings/jsonnet/Cargo.toml +++ b/bindings/jsonnet/Cargo.toml @@ -5,6 +5,7 @@ edition = "2018" [dependencies] +jrsonnet-interner = { path = "../../crates/jrsonnet-interner", version = "0.3.3" } jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.3.3" } jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "0.3.3" } --- a/bindings/jsonnet/src/import.rs +++ b/bindings/jsonnet/src/import.rs @@ -4,6 +4,7 @@ error::{Error::*, Result}, throw, EvaluationState, ImportResolver, }; +use jrsonnet_interner::IStr; use std::{ any::Any, cell::RefCell, @@ -30,7 +31,7 @@ cb: JsonnetImportCallback, ctx: *mut c_void, - out: RefCell>>, + out: RefCell>, } impl ImportResolver for CallbackImportResolver { fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result> { @@ -75,7 +76,7 @@ Ok(Rc::new(found_here_buf)) } - fn load_file_contents(&self, resolved: &PathBuf) -> Result> { + fn load_file_contents(&self, resolved: &PathBuf) -> Result { Ok(self.out.borrow().get(resolved).unwrap().clone()) } unsafe fn as_any(&self) -> &dyn Any { @@ -124,7 +125,7 @@ throw!(ImportFileNotFound(from.clone(), path.clone())) } } - fn load_file_contents(&self, id: &PathBuf) -> Result> { + fn load_file_contents(&self, id: &PathBuf) -> Result { let mut file = File::open(id).map_err(|_e| ResolvedFileNotFound(id.clone()))?; let mut out = String::new(); file.read_to_string(&mut out) --- a/bindings/jsonnet/src/lib.rs +++ b/bindings/jsonnet/src/lib.rs @@ -8,6 +8,7 @@ use import::NativeImportResolver; use jrsonnet_evaluator::{EvaluationState, ManifestFormat, Val}; +use jrsonnet_interner::IStr; use std::{ alloc::Layout, ffi::{CStr, CString}, @@ -161,7 +162,7 @@ }) } -fn multi_to_raw(multi: Vec<(Rc, Rc)>) -> *const c_char { +fn multi_to_raw(multi: Vec<(IStr, IStr)>) -> *const c_char { let mut out = Vec::new(); for (i, (k, v)) in multi.iter().enumerate() { if i != 0 { @@ -237,7 +238,7 @@ }) } -fn stream_to_raw(multi: Vec>) -> *const c_char { +fn stream_to_raw(multi: Vec) -> *const c_char { let mut out = Vec::new(); for (i, v) in multi.iter().enumerate() { if i != 0 { --- a/bindings/jsonnet/src/native.rs +++ b/bindings/jsonnet/src/native.rs @@ -35,7 +35,7 @@ vm.add_native( name, - Rc::new(NativeCallback::new(params, move |args| { + Rc::new(NativeCallback::new(params, move |_caller, args| { let mut n_args = Vec::new(); for a in args { n_args.push(Some(Box::new(a.clone()))); --- a/bindings/jsonnet/src/val_extract.rs +++ b/bindings/jsonnet/src/val_extract.rs @@ -9,7 +9,7 @@ #[no_mangle] pub extern "C" fn jsonnet_json_extract_string(_vm: &EvaluationState, v: &Val) -> *mut c_char { - match v.unwrap_if_lazy().unwrap() { + match v { Val::Str(s) => CString::new(&*s as &str).unwrap().into_raw(), _ => std::ptr::null_mut(), } @@ -20,9 +20,9 @@ v: &Val, out: &mut c_double, ) -> c_int { - match v.unwrap_if_lazy().unwrap() { + match v { Val::Num(n) => { - *out = n; + *out = *n; 1 } _ => 0, @@ -30,7 +30,7 @@ } #[no_mangle] pub extern "C" fn jsonnet_json_extract_bool(_vm: &EvaluationState, v: &Val) -> c_int { - match v.unwrap_if_lazy().unwrap() { + match v { Val::Bool(false) => 0, Val::Bool(true) => 1, _ => 2, @@ -38,7 +38,7 @@ } #[no_mangle] pub extern "C" fn jsonnet_json_extract_null(_vm: &EvaluationState, v: &Val) -> c_int { - match v.unwrap_if_lazy().unwrap() { + match v { Val::Null => 1, _ => 0, } --- a/bindings/jsonnet/src/val_make.rs +++ b/bindings/jsonnet/src/val_make.rs @@ -1,6 +1,6 @@ //! Create values in VM -use jrsonnet_evaluator::{EvaluationState, ObjValue, Val}; +use jrsonnet_evaluator::{ArrValue, EvaluationState, ObjValue, Val}; use std::{ ffi::CStr, os::raw::{c_char, c_double, c_int}, @@ -38,7 +38,7 @@ #[no_mangle] pub extern "C" fn jsonnet_json_make_array(_vm: &EvaluationState) -> *mut Val { - Box::into_raw(Box::new(Val::Arr(Rc::new(Vec::new())))) + Box::into_raw(Box::new(Val::Arr(ArrValue::Eager(Rc::new(Vec::new()))))) } #[no_mangle] --- a/bindings/jsonnet/src/val_modify.rs +++ b/bindings/jsonnet/src/val_modify.rs @@ -2,7 +2,9 @@ //! Only tested with variables, which haven't altered by code before appearing here //! In jrsonnet every value is immutable, and this code is probally broken -use jrsonnet_evaluator::{EvaluationState, LazyBinding, LazyVal, ObjMember, ObjValue, Val}; +use jrsonnet_evaluator::{ + ArrValue, EvaluationState, LazyBinding, LazyVal, ObjMember, ObjValue, Val, +}; use jrsonnet_parser::Visibility; use std::{collections::HashMap, ffi::CStr, os::raw::c_char, rc::Rc}; @@ -12,14 +14,17 @@ #[no_mangle] pub unsafe extern "C" fn jsonnet_json_array_append( _vm: &EvaluationState, - arr: *mut Val, + arr: &mut Val, val: &Val, ) { - match *Box::from_raw(arr) { + match arr { Val::Arr(old) => { - let mut new = Rc::try_unwrap(old).expect("arr with no refs"); - new.push(val.clone()); - *arr = Val::Arr(Rc::new(new)); + let mut new = Vec::new(); + for item in old.iter_lazy() { + new.push(item); + } + new.push(LazyVal::new_resolved(val.clone())); + *arr = Val::Arr(ArrValue::Lazy(Rc::new(new))); } _ => panic!("should receive array"), } --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -12,8 +12,6 @@ serialized-stdlib = ["serde", "bincode", "jrsonnet-parser/deserialize"] # Allow to convert Val into serde_json::Value and backwards serde-json = ["serde", "serde_json"] -# Same as above, but with generated code instead of serde. Reduces memory usage, but increases binary size and compilation time -codegenerated-stdlib = [] # Replace some standard library functions with faster implementations (I.e manifestJsonEx) # Library works fine without this feature, but requires more memory and time for std function calls faster = [] @@ -24,8 +22,10 @@ unstable = [] [dependencies] +jrsonnet-interner = { path = "../jrsonnet-interner" } jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.3.3" } jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.3" } +jrsonnet-types = { path = "../jrsonnet-types", version = "0.3.3" } pathdiff = "0.2.0" closure = "0.3.0" @@ -57,8 +57,7 @@ optional = true [build-dependencies] -jrsonnet-parser = { path = "../jrsonnet-parser", features = ["dump", "serialize", "deserialize"], version = "0.3.3" } +jrsonnet-parser = { path = "../jrsonnet-parser", features = ["serialize", "deserialize"], version = "0.3.3" } jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.3" } -structdump = "0.1.2" serde = "1.0" bincode = "1.3.1" --- a/crates/jrsonnet-evaluator/build.rs +++ b/crates/jrsonnet-evaluator/build.rs @@ -10,7 +10,6 @@ path::{Path, PathBuf}, rc::Rc, }; -use structdump::CodegenResult; fn main() { let parsed = parse( @@ -36,11 +35,12 @@ name: FieldName::Fixed(name), .. }) - if **name == *"join" || **name == *"manifestJsonEx" || - **name == *"escapeStringJson" || **name == *"equals" || - **name == *"base64" || **name == *"foldl" || **name == *"foldr" || - **name == *"sortImpl" || **name == *"format" || **name == *"range" || - **name == *"reverse" || **name == *"slice" || **name == *"mod" + if name == "join" || name == "manifestJsonEx" || + name == "escapeStringJson" || name == "equals" || + name == "base64" || name == "foldl" || name == "foldr" || + name == "sortImpl" || name == "format" || name == "range" || + name == "reverse" || name == "slice" || name == "mod" || + name == "strReplace" ) }) .collect(), @@ -52,15 +52,6 @@ } else { parsed }; - { - let mut codegen = CodegenResult::default(); - let code = codegen.codegen(&parsed); - - let out_dir = env::var("OUT_DIR").unwrap(); - let dest_path = Path::new(&out_dir).join("stdlib.rs"); - let mut f = File::create(&dest_path).unwrap(); - f.write_all(&code.as_bytes()).unwrap(); - } { let out_dir = env::var("OUT_DIR").unwrap(); let dest_path = Path::new(&out_dir).join("stdlib.bincode"); --- a/crates/jrsonnet-evaluator/src/builtin/format.rs +++ b/crates/jrsonnet-evaluator/src/builtin/format.rs @@ -1,7 +1,9 @@ //! faster std.format impl #![allow(clippy::too_many_arguments)] -use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val, ValType}; +use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val}; +use jrsonnet_interner::IStr; +use jrsonnet_types::ValType; use thiserror::Error; #[derive(Debug, Clone, Error)] @@ -19,7 +21,7 @@ #[error("mapping keys required")] MappingKeysRequired, #[error("no such format field: {0}")] - NoSuchFormatField(Rc), + NoSuchFormatField(IStr), } impl From for LocError { @@ -28,7 +30,6 @@ } } -use std::rc::Rc; use FormatError::*; type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>; @@ -573,7 +574,7 @@ ); } } - ConvTypeV::Char => match value.clone().unwrap_if_lazy()? { + ConvTypeV::Char => match value.clone() { Val::Num(n) => tmp_out.push( std::char::from_u32(n as u32) .ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?, @@ -590,7 +591,7 @@ throw!(TypeMismatch( "%c requires number/string", vec![ValType::Num, ValType::Str], - value.value_type()?, + value.value_type(), )); } }, @@ -679,7 +680,7 @@ } Element::Code(c) => { // TODO: Operate on ref - let f: Rc = c.mkey.into(); + let f: IStr = c.mkey.into(); let width = match c.width { Width::Star => { throw!(CannotUseStarWidthWithObject); --- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs +++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs @@ -33,16 +33,16 @@ ) -> Result<()> { use std::fmt::Write; let mtype = options.mtype; - match val.unwrap_if_lazy()? { + match val { Val::Bool(v) => { - if 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::Str(s) => buf.push_str(&escape_string_json(s)), Val::Num(n) => write!(buf, "{}", n).unwrap(), Val::Arr(items) => { buf.push('['); @@ -63,7 +63,7 @@ } } buf.push_str(cur_padding); - manifest_json_ex_buf(item, buf, cur_padding, options)?; + manifest_json_ex_buf(&item?, buf, cur_padding, options)?; } cur_padding.truncate(old_len); @@ -118,7 +118,6 @@ buf.push('}'); } Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())), - Val::Lazy(_) => unreachable!(), }; Ok(()) } --- a/crates/jrsonnet-evaluator/src/builtin/mod.rs +++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs @@ -1,28 +1,31 @@ use crate::{ equals, error::{Error::*, Result}, - evaluate, parse_args, primitive_equals, push, throw, with_state, Context, FuncVal, Val, - ValType, + parse_args, primitive_equals, push, throw, with_state, ArrValue, Context, FuncVal, LazyVal, + Val, }; use format::{format_arr, format_obj}; -use jrsonnet_parser::{ArgsDesc, ExprLocation}; -use manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType}; -use std::{path::PathBuf, rc::Rc}; +use jrsonnet_interner::IStr; +use jrsonnet_parser::{ArgsDesc, BinaryOpType, ExprLocation}; +use jrsonnet_types::ty; +use std::{collections::HashMap, path::PathBuf, rc::Rc}; pub mod stdlib; pub use stdlib::*; +use self::manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType}; + pub mod format; pub mod manifest; pub mod sort; -fn std_format(str: Rc, vals: Val) -> Result { +fn std_format(str: IStr, vals: Val) -> Result { push( - &Some(ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)), + Some(&ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)), || format!("std.format of {}", str), || { Ok(match vals { - Val::Arr(vals) => Val::Str(format_arr(&str, &vals)?.into()), + Val::Arr(vals) => Val::Str(format_arr(&str, &vals.evaluated()?)?.into()), Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()), o => Val::Str(format_arr(&str, &[o])?.into()), }) @@ -30,379 +33,566 @@ ) } -#[allow(clippy::cognitive_complexity)] -pub fn call_builtin( +type Builtin = fn(context: Context, loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result; + +type BuiltinsType = HashMap, Builtin>; + +thread_local! { + static BUILTINS: BuiltinsType = { + [ + ("length".into(), builtin_length as Builtin), + ("type".into(), builtin_type), + ("makeArray".into(), builtin_make_array), + ("codepoint".into(), builtin_codepoint), + ("objectFieldsEx".into(), builtin_object_fields_ex), + ("objectHasEx".into(), builtin_object_has_ex), + ("slice".into(), builtin_slice), + ("primitiveEquals".into(), builtin_primitive_equals), + ("equals".into(), builtin_equals), + ("modulo".into(), builtin_modulo), + ("mod".into(), builtin_mod), + ("floor".into(), builtin_floor), + ("log".into(), builtin_log), + ("pow".into(), builtin_pow), + ("extVar".into(), builtin_ext_var), + ("native".into(), builtin_native), + ("filter".into(), builtin_filter), + ("foldl".into(), builtin_foldl), + ("foldr".into(), builtin_foldr), + ("sortImpl".into(), builtin_sort_impl), + ("format".into(), builtin_format), + ("range".into(), builtin_range), + ("char".into(), builtin_char), + ("encodeUTF8".into(), builtin_encode_utf8), + ("md5".into(), builtin_md5), + ("base64".into(), builtin_base64), + ("trace".into(), builtin_trace), + ("join".into(), builtin_join), + ("escapeStringJson".into(), builtin_escape_string_json), + ("manifestJsonEx".into(), builtin_manifest_json_ex), + ("reverse".into(), builtin_reverse), + ("id".into(), builtin_id), + ("strReplace".into(), builtin_str_replace), + ].iter().cloned().collect() + }; +} + +fn builtin_length(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "length", args, 1, [ + 0, x: ty!((string | object | array)); + ], { + Ok(match x { + Val::Str(n) => Val::Num(n.chars().count() as f64), + Val::Arr(a) => Val::Num(a.len() as f64), + Val::Obj(o) => Val::Num( + o.fields_visibility() + .into_iter() + .filter(|(_k, v)| *v) + .count() as f64, + ), + _ => unreachable!(), + }) + }) +} + +fn builtin_type(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "type", args, 1, [ + 0, x: ty!(any); + ], { + Ok(Val::Str(x.value_type().name().into())) + }) +} + +fn builtin_make_array( + context: Context, + _loc: Option<&ExprLocation>, + args: &ArgsDesc, +) -> Result { + parse_args!(context, "makeArray", args, 2, [ + 0, sz: ty!(BoundedNumber<(Some(0.0)), (None)>) => Val::Num; + 1, func: ty!(function) => Val::Func; + ], { + let mut out = Vec::with_capacity(sz as usize); + for i in 0..sz as usize { + out.push(LazyVal::new_resolved(func.evaluate_values( + Context::new(), + &[Val::Num(i as f64)] + )?)) + } + Ok(Val::Arr(out.into())) + }) +} + +fn builtin_codepoint( + context: Context, + _loc: Option<&ExprLocation>, + args: &ArgsDesc, +) -> Result { + parse_args!(context, "codepoint", args, 1, [ + 0, str: ty!(char) => Val::Str; + ], { + Ok(Val::Num(str.chars().next().unwrap() as u32 as f64)) + }) +} + +fn builtin_object_fields_ex( context: Context, - loc: &Option, - name: &str, + _loc: Option<&ExprLocation>, args: &ArgsDesc, ) -> Result { - Ok(match name as &str { - // arr/string/function - "length" => parse_args!(context, "std.length", args, 1, [ - 0, x: [Val::Str|Val::Arr|Val::Obj], vec![ValType::Str, ValType::Arr, ValType::Obj]; - ], { - Ok(match x { - Val::Str(n) => Val::Num(n.chars().count() as f64), - Val::Arr(i) => Val::Num(i.len() as f64), - Val::Obj(o) => Val::Num( - o.fields_visibility() - .into_iter() - .filter(|(_k, v)| *v) - .count() as f64, - ), - _ => unreachable!(), - }) - })?, - // any - "type" => parse_args!(context, "std.type", args, 1, [ - 0, x, vec![]; - ], { - Ok(Val::Str(x.value_type()?.name().into())) - })?, - // length, idx=>any - "makeArray" => parse_args!(context, "std.makeArray", args, 2, [ - 0, sz: [Val::Num]!!Val::Num, vec![ValType::Num]; - 1, func: [Val::Func]!!Val::Func, vec![ValType::Func]; - ], { - if sz < 0.0 { - throw!(RuntimeError(format!("makeArray requires size >= 0, got {}", sz).into())); - } - let mut out = Vec::with_capacity(sz as usize); - for i in 0..sz as usize { - out.push(func.evaluate_values( - Context::new(), - &[Val::Num(i as f64)] - )?) - } - Ok(Val::Arr(Rc::new(out))) - })?, - // string - "codepoint" => parse_args!(context, "std.codepoint", args, 1, [ - 0, str: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - assert!( - str.chars().count() == 1, - "std.codepoint should receive single char string" - ); - Ok(Val::Num(str.chars().take(1).next().unwrap() as u32 as f64)) - })?, - // object, includeHidden - "objectFieldsEx" => parse_args!(context, "std.objectFieldsEx",args, 2, [ - 0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj]; - 1, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool]; - ], { - let mut out = obj.fields_visibility() + parse_args!(context, "objectFieldsEx", args, 2, [ + 0, obj: ty!(object) => Val::Obj; + 1, inc_hidden: ty!(boolean) => Val::Bool; + ], { + let mut out = obj.fields_visibility() + .into_iter() + .filter(|(_k, v)| *v || inc_hidden) + .map(|(k, _v)|k) + .collect::>(); + out.sort(); + Ok(Val::Arr(out.into_iter().map(Val::Str).collect::>().into())) + }) +} + +fn builtin_object_has_ex( + context: Context, + _loc: Option<&ExprLocation>, + args: &ArgsDesc, +) -> Result { + parse_args!(context, "objectHasEx", args, 3, [ + 0, obj: ty!(object) => Val::Obj; + 1, f: ty!(string) => Val::Str; + 2, inc_hidden: ty!(boolean) => Val::Bool; + ], { + Ok(Val::Bool( + obj.fields_visibility() .into_iter() .filter(|(_k, v)| *v || inc_hidden) - .map(|(k, _v)|k) - .collect::>(); - out.sort(); - Ok(Val::Arr(Rc::new(out.into_iter().map(Val::Str).collect()))) - })?, - // object, field, includeHidden - "objectHasEx" => parse_args!(context, "std.objectHasEx", args, 3, [ - 0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj]; - 1, f: [Val::Str]!!Val::Str, vec![ValType::Str]; - 2, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool]; - ], { - Ok(Val::Bool( - obj.fields_visibility() - .into_iter() - .filter(|(_k, v)| *v || inc_hidden) - .any(|(k, _v)| *k == *f), - )) - })?, + .any(|(k, _v)| *k == *f), + )) + }) +} - // faster - "slice" => parse_args!(context, "slice", args, 4, [ - 0, indexable: [Val::Str | Val::Arr], vec![ValType::Str, ValType::Arr]; - 1, index, vec![ValType::Num, ValType::Null]; - 2, end, vec![ValType::Num, ValType::Null]; - 3, step, vec![ValType::Num, ValType::Null]; - ], { - let index = match index { - Val::Num(v) => v as usize, - Val::Null => 0, - _ => unreachable!(), - }; - let end = match end { - Val::Num(v) => v as usize, - Val::Null => match &indexable { - Val::Str(s) => s.chars().count(), - Val::Arr(v) => v.len(), - _ => unreachable!() - }, +// faster +fn builtin_slice(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "slice", args, 4, [ + 0, indexable: ty!((string | array)); + 1, index: ty!((number | null)); + 2, end: ty!((number | null)); + 3, step: ty!((number | null)); + ], { + let index = match index { + Val::Num(v) => v as usize, + Val::Null => 0, + _ => unreachable!(), + }; + let end = match end { + Val::Num(v) => v as usize, + Val::Null => match &indexable { + Val::Str(s) => s.chars().count(), + Val::Arr(v) => v.len(), _ => unreachable!() - }; - let step = match step { - Val::Num(v) => v as usize, - Val::Null => 1, - _ => unreachable!() - }; - match &indexable { - Val::Str(s) => { - Ok(Val::Str((s.chars().skip(index).take(end-index).step_by(step).collect::()).into())) - } - Val::Arr(arr) => { - Ok(Val::Arr((arr.iter().skip(index).take(end-index).step_by(step).cloned().collect::>()).into())) - } - _ => unreachable!() + }, + _ => unreachable!() + }; + let step = match step { + Val::Num(v) => v as usize, + Val::Null => 1, + _ => unreachable!() + }; + match &indexable { + Val::Str(s) => { + Ok(Val::Str((s.chars().skip(index).take(end-index).step_by(step).collect::()).into())) } - })?, - "primitiveEquals" => parse_args!(context, "std.primitiveEquals", args, 2, [ - 0, a, vec![]; - 1, b, vec![]; - ], { - Ok(Val::Bool(primitive_equals(&a, &b)?)) - })?, - // faster - "equals" => parse_args!(context, "std.equals", args, 2, [ - 0, a, vec![]; - 1, b, vec![]; - ], { - Ok(Val::Bool(equals(&a, &b)?)) - })?, - "mod" => parse_args!(context, "std.mod", args, 2, [ - 0, a: [Val::Num | Val::Str], vec![ValType::Num, ValType::Str]; - 1, b, vec![]; - ], { - match (a, b) { - (Val::Num(a), Val::Num(b)) => Ok(Val::Num(a % b)), - (Val::Str(str), vals) => std_format(str, vals), - (a, b) => throw!(BinaryOperatorDoesNotOperateOnValues(jrsonnet_parser::BinaryOpType::Mod, a.value_type()?, b.value_type()?)) + Val::Arr(arr) => { + Ok(Val::Arr((arr.iter().skip(index).take(end-index).step_by(step).collect::>>()?).into())) } - })?, - "modulo" => parse_args!(context, "std.modulo", args, 2, [ - 0, a: [Val::Num]!!Val::Num, vec![ValType::Num]; - 1, b: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - Ok(Val::Num(a % b)) - })?, - "floor" => parse_args!(context, "std.floor", args, 1, [ - 0, x: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - Ok(Val::Num(x.floor())) - })?, - "log" => parse_args!(context, "std.log", args, 2, [ - 0, n: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - Ok(Val::Num(n.ln())) - })?, - "trace" => parse_args!(context, "std.trace", args, 2, [ - 0, str: [Val::Str]!!Val::Str, vec![ValType::Str]; - 1, rest, vec![]; - ], { - eprint!("TRACE:"); - if let Some(loc) = loc { - with_state(|s|{ - let locs = s.map_source_locations(&loc.0, &[loc.1]); - eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line); - }); - } - eprintln!(" {}", str); - Ok(rest) - })?, - "pow" => parse_args!(context, "std.modulo", args, 2, [ - 0, x: [Val::Num]!!Val::Num, vec![ValType::Num]; - 1, n: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - Ok(Val::Num(x.powf(n))) - })?, - "extVar" => parse_args!(context, "std.extVar", args, 1, [ - 0, x: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or(UndefinedExternalVariable(x))?) - })?, - "native" => parse_args!(context, "std.native", args, 1, [ - 0, x: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(Rc::new(FuncVal::NativeExt(x.clone(), v)))).ok_or(UndefinedExternalFunction(x))?) - })?, - "filter" => parse_args!(context, "std.filter", args, 2, [ - 0, func: [Val::Func]!!Val::Func, vec![ValType::Func]; - 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; - ], { - Ok(Val::Arr(Rc::new( - arr.iter() - .cloned() - .filter(|e| { - func - .evaluate_values(context.clone(), &[e.clone()]) - .unwrap() - .try_cast_bool("filter predicate") - .unwrap() - }) - .collect(), - ))) - })?, - // faster - "foldl" => parse_args!(context, "std.foldl", args, 3, [ - 0, func: [Val::Func]!!Val::Func, vec![ValType::Func]; - 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; - 2, init, vec![]; - ], { - let mut acc = init; - for i in arr.iter().cloned() { - acc = func.evaluate_values(context.clone(), &[acc, i])?; - } - Ok(acc) - })?, - // faster - "foldr" => parse_args!(context, "std.foldr", args, 3, [ - 0, func: [Val::Func]!!Val::Func, vec![ValType::Func]; - 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; - 2, init, vec![]; - ], { - let mut acc = init; - for i in arr.iter().rev().cloned() { - acc = func.evaluate_values(context.clone(), &[acc, i])?; - } - Ok(acc) - })?, - // faster - #[allow(non_snake_case)] - "sortImpl" => parse_args!(context, "std.sort", args, 2, [ - 0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; - 1, keyF: [Val::Func]!!Val::Func, vec![ValType::Func]; - ], { - if arr.len() <= 1 { - return Ok(Val::Arr(arr)) - } - Ok(Val::Arr(sort::sort(context, arr, &keyF)?)) - })?, - // faster - "format" => parse_args!(context, "std.format", args, 2, [ - 0, str: [Val::Str]!!Val::Str, vec![ValType::Str]; - 1, vals, vec![] - ], { - std_format(str, vals) - })?, - // faster - "range" => parse_args!(context, "std.range", args, 2, [ - 0, from: [Val::Num]!!Val::Num, vec![ValType::Num]; - 1, to: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - if to < from { - return Ok(Val::Arr(Rc::new(Vec::new()))) - } - let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0)); - for i in from as usize..=to as usize { - out.push(Val::Num(i as f64)); - } - Ok(Val::Arr(Rc::new(out))) - })?, - "char" => parse_args!(context, "std.char", args, 1, [ - 0, n: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - let mut out = String::new(); - out.push(std::char::from_u32(n as u32).ok_or_else(|| - InvalidUnicodeCodepointGot(n as u32) - )?); - Ok(Val::Str(out.into())) - })?, - "encodeUTF8" => parse_args!(context, "std.encodeUtf8", args, 1, [ - 0, str: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - Ok(Val::Arr(Rc::new(str.bytes().map(|b| Val::Num(b as f64)).collect()))) - })?, - "md5" => parse_args!(context, "std.md5", args, 1, [ - 0, str: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into())) - })?, - // faster - "base64" => parse_args!(context, "std.base64", args, 1, [ - 0, input: [Val::Str | Val::Arr], vec![ValType::Arr, ValType::Str]; - ], { - Ok(Val::Str(match input { - Val::Str(s) => { - base64::encode(s.bytes().collect::>()).into() - }, - Val::Arr(a) => { - base64::encode(a.iter().map(|v| { - Ok(v.clone().try_cast_num("base64 array")? as u8) - }).collect::>>()?).into() - }, - _ => unreachable!() - })) - })?, - // faster - "join" => parse_args!(context, "std.join", args, 2, [ - 0, sep: [Val::Str|Val::Arr], vec![ValType::Str, ValType::Arr]; - 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; - ], { - Ok(match sep { - Val::Arr(joiner_items) => { - let mut out = Vec::new(); + _ => unreachable!() + } + }) +} + +// faster +fn builtin_primitive_equals( + context: Context, + _loc: Option<&ExprLocation>, + args: &ArgsDesc, +) -> Result { + parse_args!(context, "primitiveEquals", args, 2, [ + 0, a: ty!(any); + 1, b: ty!(any); + ], { + Ok(Val::Bool(primitive_equals(&a, &b)?)) + }) +} + +// faster +fn builtin_equals(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "equals", args, 2, [ + 0, a: ty!(any); + 1, b: ty!(any); + ], { + Ok(Val::Bool(equals(&a, &b)?)) + }) +} + +fn builtin_modulo(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "modulo", args, 2, [ + 0, a: ty!(number) => Val::Num; + 1, b: ty!(number) => Val::Num; + ], { + Ok(Val::Num(a % b)) + }) +} + +fn builtin_mod(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "mod", args, 2, [ + 0, a: ty!((number | string)); + 1, b: ty!(any); + ], { + match (a, b) { + (Val::Num(a), Val::Num(b)) => Ok(Val::Num(a % b)), + (Val::Str(str), vals) => std_format(str, vals), + (a, b) => throw!(BinaryOperatorDoesNotOperateOnValues(BinaryOpType::Mod, a.value_type(), b.value_type())) + } + }) +} + +fn builtin_floor(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "floor", args, 1, [ + 0, x: ty!(number) => Val::Num; + ], { + Ok(Val::Num(x.floor())) + }) +} + +fn builtin_log(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "log", args, 1, [ + 0, n: ty!(number) => Val::Num; + ], { + Ok(Val::Num(n.ln())) + }) +} + +fn builtin_pow(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "pow", args, 2, [ + 0, x: ty!(number) => Val::Num; + 1, n: ty!(number) => Val::Num; + ], { + Ok(Val::Num(x.powf(n))) + }) +} + +fn builtin_ext_var(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "extVar", args, 1, [ + 0, x: ty!(string) => Val::Str; + ], { + Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or(UndefinedExternalVariable(x))?) + }) +} + +fn builtin_native(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "native", args, 1, [ + 0, x: ty!(string) => Val::Str; + ], { + Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(Rc::new(FuncVal::NativeExt(x.clone(), v)))).ok_or(UndefinedExternalFunction(x))?) + }) +} + +fn builtin_filter(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "filter", args, 2, [ + 0, func: ty!(function) => Val::Func; + 1, arr: ty!(array) => Val::Arr; + ], { + let mut out = Vec::new(); + for item in arr.iter() { + let item = item?; + if func + .evaluate_values(context.clone(), &[item.clone()])? + .try_cast_bool("filter predicate")? { + out.push(item); + } + } + Ok(Val::Arr(out.into())) + }) +} + +fn builtin_foldl(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "foldl", args, 3, [ + 0, func: ty!(function) => Val::Func; + 1, arr: ty!(array) => Val::Arr; + 2, init: ty!(any); + ], { + let mut acc = init; + for i in arr.iter() { + acc = func.evaluate_values(context.clone(), &[acc, i?])?; + } + Ok(acc) + }) +} + +fn builtin_foldr(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "foldr", args, 3, [ + 0, func: ty!(function) => Val::Func; + 1, arr: ty!(array) => Val::Arr; + 2, init: ty!(any); + ], { + let mut acc = init; + for i in arr.iter().rev() { + acc = func.evaluate_values(context.clone(), &[acc, i?])?; + } + Ok(acc) + }) +} + +#[allow(non_snake_case)] +fn builtin_sort_impl( + context: Context, + _loc: Option<&ExprLocation>, + args: &ArgsDesc, +) -> Result { + parse_args!(context, "sort", args, 2, [ + 0, arr: ty!(array) => Val::Arr; + 1, keyF: ty!(function) => Val::Func; + ], { + if arr.len() <= 1 { + return Ok(Val::Arr(arr)) + } + Ok(Val::Arr(ArrValue::Eager(sort::sort(context, arr.evaluated()?, &keyF)?))) + }) +} + +// faster +fn builtin_format(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "format", args, 2, [ + 0, str: ty!(string) => Val::Str; + 1, vals: ty!(any) + ], { + std_format(str, vals) + }) +} + +fn builtin_range(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "range", args, 2, [ + 0, from: ty!(number) => Val::Num; + 1, to: ty!(number) => Val::Num; + ], { + if to < from { + return Ok(Val::Arr(ArrValue::new_eager())) + } + let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0)); + for i in from as usize..=to as usize { + out.push(Val::Num(i as f64)); + } + Ok(Val::Arr(out.into())) + }) +} + +fn builtin_char(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "char", args, 1, [ + 0, n: ty!(number) => Val::Num; + ], { + let mut out = String::new(); + out.push(std::char::from_u32(n as u32).ok_or_else(|| + InvalidUnicodeCodepointGot(n as u32) + )?); + Ok(Val::Str(out.into())) + }) +} - let mut first = true; - for item in arr.iter().cloned() { - if let Val::Arr(items) = item.unwrap_if_lazy()? { - if !first { - out.reserve(joiner_items.len()); - out.extend(joiner_items.iter().cloned()); +fn builtin_encode_utf8( + context: Context, + _loc: Option<&ExprLocation>, + args: &ArgsDesc, +) -> Result { + parse_args!(context, "encodeUTF8", args, 1, [ + 0, str: ty!(string) => Val::Str; + ], { + Ok(Val::Arr((str.bytes().map(|b| Val::Num(b as f64)).collect::>()).into())) + }) +} + +fn builtin_md5(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "md5", args, 1, [ + 0, str: ty!(string) => Val::Str; + ], { + Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into())) + }) +} + +fn builtin_trace(context: Context, loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "trace", args, 2, [ + 0, str: ty!(string) => Val::Str; + 1, rest: ty!(any); + ], { + eprint!("TRACE:"); + if let Some(loc) = loc { + with_state(|s|{ + let locs = s.map_source_locations(&loc.0, &[loc.1]); + eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line); + }); + } + eprintln!(" {}", str); + Ok(rest) + }) +} + +fn builtin_base64(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "base64", args, 1, [ + 0, input: ty!((string | (Array))); + ], { + Ok(Val::Str(match input { + Val::Str(s) => { + base64::encode(s.bytes().collect::>()).into() + }, + Val::Arr(a) => { + base64::encode(a.iter().map(|v| { + Ok(v?.unwrap_num()? as u8) + }).collect::>>()?).into() + }, + _ => unreachable!() + })) + }) +} + +fn builtin_join(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "join", args, 2, [ + 0, sep: ty!((string | array)); + 1, arr: ty!(array) => Val::Arr; + ], { + Ok(match sep { + Val::Arr(joiner_items) => { + let mut out = Vec::new(); + + let mut first = true; + for item in arr.iter() { + let item = item?.clone(); + if let Val::Arr(items) = item { + if !first { + out.reserve(joiner_items.len()); + // TODO: extend + for item in joiner_items.iter() { + out.push(item?); } - first = false; - out.reserve(items.len()); - out.extend(items.iter().cloned()); - } else { - throw!(RuntimeError("in std.join all items should be arrays".into())); } + first = false; + out.reserve(items.len()); + // TODO: extend + for item in items.iter() { + out.push(item?); + } + } else { + throw!(RuntimeError("in std.join all items should be arrays".into())); } + } - Val::Arr(Rc::new(out)) - }, - Val::Str(sep) => { - let mut out = String::new(); + Val::Arr(out.into()) + }, + Val::Str(sep) => { + let mut out = String::new(); - let mut first = true; - for item in arr.iter().cloned() { - if let Val::Str(item) = item.unwrap_if_lazy()? { - if !first { - out += &sep; - } - first = false; - out += &item; - } else { - throw!(RuntimeError("in std.join all items should be strings".into())); + let mut first = true; + for item in arr.iter() { + let item = item?.clone(); + if let Val::Str(item) = item { + if !first { + out += &sep; } + first = false; + out += &item; + } else { + throw!(RuntimeError("in std.join all items should be strings".into())); } + } - Val::Str(out.into()) - }, - _ => unreachable!() - }) - })?, - // Faster - "escapeStringJson" => parse_args!(context, "std.escapeStringJson", args, 1, [ - 0, str_: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - Ok(Val::Str(escape_string_json(&str_).into())) - })?, - // Faster - "manifestJsonEx" => parse_args!(context, "std.manifestJsonEx", args, 2, [ - 0, value, vec![]; - 1, indent: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - Ok(Val::Str(manifest_json_ex(&value, &ManifestJsonOptions { - padding: &indent, - mtype: ManifestType::Std, - })?.into())) - })?, - // Faster - "reverse" => parse_args!(context, "std.reverse", args, 1, [ - 0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; - ], { - let mut marr = arr; - Rc::make_mut(&mut marr).reverse(); - Ok(Val::Arr(marr)) - })?, - "id" => parse_args!(context, "std.id", args, 1, [ - 0, v, vec![]; - ], { - Ok(v) - })?, - name => throw!(IntrinsicNotFound(name.into())), + Val::Str(out.into()) + }, + _ => unreachable!() + }) + }) +} + +// faster +fn builtin_escape_string_json( + context: Context, + _loc: Option<&ExprLocation>, + args: &ArgsDesc, +) -> Result { + parse_args!(context, "escapeStringJson", args, 1, [ + 0, str_: ty!(string) => Val::Str; + ], { + Ok(Val::Str(escape_string_json(&str_).into())) + }) +} + +// faster +fn builtin_manifest_json_ex( + context: Context, + _loc: Option<&ExprLocation>, + args: &ArgsDesc, +) -> Result { + parse_args!(context, "manifestJsonEx", args, 2, [ + 0, value: ty!(any); + 1, indent: ty!(string) => Val::Str; + ], { + Ok(Val::Str(manifest_json_ex(&value, &ManifestJsonOptions { + padding: &indent, + mtype: ManifestType::Std, + })?.into())) + }) +} + +// faster +fn builtin_reverse(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "reverse", args, 1, [ + 0, value: ty!(array) => Val::Arr; + ], { + Ok(Val::Arr(value.reversed())) + }) +} + +fn builtin_id(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "id", args, 1, [ + 0, v: ty!(any); + ], { + Ok(v) + }) +} + +// faster +fn builtin_str_replace( + context: Context, + _loc: Option<&ExprLocation>, + args: &ArgsDesc, +) -> Result { + parse_args!(context, "strReplace", args, 3, [ + 0, str: ty!(string) => Val::Str; + 1, from: ty!(string) => Val::Str; + 2, to: ty!(string) => Val::Str; + ], { + let mut out = String::new(); + let mut last_idx = 0; + while let Some(idx) = (&str[last_idx..]).find(&from as &str) { + out.push_str(&str[last_idx..last_idx+idx]); + out.push_str(&to); + last_idx += idx + from.len(); + } + if last_idx == 0 { + return Ok(Val::Str(str)) + } + out.push_str(&str[last_idx..]); + Ok(Val::Str(out.into())) }) } + +pub fn call_builtin( + context: Context, + loc: Option<&ExprLocation>, + name: &str, + args: &ArgsDesc, +) -> Result { + if let Some(f) = BUILTINS.with(|builtins| builtins.get(name).copied()) { + return Ok(f(context, loc, args)?); + } + throw!(IntrinsicNotFound(name.into())) +} --- a/crates/jrsonnet-evaluator/src/builtin/sort.rs +++ b/crates/jrsonnet-evaluator/src/builtin/sort.rs @@ -46,7 +46,6 @@ let mut sort_type = SortKeyType::Unknown; for i in values.iter_mut() { let i = key_getter(i); - i.inplace_unwrap()?; match (i, sort_type) { (Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String, (Val::Num(_), SortKeyType::Unknown) => sort_type = SortKeyType::Number, --- a/crates/jrsonnet-evaluator/src/ctx.rs +++ b/crates/jrsonnet-evaluator/src/ctx.rs @@ -2,6 +2,7 @@ error::Error::*, future_wrapper, map::LayeredHashMap, rc_fn_helper, resolved_lazy_val, LazyBinding, LazyVal, ObjValue, Result, Val, }; +use jrsonnet_interner::IStr; use rustc_hash::FxHashMap; use std::hash::BuildHasherDefault; use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; @@ -18,7 +19,7 @@ dollar: Option, this: Option, super_obj: Option, - bindings: LayeredHashMap, LazyVal>, + bindings: LayeredHashMap, } impl Debug for ContextInternals { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -57,7 +58,7 @@ })) } - pub fn binding(&self, name: Rc) -> Result { + pub fn binding(&self, name: IStr) -> Result { Ok(self .0 .bindings @@ -72,7 +73,7 @@ ctx.unwrap() } - pub fn with_var(self, name: Rc, value: Val) -> Self { + pub fn with_var(self, name: IStr, value: Val) -> Self { let mut new_bindings = FxHashMap::with_capacity_and_hasher(1, BuildHasherDefault::default()); new_bindings.insert(name, resolved_lazy_val!(value)); @@ -81,7 +82,7 @@ pub fn extend( self, - new_bindings: FxHashMap, LazyVal>, + new_bindings: FxHashMap, new_dollar: Option, new_this: Option, new_super_obj: Option, @@ -123,7 +124,7 @@ } pub fn extend_unbound( self, - new_bindings: HashMap, LazyBinding>, + new_bindings: HashMap, new_dollar: Option, new_this: Option, new_super_obj: Option, --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -1,15 +1,17 @@ use crate::{ builtin::{format::FormatError, sort::SortError}, - ValType, + typed::TypeLocError, }; +use jrsonnet_interner::IStr; use jrsonnet_parser::{BinaryOpType, ExprLocation, UnaryOpType}; +use jrsonnet_types::ValType; use std::{path::PathBuf, rc::Rc}; use thiserror::Error; #[derive(Error, Debug, Clone)] pub enum Error { #[error("intrinsic not found: {0}")] - IntrinsicNotFound(Rc), + IntrinsicNotFound(IStr), #[error("argument reordering in intrisics not supported yet")] IntrinsicArgumentReorderingIsNotSupportedYet, @@ -32,36 +34,36 @@ ArrayBoundsError(usize, usize), #[error("assert failed: {0}")] - AssertionFailed(Rc), + AssertionFailed(IStr), #[error("variable is not defined: {0}")] - VariableIsNotDefined(Rc), + VariableIsNotDefined(IStr), #[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{}", e)).collect::>().join(", "))] TypeMismatch(&'static str, Vec, ValType), #[error("no such field: {0}")] - NoSuchField(Rc), + NoSuchField(IStr), #[error("only functions can be called, got {0}")] OnlyFunctionsCanBeCalledGot(ValType), #[error("parameter {0} is not defined")] UnknownFunctionParameter(String), #[error("argument {0} is already bound")] - BindingParameterASecondTime(Rc), + BindingParameterASecondTime(IStr), #[error("too many args, function has {0}")] TooManyArgsFunctionHas(usize), #[error("founction argument is not passed: {0}")] - FunctionParameterNotBoundInCall(Rc), + FunctionParameterNotBoundInCall(IStr), #[error("external variable is not defined: {0}")] - UndefinedExternalVariable(Rc), + UndefinedExternalVariable(IStr), #[error("native is not defined: {0}")] - UndefinedExternalFunction(Rc), + UndefinedExternalFunction(IStr), #[error("field name should be string, got {0}")] FieldMustBeStringGot(ValType), #[error("attempted to index array with string {0}")] - AttemptedIndexAnArrayWithString(Rc), + AttemptedIndexAnArrayWithString(IStr), #[error("{0} index type should be {1}, got {2}")] ValueIndexMustBeTypeGot(ValType, ValType, ValType), #[error("cant index into {0}")] @@ -85,12 +87,12 @@ )] ImportSyntaxError { path: Rc, - source_code: Rc, + source_code: IStr, error: Box, }, #[error("runtime error: {0}")] - RuntimeError(Rc), + RuntimeError(IStr), #[error("stack overflow, try to reduce recursion, or set --max-stack to bigger value")] StackOverflow, #[error("tried to index by fractional value")] @@ -117,6 +119,8 @@ #[error("format error: {0}")] Format(#[from] FormatError), + #[error("type error: {0}")] + TypeError(TypeLocError), #[error("sort error: {0}")] Sort(#[from] SortError), } @@ -128,7 +132,7 @@ #[derive(Clone, Debug)] pub struct StackTraceElement { - pub location: ExprLocation, + pub location: Option, pub desc: String, } #[derive(Debug, Clone)] @@ -144,6 +148,9 @@ pub const fn error(&self) -> &Error { &(self.0).0 } + pub fn error_mut(&mut self) -> &mut Error { + &mut (self.0).0 + } pub const fn trace(&self) -> &StackTrace { &(self.0).1 } --- a/crates/jrsonnet-evaluator/src/evaluate.rs +++ b/crates/jrsonnet-evaluator/src/evaluate.rs @@ -1,18 +1,19 @@ use crate::{ context_creator, error::Error::*, future_wrapper, lazy_val, push, throw, with_state, Context, ContextCreator, FuncDesc, FuncVal, LazyBinding, LazyVal, ObjMember, ObjValue, Result, Val, - ValType, }; use closure::closure; +use jrsonnet_interner::IStr; use jrsonnet_parser::{ ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprLocation, FieldMember, ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, UnaryOpType, Visibility, }; +use jrsonnet_types::ValType; use rustc_hash::FxHashMap; use std::{collections::HashMap, rc::Rc}; -pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (Rc, LazyBinding) { +pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (IStr, LazyBinding) { let b = b.clone(); if let Some(params) = &b.params { let params = params.clone(); @@ -45,7 +46,7 @@ } } -pub fn evaluate_method(ctx: Context, name: Rc, params: ParamsDesc, body: LocExpr) -> Val { +pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val { Val::Func(Rc::new(FuncVal::Normal(FuncDesc { name, ctx, @@ -57,12 +58,11 @@ pub fn evaluate_field_name( context: Context, field_name: &jrsonnet_parser::FieldName, -) -> Result>> { +) -> Result> { Ok(match field_name { jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()), jrsonnet_parser::FieldName::Dyn(expr) => { - let lazy = evaluate(context, expr)?; - let value = lazy.unwrap_if_lazy()?; + let value = evaluate(context, expr)?; if matches!(value, Val::Null) { None } else { @@ -74,11 +74,10 @@ pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result { Ok(match (op, b) { - (o, Val::Lazy(l)) => evaluate_unary_op(o, &l.evaluate()?)?, (UnaryOpType::Not, Val::Bool(v)) => Val::Bool(!v), (UnaryOpType::Minus, Val::Num(n)) => Val::Num(-*n), (UnaryOpType::BitNot, Val::Num(n)) => Val::Num(!(*n as i32) as f64), - (op, o) => throw!(UnaryOperatorDoesNotOperateOnType(op, o.value_type()?)), + (op, o) => throw!(UnaryOperatorDoesNotOperateOnType(op, o.value_type())), }) } @@ -94,12 +93,17 @@ (o, Val::Str(s)) => Val::Str(format!("{}{}", o.clone().to_string()?, s).into()), (Val::Obj(v1), Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())), - (Val::Arr(a), Val::Arr(b)) => Val::Arr(Rc::new([&a[..], &b[..]].concat())), + (Val::Arr(a), Val::Arr(b)) => { + let mut out = Vec::with_capacity(a.len() + b.len()); + out.extend(a.iter_lazy()); + out.extend(b.iter_lazy()); + Val::Arr(out.into()) + } (Val::Num(v1), Val::Num(v2)) => Val::new_checked_num(v1 + v2)?, _ => throw!(BinaryOperatorDoesNotOperateOnValues( BinaryOpType::Add, - a.value_type()?, - b.value_type()?, + a.value_type(), + b.value_type(), )), }) } @@ -110,15 +114,11 @@ op: BinaryOpType, b: &LocExpr, ) -> Result { - Ok( - match (evaluate(context.clone(), a)?.unwrap_if_lazy()?, op, b) { - (Val::Bool(true), BinaryOpType::Or, _o) => Val::Bool(true), - (Val::Bool(false), BinaryOpType::And, _o) => Val::Bool(false), - (a, op, eb) => { - evaluate_binary_op_normal(&a, op, &evaluate(context, eb)?.unwrap_if_lazy()?)? - } - }, - ) + Ok(match (evaluate(context.clone(), a)?, op, b) { + (Val::Bool(true), BinaryOpType::Or, _o) => Val::Bool(true), + (Val::Bool(false), BinaryOpType::And, _o) => Val::Bool(false), + (a, op, eb) => evaluate_binary_op_normal(&a, op, &evaluate(context, eb)?)?, + }) } pub fn evaluate_binary_op_normal(a: &Val, op: BinaryOpType, b: &Val) -> Result { @@ -177,13 +177,13 @@ _ => throw!(BinaryOperatorDoesNotOperateOnValues( op, - a.value_type()?, - b.value_type()?, + a.value_type(), + b.value_type(), )), }) } -future_wrapper!(HashMap, LazyBinding>, FutureNewBindings); +future_wrapper!(HashMap, FutureNewBindings); future_wrapper!(ObjValue, FutureObjValue); pub fn evaluate_comp( @@ -200,23 +200,20 @@ None } } - Some(CompSpec::ForSpec(ForSpecData(var, expr))) => { - match evaluate(context.clone(), expr)?.unwrap_if_lazy()? { - Val::Arr(list) => { - let mut out = Vec::new(); - for item in list.iter() { - let item = item.unwrap_if_lazy()?; - out.push(evaluate_comp( - context.clone().with_var(var.clone(), item.clone()), - value, - &specs[1..], - )?); - } - Some(out.into_iter().flatten().flatten().collect()) + Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(context.clone(), expr)? { + Val::Arr(list) => { + let mut out = Vec::new(); + for item in list.iter() { + out.push(evaluate_comp( + context.clone().with_var(var.clone(), item?.clone()), + value, + &specs[1..], + )?); } - _ => throw!(InComprehensionCanOnlyIterateOverArray), + Some(out.into_iter().flatten().flatten().collect()) } - } + _ => throw!(InComprehensionCanOnlyIterateOverArray), + }, }) } @@ -234,7 +231,7 @@ }) ); { - let mut bindings: HashMap, LazyBinding> = HashMap::new(); + let mut bindings: HashMap = HashMap::new(); for (n, b) in members .iter() .filter_map(|m| match m { @@ -338,7 +335,7 @@ )?) }) ); - let mut bindings: HashMap, LazyBinding> = HashMap::new(); + let mut bindings: HashMap = HashMap::new(); for (n, b) in obj .pre_locals .iter() @@ -375,7 +372,7 @@ }, ); } - v => throw!(FieldMustBeStringGot(v.value_type()?)), + v => throw!(FieldMustBeStringGot(v.value_type())), } } @@ -388,11 +385,10 @@ context: Context, value: &LocExpr, args: &ArgsDesc, - loc: &Option, + loc: Option<&ExprLocation>, tailstrict: bool, ) -> Result { - let lazy = evaluate(context.clone(), value)?; - let value = lazy.unwrap_if_lazy()?; + let value = evaluate(context.clone(), value)?; Ok(match value { Val::Func(f) => { let body = || f.evaluate(context, loc, args, tailstrict); @@ -402,11 +398,11 @@ push(loc, || format!("function <{}> call", f.name()), body)? } } - v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type()?)), + v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())), }) } -pub fn evaluate_named(context: Context, lexpr: &LocExpr, name: Rc) -> Result { +pub fn evaluate_named(context: Context, lexpr: &LocExpr, name: IStr) -> Result { use Expr::*; let LocExpr(expr, _loc) = lexpr; Ok(match &**expr { @@ -434,9 +430,9 @@ BinaryOp(v1, o, v2) => evaluate_binary_op_special(context, v1, *o, v2)?, UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?, Var(name) => push( - loc, + loc.as_ref(), || format!("variable <{}>", name), - || Ok(Val::Lazy(context.binding(name.clone())?).unwrap_if_lazy()?), + || Ok(context.binding(name.clone())?.evaluate()?), )?, Index(LocExpr(v, _), index) if matches!(&**v, Expr::Literal(LiteralType::Super)) => { let name = evaluate(context.clone(), index)?.try_cast_str("object index")?; @@ -444,22 +440,19 @@ .super_obj() .clone() .expect("no super found") - .get_raw(name, &context.this().clone().expect("no this found"))? + .get_raw(name, Some(&context.this().clone().expect("no this found")))? .expect("value not found") } Index(value, index) => { - match ( - evaluate(context.clone(), value)?.unwrap_if_lazy()?, - evaluate(context, index)?, - ) { + match (evaluate(context.clone(), value)?, evaluate(context, index)?) { (Val::Obj(v), Val::Str(s)) => { let sn = s.clone(); push( - loc, + loc.as_ref(), || format!("field <{}> access", sn), || { if let Some(v) = v.get(s.clone())? { - Ok(v.unwrap_if_lazy()?) + Ok(v) } else if v.get("__intrinsic_namespace__".into())?.is_some() { Ok(Val::Func(Rc::new(FuncVal::Intrinsic(s)))) } else { @@ -471,23 +464,21 @@ (Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot( ValType::Obj, ValType::Str, - n.value_type()?, + n.value_type(), )), (Val::Arr(v), Val::Num(n)) => { if n.fract() > f64::EPSILON { throw!(FractionalIndex) } - v.get(n as usize) + v.get(n as usize)? .ok_or_else(|| ArrayBoundsError(n as usize, v.len()))? - .clone() - .unwrap_if_lazy()? } (Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)), (Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot( ValType::Arr, ValType::Num, - n.value_type()?, + n.value_type(), )), (Val::Str(s), Val::Num(n)) => Val::Str( @@ -500,14 +491,14 @@ (Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot( ValType::Str, ValType::Num, - n.value_type()?, + n.value_type(), )), - (v, _) => throw!(CantIndexInto(v.value_type()?)), + (v, _) => throw!(CantIndexInto(v.value_type())), } } LocalExpr(bindings, returned) => { - let mut new_bindings: HashMap, LazyBinding> = HashMap::new(); + let mut new_bindings: HashMap = HashMap::new(); let future_context = Context::new_future(); let context_creator = context_creator!( @@ -529,31 +520,35 @@ Arr(items) => { let mut out = Vec::with_capacity(items.len()); for item in items { - out.push(Val::Lazy(lazy_val!( + out.push(LazyVal::new(Box::new( closure!(clone context, clone item, || { evaluate(context.clone(), &item) - }) + }), ))); } - Val::Arr(Rc::new(out)) + Val::Arr(out.into()) } ArrComp(expr, comp_specs) => Val::Arr( // First comp_spec should be for_spec, so no "None" possible here - Rc::new(evaluate_comp(context, &|ctx| evaluate(ctx, expr), comp_specs)?.unwrap()), + evaluate_comp(context, &|ctx| evaluate(ctx, expr), comp_specs)? + .unwrap() + .into(), ), Obj(body) => Val::Obj(evaluate_object(context, body)?), ObjExtend(s, t) => evaluate_add_op( &evaluate(context.clone(), s)?, &Val::Obj(evaluate_object(context, t)?), )?, - Apply(value, args, tailstrict) => evaluate_apply(context, value, args, loc, *tailstrict)?, + Apply(value, args, tailstrict) => { + evaluate_apply(context, value, args, loc.as_ref(), *tailstrict)? + } Function(params, body) => { evaluate_method(context, "anonymous".into(), params.clone(), body.clone()) } Intrinsic(name) => Val::Func(Rc::new(FuncVal::Intrinsic(name.clone()))), AssertExpr(AssertStmt(value, msg), returned) => { let assertion_result = push( - &value.1, + value.1.as_ref(), || "assertion condition".to_owned(), || { evaluate(context.clone(), value)? @@ -562,14 +557,22 @@ )?; if assertion_result { evaluate(context, returned)? - } else if let Some(msg) = msg { - throw!(AssertionFailed(evaluate(context, msg)?.to_string()?)); } else { - throw!(AssertionFailed(Val::Null.to_string()?)); + push( + value.1.as_ref(), + || "assertion failure".to_owned(), + || { + if let Some(msg) = msg { + throw!(AssertionFailed(evaluate(context, msg)?.to_string()?)); + } else { + throw!(AssertionFailed(Val::Null.to_string()?)); + } + }, + )? } } ErrorStmt(e) => push( - loc, + loc.as_ref(), || "error statement".to_owned(), || { throw!(RuntimeError( @@ -583,7 +586,7 @@ cond_else, } => { if push( - loc, + loc.as_ref(), || "if condition".to_owned(), || evaluate(context.clone(), &cond.0)?.try_cast_bool("in if condition"), )? { @@ -603,7 +606,7 @@ let import_location = Rc::make_mut(&mut tmp); import_location.pop(); push( - loc, + loc.as_ref(), || format!("import {:?}", path), || with_state(|s| s.import_file(import_location, path)), )? --- a/crates/jrsonnet-evaluator/src/function.rs +++ b/crates/jrsonnet-evaluator/src/function.rs @@ -1,8 +1,9 @@ use crate::{error::Error::*, evaluate, lazy_val, resolved_lazy_val, throw, Context, Result, Val}; use closure::closure; +use jrsonnet_interner::IStr; use jrsonnet_parser::{ArgsDesc, ParamsDesc}; use rustc_hash::FxHashMap; -use std::{collections::HashMap, hash::BuildHasherDefault, rc::Rc}; +use std::{collections::HashMap, hash::BuildHasherDefault}; const NO_DEFAULT_CONTEXT: &str = "no default context set for call with defined default parameter value"; @@ -66,7 +67,7 @@ ctx: Context, body_ctx: Option, params: &ParamsDesc, - args: &HashMap, Val>, + args: &HashMap, tailstrict: bool, ) -> Result { let mut out = FxHashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default()); @@ -143,9 +144,10 @@ #[macro_export] macro_rules! parse_args { ($ctx: expr, $fn_name: expr, $args: expr, $total_args: expr, [ - $($id: expr, $name: ident $(: [$($p: path)|+] $(!! $a: path)?)?, $nt: expr);+ $(;)? + $($id: expr, $name: ident: $ty: expr $(=>$match: path)?);+ $(;)? ], $handler:block) => {{ - use crate::{throw, error::Error::*}; + use $crate::{error::Error::*, throw, evaluate, push_stack_frame, typed::CheckType}; + let args = $args; if args.len() > $total_args { throw!(TooManyArgsFunctionHas($total_args)); @@ -160,47 +162,19 @@ throw!(IntrinsicArgumentReorderingIsNotSupportedYet); } } - let $name = evaluate($ctx.clone(), &$name.1)?; + let $name = push_stack_frame(None, || format!("evaluating argument"), || { + let value = evaluate($ctx.clone(), &$name.1)?; + $ty.check(&value)?; + Ok(value) + })?; $( - match $name { - $($p(_))|+ => {}, - _ => throw!(TypeMismatch( - concat!($fn_name, " ", stringify!($id), "nd (", stringify!($name), ") argument"), - $nt, $name.value_type()? - )), + let $name = if let $match(v) = $name { + v + } else { + unreachable!(); }; - $( - let $name = match $name { - $a(v) => v, - _ =>throw!(TypeMismatch(concat!($fn_name, " ", stringify!($id), "nd (", stringify!($name), ") argument"), $nt, $name.value_type()?)), - }; - )* - )* + )? )+ ($handler as crate::Result<_>) }}; -} - -#[test] -fn test() -> Result<()> { - use crate::val::ValType; - use jrsonnet_parser::*; - let state = crate::EvaluationState::default(); - let evaluator = state.with_stdlib(); - let ctx = evaluator.create_default_context()?; - evaluator.run_in_state(|| { - parse_args!(ctx, "test", ArgsDesc(vec![ - Arg(None, el!(Expr::Num(2.0))), - Arg(Some("b".into()), el!(Expr::Num(1.0))), - ]), 2, [ - 0, a: [Val::Num]!!Val::Num, vec![ValType::Num]; - 1, b: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - assert!((a - 2.0).abs() <= f64::EPSILON); - assert!((b - 1.0).abs() <= f64::EPSILON); - Ok(()) - }) - .unwrap(); - Ok(()) - }) } --- a/crates/jrsonnet-evaluator/src/import.rs +++ b/crates/jrsonnet-evaluator/src/import.rs @@ -3,6 +3,7 @@ throw, }; use fs::File; +use jrsonnet_interner::IStr; use std::fs; use std::io::Read; use std::{any::Any, cell::RefCell, collections::HashMap, path::PathBuf, rc::Rc}; @@ -15,7 +16,7 @@ fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result>; /// Reads file from filesystem, should be used only with path received from `resolve_file` - fn load_file_contents(&self, resolved: &PathBuf) -> Result>; + fn load_file_contents(&self, resolved: &PathBuf) -> Result; /// # Safety /// @@ -32,7 +33,7 @@ throw!(ImportNotSupported(from.clone(), path.clone())) } - fn load_file_contents(&self, _resolved: &PathBuf) -> Result> { + fn load_file_contents(&self, _resolved: &PathBuf) -> Result { // Can be only caused by library direct consumer, not by supplied jsonnet panic!("dummy resolver can't load any file") } @@ -72,7 +73,7 @@ throw!(ImportFileNotFound(from.clone(), path.clone())) } } - fn load_file_contents(&self, id: &PathBuf) -> Result> { + fn load_file_contents(&self, id: &PathBuf) -> Result { let mut file = File::open(id).map_err(|_e| ResolvedFileNotFound(id.clone()))?; let mut out = String::new(); file.read_to_string(&mut out) @@ -89,7 +90,7 @@ /// Caches results of the underlying resolver pub struct CachingImportResolver { resolution_cache: RefCell>>>, - loading_cache: RefCell>>>, + loading_cache: RefCell>>, inner: Box, } impl ImportResolver for CachingImportResolver { @@ -101,7 +102,7 @@ .clone() } - fn load_file_contents(&self, resolved: &PathBuf) -> Result> { + fn load_file_contents(&self, resolved: &PathBuf) -> Result { self.loading_cache .borrow_mut() .entry(resolved.clone()) --- a/crates/jrsonnet-evaluator/src/integrations/serde.rs +++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs @@ -22,11 +22,10 @@ } else { Number::from_f64(*n).expect("to json number") }), - Val::Lazy(v) => (&v.evaluate()?).try_into()?, Val::Arr(a) => { let mut out = Vec::with_capacity(a.len()); for item in a.iter() { - out.push(item.try_into()?); + out.push((&item?).try_into()?); } Self::Array(out) } @@ -55,9 +54,9 @@ Value::Array(a) => { let mut out = Vec::with_capacity(a.len()); for v in a { - out.push(v.into()); + out.push(LazyVal::new_resolved(v.into())); } - Self::Arr(Rc::new(out)) + Self::Arr(out.into()) } Value::Object(o) => { let mut entries = HashMap::with_capacity(o.len()); --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -14,6 +14,7 @@ pub mod native; mod obj; pub mod trace; +pub mod typed; mod val; pub use ctx::*; @@ -22,6 +23,7 @@ pub use evaluate::*; pub use function::parse_function_call; pub use import::*; +use jrsonnet_interner::IStr; use jrsonnet_parser::*; use native::NativeCallback; pub use obj::*; @@ -62,13 +64,13 @@ /// Limits amount of stack trace items preserved pub max_trace: usize, /// Used for s`td.extVar` - pub ext_vars: HashMap, Val>, + pub ext_vars: HashMap, /// Used for ext.native - pub ext_natives: HashMap, Rc>, + pub ext_natives: HashMap>, /// TLA vars - pub tla_vars: HashMap, Val>, + pub tla_vars: HashMap, /// Global variables are inserted in default context - pub globals: HashMap, Val>, + pub globals: HashMap, /// Used to resolve file locations/contents pub import_resolver: Box, /// Used in manifestification functions @@ -101,11 +103,11 @@ stack_depth: usize, /// Contains file source codes and evaluation results for imports and pretty-printed stacktraces files: HashMap, FileData>, - str_files: HashMap, Rc>, + str_files: HashMap, IStr>, } pub struct FileData { - source_code: Rc, + source_code: IStr, parsed: LocExpr, evaluated: Option, } @@ -126,24 +128,28 @@ EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap())) } pub(crate) fn push( - e: &Option, + e: Option<&ExprLocation>, frame_desc: impl FnOnce() -> String, f: impl FnOnce() -> Result, ) -> Result { - if let Some(v) = e { - with_state(|s| s.push(v, frame_desc, f)) - } else { - f() - } + with_state(|s| s.push(e, frame_desc, f)) } +pub fn push_stack_frame( + e: Option<&ExprLocation>, + frame_desc: impl FnOnce() -> String, + f: impl FnOnce() -> Result, +) -> Result { + push(e, frame_desc, f) +} + /// Maintains stack trace and import resolution #[derive(Default, Clone)] pub struct EvaluationState(Rc); impl EvaluationState { /// Parses and adds file as loaded - pub fn add_file(&self, path: Rc, source_code: Rc) -> Result<()> { + pub fn add_file(&self, path: Rc, source_code: IStr) -> Result<()> { self.add_parsed_file( path.clone(), source_code.clone(), @@ -168,7 +174,7 @@ pub fn add_parsed_file( &self, name: Rc, - source_code: Rc, + source_code: IStr, parsed: LocExpr, ) -> Result<()> { self.data_mut().files.insert( @@ -182,7 +188,7 @@ Ok(()) } - pub fn get_source(&self, name: &PathBuf) -> Option> { + pub fn get_source(&self, name: &PathBuf) -> Option { let ro_map = &self.data().files; ro_map.get(name).map(|value| value.source_code.clone()) } @@ -204,7 +210,7 @@ self.add_file(file_path.clone(), contents)?; self.evaluate_loaded_file_raw(&file_path) } - pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result> { + pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result { let path = self.resolve_file(from, path)?; if !self.data().str_files.contains_key(&path) { let file_str = self.load_file_contents(&path)?; @@ -256,7 +262,7 @@ /// Creates context with all passed global variables pub fn create_default_context(&self) -> Result { let globals = &self.settings().globals; - let mut new_bindings: HashMap, LazyBinding> = HashMap::new(); + let mut new_bindings: HashMap = HashMap::new(); for (name, value) in globals.iter() { new_bindings.insert( name.clone(), @@ -269,7 +275,7 @@ /// Executes code creating a new stack frame pub fn push( &self, - e: &ExprLocation, + e: Option<&ExprLocation>, frame_desc: impl FnOnce() -> String, f: impl FnOnce() -> Result, ) -> Result { @@ -288,7 +294,7 @@ self.data_mut().stack_depth -= 1; if let Err(mut err) = result { err.trace_mut().0.push(StackTraceElement { - location: e.clone(), + location: e.cloned(), desc: frame_desc(), }); return Err(err); @@ -320,13 +326,13 @@ out } - pub fn manifest(&self, val: Val) -> Result> { + pub fn manifest(&self, val: Val) -> Result { self.run_in_state(|| val.manifest(&self.manifest_format())) } - pub fn manifest_multi(&self, val: Val) -> Result, Rc)>> { + pub fn manifest_multi(&self, val: Val) -> Result> { self.run_in_state(|| val.manifest_multi(&self.manifest_format())) } - pub fn manifest_stream(&self, val: Val) -> Result>> { + pub fn manifest_stream(&self, val: Val) -> Result> { self.run_in_state(|| val.manifest_stream(&self.manifest_format())) } @@ -334,10 +340,16 @@ pub fn with_tla(&self, val: Val) -> Result { self.run_in_state(|| { Ok(match val { - Val::Func(func) => func.evaluate_map( - self.create_default_context()?, - &self.settings().tla_vars, - true, + Val::Func(func) => push( + None, + || "during TLA call".to_owned(), + || { + Ok(func.evaluate_map( + self.create_default_context()?, + &self.settings().tla_vars, + true, + )?) + }, )?, v => v, }) @@ -370,7 +382,7 @@ self.run_in_state(|| self.import_file(&PathBuf::from("."), name)) } /// Parses and evaluates the given snippet - pub fn evaluate_snippet_raw(&self, source: Rc, code: Rc) -> Result { + pub fn evaluate_snippet_raw(&self, source: Rc, code: IStr) -> Result { let parsed = parse( &code, &ParserSettings { @@ -390,26 +402,26 @@ /// Settings utilities impl EvaluationState { - pub fn add_ext_var(&self, name: Rc, value: Val) { + pub fn add_ext_var(&self, name: IStr, value: Val) { self.settings_mut().ext_vars.insert(name, value); } - pub fn add_ext_str(&self, name: Rc, value: Rc) { + pub fn add_ext_str(&self, name: IStr, value: IStr) { self.add_ext_var(name, Val::Str(value)); } - pub fn add_ext_code(&self, name: Rc, code: Rc) -> Result<()> { + pub fn add_ext_code(&self, name: IStr, code: IStr) -> Result<()> { let value = self.evaluate_snippet_raw(Rc::new(PathBuf::from(format!("ext_code {}", name))), code)?; self.add_ext_var(name, value); Ok(()) } - pub fn add_tla(&self, name: Rc, value: Val) { + pub fn add_tla(&self, name: IStr, value: Val) { self.settings_mut().tla_vars.insert(name, value); } - pub fn add_tla_str(&self, name: Rc, value: Rc) { + pub fn add_tla_str(&self, name: IStr, value: IStr) { self.add_tla(name, Val::Str(value)); } - pub fn add_tla_code(&self, name: Rc, code: Rc) -> Result<()> { + pub fn add_tla_code(&self, name: IStr, code: IStr) -> Result<()> { let value = self.evaluate_snippet_raw(Rc::new(PathBuf::from(format!("tla_code {}", name))), code)?; self.add_tla(name, value); @@ -419,7 +431,7 @@ pub fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result> { Ok(self.settings().import_resolver.resolve_file(from, path)?) } - pub fn load_file_contents(&self, path: &PathBuf) -> Result> { + pub fn load_file_contents(&self, path: &PathBuf) -> Result { Ok(self.settings().import_resolver.load_file_contents(path)?) } @@ -430,7 +442,7 @@ self.settings_mut().import_resolver = resolver; } - pub fn add_native(&self, name: Rc, cb: Rc) { + pub fn add_native(&self, name: IStr, cb: Rc) { self.settings_mut().ext_natives.insert(name, cb); } @@ -467,6 +479,7 @@ pub mod tests { use super::Val; use crate::{error::Error::*, primitive_equals, EvaluationState}; + use jrsonnet_interner::IStr; use jrsonnet_parser::*; use std::{path::PathBuf, rc::Rc}; @@ -477,11 +490,19 @@ state.run_in_state(|| { state .push( - &ExprLocation(Rc::new(PathBuf::from("test1.jsonnet")), 10, 20), + Some(&ExprLocation( + Rc::new(PathBuf::from("test1.jsonnet")), + 10, + 20, + )), || "outer".to_owned(), || { state.push( - &ExprLocation(Rc::new(PathBuf::from("test2.jsonnet")), 30, 40), + Some(&ExprLocation( + Rc::new(PathBuf::from("test2.jsonnet")), + 30, + 40, + )), || "inner".to_owned(), || Err(RuntimeError("".into()).into()), )?; @@ -883,14 +904,20 @@ Param("a".into(), None), Param("b".into(), None), ])), - |args| match (&args[0], &args[1]) { - (Val::Num(a), Val::Num(b)) => Ok(Val::Num(a + b)), - (_, _) => todo!(), + |caller, args| { + assert_eq!( + caller.unwrap(), + Rc::new(PathBuf::from("native_caller.jsonnet")) + ); + match (&args[0], &args[1]) { + (Val::Num(a), Val::Num(b)) => Ok(Val::Num(a + b)), + (_, _) => unreachable!(), + } }, )), ); evaluator.evaluate_snippet_raw( - Rc::new(PathBuf::from("test.jsonnet")), + Rc::new(PathBuf::from("native_caller.jsonnet")), "std.assertEqual(std.native(\"native_add\")(1, 2), 3)".into(), )?; Ok(()) @@ -904,13 +931,13 @@ Ok(()) } - struct TestImportResolver(Rc); + struct TestImportResolver(IStr); impl crate::import::ImportResolver for TestImportResolver { fn resolve_file(&self, _: &PathBuf, _: &PathBuf) -> crate::error::Result> { Ok(Rc::new(PathBuf::from("/test"))) } - fn load_file_contents(&self, _: &PathBuf) -> crate::error::Result> { + fn load_file_contents(&self, _: &PathBuf) -> crate::error::Result { Ok(self.0.clone()) } --- a/crates/jrsonnet-evaluator/src/map.rs +++ b/crates/jrsonnet-evaluator/src/map.rs @@ -1,17 +1,18 @@ +use jrsonnet_interner::IStr; use rustc_hash::FxHashMap; -use std::{borrow::Borrow, hash::Hash, rc::Rc}; +use std::rc::Rc; #[derive(Default, Debug)] -struct LayeredHashMapInternals { - parent: Option>, - current: FxHashMap, +struct LayeredHashMapInternals { + parent: Option>, + current: FxHashMap, } #[derive(Debug)] -pub struct LayeredHashMap(Rc>); +pub struct LayeredHashMap(Rc>); -impl LayeredHashMap { - pub fn extend(self, new_layer: FxHashMap) -> Self { +impl LayeredHashMap { + pub fn extend(self, new_layer: FxHashMap) -> Self { match Rc::try_unwrap(self.0) { Ok(mut map) => { map.current.extend(new_layer); @@ -24,11 +25,7 @@ } } - pub fn get(&self, key: &Q) -> Option<&V> - where - K: Borrow, - Q: Hash + Eq, - { + pub fn get(&self, key: &IStr) -> Option<&V> { (self.0) .current .get(key) @@ -36,13 +33,13 @@ } } -impl Clone for LayeredHashMap { +impl Clone for LayeredHashMap { fn clone(&self) -> Self { Self(self.0.clone()) } } -impl Default for LayeredHashMap { +impl Default for LayeredHashMap { fn default() -> Self { Self(Rc::new(LayeredHashMapInternals { parent: None, --- a/crates/jrsonnet-evaluator/src/native.rs +++ b/crates/jrsonnet-evaluator/src/native.rs @@ -1,20 +1,27 @@ +#![allow(clippy::type_complexity)] + use crate::{error::Result, Val}; use jrsonnet_parser::ParamsDesc; use std::fmt::Debug; +use std::path::PathBuf; +use std::rc::Rc; pub struct NativeCallback { pub params: ParamsDesc, - handler: Box Result>, + handler: Box>, &[Val]) -> Result>, } impl NativeCallback { - pub fn new(params: ParamsDesc, handler: impl Fn(&[Val]) -> Result + 'static) -> Self { + pub fn new( + params: ParamsDesc, + handler: impl Fn(Option>, &[Val]) -> Result + 'static, + ) -> Self { Self { params, handler: Box::new(handler), } } - pub fn call(&self, args: &[Val]) -> Result { - (self.handler)(args) + pub fn call(&self, caller: Option>, args: &[Val]) -> Result { + (self.handler)(caller, args) } } impl Debug for NativeCallback { --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -1,5 +1,6 @@ use crate::{evaluate_add_op, LazyBinding, Result, Val}; use indexmap::IndexMap; +use jrsonnet_interner::IStr; use jrsonnet_parser::{ExprLocation, Visibility}; use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; @@ -12,11 +13,11 @@ } // Field => This -type CacheKey = (Rc, usize); +type CacheKey = (IStr, usize); #[derive(Debug)] pub struct ObjValueInternals { super_obj: Option, - this_entries: Rc, ObjMember>>, + this_entries: Rc>, value_cache: RefCell>>, } #[derive(Clone)] @@ -47,7 +48,7 @@ } impl ObjValue { - pub fn new(super_obj: Option, this_entries: Rc, ObjMember>>) -> Self { + pub fn new(super_obj: Option, this_entries: Rc>) -> Self { Self(Rc::new(ObjValueInternals { super_obj, this_entries, @@ -63,7 +64,7 @@ Some(v) => Self::new(Some(v.with_super(super_obj)), self.0.this_entries.clone()), } } - pub fn enum_fields(&self, handler: &impl Fn(&Rc, &Visibility)) { + pub fn enum_fields(&self, handler: &impl Fn(&IStr, &Visibility)) { if let Some(s) = &self.0.super_obj { s.enum_fields(handler); } @@ -71,7 +72,7 @@ handler(name, &member.visibility); } } - pub fn fields_visibility(&self) -> IndexMap, bool> { + pub fn fields_visibility(&self) -> IndexMap { let out = Rc::new(RefCell::new(IndexMap::new())); self.enum_fields(&|name, visibility| { let mut out = out.borrow_mut(); @@ -91,7 +92,7 @@ }); Rc::try_unwrap(out).unwrap().into_inner() } - pub fn visible_fields(&self) -> Vec> { + pub fn visible_fields(&self) -> Vec { let mut visible_fields: Vec<_> = self .fields_visibility() .into_iter() @@ -101,10 +102,11 @@ visible_fields.sort(); visible_fields } - pub fn get(&self, key: Rc) -> Result> { - Ok(self.get_raw(key, self)?) + pub fn get(&self, key: IStr) -> Result> { + Ok(self.get_raw(key, None)?) } - pub(crate) fn get_raw(&self, key: Rc, real_this: &Self) -> Result> { + pub(crate) fn get_raw(&self, key: IStr, real_this: Option<&Self>) -> Result> { + let real_this = real_this.unwrap_or(self); let cache_key = (key.clone(), Rc::as_ptr(&real_this.0) as usize); if let Some(v) = self.0.value_cache.borrow().get(&cache_key) { @@ -115,7 +117,7 @@ (Some(k), Some(s)) => { let our = self.evaluate_this(k, real_this)?; if k.add { - s.get_raw(key, real_this)? + s.get_raw(key, Some(real_this))? .map_or(Ok(Some(our.clone())), |v| { Ok(Some(evaluate_add_op(&v, &our)?)) }) @@ -123,7 +125,7 @@ Ok(Some(our)) } } - (None, Some(s)) => s.get_raw(key, real_this), + (None, Some(s)) => s.get_raw(key, Some(real_this)), (None, None) => Ok(None), }?; self.0 @@ -137,6 +139,10 @@ .evaluate(Some(real_this.clone()), self.0.super_obj.clone())? .evaluate()?) } + + pub fn ptr_eq(a: &Self, b: &Self) -> bool { + Rc::ptr_eq(&a.0, &b.0) + } } impl PartialEq for ObjValue { fn eq(&self, other: &Self) -> bool { --- a/crates/jrsonnet-evaluator/src/trace/location.rs +++ b/crates/jrsonnet-evaluator/src/trace/location.rs @@ -37,7 +37,11 @@ ]; let mut with_no_known_line_ending = vec![]; let mut this_line_offset = 0; - for (pos, ch) in file.chars().enumerate() { + for (pos, ch) in file + .chars() + .enumerate() + .chain(std::iter::once((file.len(), ' '))) + { column += 1; match offset_map.last() { Some(x) if x.0 == pos => { --- a/crates/jrsonnet-evaluator/src/trace/mod.rs +++ b/crates/jrsonnet-evaluator/src/trace/mod.rs @@ -65,7 +65,7 @@ out, "{}:{}-{}:{}", start.line, - end.column - 1, + end.column.saturating_sub(1), start.line, end.column )?; @@ -119,20 +119,23 @@ .0 .iter() .map(|el| { - let resolved_path = self.resolver.resolve(&el.location.0); - // TODO: Process all trace elements first - let location = evaluation_state - .map_source_locations(&el.location.0, &[el.location.1, el.location.2]); - (resolved_path, location) - }) - .map(|(mut n, location)| { - use std::fmt::Write; - write!(n, ":").unwrap(); - print_code_location(&mut n, &location[0], &location[1]).unwrap(); - n + el.location.as_ref().map(|l| { + use std::fmt::Write; + let mut resolved_path = self.resolver.resolve(&l.0); + // TODO: Process all trace elements first + let location = evaluation_state.map_source_locations(&l.0, &[l.1, l.2]); + write!(resolved_path, ":").unwrap(); + print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap(); + resolved_path + }) }) .collect::>(); - let align = file_names.iter().map(|e| e.len()).max().unwrap_or(0); + let align = file_names + .iter() + .flatten() + .map(|e| e.len()) + .max() + .unwrap_or(0); for (i, (el, file)) in error.trace().0.iter().zip(file_names).enumerate() { if i != 0 { writeln!(out)?; @@ -141,7 +144,7 @@ out, "{: $match: path) => {{ + use $crate::{push_stack_frame, typed::CheckType}; + push_stack_frame(None, $desc, || Ok($typ.check(&$value)?))?; + match $value { + $match(v) => v, + _ => unreachable!(), + } + }}; +} + +#[derive(Debug, Error, Clone)] +pub enum TypeError { + #[error("expected {0}, got {1}")] + ExpectedGot(ComplexValType, ValType), + #[error("missing property {0} from {1:?}")] + MissingProperty(Rc, ComplexValType), + #[error("every failed from {0}:\n{1}")] + UnionFailed(ComplexValType, TypeLocErrorList), + #[error("number out of bounds: {0} not in {1:?}..{2:?}")] + BoundsFailed(f64, Option, Option), +} +impl From for LocError { + fn from(e: TypeError) -> Self { + Error::TypeError(e.into()).into() + } +} + +#[derive(Debug, Clone)] +pub struct TypeLocError(Box, ValuePathStack); +impl From for TypeLocError { + fn from(e: TypeError) -> Self { + Self(Box::new(e), ValuePathStack(Vec::new())) + } +} +impl From for LocError { + fn from(e: TypeLocError) -> Self { + Error::TypeError(e).into() + } +} +impl Display for TypeLocError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0)?; + if !(self.1).0.is_empty() { + write!(f, "at {}", self.1)?; + } + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct TypeLocErrorList(Vec); +impl Display for TypeLocErrorList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use std::fmt::Write; + let mut out = String::new(); + for (i, err) in self.0.iter().enumerate() { + if i != 0 { + writeln!(f)?; + } + out.clear(); + write!(out, "{}", err)?; + + for (i, line) in out.lines().enumerate() { + if line.trim().is_empty() { + continue; + } + if i != 0 { + writeln!(f)?; + write!(f, " ")?; + } else { + write!(f, " - ")?; + } + write!(f, "{}", line)?; + } + } + Ok(()) + } +} + +fn push_type( + location: Option<&ExprLocation>, + error_reason: impl Fn() -> String, + path: impl Fn() -> ValuePathItem, + item: impl Fn() -> Result<()>, +) -> Result<()> { + push(location, error_reason, || match item() { + Ok(_) => Ok(()), + Err(mut e) => { + if let Error::TypeError(e) = &mut e.error_mut() { + (e.1).0.push(path()) + } + Err(e) + } + }) +} + +// TODO: check_fast for fast path of union type checking +pub trait CheckType { + fn check(&self, value: &Val) -> Result<()>; +} + +impl CheckType for ValType { + fn check(&self, value: &Val) -> Result<()> { + let got = value.value_type(); + if got != *self { + let loc_error: TypeLocError = TypeError::ExpectedGot((*self).into(), got).into(); + return Err(loc_error.into()); + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +enum ValuePathItem { + Field(Rc), + Index(u64), +} +impl Display for ValuePathItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Field(name) => write!(f, ".{}", name)?, + Self::Index(idx) => write!(f, "[{}]", idx)?, + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +struct ValuePathStack(Vec); +impl Display for ValuePathStack { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "self")?; + for elem in self.0.iter().rev() { + write!(f, "{}", elem)?; + } + Ok(()) + } +} + +impl CheckType for ComplexValType { + fn check(&self, value: &Val) -> Result<()> { + match self { + Self::Any => Ok(()), + Self::Simple(s) => s.check(value), + Self::Char => match value { + Val::Str(s) if s.len() == 1 || s.chars().count() == 1 => Ok(()), + v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()), + }, + Self::BoundedNumber(from, to) => { + if let Val::Num(n) = value { + if from.map(|from| from > *n).unwrap_or(false) + || to.map(|to| to <= *n).unwrap_or(false) + { + return Err(TypeError::BoundsFailed(*n, *from, *to).into()); + } + Ok(()) + } else { + Err(TypeError::ExpectedGot(self.clone(), value.value_type()).into()) + } + } + Self::Array(elem_type) => match value { + Val::Arr(a) => { + for (i, item) in a.iter().enumerate() { + push_type( + None, + || format!("array index {}", i), + || ValuePathItem::Index(i as u64), + || Ok(elem_type.check(&item.clone()?)?), + )?; + } + Ok(()) + } + v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()), + }, + Self::ArrayRef(elem_type) => match value { + Val::Arr(a) => { + for (i, item) in a.iter().enumerate() { + push_type( + None, + || format!("array index {}", i), + || ValuePathItem::Index(i as u64), + || Ok(elem_type.check(&item.clone()?)?), + )?; + } + Ok(()) + } + v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()), + }, + Self::ObjectRef(elems) => match value { + Val::Obj(obj) => { + for (k, v) in elems.iter() { + if let Some(got_v) = obj.get((*k).into())? { + push_type( + None, + || format!("property {}", k), + || ValuePathItem::Field((*k).into()), + || v.check(&got_v), + )? + } else { + return Err( + TypeError::MissingProperty((*k).into(), self.clone()).into() + ); + } + } + Ok(()) + } + v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()), + }, + Self::Union(types) => { + let mut errors = Vec::new(); + for ty in types.iter() { + match ty.check(value) { + Ok(()) => { + return Ok(()); + } + Err(e) => match e.error() { + Error::TypeError(e) => errors.push(e.clone()), + _ => return Err(e), + }, + } + } + Err(TypeError::UnionFailed(self.clone(), TypeLocErrorList(errors)).into()) + } + Self::UnionRef(types) => { + let mut errors = Vec::new(); + for ty in types.iter() { + match ty.check(value) { + Ok(()) => { + return Ok(()); + } + Err(e) => match e.error() { + Error::TypeError(e) => errors.push(e.clone()), + _ => return Err(e), + }, + } + } + Err(TypeError::UnionFailed(self.clone(), TypeLocErrorList(errors)).into()) + } + Self::Sum(types) => { + for ty in types.iter() { + ty.check(value)? + } + Ok(()) + } + Self::SumRef(types) => { + for ty in types.iter() { + ty.check(value)? + } + Ok(()) + } + } + } +} --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -9,13 +9,10 @@ native::NativeCallback, throw, with_state, Context, ObjValue, Result, }; +use jrsonnet_interner::IStr; use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, ExprLocation, LiteralType, LocExpr, ParamsDesc}; -use std::{ - cell::RefCell, - collections::HashMap, - fmt::{Debug, Display}, - rc::Rc, -}; +use jrsonnet_types::ValType; +use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; enum LazyValInternals { Computed(Val), @@ -65,7 +62,7 @@ #[derive(Debug, PartialEq)] pub struct FuncDesc { - pub name: Rc, + pub name: IStr, pub ctx: Context, pub params: ParamsDesc, pub body: LocExpr, @@ -76,9 +73,9 @@ /// Plain function implemented in jsonnet Normal(FuncDesc), /// Standard library function - Intrinsic(Rc), + Intrinsic(IStr), /// Library functions implemented in native - NativeExt(Rc, Rc), + NativeExt(IStr, Rc), } impl PartialEq for FuncVal { @@ -95,7 +92,7 @@ pub fn is_ident(&self) -> bool { matches!(&self, Self::Intrinsic(n) if n as &str == "id") } - pub fn name(&self) -> Rc { + pub fn name(&self) -> IStr { match self { Self::Normal(normal) => normal.name.clone(), Self::Intrinsic(name) => format!("std.{}", name).into(), @@ -105,7 +102,7 @@ pub fn evaluate( &self, call_ctx: Context, - loc: &Option, + loc: Option<&ExprLocation>, args: &ArgsDesc, tailstrict: bool, ) -> Result { @@ -127,7 +124,7 @@ for p in handler.params.0.iter() { out_args.push(args.binding(p.0.clone())?.evaluate()?); } - Ok(handler.call(&out_args)?) + Ok(handler.call(loc.clone().map(|l| l.0.clone()), &out_args)?) } } } @@ -135,7 +132,7 @@ pub fn evaluate_map( &self, call_ctx: Context, - args: &HashMap, Val>, + args: &HashMap, tailstrict: bool, ) -> Result { match self { @@ -166,53 +163,154 @@ } } -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ValType { - Bool, - Null, - Str, - Num, - Arr, - Obj, - Func, +#[derive(Clone)] +pub enum ManifestFormat { + YamlStream(Box), + Yaml(usize), + Json(usize), + ToString, + String, } -impl ValType { - pub const fn name(&self) -> &'static str { - use ValType::*; + +#[derive(Debug, Clone)] +pub enum ArrValue { + Lazy(Rc>), + Eager(Rc>), + Extended(Box<(Self, Self)>), +} +impl ArrValue { + pub fn new_eager() -> Self { + Self::Eager(Rc::new(Vec::new())) + } + + pub fn len(&self) -> usize { + match self { + Self::Lazy(l) => l.len(), + Self::Eager(e) => e.len(), + Self::Extended(v) => v.0.len() + v.1.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn get(&self, index: usize) -> Result> { + match self { + Self::Lazy(vec) => { + if let Some(v) = vec.get(index) { + Ok(Some(v.evaluate()?)) + } else { + Ok(None) + } + } + Self::Eager(vec) => Ok(vec.get(index).cloned()), + Self::Extended(v) => { + let a_len = v.0.len(); + if a_len > index { + v.0.get(index) + } else { + v.1.get(index - a_len) + } + } + } + } + + pub fn get_lazy(&self, index: usize) -> Option { + match self { + Self::Lazy(vec) => vec.get(index).cloned(), + Self::Eager(vec) => vec.get(index).cloned().map(LazyVal::new_resolved), + Self::Extended(v) => { + let a_len = v.0.len(); + if a_len > index { + v.0.get_lazy(index) + } else { + v.1.get_lazy(index - a_len) + } + } + } + } + + pub fn evaluated(&self) -> Result>> { + Ok(match self { + Self::Lazy(vec) => { + let mut out = Vec::with_capacity(vec.len()); + for item in vec.iter() { + out.push(item.evaluate()?); + } + Rc::new(out) + } + Self::Eager(vec) => vec.clone(), + Self::Extended(_v) => { + let mut out = Vec::with_capacity(self.len()); + for item in self.iter() { + out.push(item?); + } + Rc::new(out) + } + }) + } + + pub fn iter(&self) -> impl DoubleEndedIterator> + '_ { + (0..self.len()).map(move |idx| match self { + Self::Lazy(l) => l[idx].evaluate(), + Self::Eager(e) => Ok(e[idx].clone()), + Self::Extended(_) => self.get(idx).map(|e| e.unwrap()), + }) + } + + pub fn iter_lazy(&self) -> impl DoubleEndedIterator + '_ { + (0..self.len()).map(move |idx| match self { + Self::Lazy(l) => l[idx].clone(), + Self::Eager(e) => LazyVal::new_resolved(e[idx].clone()), + Self::Extended(_) => self.get_lazy(idx).unwrap(), + }) + } + + pub fn reversed(self) -> Self { match self { - Bool => "boolean", - Null => "null", - Str => "string", - Num => "number", - Arr => "array", - Obj => "object", - Func => "function", + Self::Lazy(vec) => { + let mut out = (&vec as &Vec<_>).clone(); + out.reverse(); + Self::Lazy(Rc::new(out)) + } + Self::Eager(vec) => { + let mut out = (&vec as &Vec<_>).clone(); + out.reverse(); + Self::Eager(Rc::new(out)) + } + Self::Extended(b) => Self::Extended(Box::new((b.1.reversed(), b.0.reversed()))), } } + + pub fn ptr_eq(a: &Self, b: &Self) -> bool { + match (a, b) { + (Self::Lazy(a), Self::Lazy(b)) => Rc::ptr_eq(a, b), + (Self::Eager(a), Self::Eager(b)) => Rc::ptr_eq(a, b), + _ => false, + } + } } -impl Display for ValType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.name()) + +impl From> for ArrValue { + fn from(v: Vec) -> Self { + Self::Lazy(Rc::new(v)) } } -#[derive(Clone)] -pub enum ManifestFormat { - YamlStream(Box), - Yaml(usize), - Json(usize), - ToString, - String, +impl From> for ArrValue { + fn from(v: Vec) -> Self { + Self::Eager(Rc::new(v)) + } } #[derive(Debug, Clone)] pub enum Val { Bool(bool), Null, - Str(Rc), + Str(IStr), Num(f64), - Lazy(LazyVal), - Arr(Rc>), + Arr(ArrValue), Obj(ObjValue), Func(Rc), } @@ -237,40 +335,33 @@ } pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> { - let this_type = self.value_type()?; + let this_type = self.value_type(); if this_type != val_type { throw!(TypeMismatch(context, vec![val_type], this_type)) } else { Ok(()) } } + pub fn unwrap_num(self) -> Result { + Ok(matches_unwrap!(self, Self::Num(v), v)) + } + pub fn unwrap_func(self) -> Result> { + Ok(matches_unwrap!(self, Self::Func(v), v)) + } pub fn try_cast_bool(self, context: &'static str) -> Result { self.assert_type(context, ValType::Bool)?; - Ok(matches_unwrap!(self.unwrap_if_lazy()?, Self::Bool(v), v)) + Ok(matches_unwrap!(self, Self::Bool(v), v)) } - pub fn try_cast_str(self, context: &'static str) -> Result> { + pub fn try_cast_str(self, context: &'static str) -> Result { self.assert_type(context, ValType::Str)?; - Ok(matches_unwrap!(self.unwrap_if_lazy()?, Self::Str(v), v)) + Ok(matches_unwrap!(self, Self::Str(v), v)) } pub fn try_cast_num(self, context: &'static str) -> Result { self.assert_type(context, ValType::Num)?; - Ok(matches_unwrap!(self.unwrap_if_lazy()?, Self::Num(v), v)) - } - pub fn inplace_unwrap(&mut self) -> Result<()> { - while let Self::Lazy(lazy) = self { - *self = lazy.evaluate()?; - } - Ok(()) - } - pub fn unwrap_if_lazy(&self) -> Result { - Ok(if let Self::Lazy(v) = self { - v.evaluate()?.unwrap_if_lazy()? - } else { - self.clone() - }) + self.unwrap_num() } - pub fn value_type(&self) -> Result { - Ok(match self { + pub const fn value_type(&self) -> ValType { + match self { Self::Str(..) => ValType::Str, Self::Num(..) => ValType::Num, Self::Arr(..) => ValType::Arr, @@ -278,18 +369,17 @@ Self::Bool(_) => ValType::Bool, Self::Null => ValType::Null, Self::Func(..) => ValType::Func, - Self::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?, - }) + } } - pub fn to_string(&self) -> Result> { - Ok(match self.unwrap_if_lazy()? { + 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, + Self::Str(s) => s.clone(), v => manifest_json_ex( - &v, + v, &ManifestJsonOptions { padding: "", mtype: ManifestType::ToString, @@ -300,7 +390,7 @@ } /// Expects value to be object, outputs (key, manifested value) pairs - pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result, Rc)>> { + pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result> { let obj = match self { Self::Obj(obj) => obj, _ => throw!(MultiManifestOutputIsNotAObject), @@ -318,19 +408,19 @@ } /// Expects value to be array, outputs manifested values - pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result>> { + pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result> { let arr = match self { Self::Arr(a) => a, _ => throw!(StreamManifestOutputIsNotAArray), }; let mut out = Vec::with_capacity(arr.len()); for i in arr.iter() { - out.push(i.manifest(ty)?); + out.push(i?.manifest(ty)?); } Ok(out) } - pub fn manifest(&self, ty: &ManifestFormat) -> Result> { + pub fn manifest(&self, ty: &ManifestFormat) -> Result { Ok(match ty { ManifestFormat::YamlStream(format) => { let arr = match self { @@ -348,7 +438,7 @@ if !arr.is_empty() { for v in arr.iter() { out.push_str("---\n"); - out.push_str(&v.manifest(format)?); + out.push_str(&v?.manifest(format)?); out.push('\n'); } out.push_str("..."); @@ -367,7 +457,7 @@ } /// For manifestification - pub fn to_json(&self, padding: usize) -> Result> { + pub fn to_json(&self, padding: usize) -> Result { manifest_json_ex( self, &ManifestJsonOptions { @@ -419,7 +509,7 @@ .try_cast_str("to json")?) }) } - pub fn to_yaml(&self, padding: usize) -> Result> { + pub fn to_yaml(&self, padding: usize) -> Result { with_state(|s| { let ctx = s .create_default_context()? @@ -456,7 +546,7 @@ /// Native implementation of `std.primitiveEquals` pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result { - Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) { + Ok(match (val_a, val_b) { (Val::Bool(a), Val::Bool(b)) => a == b, (Val::Null, Val::Null) => true, (Val::Str(a), Val::Str(b)) => a == b, @@ -467,7 +557,7 @@ (Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError( "primitiveEquals operates on primitive types, got object".into(), )), - (a, b) if is_function_like(&a) && is_function_like(&b) => { + (a, b) if is_function_like(a) && is_function_like(b) => { throw!(RuntimeError("cannot test equality of functions".into())) } (_, _) => false, @@ -476,26 +566,28 @@ /// Native implementation of `std.equals` pub fn equals(val_a: &Val, val_b: &Val) -> Result { - let val_a = val_a.unwrap_if_lazy()?; - let val_b = val_b.unwrap_if_lazy()?; - - if val_a.value_type()? != val_b.value_type()? { + if val_a.value_type() != val_b.value_type() { return Ok(false); } match (val_a, val_b) { - // Cant test for ptr equality, because all fields needs to be evaluated (Val::Arr(a), Val::Arr(b)) => { + if ArrValue::ptr_eq(a, b) { + return Ok(true); + } if a.len() != b.len() { return Ok(false); } for (a, b) in a.iter().zip(b.iter()) { - if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? { + if !equals(&a?, &b?)? { return Ok(false); } } Ok(true) } (Val::Obj(a), Val::Obj(b)) => { + if ObjValue::ptr_eq(a, b) { + return Ok(true); + } let fields = a.visible_fields(); if fields != b.visible_fields() { return Ok(false); @@ -507,6 +599,6 @@ } Ok(true) } - (a, b) => Ok(primitive_equals(&a, &b)?), + (a, b) => Ok(primitive_equals(a, b)?), } } --- /dev/null +++ b/crates/jrsonnet-interner/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock --- /dev/null +++ b/crates/jrsonnet-interner/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "jrsonnet-interner" +version = "0.3.3" +authors = ["Yaroslav Bolyukin "] +edition = "2018" + +[dependencies] +serde = { version = "1.0" } +rustc-hash = "1.1.0" --- /dev/null +++ b/crates/jrsonnet-interner/src/lib.rs @@ -0,0 +1,104 @@ +use rustc_hash::FxHashMap; +use serde::{Deserialize, Serialize}; +use std::{ + cell::RefCell, + fmt::{self, Display}, + hash::{BuildHasherDefault, Hash, Hasher}, + ops::Deref, + rc::Rc, +}; + +#[derive(Clone, PartialOrd, Ord, Eq)] +pub struct IStr(Rc); + +impl Deref for IStr { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PartialEq for IStr { + fn eq(&self, other: &Self) -> bool { + // It is ok, since all IStr should be inlined into same pool + Rc::ptr_eq(&self.0, &other.0) + } +} + +impl PartialEq for IStr { + fn eq(&self, other: &str) -> bool { + &self.0 as &str == other + } +} + +impl Hash for IStr { + fn hash(&self, state: &mut H) { + state.write_usize(Rc::as_ptr(&self.0) as *const () as usize) + } +} + +impl Drop for IStr { + fn drop(&mut self) { + // First reference - current object, second - POOL + if Rc::strong_count(&self.0) <= 2 { + STR_POOL.with(|pool| pool.borrow_mut().remove(&self.0)); + } + } +} + +impl fmt::Debug for IStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", &self.0) + } +} + +impl Display for IStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +thread_local! { + static STR_POOL: RefCell, ()>> = RefCell::new(FxHashMap::with_capacity_and_hasher(200, BuildHasherDefault::default())); +} + +impl From<&str> for IStr { + fn from(str: &str) -> Self { + IStr(STR_POOL.with(|pool| { + let mut pool = pool.borrow_mut(); + if let Some((k, _)) = pool.get_key_value(str) { + k.clone() + } else { + let rc: Rc = str.into(); + pool.insert(rc.clone(), ()); + rc + } + })) + } +} + +impl From for IStr { + fn from(str: String) -> Self { + (&str as &str).into() + } +} + +impl Serialize for IStr { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (&self.0 as &str).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for IStr { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = <&str>::deserialize(deserializer)?; + Ok(s.into()) + } +} --- a/crates/jrsonnet-parser/Cargo.toml +++ b/crates/jrsonnet-parser/Cargo.toml @@ -10,16 +10,14 @@ default = [] serialize = ["serde"] deserialize = ["serde"] -# Adds ability to dump AST as source code for easy embedding -dump = ["structdump", "structdump-derive"] [dependencies] +jrsonnet-interner = { path = "../jrsonnet-interner" } + peg = "0.6.3" unescape = "0.1.0" serde = { version = "1.0", features = ["derive", "rc"], optional = true } -structdump = { version = "0.1.2", optional = true } -structdump-derive = { version = "0.1.2", optional = true } [dev-dependencies] jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.3" } --- a/crates/jrsonnet-parser/src/expr.rs +++ b/crates/jrsonnet-parser/src/expr.rs @@ -1,3 +1,4 @@ +use jrsonnet_interner::IStr; #[cfg(feature = "deserialize")] use serde::Deserialize; #[cfg(feature = "serialize")] @@ -8,21 +9,17 @@ path::PathBuf, rc::Rc, }; -#[cfg(feature = "dump")] -use structdump_derive::Codegen; -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] pub enum FieldName { /// {fixed: 2} - Fixed(Rc), + Fixed(IStr), /// {["dyn"+"amic"]: 3} Dyn(LocExpr), } -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, Clone, Copy, PartialEq)] @@ -35,13 +32,11 @@ Unhide, } -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] pub struct AssertStmt(pub LocExpr, pub Option); -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] @@ -53,7 +48,6 @@ pub value: LocExpr, } -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] @@ -63,7 +57,6 @@ AssertStmt(AssertStmt), } -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, Clone, Copy, PartialEq)] @@ -89,7 +82,6 @@ } } -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, Clone, Copy, PartialEq)] @@ -97,6 +89,7 @@ Mul, Div, + /// Implemented as intrinsic, put here for completeness Mod, Add, @@ -146,14 +139,12 @@ } /// name, default value -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] -pub struct Param(pub Rc, pub Option); +pub struct Param(pub IStr, pub Option); /// Defined function parameters -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, Clone, PartialEq)] @@ -165,13 +156,11 @@ } } -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] pub struct Arg(pub Option, pub LocExpr); -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] @@ -183,29 +172,25 @@ } } -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, Clone, PartialEq)] pub struct BindSpec { - pub name: Rc, + pub name: IStr, pub params: Option, pub value: LocExpr, } -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] pub struct IfSpecData(pub LocExpr); -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] -pub struct ForSpecData(pub Rc, pub LocExpr); +pub struct ForSpecData(pub IStr, pub LocExpr); -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] @@ -214,7 +199,6 @@ ForSpec(ForSpecData), } -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] @@ -226,7 +210,6 @@ pub compspecs: Vec, } -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] @@ -235,7 +218,6 @@ ObjComp(ObjComp), } -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq, Clone, Copy)] @@ -256,7 +238,6 @@ } /// Syntax base -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, PartialEq)] @@ -264,11 +245,11 @@ Literal(LiteralType), /// String value: "hello" - Str(Rc), + Str(IStr), /// Number: 1, 2.0, 2e+20 Num(f64), /// Variable name: test - Var(Rc), + Var(IStr), /// Array of expressions: [1, 2, "Hello"] Arr(Vec), @@ -315,7 +296,7 @@ /// function(x) x Function(ParamsDesc, LocExpr), /// std.primitiveEquals - Intrinsic(Rc), + Intrinsic(IStr), /// if true == false then 1 else 2 IfElse { cond: IfSpecData, @@ -325,7 +306,6 @@ } /// file, begin offset, end offset -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Clone, PartialEq)] @@ -337,7 +317,6 @@ } /// Holds AST expression and its location in source file -#[cfg_attr(feature = "dump", derive(Codegen))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Clone, PartialEq)] --- /dev/null +++ b/crates/jrsonnet-types/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "jrsonnet-types" +version = "0.3.3" +authors = ["Yaroslav Bolyukin "] +edition = "2018" + +[dependencies] +peg = "0.6.3" \ No newline at end of file --- /dev/null +++ b/crates/jrsonnet-types/src/lib.rs @@ -0,0 +1,285 @@ +use std::fmt::Display; + +#[macro_export] +macro_rules! ty { + ((Array)) => {{ + $crate::ComplexValType::ArrayRef(&$crate::ComplexValType::Simple($crate::ValType::Num)) + }}; + (array) => { + $crate::ComplexValType::Simple($crate::ValType::Arr) + }; + (boolean) => { + $crate::ComplexValType::Simple($crate::ValType::Bool) + }; + (null) => { + $crate::ComplexValType::Simple($crate::ValType::Null) + }; + (string) => { + $crate::ComplexValType::Simple($crate::ValType::Str) + }; + (char) => { + $crate::ComplexValType::Char + }; + (number) => { + $crate::ComplexValType::Simple($crate::ValType::Num) + }; + (BoundedNumber<($min:expr), ($max:expr)>) => {{ + $crate::ComplexValType::BoundedNumber($min, $max) + }}; + (object) => { + $crate::ComplexValType::Simple($crate::ValType::Obj) + }; + (any) => { + $crate::ComplexValType::Any + }; + (function) => { + $crate::ComplexValType::Simple($crate::ValType::Func) + }; + (($($a:tt) |+)) => {{ + static CONTENTS: &'static [$crate::ComplexValType] = &[ + $(ty!($a)),+ + ]; + $crate::ComplexValType::UnionRef(CONTENTS) + }}; + (($($a:tt) &+)) => {{ + static CONTENTS: &'static [$crate::ComplexValType] = &[ + $(ty!($a)),+ + ]; + $crate::ComplexValType::SumRef(CONTENTS) + }}; +} + +#[test] +fn test() { + assert_eq!( + ty!((Array)), + ComplexValType::ArrayRef(&ComplexValType::Simple(ValType::Num)) + ); + assert_eq!(ty!(array), ComplexValType::Simple(ValType::Arr)); + assert_eq!(ty!(any), ComplexValType::Any); + assert_eq!( + ty!((string | number)), + ComplexValType::UnionRef(&[ + ComplexValType::Simple(ValType::Str), + ComplexValType::Simple(ValType::Num) + ]) + ); + assert_eq!( + format!("{}", ty!(((string & number) | (object & null)))), + "string & number | object & null" + ); + assert_eq!(format!("{}", ty!((string | array))), "string | array"); + assert_eq!( + format!("{}", ty!(((string & number) | array))), + "string & number | array" + ); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ValType { + Bool, + Null, + Str, + Num, + Arr, + Obj, + Func, +} + +impl ValType { + pub const fn name(&self) -> &'static str { + use ValType::*; + match self { + Bool => "boolean", + Null => "null", + Str => "string", + Num => "number", + Arr => "array", + Obj => "object", + Func => "function", + } + } +} + +impl Display for ValType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name()) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ComplexValType { + Any, + Char, + Simple(ValType), + BoundedNumber(Option, Option), + Array(Box), + ArrayRef(&'static ComplexValType), + ObjectRef(&'static [(&'static str, ComplexValType)]), + Union(Vec), + UnionRef(&'static [ComplexValType]), + Sum(Vec), + SumRef(&'static [ComplexValType]), +} +impl From for ComplexValType { + fn from(s: ValType) -> Self { + Self::Simple(s) + } +} + +fn write_union( + f: &mut std::fmt::Formatter<'_>, + is_union: bool, + union: &[ComplexValType], +) -> std::fmt::Result { + for (i, v) in union.iter().enumerate() { + let should_add_braces = + matches!(v, ComplexValType::UnionRef(_) | ComplexValType::Union(_) if !is_union); + if i != 0 { + write!(f, " {} ", if is_union { '|' } else { '&' })?; + } + if should_add_braces { + write!(f, "(")?; + } + write!(f, "{}", v)?; + if should_add_braces { + write!(f, ")")?; + } + } + Ok(()) +} + +fn print_array(a: &ComplexValType, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if *a == ComplexValType::Any { + write!(f, "array")? + } else { + write!(f, "Array<{}>", a)? + } + Ok(()) +} + +impl Display for ComplexValType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ComplexValType::Any => write!(f, "any")?, + ComplexValType::Simple(s) => write!(f, "{}", s)?, + ComplexValType::Char => write!(f, "char")?, + ComplexValType::BoundedNumber(a, b) => write!( + f, + "BoundedNumber<{}, {}>", + a.map(|e| e.to_string()).unwrap_or_else(|| "".into()), + b.map(|e| e.to_string()).unwrap_or_else(|| "".into()) + )?, + ComplexValType::ArrayRef(a) => print_array(a, f)?, + ComplexValType::Array(a) => print_array(a, f)?, + ComplexValType::ObjectRef(fields) => { + write!(f, "{{")?; + for (i, (k, v)) in fields.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{}: {}", k, v)?; + } + write!(f, "}}")?; + } + ComplexValType::Union(v) => write_union(f, true, v)?, + ComplexValType::UnionRef(v) => write_union(f, true, v)?, + ComplexValType::Sum(v) => write_union(f, false, v)?, + ComplexValType::SumRef(v) => write_union(f, false, v)?, + }; + Ok(()) + } +} + +peg::parser! { +pub grammar parser() for str { + rule number() -> f64 + = n:$(['0'..='9']+) { n.parse().unwrap() } + + rule any_ty() -> ComplexValType = "any" { ComplexValType::Any } + rule char_ty() -> ComplexValType = "character" { ComplexValType::Char } + rule bool_ty() -> ComplexValType = "boolean" { ComplexValType::Simple(ValType::Bool) } + rule null_ty() -> ComplexValType = "null" { ComplexValType::Simple(ValType::Null) } + rule str_ty() -> ComplexValType = "string" { ComplexValType::Simple(ValType::Str) } + rule num_ty() -> ComplexValType = "number" { ComplexValType::Simple(ValType::Num) } + rule simple_array_ty() -> ComplexValType = "array" { ComplexValType::Simple(ValType::Arr) } + rule simple_object_ty() -> ComplexValType = "object" { ComplexValType::Simple(ValType::Obj) } + rule simple_function_ty() -> ComplexValType = "function" { ComplexValType::Simple(ValType::Func) } + + rule array_ty() -> ComplexValType + = "Array<" t:ty() ">" { ComplexValType::Array(Box::new(t)) } + + rule bounded_number_ty() -> ComplexValType + = "BoundedNumber<" a:number() ", " b:number() ">" { ComplexValType::BoundedNumber(Some(a), Some(b)) } + + rule ty_basic() -> ComplexValType + = any_ty() + / char_ty() + / bool_ty() + / null_ty() + / str_ty() + / num_ty() + / simple_array_ty() + / simple_object_ty() + / simple_function_ty() + / array_ty() + / bounded_number_ty() + + pub rule ty() -> ComplexValType + = precedence! { + a:(@) " | " b:@ { + match a { + ComplexValType::Union(mut a) => { + a.push(b); + ComplexValType::Union(a) + } + _ => ComplexValType::Union(vec![a, b]), + } + } + -- + a:(@) " & " b:@ { + match a { + ComplexValType::Sum(mut a) => { + a.push(b); + ComplexValType::Sum(a) + } + _ => ComplexValType::Sum(vec![a, b]), + } + } + -- + "(" t:ty() ")" { t } + t:ty_basic() { t } + } +} +} + +#[cfg(test)] +pub mod tests { + use super::parser; + + #[test] + fn precedence() { + assert_eq!( + parser::ty("(any & any) | (any | any) & any") + .unwrap() + .to_string(), + "any & any | (any | any) & any" + ); + } + + #[test] + fn array() { + assert_eq!(parser::ty("Array").unwrap().to_string(), "array"); + assert_eq!( + parser::ty("Array").unwrap().to_string(), + "Array" + ); + } + #[test] + fn bounded_number() { + assert_eq!( + parser::ty("BoundedNumber<1, 2>").unwrap().to_string(), + "BoundedNumber<1, 2>" + ); + } +} -- gitstuff