git.delta.rocks / jrsonnet / refs/commits / afca25294a05

difftreelog

feat allow unnamed builtin arguments

Yaroslav Bolyukin2022-10-11parent: #1178a0b.patch.diff
in: master

5 files changed

modifiedbindings/jsonnet/src/native.rsdiffbeforeafterboth
1use std::{1use std::{
2 borrow::Cow,
2 ffi::{c_void, CStr},3 ffi::{c_void, CStr},
3 os::raw::{c_char, c_int},4 os::raw::{c_char, c_int},
4};5};
56
6use jrsonnet_evaluator::{7use jrsonnet_evaluator::{
7 error::{Error, LocError},8 error::{Error, LocError},
8 function::builtin::{BuiltinParam, NativeCallback, NativeCallbackHandler},9 function::builtin::{NativeCallback, NativeCallbackHandler},
9 tb,10 tb,
10 typed::Typed,11 typed::Typed,
11 IStr, State, Val,12 IStr, State, Val,
87 let param = CStr::from_ptr(*raw_params)88 let param = CStr::from_ptr(*raw_params)
88 .to_str()89 .to_str()
89 .expect("param name is not utf-8");90 .expect("param name is not utf-8");
90 params.push(BuiltinParam {91 params.push(Cow::Owned(param.into()));
91 name: param.into(),
92 has_default: false,
93 });
94 raw_params = raw_params.offset(1);92 raw_params = raw_params.offset(1);
95 }93 }
9694
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
113 BindingParameterASecondTime(IStr),113 BindingParameterASecondTime(IStr),
114 #[error("too many args, function has {0}{}", format_signature(.1))]114 #[error("too many args, function has {0}{}", format_signature(.1))]
115 TooManyArgsFunctionHas(usize, FunctionSignature),115 TooManyArgsFunctionHas(usize, FunctionSignature),
116 #[error("function argument is not passed: {0}{}", format_signature(.1))]116 #[error("function argument is not passed: {}{}", .0.as_ref().map(|n| n.as_str()).unwrap_or("<unnamed>"), format_signature(.1))]
117 FunctionParameterNotBoundInCall(IStr, FunctionSignature),117 FunctionParameterNotBoundInCall(Option<IStr>, FunctionSignature),
118118
119 #[error("external variable is not defined: {0}")]119 #[error("external variable is not defined: {0}")]
120 UndefinedExternalVariable(IStr),120 UndefinedExternalVariable(IStr),
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
99
10#[derive(Clone, Trace)]10#[derive(Clone, Trace)]
11pub struct BuiltinParam {11pub struct BuiltinParam {
12 /// Parameter name for named call parsing
12 pub name: BuiltinParamName,13 pub name: Option<BuiltinParamName>,
14 /// Is implementation allowed to return empty value
13 pub has_default: bool,15 pub has_default: bool,
14}16}
1517
40}42}
41impl NativeCallback {43impl NativeCallback {
42 #[deprecated = "prefer using builtins directly, use this interface only for bindings"]44 #[deprecated = "prefer using builtins directly, use this interface only for bindings"]
43 pub fn new(params: Vec<BuiltinParam>, handler: TraceBox<dyn NativeCallbackHandler>) -> Self {45 pub fn new(
46 params: Vec<Cow<'static, str>>,
47 handler: TraceBox<dyn NativeCallbackHandler>,
48 ) -> Self {
44 Self { params, handler }49 Self {
50 params: params
51 .into_iter()
52 .map(|n| BuiltinParam {
53 name: Some(n),
54 has_default: false,
55 })
56 .collect(),
57 handler,
58 }
45 }59 }
46}60}
5872
59 fn call(&self, s: State, ctx: Context, _loc: CallLocation, args: &dyn ArgsLike) -> Result<Val> {73 fn call(&self, s: State, ctx: Context, _loc: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
60 let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?;74 let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?;
61 let mut out_args = Vec::with_capacity(self.params.len());75 let args = args
62 for p in &self.params {
63 out_args.push(args[&p.name].evaluate(s.clone())?);76 .into_iter()
64 }77 .map(|a| a.expect("legacy natives have no default params"))
78 .map(|a| a.evaluate(s.clone()))
79 .collect::<Result<Vec<Val>>>()?;
65 self.handler.call(s, &out_args)80 self.handler.call(s, &args)
66 }81 }
67}82}
6883
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
1use std::mem::replace;
2
1use jrsonnet_gcmodule::Trace;3use jrsonnet_gcmodule::Trace;
2use jrsonnet_interner::IStr;4use jrsonnet_interner::IStr;
3use jrsonnet_parser::{LocExpr, ParamsDesc};5use jrsonnet_parser::{LocExpr, ParamsDesc};
46
5use super::{7use super::{arglike::ArgsLike, builtin::BuiltinParam};
6 arglike::ArgsLike,
7 builtin::{BuiltinParam, BuiltinParamName},
8};
9use crate::{8use crate::{
10 destructure::destruct,9 destructure::destruct,
129 .0
130 .clone()
131 .name()
132 .unwrap_or_else(|| "<destruct>".into()),
133 params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()128 params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()
134 ));129 ));
135 }130 }
160 params: &[BuiltinParam],155 params: &[BuiltinParam],
161 args: &dyn ArgsLike,156 args: &dyn ArgsLike,
162 tailstrict: bool,157 tailstrict: bool,
163) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {158) -> Result<Vec<Option<Thunk<Val>>>> {
164 let mut passed_args = GcHashMap::with_capacity(params.len());159 let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];
165 if args.unnamed_len() > params.len() {160 if args.unnamed_len() > params.len() {
166 throw!(TooManyArgsFunctionHas(161 throw!(TooManyArgsFunctionHas(
167 params.len(),162 params.len(),
168 params163 params
169 .iter()164 .iter()
170 .map(|p| (Some(p.name.as_ref().into()), p.has_default))165 .map(|p| (p.name.as_ref().map(|v| v.as_ref().into()), p.has_default))
171 .collect()166 .collect()
172 ))167 ))
173 }168 }
174169
175 let mut filled_args = 0;170 let mut filled_args = 0;
176171
177 args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {172 args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
178 let name = params[id].name.clone();173 passed_args[id] = Some(arg);
179 passed_args.insert(name, arg);
180 filled_args += 1;174 filled_args += 1;
181 Ok(())175 Ok(())
182 })?;176 })?;
183177
184 args.named_iter(s, ctx, tailstrict, &mut |name, arg| {178 args.named_iter(s, ctx, tailstrict, &mut |name, arg| {
185 // FIXME: O(n) for arg existence check179 // FIXME: O(n) for arg existence check
186 let p = params180 let id = params
187 .iter()181 .iter()
188 .find(|p| p.name == name as &str)182 .position(|p| {
183 p.name
184 .as_ref()
185 .map(|v| &v as &str == name as &str)
186 .unwrap_or(false)
187 })
189 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;188 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;
190 if passed_args.insert(p.name.clone(), arg).is_some() {189 if replace(&mut passed_args[id], Some(arg)).is_some() {
191 throw!(BindingParameterASecondTime(name.clone()));190 throw!(BindingParameterASecondTime(name.clone()));
192 }191 }
193 filled_args += 1;192 filled_args += 1;
194 Ok(())193 Ok(())
195 })?;194 })?;
196195
197 if filled_args < params.len() {196 if filled_args < params.len() {
198 for param in params.iter().filter(|p| p.has_default) {197 for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default) {
199 if passed_args.contains_key(&param.name) {198 if passed_args[id].is_some() {
200 continue;199 continue;
201 }200 }
202 filled_args += 1;201 filled_args += 1;
207 for param in params.iter().skip(args.unnamed_len()) {206 for param in params.iter().skip(args.unnamed_len()) {
208 let mut found = false;207 let mut found = false;
209 args.named_names(&mut |name| {208 args.named_names(&mut |name| {
210 if name as &str == &param.name as &str {209 if param
210 .name
211 .as_ref()
212 .map(|v| &v as &str == name as &str)
213 .unwrap_or(false)
214 {
211 found = true;215 found = true;
212 }216 }
213 });217 });
214 if !found {218 if !found {
215 throw!(FunctionParameterNotBoundInCall(219 throw!(FunctionParameterNotBoundInCall(
216 param.name.clone().into(),220 param.name.as_ref().map(|v| v.as_ref().into()),
217 params221 params
218 .iter()222 .iter()
219 .map(|p| (Some(p.name.as_ref().into()), p.has_default))223 .map(|p| (p.name.as_ref().map(|p| p.as_ref().into()), p.has_default))
220 .collect()224 .collect()
221 ));225 ));
222 }226 }
236 type Output = Val;240 type Output = Val;
237 fn get(self: Box<Self>, _: State) -> Result<Val> {241 fn get(self: Box<Self>, _: State) -> Result<Val> {
238 Err(FunctionParameterNotBoundInCall(242 Err(FunctionParameterNotBoundInCall(
239 self.0.clone(),243 Some(self.0.clone()),
240 self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),244 self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),
241 )245 )
242 .into())246 .into())
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
122 Normal {122 Normal {
123 ty: Box<Type>,123 ty: Box<Type>,
124 is_option: bool,124 is_option: bool,
125 name: String,125 name: Option<String>,
126 cfg_attrs: Vec<Attribute>,126 cfg_attrs: Vec<Attribute>,
127 // ident: Ident,127 // ident: Ident,
128 },128 },
129 Lazy {129 Lazy {
130 is_option: bool,130 is_option: bool,
131 name: String,131 name: Option<String>,
132 },132 },
133 State,133 State,
134 Location,134 Location,
142 FnArg::Typed(a) => a,142 FnArg::Typed(a) => a,
143 };143 };
144 let ident = match &arg.pat as &Pat {144 let ident = match &arg.pat as &Pat {
145 Pat::Ident(i) => i.ident.clone(),145 Pat::Ident(i) => Some(i.ident.clone()),
146 _ => return Err(Error::new(arg.pat.span(), "arg should be plain identifier")),146 _ => None,
147 };147 };
148 let ty = &arg.ty;148 let ty = &arg.ty;
149 if type_is_path(ty, "State").is_some() {149 if type_is_path(ty, "State").is_some() {
153 } else if type_is_path(ty, "Thunk").is_some() {153 } else if type_is_path(ty, "Thunk").is_some() {
154 return Ok(Self::Lazy {154 return Ok(Self::Lazy {
155 is_option: false,155 is_option: false,
156 name: ident.to_string(),156 name: ident.map(|v| v.to_string()),
157 });157 });
158 }158 }
159159
166 if type_is_path(ty, "Thunk").is_some() {166 if type_is_path(ty, "Thunk").is_some() {
167 return Ok(Self::Lazy {167 return Ok(Self::Lazy {
168 is_option: true,168 is_option: true,
169 name: ident.to_string(),169 name: ident.map(|v| v.to_string()),
170 });170 });
171 }171 }
172172
185 Ok(Self::Normal {185 Ok(Self::Normal {
186 ty,186 ty,
187 is_option,187 is_option,
188 name: ident.to_string(),188 name: ident.map(|v| v.to_string()),
189 cfg_attrs,189 cfg_attrs,
190 })190 })
191 }191 }
248 name,248 name,
249 cfg_attrs,249 cfg_attrs,
250 ..250 ..
251 } => Some(quote! {251 } => {
252 let name = name
253 .as_ref()
254 .map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))})
255 .unwrap_or_else(|| quote! {None});
256 Some(quote! {
252 #(#cfg_attrs)*257 #(#cfg_attrs)*
253 BuiltinParam {258 BuiltinParam {
254 name: std::borrow::Cow::Borrowed(#name),259 name: #name,
255 has_default: #is_option,260 has_default: #is_option,
256 },261 },
257 }),262 })
263 }
258 ArgInfo::Lazy { is_option, name } => Some(quote! {264 ArgInfo::Lazy { is_option, name } => {
265 let name = name
266 .as_ref()
267 .map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))})
268 .unwrap_or_else(|| quote! {None});
269 Some(quote! {
259 BuiltinParam {270 BuiltinParam {
260 name: std::borrow::Cow::Borrowed(#name),271 name: #name,
261 has_default: #is_option,272 has_default: #is_option,
262 },273 },
263 }),274 })
275 }
264 ArgInfo::State => None,276 ArgInfo::State => None,
265 ArgInfo::Location => None,277 ArgInfo::Location => None,
266 ArgInfo::This => None,278 ArgInfo::This => None,
267 });279 });
268280
281 let mut id = 0usize;
269 let pass = args.iter().map(|a| match a {282 let pass = args
283 .iter()
284 .map(|a| match a {
285 ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {
286 let cid = id;
287 id += 1;
288 (quote! {#cid}, a)
289 }
290 ArgInfo::State | ArgInfo::Location | ArgInfo::This => {
291 (quote! {compile_error!("should not use id")}, a)
292 }
293 })
294 .map(|(id, a)| match a {
270 ArgInfo::Normal {295 ArgInfo::Normal {
271 ty,296 ty,
272 is_option,297 is_option,
273 name,298 name,
274 cfg_attrs,299 cfg_attrs,
275 } => {300 } => {
301 let name = name.as_ref().map(|v| v.as_str()).unwrap_or("<unnamed>");
276 let eval = quote! {s.push_description(302 let eval = quote! {s.push_description(
277 || format!("argument <{}> evaluation", #name),303 || format!("argument <{}> evaluation", #name),
278 || <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),304 || <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),
279 )?};305 )?};
280 let value = if *is_option {306 let value = if *is_option {
281 quote! {if let Some(value) = parsed.get(#name) {307 quote! {if let Some(value) = &parsed[#id] {
282 Some(#eval)308 Some(#eval)
283 } else {309 } else {
284 None310 None
285 },}311 },}
286 } else {312 } else {
287 quote! {{313 quote! {{
288 let value = parsed.get(#name).expect("args shape is checked");314 let value = parsed[#id].as_ref().expect("args shape is checked");
289 #eval315 #eval
290 },}316 },}
291 };317 };
294 #value320 #value
295 }321 }
296 }322 }
297 ArgInfo::Lazy { is_option, name } => {323 ArgInfo::Lazy { is_option, .. } => {
298 if *is_option {324 if *is_option {
299 quote! {if let Some(value) = parsed.get(#name) {325 quote! {if let Some(value) = &parsed[#id] {
300 Some(value.clone())326 Some(value.clone())
301 } else {327 } else {
302 None328 None
303 }}329 }}
304 } else {330 } else {
305 quote! {331 quote! {
306 parsed.get(#name).expect("args shape is correct").clone(),332 parsed[#id].as_ref().expect("args shape is correct").clone(),
307 }333 }
308 }334 }
309 }335 }