--- a/Cargo.lock +++ b/Cargo.lock @@ -70,8 +70,9 @@ [[package]] name = "clap" -version = "3.0.0-beta.2" -source = "git+https://github.com/clap-rs/clap?rev=f0c5ea5e1503de5c8e74d8c047a799cf51498e83#f0c5ea5e1503de5c8e74d8c047a799cf51498e83" +version = "3.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c" dependencies = [ "atty", "bitflags", @@ -82,27 +83,28 @@ "strsim", "termcolor", "textwrap", - "vec_map", ] [[package]] +name = "clap_complete" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6f3613c0a3cddfd78b41b10203eb322cb29b600cbdf808a7d3db95691b8e25" +dependencies = [ + "clap", +] + +[[package]] name = "clap_derive" -version = "3.0.0-beta.2" -source = "git+https://github.com/clap-rs/clap?rev=f0c5ea5e1503de5c8e74d8c047a799cf51498e83#f0c5ea5e1503de5c8e74d8c047a799cf51498e83" +version = "3.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", "syn", -] - -[[package]] -name = "clap_generate" -version = "3.0.0-beta.2" -source = "git+https://github.com/clap-rs/clap?rev=f0c5ea5e1503de5c8e74d8c047a799cf51498e83#f0c5ea5e1503de5c8e74d8c047a799cf51498e83" -dependencies = [ - "clap", ] [[package]] @@ -148,12 +150,9 @@ [[package]] name = "heck" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" @@ -185,7 +184,7 @@ version = "0.4.2" dependencies = [ "clap", - "clap_generate", + "clap_complete", "gcmodule", "jrsonnet-cli", "jrsonnet-evaluator", @@ -214,6 +213,7 @@ "bincode", "gcmodule", "jrsonnet-interner", + "jrsonnet-macros", "jrsonnet-parser", "jrsonnet-stdlib", "jrsonnet-types", @@ -236,6 +236,15 @@ ] [[package]] +name = "jrsonnet-macros" +version = "0.4.2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "jrsonnet-parser" version = "0.4.2" dependencies = [ @@ -301,6 +310,12 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] name = "mimalloc-sys" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -321,9 +336,12 @@ [[package]] name = "os_str_bytes" -version = "3.1.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6acbef58a60fe69ab50510a55bc8cdd4d6cf2283d27ad338f54cb52747a9cf2d" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] [[package]] name = "parking_lot" @@ -357,9 +375,9 @@ [[package]] name = "peg" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" +checksum = "af728fe826811af3b38c37e93de6d104485953ea373d656eebae53d6987fcd2c" dependencies = [ "peg-macros", "peg-runtime", @@ -367,9 +385,9 @@ [[package]] name = "peg-macros" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" +checksum = "4536be147b770b824895cbad934fccce8e49f14b4c4946eaa46a6e4a12fcdc16" dependencies = [ "peg-runtime", "proc-macro2", @@ -378,9 +396,9 @@ [[package]] name = "peg-runtime" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" +checksum = "f9b0efd3ba03c3a409d44d60425f279ec442bcf0b9e63ff4e410da31c8b0f69f" [[package]] name = "proc-macro-error" @@ -536,12 +554,9 @@ [[package]] name = "textwrap" -version = "0.14.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" -dependencies = [ - "unicode-width", -] +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" @@ -562,12 +577,6 @@ "quote", "syn", ] - -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" @@ -580,12 +589,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -179,7 +179,7 @@ } } -pub type Result = std::result::Result; +pub type Result = std::result::Result; #[macro_export] macro_rules! throw { --- a/crates/jrsonnet-evaluator/src/function.rs +++ b/crates/jrsonnet-evaluator/src/function.rs @@ -8,6 +8,7 @@ }; use gcmodule::Trace; use jrsonnet_interner::IStr; +pub use jrsonnet_macros::builtin; use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc}; use std::{borrow::Cow, collections::HashMap, convert::TryFrom}; @@ -377,6 +378,7 @@ pub has_default: bool, } +/// Do not implement it directly, instead use #[builtin] macro pub trait Builtin: Trace { fn name(&self) -> &str; fn params(&self) -> &[BuiltinParam]; --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -174,7 +174,11 @@ pub(crate) static EVAL_STATE: RefCell> = RefCell::new(None) } pub(crate) fn with_state(f: impl FnOnce(&EvaluationState) -> T) -> T { - EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap())) + EVAL_STATE.with(|s| { + f(s.borrow().as_ref().expect( + "missing evaluation state, some functions should be called inside of run_in_state call", + )) + }) } pub fn push_frame( e: Option<&ExprLocation>, @@ -728,12 +732,15 @@ } macro_rules! eval { - ($str: expr) => { - EvaluationState::default() - .with_stdlib() - .evaluate_snippet_raw(PathBuf::from("raw.jsonnet").into(), $str.into()) - .unwrap() - }; + ($str: expr) => {{ + let evaluator = EvaluationState::default(); + evaluator.with_stdlib(); + evaluator.run_in_state(|| { + evaluator + .evaluate_snippet_raw(PathBuf::from("raw.jsonnet").into(), $str.into()) + .unwrap() + }) + }}; } macro_rules! eval_json { ($str: expr) => {{ @@ -1265,4 +1272,47 @@ assert_eval!(r#"std.assertEqual(std.count(["a", "b", "a"], "d"), 0)"#); assert_eval!(r#"std.assertEqual(std.count(["a", "b", "a"], "a"), 2)"#); } + + mod derive_typed { + use crate::{typed::Typed, EvaluationState}; + use std::path::PathBuf; + + #[derive(Typed, PartialEq, Debug)] + struct MyTyped { + a: u32, + b: String, + } + + #[test] + fn test() { + let es = EvaluationState::default(); + let val = eval!("{a: 14, b: 'Hello, world!'}"); + let typed = es.run_in_state(|| MyTyped::try_from(val).unwrap()); + + assert_eq!( + typed, + MyTyped { + a: 14, + b: "Hello, world!".to_string() + } + ); + es.settings_mut().globals.insert( + "mytyped".into(), + es.run_in_state(|| typed.try_into()).unwrap(), + ); + + let v = es + .evaluate_snippet_raw( + PathBuf::from("raw.jsonnet").into(), + " + mytyped == {a: 14, b: 'Hello, world!'} + " + .into(), + ) + .unwrap() + .as_bool() + .unwrap(); + assert!(v) + } + } } --- a/crates/jrsonnet-evaluator/src/typed/conversions.rs +++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs @@ -1,6 +1,7 @@ use std::convert::{TryFrom, TryInto}; use jrsonnet_interner::IStr; +pub use jrsonnet_macros::Typed; use jrsonnet_types::{ComplexValType, ValType}; use crate::{ --- a/crates/jrsonnet-evaluator/src/typed/mod.rs +++ b/crates/jrsonnet-evaluator/src/typed/mod.rs @@ -8,20 +8,8 @@ push_description_frame, Val, }; use gcmodule::Trace; -use jrsonnet_types::{ComplexValType, ValType}; +pub 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_frame, typed::CheckType}; - push_frame(None, $desc, || Ok($typ.check(&$value)?))?; - match $value { - $match(v) => v, - _ => unreachable!(), - } - }}; -} #[derive(Debug, Error, Clone, Trace)] pub enum TypeError { @@ -136,7 +124,7 @@ impl Display for ValuePathItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Field(name) => write!(f, ".{}", name)?, + Self::Field(name) => write!(f, ".{:?}", name)?, Self::Index(idx) => write!(f, "[{}]", idx)?, } Ok(()) --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -91,7 +91,7 @@ Normal(Cc), /// Standard library function StaticBuiltin(#[skip_trace] &'static dyn StaticBuiltin), - + /// User-provided function Builtin(Cc>), } @@ -99,8 +99,10 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(), - Self::StaticBuiltin(arg0) => f.debug_tuple("Intrinsic").field(&arg0.name()).finish(), - Self::Builtin(arg0) => f.debug_tuple("Intrinsic").field(&arg0.name()).finish(), + Self::StaticBuiltin(arg0) => { + f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish() + } + Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(), } } } @@ -338,6 +340,49 @@ } impl Val { + pub fn as_bool(&self) -> Option { + match self { + Val::Bool(v) => Some(*v), + _ => None, + } + } + pub fn as_null(&self) -> Option<()> { + match self { + Val::Null => Some(()), + _ => None, + } + } + pub fn as_str(&self) -> Option { + match self { + Val::Str(s) => Some(s.clone()), + _ => None, + } + } + pub fn as_num(&self) -> Option { + match self { + Val::Num(n) => Some(*n), + _ => None, + } + } + pub fn as_arr(&self) -> Option { + match self { + Val::Arr(a) => Some(a.clone()), + _ => None, + } + } + pub fn as_obj(&self) -> Option { + match self { + Val::Obj(o) => Some(o.clone()), + _ => None, + } + } + pub fn as_func(&self) -> Option { + match self { + Val::Func(f) => Some(f.clone()), + _ => None, + } + } + /// Creates `Val::Num` after checking for numeric overflow. /// As numbers are `f64`, we can just check for their finity. pub fn new_checked_num(num: f64) -> Result { --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -1,8 +1,8 @@ use quote::{quote, quote_spanned}; use syn::{ parenthesized, parse::Parse, parse_macro_input, punctuated::Punctuated, spanned::Spanned, - token::Comma, FnArg, GenericArgument, Ident, ItemFn, Pat, PatType, Path, PathArguments, Token, - Type, + token::Comma, DeriveInput, FnArg, GenericArgument, Ident, ItemFn, Pat, PatType, Path, + PathArguments, Token, Type, }; fn is_location_arg(t: &PatType) -> bool { @@ -254,3 +254,86 @@ }) .into() } + +#[proc_macro_derive(Typed)] +pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as DeriveInput); + let data = match &input.data { + syn::Data::Struct(s) => s, + _ => { + return syn::Error::new(input.span(), "only structs supported") + .to_compile_error() + .into() + } + }; + + let ident = &input.ident; + + let fields_def = data.fields.iter().map(|f| { + let name = f + .ident + .as_ref() + .expect("only named fields supported") + .to_string(); + let ty = &f.ty; + quote! { + (#name, #ty::TYPE), + } + }); + let fields_parse = data.fields.iter().map(|f| { + let ident = f.ident.as_ref().unwrap(); + let name = ident.to_string(); + let ty = &f.ty; + quote! { + #ident: #ty::try_from(obj.get(#name.into())?.expect("shape is correct"))?, + } + }); + let fields_serialize = data.fields.iter().map(|f| { + let ident = f.ident.as_ref().unwrap(); + let name = ident.to_string(); + quote! { + out.member(#name.into()).value(self.#ident.try_into()?); + } + }); + let field_count = data.fields.len(); + + quote! { + const _: () = { + use ::jrsonnet_evaluator::{ + typed::{ComplexValType, Typed, CheckType}, + Val, + error::LocError, + obj::ObjValueBuilder, + }; + + const ITEMS: [(&'static str, &'static ComplexValType); #field_count] = [ + #(#fields_def)* + ]; + impl Typed for #ident { + const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS); + } + + impl TryFrom for #ident { + type Error = LocError; + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + let obj = value.as_obj().expect("shape is correct"); + + Ok(Self { + #(#fields_parse)* + }) + } + } + impl TryInto for #ident { + type Error = LocError; + fn try_into(self) -> Result { + let mut out = ObjValueBuilder::new(); + #(#fields_serialize)* + Ok(Val::Obj(out.build())) + } + } + () + }; + } + .into() +} --- a/crates/jrsonnet-types/src/lib.rs +++ b/crates/jrsonnet-types/src/lib.rs @@ -122,7 +122,7 @@ BoundedNumber(Option, Option), Array(Box), ArrayRef(&'static ComplexValType), - ObjectRef(&'static [(&'static str, ComplexValType)]), + ObjectRef(&'static [(&'static str, &'static ComplexValType)]), Union(Vec), UnionRef(&'static [&'static ComplexValType]), Sum(Vec),