From a5471f26b10f57fb495de34f344dfbde73cccbe5 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Mon, 29 Nov 2021 19:27:23 +0000 Subject: [PATCH] feat: implement argument parsing with proc macro --- --- a/bindings/jsonnet/src/import.rs +++ b/bindings/jsonnet/src/import.rs @@ -49,8 +49,8 @@ }; // Release memory occipied by arguments passed unsafe { - CString::from_raw(base); - CString::from_raw(rel); + let _ = CString::from_raw(base); + let _ = CString::from_raw(rel); } let result_raw = unsafe { CStr::from_ptr(result_ptr) }; let result_str = result_raw.to_str().unwrap(); @@ -64,7 +64,7 @@ let found_here_raw = unsafe { CStr::from_ptr(found_here) }; let found_here_buf = PathBuf::from(found_here_raw.to_str().unwrap()); unsafe { - CString::from_raw(found_here); + let _ = CString::from_raw(found_here); } let mut out = self.out.borrow_mut(); --- a/bindings/jsonnet/src/native.rs +++ b/bindings/jsonnet/src/native.rs @@ -3,10 +3,11 @@ error::{Error, LocError}, gc::TraceBox, native::{NativeCallback, NativeCallbackHandler}, - EvaluationState, Val, + EvaluationState, IStr, Val, }; use jrsonnet_parser::{Param, ParamsDesc}; use std::{ + convert::TryFrom, ffi::{c_void, CStr}, os::raw::{c_char, c_int}, path::Path, @@ -45,7 +46,7 @@ if success == 1 { Ok(v) } else { - let e = v.try_cast_str("native error").expect("error msg"); + let e = IStr::try_from(v).expect("error msg"); Err(Error::RuntimeError(e).into()) } } --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -23,6 +23,7 @@ jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.4.2" } jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.4.2" } jrsonnet-types = { path = "../jrsonnet-types", version = "0.4.2" } +jrsonnet-macros = { path = "../jrsonnet-macros", version = "0.4.2" } pathdiff = "0.2.0" md5 = "0.7.0" --- a/crates/jrsonnet-evaluator/src/builtin/format.rs +++ b/crates/jrsonnet-evaluator/src/builtin/format.rs @@ -5,6 +5,7 @@ use gcmodule::Trace; use jrsonnet_interner::IStr; use jrsonnet_types::ValType; +use std::convert::TryFrom; use thiserror::Error; #[derive(Debug, Clone, Error, Trace)] @@ -484,7 +485,7 @@ match code.convtype { ConvTypeV::String => tmp_out.push_str(&value.clone().to_string()?), ConvTypeV::Decimal => { - let value = value.clone().try_cast_num("%d/%u/%i requires number")?; + let value = f64::try_from(value.clone())?; render_decimal( &mut tmp_out, value as i64, @@ -495,7 +496,7 @@ ); } ConvTypeV::Octal => { - let value = value.clone().try_cast_num("%o requires number")?; + let value = f64::try_from(value.clone())?; render_octal( &mut tmp_out, value as i64, @@ -507,7 +508,7 @@ ); } ConvTypeV::Hexadecimal => { - let value = value.clone().try_cast_num("%x/%X requires number")?; + let value = f64::try_from(value.clone())?; render_hexadecimal( &mut tmp_out, value as i64, @@ -520,7 +521,7 @@ ); } ConvTypeV::Scientific => { - let value = value.clone().try_cast_num("%e/%E requires number")?; + let value = f64::try_from(value.clone())?; render_float_sci( &mut tmp_out, value, @@ -534,7 +535,7 @@ ); } ConvTypeV::Float => { - let value = value.clone().try_cast_num("%e/%E requires number")?; + let value = f64::try_from(value.clone())?; render_float( &mut tmp_out, value, @@ -547,7 +548,7 @@ ); } ConvTypeV::Shorter => { - let value = value.clone().try_cast_num("%g/%G requires number")?; + let value = f64::try_from(value.clone())?; let exponent = value.log10().floor(); if exponent < -4.0 || exponent >= fpprec as f64 { render_float_sci( @@ -633,7 +634,7 @@ } let value = &values[0]; values = &values[1..]; - value.clone().try_cast_num("field width")? as usize + usize::try_from(value.clone())? } Width::Fixed(n) => n, }; @@ -644,7 +645,7 @@ } let value = &values[0]; values = &values[1..]; - Some(value.clone().try_cast_num("field precision")? as usize) + Some(usize::try_from(value.clone())?) } Some(Width::Fixed(n)) => Some(n), None => None, --- a/crates/jrsonnet-evaluator/src/builtin/mod.rs +++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs @@ -1,10 +1,12 @@ +use crate::typed::{Any, Either, Null, PositiveF64, VecVal, M1}; +use crate::{self as jrsonnet_evaluator, ObjValue}; use crate::{ builtin::manifest::{manifest_yaml_ex, ManifestYamlOptions}, equals, error::{Error::*, Result}, operator::evaluate_mod_op, parse_args, primitive_equals, push_frame, throw, with_state, ArrValue, Context, FuncVal, - IndexableVal, LazyVal, Val, + IndexableVal, Val, }; use format::{format_arr, format_obj}; use gcmodule::Cc; @@ -13,7 +15,12 @@ use jrsonnet_types::ty; use serde::Deserialize; use serde_yaml::DeserializingQuirks; -use std::{collections::HashMap, convert::TryFrom, path::PathBuf, rc::Rc}; +use std::{ + collections::HashMap, + convert::{TryFrom, TryInto}, + path::PathBuf, + rc::Rc, +}; pub mod stdlib; pub use stdlib::*; @@ -24,15 +31,15 @@ pub mod manifest; pub mod sort; -pub fn std_format(str: IStr, vals: Val) -> Result { +pub fn std_format(str: IStr, vals: Val) -> Result { push_frame( &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.evaluated()?)?.into()), - Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()), - o => Val::Str(format_arr(&str, &[o])?.into()), + Val::Arr(vals) => format_arr(&str, &vals.evaluated()?)?, + Val::Obj(obj) => format_obj(&str, &obj)?, + o => format_arr(&str, &[o])?, }) }, ) @@ -139,265 +146,177 @@ }; } -fn builtin_length(context: Context, _loc: &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!(), - }) +#[jrsonnet_macros::builtin] +fn builtin_length(x: Either>) -> Result { + Ok(match x { + Either::Left(x) => x.len(), + Either::Right(Either::Left(x)) => x.0.len(), + Either::Right(Either::Right(x)) => x + .fields_visibility() + .into_iter() + .filter(|(_k, v)| *v) + .count(), }) } -fn builtin_type(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "type", args, 1, [ - 0, x: ty!(any); - ], { - Ok(Val::Str(x.value_type().name().into())) - }) +#[jrsonnet_macros::builtin] +fn builtin_type(x: Any) -> Result { + Ok(x.0.value_type().name().into()) } -fn builtin_make_array(context: Context, _loc: &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.clone(), - &[Val::Num(i as f64)] - )?)) - } - Ok(Val::Arr(out.into())) - }) +#[jrsonnet_macros::builtin] +fn builtin_make_array(sz: usize, func: Cc) -> Result { + let mut out = Vec::with_capacity(sz); + for i in 0..sz { + out.push(func.evaluate_values(&[Val::Num(i as f64)])?) + } + Ok(VecVal(out)) } -fn builtin_codepoint(context: Context, _loc: &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)) - }) +#[jrsonnet_macros::builtin] +const fn builtin_codepoint(str: char) -> Result { + Ok(str as u32) } -fn builtin_object_fields_ex(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "objectFieldsEx", args, 2, [ - 0, obj: ty!(object) => Val::Obj; - 1, inc_hidden: ty!(boolean) => Val::Bool; - ], { - let out = obj.fields_ex(inc_hidden); - Ok(Val::Arr(out.into_iter().map(Val::Str).collect::>().into())) - }) +#[jrsonnet_macros::builtin] +fn builtin_object_fields_ex(obj: ObjValue, inc_hidden: bool) -> Result { + let out = obj.fields_ex(inc_hidden); + Ok(VecVal(out.into_iter().map(Val::Str).collect::>())) } -fn builtin_object_has_ex(context: Context, _loc: &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.has_field_ex(f, inc_hidden))) - }) +#[jrsonnet_macros::builtin] +fn builtin_object_has_ex(obj: ObjValue, f: IStr, inc_hidden: bool) -> Result { + Ok(obj.has_field_ex(f, inc_hidden)) } -fn builtin_parse_json(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "parseJson", args, 1, [ - 0, s: ty!(string) => Val::Str; - ], { - let value: serde_json::Value = serde_json::from_str(&s).map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?; - Ok(Val::try_from(&value)?) - }) +#[jrsonnet_macros::builtin] +fn builtin_parse_json(s: IStr) -> Result { + let value: serde_json::Value = serde_json::from_str(&s) + .map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?; + Ok(Any(Val::try_from(&value)?)) } -fn builtin_parse_yaml(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "parseYaml", args, 1, [ - 0, s: ty!(string) => Val::Str; - ], { - let value = serde_yaml::Deserializer::from_str_with_quirks(&s, DeserializingQuirks { old_octals: true }); - let mut out = vec![]; - for item in value { - let value = serde_json::Value::deserialize(item) - .map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?; - let val = Val::try_from(&value)?; - out.push(val); - } - if out.is_empty() { - Ok(Val::Null) - } else if out.len() == 1 { - Ok(out.into_iter().next().unwrap()) - } else { - Ok(Val::Arr(out.into())) - } - }) +#[jrsonnet_macros::builtin] +fn builtin_parse_yaml(s: IStr) -> Result { + let value = serde_yaml::Deserializer::from_str_with_quirks( + &s, + DeserializingQuirks { old_octals: true }, + ); + let mut out = vec![]; + for item in value { + let value = serde_json::Value::deserialize(item) + .map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?; + let val = Val::try_from(&value)?; + out.push(val); + } + Ok(Any(if out.is_empty() { + Val::Null + } else if out.len() == 1 { + out.into_iter().next().unwrap() + } else { + Val::Arr(out.into()) + })) } -fn builtin_slice(context: Context, _loc: &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)); - ], { - std_slice( - indexable.into_indexable()?, - index.try_cast_nullable_num("index")?.map(|v| v as usize), - end.try_cast_nullable_num("end")?.map(|v| v as usize), - step.try_cast_nullable_num("step")?.map(|v| v as usize), - ) - }) +#[jrsonnet_macros::builtin] +fn builtin_slice( + indexable: IndexableVal, + index: Either, + end: Either, + step: Either, +) -> Result { + std_slice(indexable, index.left(), end.left(), step.left()).map(Any) } -fn builtin_substr(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "substr", args, 3, [ - 0, str: ty!(string) => Val::Str; - 1, from: ty!(BoundedNumber<(Some(0.0)), (None)>) => Val::Num; - 2, len: ty!(BoundedNumber<(Some(0.0)), (None)>) => Val::Num; - ], { - let out: String = str.chars().skip(from as usize).take(len as usize).collect(); - Ok(Val::Str(out.into())) - }) +#[jrsonnet_macros::builtin] +fn builtin_substr(str: IStr, from: usize, len: usize) -> Result { + Ok(str.chars().skip(from as usize).take(len as usize).collect()) } -fn builtin_primitive_equals(context: Context, _loc: &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)?)) - }) +#[jrsonnet_macros::builtin] +fn builtin_primitive_equals(a: Any, b: Any) -> Result { + primitive_equals(&a.0, &b.0) } -fn builtin_equals(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "equals", args, 2, [ - 0, a: ty!(any); - 1, b: ty!(any); - ], { - Ok(Val::Bool(equals(&a, &b)?)) - }) +#[jrsonnet_macros::builtin] +fn builtin_equals(a: Any, b: Any) -> Result { + equals(&a.0, &b.0) } -fn builtin_modulo(context: Context, _loc: &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)) - }) +#[jrsonnet_macros::builtin] +fn builtin_modulo(a: f64, b: f64) -> Result { + Ok(a % b) } -fn builtin_mod(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "mod", args, 2, [ - 0, a: ty!((number | string)); - 1, b: ty!(any); - ], { - evaluate_mod_op(&a, &b) - }) +#[jrsonnet_macros::builtin] +fn builtin_mod(a: Either, b: Any) -> Result { + Ok(Any(evaluate_mod_op( + &match a { + Either::Left(v) => Val::Num(v), + Either::Right(s) => Val::Str(s), + }, + &b.0, + )?)) } -fn builtin_floor(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "floor", args, 1, [ - 0, x: ty!(number) => Val::Num; - ], { - Ok(Val::Num(x.floor())) - }) +#[jrsonnet_macros::builtin] +fn builtin_floor(x: f64) -> Result { + Ok(x.floor()) } -fn builtin_ceil(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "ceil", args, 1, [ - 0, x: ty!(number) => Val::Num; - ], { - Ok(Val::Num(x.ceil())) - }) +#[jrsonnet_macros::builtin] +fn builtin_ceil(x: f64) -> Result { + Ok(x.ceil()) } -fn builtin_log(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "log", args, 1, [ - 0, n: ty!(number) => Val::Num; - ], { - Ok(Val::Num(n.ln())) - }) +#[jrsonnet_macros::builtin] +fn builtin_log(n: f64) -> Result { + Ok(n.ln()) } -fn builtin_pow(context: Context, _loc: &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))) - }) +#[jrsonnet_macros::builtin] +fn builtin_pow(x: f64, n: f64) -> Result { + Ok(x.powf(n)) } -fn builtin_sqrt(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "sqrt", args, 1, [ - 0, x: ty!(BoundedNumber<(Some(0.0)), (None)>) => Val::Num; - ], { - Ok(Val::Num(x.sqrt())) - }) +#[jrsonnet_macros::builtin] +fn builtin_sqrt(x: PositiveF64) -> Result { + Ok(x.0.sqrt()) } -fn builtin_sin(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "sin", args, 1, [ - 0, x: ty!(number) => Val::Num; - ], { - Ok(Val::Num(x.sin())) - }) +#[jrsonnet_macros::builtin] +fn builtin_sin(x: f64) -> Result { + Ok(x.sin()) } -fn builtin_cos(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "cos", args, 1, [ - 0, x: ty!(number) => Val::Num; - ], { - Ok(Val::Num(x.cos())) - }) +#[jrsonnet_macros::builtin] +fn builtin_cos(x: f64) -> Result { + Ok(x.cos()) } -fn builtin_tan(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "tan", args, 1, [ - 0, x: ty!(number) => Val::Num; - ], { - Ok(Val::Num(x.tan())) - }) +#[jrsonnet_macros::builtin] +fn builtin_tan(x: f64) -> Result { + Ok(x.tan()) } -fn builtin_asin(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "asin", args, 1, [ - 0, x: ty!(number) => Val::Num; - ], { - Ok(Val::Num(x.asin())) - }) +#[jrsonnet_macros::builtin] +fn builtin_asin(x: f64) -> Result { + Ok(x.asin()) } -fn builtin_acos(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "acos", args, 1, [ - 0, x: ty!(number) => Val::Num; - ], { - Ok(Val::Num(x.acos())) - }) +#[jrsonnet_macros::builtin] +fn builtin_acos(x: f64) -> Result { + Ok(x.acos()) } -fn builtin_atan(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "atan", args, 1, [ - 0, x: ty!(number) => Val::Num; - ], { - Ok(Val::Num(x.atan())) - }) +#[jrsonnet_macros::builtin] +fn builtin_atan(x: f64) -> Result { + Ok(x.atan()) } -fn builtin_exp(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "exp", args, 1, [ - 0, x: ty!(number) => Val::Num; - ], { - Ok(Val::Num(x.exp())) - }) +#[jrsonnet_macros::builtin] +fn builtin_exp(x: f64) -> Result { + Ok(x.exp()) } fn frexp(s: f64) -> (f64, i16) { @@ -411,198 +330,140 @@ } } -fn builtin_mantissa(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "mantissa", args, 1, [ - 0, x: ty!(number) => Val::Num; - ], { - Ok(Val::Num(frexp(x).0)) - }) +#[jrsonnet_macros::builtin] +fn builtin_mantissa(x: f64) -> Result { + Ok(frexp(x).0) } -fn builtin_exponent(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "exponent", args, 1, [ - 0, x: ty!(number) => Val::Num; - ], { - Ok(Val::Num(frexp(x).1.into())) - }) +#[jrsonnet_macros::builtin] +fn builtin_exponent(x: f64) -> Result { + Ok(frexp(x).1) } -fn builtin_ext_var(context: Context, _loc: &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))?) - }) +#[jrsonnet_macros::builtin] +fn builtin_ext_var(x: IStr) -> Result { + Ok(Any(with_state(|s| s.settings().ext_vars.get(&x).cloned()) + .ok_or(UndefinedExternalVariable(x))?)) } -fn builtin_native(context: Context, _loc: &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(Cc::new(FuncVal::NativeExt(x.clone(), v)))).ok_or(UndefinedExternalFunction(x))?) - }) +#[jrsonnet_macros::builtin] +fn builtin_native(name: IStr) -> Result> { + Ok(with_state(|s| s.settings().ext_natives.get(&name).cloned()) + .map(|v| Cc::new(FuncVal::NativeExt(name.clone(), v))) + .ok_or(UndefinedExternalFunction(name))?) } -fn builtin_filter(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "filter", args, 2, [ - 0, func: ty!(function) => Val::Func; - 1, arr: ty!(array) => Val::Arr; - ], { - Ok(Val::Arr(arr.filter(|val| func - .evaluate_values(context.clone(), &[val.clone()])? - .try_cast_bool("filter predicate"))?)) - }) +#[jrsonnet_macros::builtin] +fn builtin_filter(func: Cc, arr: ArrValue) -> Result { + arr.filter(|val| bool::try_from(func.evaluate_values(&[val.clone()])?)) } -fn builtin_map(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "map", args, 2, [ - 0, func: ty!(function) => Val::Func; - 1, arr: ty!(array) => Val::Arr; - ], { - Ok(Val::Arr(arr.map(|val| func - .evaluate_values(context.clone(), &[val]))?)) - }) +#[jrsonnet_macros::builtin] +fn builtin_map(func: Cc, arr: ArrValue) -> Result { + arr.map(|val| func.evaluate_values(&[val])) } -fn builtin_flatmap(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "flatMap", args, 2, [ - 0, func: ty!(function) => Val::Func; - 1, arr: ty!((array | string)); - ], { - match arr { - Val::Str(s) => { - let mut out = String::new(); - for c in s.chars() { - match func.evaluate_values(context.clone(), &[Val::Str(c.to_string().into())])? { - Val::Str(o) => out.push_str(&o), - _ => throw!(RuntimeError("in std.join all items should be strings".into())), - }; - } - Ok(Val::Str(out.into())) - }, - Val::Arr(a) => { - let mut out = Vec::new(); - for el in a.iter() { - let el = el?; - match func.evaluate_values(context.clone(), &[el])? { - Val::Arr(o) => for oe in o.iter() { +#[jrsonnet_macros::builtin] +fn builtin_flatmap(func: Cc, arr: IndexableVal) -> Result { + match arr { + IndexableVal::Str(s) => { + let mut out = String::new(); + for c in s.chars() { + match func.evaluate_values(&[Val::Str(c.to_string().into())])? { + Val::Str(o) => out.push_str(&o), + _ => throw!(RuntimeError( + "in std.join all items should be strings".into() + )), + }; + } + Ok(IndexableVal::Str(out.into())) + } + IndexableVal::Arr(a) => { + let mut out = Vec::new(); + for el in a.iter() { + let el = el?; + match func.evaluate_values(&[el])? { + Val::Arr(o) => { + for oe in o.iter() { out.push(oe?) - }, - _ => throw!(RuntimeError("in std.join all items should be arrays".into())), - }; - } - Ok(Val::Arr(out.into())) - }, - _ => unreachable!(), + } + } + _ => throw!(RuntimeError( + "in std.join all items should be arrays".into() + )), + }; + } + Ok(IndexableVal::Arr(out.into())) } - }) + } } -fn builtin_foldl(context: Context, _loc: &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) - }) +#[jrsonnet_macros::builtin] +fn builtin_foldl(func: Cc, arr: ArrValue, init: Any) -> Result { + let mut acc = init.0; + for i in arr.iter() { + acc = func.evaluate_values(&[acc, i?])?; + } + Ok(Any(acc)) } -fn builtin_foldr(context: Context, _loc: &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(), &[i?, acc])?; - } - Ok(acc) - }) +#[jrsonnet_macros::builtin] +fn builtin_foldr(func: Cc, arr: ArrValue, init: Any) -> Result { + let mut acc = init.0; + for i in arr.iter().rev() { + acc = func.evaluate_values(&[i?, acc])?; + } + Ok(Any(acc)) } +#[jrsonnet_macros::builtin] #[allow(non_snake_case)] -fn builtin_sort_impl(context: Context, _loc: &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)?))) - }) +fn builtin_sort_impl(arr: ArrValue, keyF: Cc) -> Result { + if arr.len() <= 1 { + return Ok(arr); + } + Ok(ArrValue::Eager(sort::sort(arr.evaluated()?, &keyF)?)) } -fn builtin_format(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "format", args, 2, [ - 0, str: ty!(string) => Val::Str; - 1, vals: ty!(any) - ], { - std_format(str, vals) - }) +#[jrsonnet_macros::builtin] +fn builtin_format(str: IStr, vals: Any) -> Result { + std_format(str, vals.0) } -fn builtin_range(context: Context, _loc: &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())) - }) +#[jrsonnet_macros::builtin] +fn builtin_range(from: i32, to: i32) -> Result { + if to < from { + return Ok(VecVal(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(VecVal(out)) } -fn builtin_char(context: Context, _loc: &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())) - }) +#[jrsonnet_macros::builtin] +fn builtin_char(n: u32) -> Result { + Ok(std::char::from_u32(n as u32).ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?) } -fn builtin_encode_utf8(context: Context, _loc: &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())) - }) +#[jrsonnet_macros::builtin] +fn builtin_encode_utf8(str: IStr) -> Result { + Ok(VecVal( + str.bytes() + .map(|b| Val::Num(b as f64)) + .collect::>(), + )) } -fn builtin_decode_utf8(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "decodeUTF8", args, 1, [ - 0, arr: ty!((Array)) => Val::Arr; - ], { - let data: Result> = arr.iter().map(|v| v.map(|v| match v{ - Val::Num(n) => n as u8, - _ => unreachable!(), - })).collect(); - let data = data?; - Ok(Val::Str(String::from_utf8(data).map_err(|_| RuntimeError("bad utf8".into()))?.into())) - }) +#[jrsonnet_macros::builtin] +fn builtin_decode_utf8(arr: Vec) -> Result { + Ok(String::from_utf8(arr).map_err(|_| RuntimeError("bad utf8".into()))?) } -fn builtin_md5(context: Context, _loc: &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())) - }) +#[jrsonnet_macros::builtin] +fn builtin_md5(str: IStr) -> Result { + Ok(format!("{:x}", md5::compute(&str.as_bytes()))) } fn builtin_trace(context: Context, loc: &ExprLocation, args: &ArgsDesc) -> Result { @@ -620,251 +481,174 @@ }) } -fn builtin_base64(context: Context, _loc: &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!() - })) +#[jrsonnet_macros::builtin] +fn builtin_base64(input: Either, IStr>) -> Result { + Ok(match input { + Either::Left(a) => base64::encode(a), + Either::Right(l) => base64::encode(l.bytes().collect::>()), }) } -fn builtin_base64_decode_bytes( - context: Context, - _loc: &ExprLocation, - args: &ArgsDesc, -) -> Result { - parse_args!(context, "base64DecodeBytes", args, 1, [ - 0, input: ty!(string) => Val::Str; - ], { - Ok(Val::Arr( - base64::decode(&input.as_bytes()) - .map_err(|_| RuntimeError("bad base64".into()))? - .iter() - .map(|v| Val::Num(*v as f64)).collect::>().into() - )) - }) +#[jrsonnet_macros::builtin] +fn builtin_base64_decode_bytes(input: IStr) -> Result> { + Ok(base64::decode(&input.as_bytes()).map_err(|_| RuntimeError("bad base64".into()))?) } -fn builtin_base64_decode(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "base64Decode", args, 1, [ - 0, input: ty!(string) => Val::Str; - ], { - Ok(Val::Str( - String::from_utf8(base64::decode(&input.as_bytes()) - .map_err(|_| RuntimeError("bad base64".into()))?) - .map_err(|_| RuntimeError("bad utf8".into()))?.into() - )) - }) +#[jrsonnet_macros::builtin] +fn builtin_base64_decode(input: IStr) -> Result { + let bytes = base64::decode(&input.as_bytes()).map_err(|_| RuntimeError("bad base64".into()))?; + Ok(String::from_utf8(bytes).map_err(|_| RuntimeError("bad utf8".into()))?) } -fn builtin_join(context: Context, _loc: &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(); +#[jrsonnet_macros::builtin] +fn builtin_join(sep: IndexableVal, arr: ArrValue) -> Result { + Ok(match sep { + IndexableVal::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()); + 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 items.iter() { + for item in joiner_items.iter() { out.push(item?); } - } 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(out.into()) - }, - Val::Str(sep) => { - let mut out = String::new(); + IndexableVal::Arr(out.into()) + } + IndexableVal::Str(sep) => { + let mut out = String::new(); - 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())); + 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!() - }) + IndexableVal::Str(out.into()) + } }) } -fn builtin_escape_string_json( - context: Context, - _loc: &ExprLocation, - args: &ArgsDesc, -) -> Result { - parse_args!(context, "escapeStringJson", args, 1, [ - 0, str_: ty!(string) => Val::Str; - ], { - Ok(Val::Str(escape_string_json(&str_).into())) - }) +#[jrsonnet_macros::builtin] +fn builtin_escape_string_json(str_: IStr) -> Result { + Ok(escape_string_json(&str_)) } -fn builtin_manifest_json_ex(context: Context, _loc: &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 { +#[jrsonnet_macros::builtin] +fn builtin_manifest_json_ex(value: Any, indent: IStr) -> Result { + manifest_json_ex( + &value.0, + &ManifestJsonOptions { padding: &indent, mtype: ManifestType::Std, - })?.into())) - }) + }, + ) } +#[jrsonnet_macros::builtin] fn builtin_manifest_yaml_doc( - context: Context, - _loc: &ExprLocation, - args: &ArgsDesc, -) -> Result { - parse_args!(context, "manifestYamlDoc", args, 3, [ - 0, value: ty!(any); - 1, indent_array_in_object: ty!(boolean) => Val::Bool; - 2, quote_keys: ty!(boolean) => Val::Bool; - ], { - Ok(Val::Str(manifest_yaml_ex(&value, &ManifestYamlOptions { + value: Any, + indent_array_in_object: bool, + quote_keys: bool, +) -> Result { + manifest_yaml_ex( + &value.0, + &ManifestYamlOptions { padding: " ", arr_element_padding: if indent_array_in_object { " " } else { "" }, quote_keys, - })?.into())) - }) + }, + ) } -fn builtin_reverse(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "reverse", args, 1, [ - 0, value: ty!(array) => Val::Arr; - ], { - Ok(Val::Arr(value.reversed())) - }) +#[jrsonnet_macros::builtin] +fn builtin_reverse(value: ArrValue) -> Result { + Ok(value.reversed()) } -fn builtin_id(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "id", args, 1, [ - 0, v: ty!(any); - ], { - Ok(v) - }) +#[jrsonnet_macros::builtin] +const fn builtin_id(v: Any) -> Result { + Ok(v) } -fn builtin_str_replace(context: Context, _loc: &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; - ], { - Ok(Val::Str(str.replace(&from as &str, &to as &str).into())) - }) +#[jrsonnet_macros::builtin] +fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result { + Ok(str.replace(&from as &str, &to as &str)) } - -fn builtin_splitlimit(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "splitLimit", args, 3, [ - 0, str: ty!(string) => Val::Str; - 1, c: ty!(char) => Val::Str; - 2, maxsplits: ty!(number) => Val::Num; - ], { - let maxsplits = maxsplits as isize; - let c = c.chars().next().unwrap(); - let out: Vec = if maxsplits == -1 { - str.split(c).map(|s| Val::Str(s.into())).collect() - } else { - str.splitn(maxsplits as usize + 1, c).map(|s| Val::Str(s.into())).collect() - }; - - Ok(Val::Arr(out.into())) - }) +#[jrsonnet_macros::builtin] +fn builtin_splitlimit(str: IStr, c: char, maxsplits: Either) -> Result { + Ok(VecVal(match maxsplits { + Either::Left(n) => str.splitn(n + 1, c).map(|s| Val::Str(s.into())).collect(), + Either::Right(_) => str.split(c).map(|s| Val::Str(s.into())).collect(), + })) } -fn builtin_ascii_upper(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "asciiUpper", args, 1, [ - 0, str: ty!(string) => Val::Str; - ], { - Ok(Val::Str(str.to_ascii_uppercase().into())) - }) +#[jrsonnet_macros::builtin] +fn builtin_ascii_upper(str: IStr) -> Result { + Ok(str.to_ascii_uppercase()) } -fn builtin_ascii_lower(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "asciiLower", args, 1, [ - 0, str: ty!(string) => Val::Str; - ], { - Ok(Val::Str(str.to_ascii_lowercase().into())) - }) +#[jrsonnet_macros::builtin] +fn builtin_ascii_lower(str: IStr) -> Result { + Ok(str.to_ascii_lowercase()) } -fn builtin_member(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "member", args, 2, [ - 0, arr: ty!((array | string)); - 1, x: ty!(any); - ], { - match arr { - Val::Str(s) => { - let x = x.try_cast_str("x should be string")?; - Ok(Val::Bool(!x.is_empty() && s.contains(&*x))) - } - Val::Arr(a) => { - for item in a.iter() { - let item = item?; - if equals(&item, &x)? { - return Ok(Val::Bool(true)); - } +#[jrsonnet_macros::builtin] +fn builtin_member(arr: IndexableVal, x: Any) -> Result { + match arr { + IndexableVal::Str(s) => { + let x: IStr = IStr::try_from(x.0)?; + Ok(!x.is_empty() && s.contains(&*x)) + } + IndexableVal::Arr(a) => { + for item in a.iter() { + let item = item?; + if equals(&item, &x.0)? { + return Ok(true); } - Ok(Val::Bool(false)) } - _ => unreachable!(), + Ok(false) } - }) + } } -fn builtin_count(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { - parse_args!(context, "count", args, 2, [ - 0, arr: ty!(array) => Val::Arr; - 1, x: ty!(any); - ], { - let mut count = 0; - for item in arr.iter() { - let item = item?; - if equals(&item, &x)? { - count += 1; - } +#[jrsonnet_macros::builtin] +fn builtin_count(arr: Vec, v: Any) -> Result { + let mut count = 0; + for item in arr.iter() { + if equals(&item.0, &v.0)? { + count += 1; } - Ok(Val::Num(count as f64)) - }) + } + Ok(count) } pub fn call_builtin( --- a/crates/jrsonnet-evaluator/src/builtin/sort.rs +++ b/crates/jrsonnet-evaluator/src/builtin/sort.rs @@ -1,6 +1,6 @@ use crate::{ error::{Error, LocError, Result}, - throw, Context, FuncVal, Val, + throw, FuncVal, Val, }; use gcmodule::{Cc, Trace}; @@ -59,7 +59,7 @@ Ok(sort_type) } -pub fn sort(ctx: Context, values: Cc>, key_getter: &FuncVal) -> Result>> { +pub fn sort(values: Cc>, key_getter: &FuncVal) -> Result>> { if values.len() <= 1 { return Ok(values); } @@ -81,10 +81,7 @@ } else { let mut vk = Vec::with_capacity(values.len()); for value in values.iter() { - vk.push(( - value.clone(), - key_getter.evaluate_values(ctx.clone(), &[value.clone()])?, - )); + vk.push((value.clone(), key_getter.evaluate_values(&[value.clone()])?)); } let sort_type = get_sort_type(&mut vk, |v| &mut v.1)?; match sort_type { --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + use crate::{ builtin::std_slice, error::Error::*, @@ -189,14 +191,18 @@ ) -> Result> { Ok(match field_name { jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()), - jrsonnet_parser::FieldName::Dyn(expr) => { - let value = evaluate(context, expr)?; - if matches!(value, Val::Null) { - None - } else { - Some(value.try_cast_str("dynamic field name")?) - } - } + jrsonnet_parser::FieldName::Dyn(expr) => push_frame( + &expr.1, + || "evaluating field name".to_string(), + || { + let value = evaluate(context, expr)?; + if matches!(value, Val::Null) { + Ok(None) + } else { + Ok(Some(IStr::try_from(value)?)) + } + }, + )?, }) } @@ -208,7 +214,7 @@ match specs.get(0) { None => callback(context)?, Some(CompSpec::IfSpec(IfSpecData(cond))) => { - if evaluate(context.clone(), cond)?.try_cast_bool("if spec")? { + if bool::try_from(evaluate(context.clone(), cond)?)? { evaluate_comp(context, &specs[1..], callback)? } } @@ -459,10 +465,7 @@ let assertion_result = push_frame( &value.1, || "assertion condition".to_owned(), - || { - evaluate(context.clone(), value)? - .try_cast_bool("assertion condition should be of type `boolean`") - }, + || bool::try_from(evaluate(context.clone(), value)?), )?; if !assertion_result { push_frame( @@ -633,11 +636,7 @@ ErrorStmt(e) => push_frame( loc, || "error statement".to_owned(), - || { - throw!(RuntimeError( - evaluate(context, e)?.try_cast_str("error text should be of type `string`")?, - )) - }, + || throw!(RuntimeError(IStr::try_from(evaluate(context, e)?)?,)), )?, IfElse { cond, @@ -647,7 +646,7 @@ if push_frame( loc, || "if condition".to_owned(), - || evaluate(context.clone(), &cond.0)?.try_cast_bool("in if condition"), + || bool::try_from(evaluate(context.clone(), &cond.0)?), )? { evaluate(context, cond_then)? } else { --- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs @@ -1,3 +1,5 @@ +use std::convert::TryInto; + use crate::builtin::std_format; use crate::{equals, evaluate, Context, Val}; use crate::{error::Error::*, throw, Result}; @@ -46,7 +48,7 @@ use Val::*; match (a, b) { (Num(a), Num(b)) => Ok(Num(a % b)), - (Str(str), vals) => std_format(str.clone(), vals.clone()), + (Str(str), vals) => std_format(str.clone(), vals.clone())?.try_into(), (a, b) => throw!(BinaryOperatorDoesNotOperateOnValues( BinaryOpType::Mod, a.value_type(), --- a/crates/jrsonnet-evaluator/src/function.rs +++ b/crates/jrsonnet-evaluator/src/function.rs @@ -141,6 +141,93 @@ } } +#[derive(Clone, Copy)] +pub struct BuiltinParam { + pub name: &'static str, + pub has_default: bool, +} + +/// You shouldn't probally use this function, use jrsonnet_macros::builtin instead +/// +/// ## Parameters +/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set) +/// * `params`: function parameters' definition +/// * `args`: passed function arguments +/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily +pub fn parse_builtin_call<'k>( + ctx: Context, + params: &'static [BuiltinParam], + args: &'k ArgsDesc, + tailstrict: bool, +) -> Result> { + let mut passed_args = GcHashMap::with_capacity(params.len()); + if args.unnamed.len() > params.len() { + throw!(TooManyArgsFunctionHas(params.len())) + } + + let mut filled_args = 0; + + for (id, arg) in args.unnamed.iter().enumerate() { + let name = params[id].name; + passed_args.insert( + name, + if tailstrict { + LazyVal::new_resolved(evaluate(ctx.clone(), arg)?) + } else { + LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { + context: ctx.clone(), + expr: arg.clone(), + }))) + }, + ); + filled_args += 1; + } + + for (name, value) in args.named.iter() { + // FIXME: O(n) for arg existence check + if !params.iter().any(|p| p.name == name as &str) { + throw!(UnknownFunctionParameter((name as &str).to_owned())); + } + if passed_args + .insert( + name, + if tailstrict { + LazyVal::new_resolved(evaluate(ctx.clone(), value)?) + } else { + LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { + context: ctx.clone(), + expr: value.clone(), + }))) + }, + ) + .is_some() + { + throw!(BindingParameterASecondTime(name.clone())); + } + filled_args += 1; + } + + if filled_args < params.len() { + for param in params.iter().filter(|p| p.has_default) { + if passed_args.contains_key(¶m.name) { + continue; + } + filled_args += 1; + } + + // Some args still wasn't filled + if filled_args != params.len() { + for param in params.iter().skip(args.unnamed.len()) { + if !args.named.iter().any(|a| &a.0 as &str == param.name) { + throw!(FunctionParameterNotBoundInCall(param.name.into())); + } + } + unreachable!(); + } + } + Ok(passed_args) +} + pub fn parse_function_call_map( ctx: Context, body_ctx: Option, @@ -201,12 +288,7 @@ Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)) } -pub fn place_args( - ctx: Context, - body_ctx: Option, - params: &ParamsDesc, - args: &[Val], -) -> Result { +pub fn place_args(body_ctx: Context, params: &ParamsDesc, args: &[Val]) -> Result { let mut out = GcHashMap::with_capacity(params.len()); let mut positioned_args = vec![None; params.0.len()]; for (id, arg) in args.iter().enumerate() { @@ -220,14 +302,14 @@ let val = if let Some(arg) = &positioned_args[id] { (*arg).clone() } else if let Some(default) = &p.1 { - evaluate(ctx.clone(), default)? + evaluate(body_ctx.clone(), default)? } else { throw!(FunctionParameterNotBoundInCall(p.0.clone())); }; out.insert(p.0.clone(), LazyVal::new_resolved(val)); } - Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)) + Ok(body_ctx.extend(out, None, None, None)) } #[macro_export] --- a/crates/jrsonnet-evaluator/src/trace/mod.rs +++ b/crates/jrsonnet-evaluator/src/trace/mod.rs @@ -122,6 +122,7 @@ .map(|el| &el.location) .map(|location| { use std::fmt::Write; + #[allow(clippy::option_if_let_else)] if let Some(location) = location { let mut resolved_path = self.resolver.resolve(&location.0); // TODO: Process all trace elements first --- a/crates/jrsonnet-evaluator/src/typed.rs +++ /dev/null @@ -1,265 +0,0 @@ -use std::{fmt::Display, rc::Rc}; - -use crate::{ - error::{Error, LocError, Result}, - push_description_frame, Val, -}; -use gcmodule::Trace; -use jrsonnet_types::{ComplexValType, ValType}; -use thiserror::Error; - -#[macro_export] -macro_rules! unwrap_type { - ($desc: expr, $value: expr, $typ: expr => $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, Trace)] -pub enum TypeError { - #[error("expected {0}, got {1}")] - ExpectedGot(ComplexValType, ValType), - #[error("missing property {0} from {1:?}")] - MissingProperty(#[skip_trace] Rc, ComplexValType), - #[error("every failed from {0}:\n{1}")] - UnionFailed(ComplexValType, TypeLocErrorList), - #[error( - "number out of bounds: {0} not in {}..{}", - .1.map(|v|v.to_string()).unwrap_or_else(|| "".to_owned()), - .2.map(|v|v.to_string()).unwrap_or_else(|| "".to_owned()), - )] - BoundsFailed(f64, Option, Option), -} -impl From for LocError { - fn from(e: TypeError) -> Self { - Error::TypeError(e.into()).into() - } -} - -#[derive(Debug, Clone, Trace)] -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, Trace)] -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_description( - error_reason: impl Fn() -> String, - path: impl Fn() -> ValuePathItem, - item: impl Fn() -> Result<()>, -) -> Result<()> { - push_description_frame(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, Trace)] -enum ValuePathItem { - Field(#[skip_trace] 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, Trace)] -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_description( - || format!("array index {}", i), - || ValuePathItem::Index(i as u64), - || 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_description( - || format!("array index {}", i), - || ValuePathItem::Index(i as u64), - || 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_description( - || 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(()) - } - } - } -} --- /dev/null +++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs @@ -0,0 +1,508 @@ +use std::convert::{TryFrom, TryInto}; + +use gcmodule::Cc; +use jrsonnet_interner::IStr; +use jrsonnet_types::{ComplexValType, ValType}; + +use crate::{ + error::{Error::*, LocError, Result}, + throw, + typed::CheckType, + ArrValue, FuncVal, IndexableVal, ObjValue, Val, +}; + +pub trait Typed: TryFrom + TryInto { + const TYPE: &'static ComplexValType; +} + +macro_rules! impl_int { + ($($ty:ty)*) => {$( + impl Typed for $ty { + const TYPE: &'static ComplexValType = + &ComplexValType::BoundedNumber(Some(<$ty>::MIN as f64), Some(<$ty>::MAX as f64)); + } + impl TryFrom for $ty { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Num(n) => { + if n.trunc() != n { + throw!(RuntimeError( + format!( + "cannot convert number with fractional part to {}", + stringify!($ty) + ) + .into() + )) + } + Ok(n as $ty) + } + _ => unreachable!(), + } + } + } + impl TryFrom<$ty> for Val { + type Error = LocError; + + fn try_from(value: $ty) -> Result { + Ok(Self::Num(value as f64)) + } + } + )*}; +} + +impl_int!(i8 u8 i16 u16 i32 u32); + +impl Typed for f64 { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num); +} +impl TryFrom for f64 { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Num(n) => Ok(n), + _ => unreachable!(), + } + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: f64) -> Result { + Ok(Self::Num(value)) + } +} + +pub struct PositiveF64(pub f64); +impl Typed for PositiveF64 { + const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None); +} +impl TryFrom for PositiveF64 { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Num(n) => Ok(Self(n)), + _ => unreachable!(), + } + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: PositiveF64) -> Result { + Ok(Self::Num(value.0)) + } +} + +impl Typed for usize { + // It is possible to store 54 bits of precision in f64, but leaving u32::MAX here for compatibility + const TYPE: &'static ComplexValType = + &ComplexValType::BoundedNumber(Some(0.0), Some(4294967295.0)); +} +impl TryFrom for usize { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Num(n) => { + if n.trunc() != n { + throw!(RuntimeError( + "cannot convert number with fractional part to usize".into() + )) + } + Ok(n as Self) + } + _ => unreachable!(), + } + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: usize) -> Result { + if value > u32::MAX as usize { + throw!(RuntimeError("number is too large".into())) + } + Ok(Self::Num(value as f64)) + } +} + +impl Typed for IStr { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str); +} +impl TryFrom for IStr { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Str(s) => Ok(s), + _ => unreachable!(), + } + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: IStr) -> Result { + Ok(Self::Str(value)) + } +} + +impl Typed for String { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str); +} +impl TryFrom for String { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Str(s) => Ok(s.to_string()), + _ => unreachable!(), + } + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: String) -> Result { + Ok(Self::Str(value.into())) + } +} + +impl Typed for char { + const TYPE: &'static ComplexValType = &ComplexValType::Char; +} +impl TryFrom for char { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Str(s) => Ok(s.chars().next().unwrap()), + _ => unreachable!(), + } + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: char) -> Result { + Ok(Self::Str(value.to_string().into())) + } +} + +impl Typed for Vec +where + T: Typed, + T: TryFrom, + T: TryInto, +{ + const TYPE: &'static ComplexValType = &ComplexValType::ArrayRef(T::TYPE); +} +impl TryFrom for Vec +where + T: Typed, + T: TryFrom, + T: TryInto, +{ + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Arr(a) => { + let mut o = Self::with_capacity(a.len()); + for i in a.iter() { + o.push(T::try_from(i?)?); + } + Ok(o) + } + _ => unreachable!(), + } + } +} +impl TryFrom> for Val +where + T: Typed, + T: TryFrom, + T: TryInto, +{ + type Error = LocError; + + fn try_from(value: Vec) -> Result { + let mut o = Vec::with_capacity(value.len()); + for i in value { + o.push(i.try_into()?); + } + Ok(Self::Arr(o.into())) + } +} + +/// To be used in Vec +/// Regular Val can't be used here, because it has wrong TryFrom::Error type +pub struct Any(pub Val); + +impl Typed for Any { + const TYPE: &'static ComplexValType = &ComplexValType::Any; +} +impl TryFrom for Any { + type Error = LocError; + + fn try_from(value: Val) -> Result { + Ok(Self(value)) + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: Any) -> Result { + Ok(value.0) + } +} + +/// Specialization, provides faster TryFrom for Val +pub struct VecVal(pub Vec); + +impl Typed for VecVal { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr); +} +impl TryFrom for VecVal { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Arr(a) => Ok(Self(a.evaluated()?.to_vec())), + _ => unreachable!(), + } + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: VecVal) -> Result { + Ok(Self::Arr(value.0.into())) + } +} + +pub struct M1; +impl Typed for M1 { + const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0)); +} +impl TryFrom for M1 { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + Ok(Self) + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(_: M1) -> Result { + Ok(Self::Num(-1.0)) + } +} + +pub enum Either { + Left(A), + Right(B), +} + +impl Either { + pub fn to_left(self, f: impl FnOnce(B) -> A) -> A { + match self { + Either::Left(l) => l, + Either::Right(r) => f(r), + } + } + #[allow(clippy::missing_const_for_fn)] + pub fn left(self) -> Option { + match self { + Either::Left(a) => Some(a), + Either::Right(_) => None, + } + } +} + +impl Typed for Either +where + A: Typed, + B: Typed, +{ + const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[A::TYPE, B::TYPE]); +} +impl TryFrom for Either +where + A: Typed, + B: Typed, +{ + type Error = LocError; + + fn try_from(value: Val) -> Result { + if A::TYPE.check(&value).is_ok() { + A::try_from(value).map(Self::Left) + } else if B::TYPE.check(&value).is_ok() { + B::try_from(value).map(Self::Right) + } else { + ::TYPE.check(&value)?; + unreachable!() + } + } +} +impl TryFrom> for Val +where + A: Typed, + B: Typed, +{ + type Error = LocError; + + fn try_from(value: Either) -> Result { + match value { + Either::Left(a) => a.try_into(), + Either::Right(b) => b.try_into(), + } + } +} + +impl Typed for ArrValue { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr); +} +impl TryFrom for ArrValue { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Arr(a) => Ok(a), + _ => unreachable!(), + } + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: ArrValue) -> Result { + Ok(Self::Arr(value)) + } +} + +impl Typed for Cc { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func); +} +impl TryFrom for Cc { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Func(a) => Ok(a), + _ => unreachable!(), + } + } +} +impl TryFrom> for Val { + type Error = LocError; + + fn try_from(value: Cc) -> Result { + Ok(Self::Func(value)) + } +} +impl Typed for ObjValue { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj); +} +impl TryFrom for ObjValue { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Obj(a) => Ok(a), + _ => unreachable!(), + } + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: ObjValue) -> Result { + Ok(Self::Obj(value)) + } +} + +impl Typed for bool { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Bool); +} +impl TryFrom for bool { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Bool(a) => Ok(a), + _ => unreachable!(), + } + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: bool) -> Result { + Ok(Self::Bool(value)) + } +} + +impl Typed for IndexableVal { + const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[ + &ComplexValType::Simple(ValType::Arr), + &ComplexValType::Simple(ValType::Str), + ]); +} +impl TryFrom for IndexableVal { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + value.into_indexable() + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(value: IndexableVal) -> Result { + match value { + IndexableVal::Str(s) => Ok(Self::Str(s)), + IndexableVal::Arr(a) => Ok(Self::Arr(a)), + } + } +} + +pub struct Null; +impl Typed for Null { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Null); +} +impl TryFrom for Null { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + Ok(Self) + } +} +impl TryFrom for Val { + type Error = LocError; + + fn try_from(_: Null) -> Result { + Ok(Self::Null) + } +} --- /dev/null +++ b/crates/jrsonnet-evaluator/src/typed/mod.rs @@ -0,0 +1,268 @@ +use std::{fmt::Display, rc::Rc}; + +mod conversions; +pub use conversions::*; + +use crate::{ + error::{Error, LocError, Result}, + push_description_frame, Val, +}; +use gcmodule::Trace; +use jrsonnet_types::{ComplexValType, ValType}; +use thiserror::Error; + +#[macro_export] +macro_rules! unwrap_type { + ($desc: expr, $value: expr, $typ: expr => $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, Trace)] +pub enum TypeError { + #[error("expected {0}, got {1}")] + ExpectedGot(ComplexValType, ValType), + #[error("missing property {0} from {1:?}")] + MissingProperty(#[skip_trace] Rc, ComplexValType), + #[error("every failed from {0}:\n{1}")] + UnionFailed(ComplexValType, TypeLocErrorList), + #[error( + "number out of bounds: {0} not in {}..{}", + .1.map(|v|v.to_string()).unwrap_or_else(|| "".to_owned()), + .2.map(|v|v.to_string()).unwrap_or_else(|| "".to_owned()), + )] + BoundsFailed(f64, Option, Option), +} +impl From for LocError { + fn from(e: TypeError) -> Self { + Error::TypeError(e.into()).into() + } +} + +#[derive(Debug, Clone, Trace)] +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, Trace)] +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_description( + error_reason: impl Fn() -> String, + path: impl Fn() -> ValuePathItem, + item: impl Fn() -> Result<()>, +) -> Result<()> { + push_description_frame(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, Trace)] +enum ValuePathItem { + Field(#[skip_trace] 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, Trace)] +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_description( + || format!("array index {}", i), + || ValuePathItem::Index(i as u64), + || 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_description( + || format!("array index {}", i), + || ValuePathItem::Index(i as u64), + || 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_description( + || 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 @@ -170,10 +170,10 @@ } } - pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result { + pub fn evaluate_values(&self, args: &[Val]) -> Result { match self { Self::Normal(func) => { - let ctx = place_args(call_ctx, Some(func.ctx.clone()), &func.params, args)?; + let ctx = place_args(func.ctx.clone(), &func.params, args)?; evaluate(ctx, &func.body) } Self::Intrinsic(_) => todo!(), @@ -363,14 +363,6 @@ Func(Cc), } -macro_rules! matches_unwrap { - ($e: expr, $p: pat, $r: expr) => { - match $e { - $p => $r, - _ => panic!("no match"), - } - }; -} impl Val { /// Creates `Val::Num` after checking for numeric overflow. /// As numbers are `f64`, we can just check for their finity. @@ -382,38 +374,6 @@ } } - pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> { - 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_str(self) -> Result { - Ok(matches_unwrap!(self, Self::Str(v), v)) - } - pub fn unwrap_arr(self) -> Result { - Ok(matches_unwrap!(self, Self::Arr(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, Self::Bool(v), v)) - } - pub fn try_cast_str(self, context: &'static str) -> Result { - self.assert_type(context, ValType::Str)?; - Ok(matches_unwrap!(self, Self::Str(v), v)) - } - pub fn try_cast_num(self, context: &'static str) -> Result { - self.assert_type(context, ValType::Num)?; - self.unwrap_num() - } pub fn try_cast_nullable_num(self, context: &'static str) -> Result> { Ok(match self { Val::Null => None, --- /dev/null +++ b/crates/jrsonnet-macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "jrsonnet-macros" +version = "0.4.2" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.32" +quote = "1.0.10" +syn = { version = "1.0.82", features = ["full"] } --- /dev/null +++ b/crates/jrsonnet-macros/src/lib.rs @@ -0,0 +1,87 @@ +use proc_macro2::Span; +use quote::quote; +use syn::{parse_macro_input, FnArg, Ident, ItemFn, Pat}; + +#[proc_macro_attribute] +pub fn builtin( + _attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + // syn::ItemFn::parse(input) + let fun: ItemFn = parse_macro_input!(item); + + let inner_name = Ident::new("inner", Span::call_site()); + let mut inner_fun = fun.clone(); + inner_fun.sig.ident = inner_name.clone(); + let result = match fun.sig.output { + syn::ReturnType::Default => panic!("builtin should return something"), + syn::ReturnType::Type(_, ty) => ty, + }; + + let params = fun + .sig + .inputs + .iter() + .map(|i| match i { + FnArg::Receiver(_) => unreachable!(), + FnArg::Typed(t) => t, + }) + .map(|t| { + let ident = match &t.pat as &Pat { + Pat::Ident(i) => i.ident.to_string(), + _ => panic!("only idents supported yet"), + }; + // TODO: Check if ty == Option<_> + let optional = false; + quote! { + BuiltinParam { + name: #ident, + has_default: #optional, + } + } + }); + + let args = fun + .sig + .inputs + .iter() + .map(|i| match i { + FnArg::Receiver(_) => unreachable!(), + FnArg::Typed(t) => t, + }) + .map(|t| { + let ident = match &t.pat as &Pat { + Pat::Ident(i) => i.ident.to_string(), + _ => panic!("only idents supported yet"), + }; + let ty = &t.ty; + quote! {{ + let value = parsed.get(#ident).unwrap(); + + jrsonnet_evaluator::push_description_frame( + || format!("argument <{}> evaluation", #ident), + || <#ty>::try_from(value.evaluate()?), + )? + }} + }); + + let attrs = &fun.attrs; + let vis = &fun.vis; + let name = &fun.sig.ident; + (quote! { + #(#attrs)* + #vis fn #name(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result { + #inner_fun + use jrsonnet_evaluator::function::BuiltinParam; + const PARAMS: &'static [BuiltinParam] = &[ + #(#params),* + ]; + let parsed = jrsonnet_evaluator::function::parse_builtin_call(context, &PARAMS, args, false)?; + + let result: #result = #inner_name(#(#args),*); + let result = result?; + result.try_into() + } + }) + .into() +} --- a/crates/jrsonnet-types/src/lib.rs +++ b/crates/jrsonnet-types/src/lib.rs @@ -42,14 +42,14 @@ $crate::ComplexValType::Simple($crate::ValType::Func) }; (($($a:tt) |+)) => {{ - static CONTENTS: &'static [$crate::ComplexValType] = &[ - $(ty!($a)),+ + static CONTENTS: &'static [&'static $crate::ComplexValType] = &[ + $(&ty!($a)),+ ]; $crate::ComplexValType::UnionRef(CONTENTS) }}; (($($a:tt) &+)) => {{ - static CONTENTS: &'static [$crate::ComplexValType] = &[ - $(ty!($a)),+ + static CONTENTS: &'static [&'static $crate::ComplexValType] = &[ + $(&ty!($a)),+ ]; $crate::ComplexValType::SumRef(CONTENTS) }}; @@ -66,8 +66,8 @@ assert_eq!( ty!((string | number)), ComplexValType::UnionRef(&[ - ComplexValType::Simple(ValType::Str), - ComplexValType::Simple(ValType::Num) + &ComplexValType::Simple(ValType::Str), + &ComplexValType::Simple(ValType::Num) ]) ); assert_eq!( @@ -124,9 +124,9 @@ ArrayRef(&'static ComplexValType), ObjectRef(&'static [(&'static str, ComplexValType)]), Union(Vec), - UnionRef(&'static [ComplexValType]), + UnionRef(&'static [&'static ComplexValType]), Sum(Vec), - SumRef(&'static [ComplexValType]), + SumRef(&'static [&'static ComplexValType]), } impl From for ComplexValType { @@ -135,12 +135,12 @@ } } -fn write_union( +fn write_union<'i>( f: &mut std::fmt::Formatter<'_>, is_union: bool, - union: &[ComplexValType], + union: impl Iterator, ) -> std::fmt::Result { - for (i, v) in union.iter().enumerate() { + for (i, v) in union.enumerate() { let should_add_braces = matches!(v, ComplexValType::UnionRef(_) | ComplexValType::Union(_) if !is_union); if i != 0 { @@ -190,10 +190,10 @@ } 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)?, + ComplexValType::Union(v) => write_union(f, true, v.iter())?, + ComplexValType::UnionRef(v) => write_union(f, true, v.iter().map(|v| *v))?, + ComplexValType::Sum(v) => write_union(f, false, v.iter())?, + ComplexValType::SumRef(v) => write_union(f, false, v.iter().map(|v| *v))?, }; Ok(()) } -- gitstuff