difftreelog
feat lazy values in builtin
in: master
8 files changed
crates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -1,4 +1,4 @@
-use crate::function::StaticBuiltin;
+use crate::function::{CallLocation, StaticBuiltin};
use crate::typed::{Any, PositiveF64, VecVal, M1};
use crate::{
builtin::manifest::{manifest_yaml_ex, ManifestYamlOptions},
@@ -12,7 +12,6 @@
use crate::{Either, ObjValue};
use format::{format_arr, format_obj};
use jrsonnet_interner::IStr;
-use jrsonnet_parser::ExprLocation;
use serde::Deserialize;
use serde_yaml::DeserializingQuirks;
use std::collections::HashMap;
@@ -29,7 +28,7 @@
pub fn std_format(str: IStr, vals: Val) -> Result<String> {
push_frame(
- None,
+ CallLocation::native(),
|| format!("std.format of {}", str),
|| {
Ok(match vals {
@@ -467,9 +466,9 @@
}
#[jrsonnet_macros::builtin]
-fn builtin_trace(#[location] loc: Option<&ExprLocation>, str: IStr, rest: Any) -> Result<Any> {
+fn builtin_trace(loc: CallLocation, str: IStr, rest: Any) -> Result<Any> {
eprint!("TRACE:");
- if let Some(loc) = loc {
+ if let Some(loc) = loc.0 {
with_state(|s| {
let locs = s.map_source_locations(&loc.0, &[loc.1]);
eprint!(
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth1use std::convert::TryFrom;23use crate::{4 builtin::{std_slice, BUILTINS},5 error::Error::*,6 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},7 gc::TraceBox,8 push_frame, throw, with_state, ArrValue, Bindable, Context, ContextCreator, FuncDesc, FuncVal,9 FutureWrapper, GcHashMap, LazyBinding, LazyVal, LazyValValue, ObjValue, ObjValueBuilder,10 ObjectAssertion, Result, Val,11};12use gcmodule::{Cc, Trace};13use jrsonnet_interner::IStr;14use jrsonnet_parser::{15 ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, ExprLocation, FieldMember, ForSpecData,16 IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc,17};18use jrsonnet_types::ValType;19pub mod operator;2021pub fn evaluate_binding_in_future(22 b: &BindSpec,23 context_creator: FutureWrapper<Context>,24) -> LazyVal {25 let b = b.clone();26 if let Some(params) = &b.params {27 let params = params.clone();2829 #[derive(Trace)]30 struct LazyMethodBinding {31 context_creator: FutureWrapper<Context>,32 name: IStr,33 params: ParamsDesc,34 value: LocExpr,35 }36 impl LazyValValue for LazyMethodBinding {37 fn get(self: Box<Self>) -> Result<Val> {38 Ok(evaluate_method(39 self.context_creator.unwrap(),40 self.name,41 self.params,42 self.value,43 ))44 }45 }4647 LazyVal::new(TraceBox(Box::new(LazyMethodBinding {48 context_creator,49 name: b.name.clone(),50 params,51 value: b.value.clone(),52 })))53 } else {54 #[derive(Trace)]55 struct LazyNamedBinding {56 context_creator: FutureWrapper<Context>,57 name: IStr,58 value: LocExpr,59 }60 impl LazyValValue for LazyNamedBinding {61 fn get(self: Box<Self>) -> Result<Val> {62 evaluate_named(self.context_creator.unwrap(), &self.value, self.name)63 }64 }65 LazyVal::new(TraceBox(Box::new(LazyNamedBinding {66 context_creator,67 name: b.name.clone(),68 value: b.value,69 })))70 }71}7273pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (IStr, LazyBinding) {74 let b = b.clone();75 if let Some(params) = &b.params {76 let params = params.clone();7778 #[derive(Trace)]79 struct BindableMethodLazyVal {80 this: Option<ObjValue>,81 super_obj: Option<ObjValue>,8283 context_creator: ContextCreator,84 name: IStr,85 params: ParamsDesc,86 value: LocExpr,87 }88 impl LazyValValue for BindableMethodLazyVal {89 fn get(self: Box<Self>) -> Result<Val> {90 Ok(evaluate_method(91 self.context_creator.create(self.this, self.super_obj)?,92 self.name,93 self.params,94 self.value,95 ))96 }97 }9899 #[derive(Trace)]100 struct BindableMethod {101 context_creator: ContextCreator,102 name: IStr,103 params: ParamsDesc,104 value: LocExpr,105 }106 impl Bindable for BindableMethod {107 fn bind(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<LazyVal> {108 Ok(LazyVal::new(TraceBox(Box::new(BindableMethodLazyVal {109 this,110 super_obj,111112 context_creator: self.context_creator.clone(),113 name: self.name.clone(),114 params: self.params.clone(),115 value: self.value.clone(),116 }))))117 }118 }119120 (121 b.name.clone(),122 LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableMethod {123 context_creator,124 name: b.name.clone(),125 params,126 value: b.value.clone(),127 })))),128 )129 } else {130 #[derive(Trace)]131 struct BindableNamedLazyVal {132 this: Option<ObjValue>,133 super_obj: Option<ObjValue>,134135 context_creator: ContextCreator,136 name: IStr,137 value: LocExpr,138 }139 impl LazyValValue for BindableNamedLazyVal {140 fn get(self: Box<Self>) -> Result<Val> {141 evaluate_named(142 self.context_creator.create(self.this, self.super_obj)?,143 &self.value,144 self.name,145 )146 }147 }148149 #[derive(Trace)]150 struct BindableNamed {151 context_creator: ContextCreator,152 name: IStr,153 value: LocExpr,154 }155 impl Bindable for BindableNamed {156 fn bind(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<LazyVal> {157 Ok(LazyVal::new(TraceBox(Box::new(BindableNamedLazyVal {158 this,159 super_obj,160161 context_creator: self.context_creator.clone(),162 name: self.name.clone(),163 value: self.value.clone(),164 }))))165 }166 }167168 (169 b.name.clone(),170 LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableNamed {171 context_creator,172 name: b.name.clone(),173 value: b.value.clone(),174 })))),175 )176 }177}178179pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {180 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {181 name,182 ctx,183 params,184 body,185 })))186}187188pub fn evaluate_field_name(189 context: Context,190 field_name: &jrsonnet_parser::FieldName,191) -> Result<Option<IStr>> {192 Ok(match field_name {193 jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),194 jrsonnet_parser::FieldName::Dyn(expr) => push_frame(195 Some(&expr.1),196 || "evaluating field name".to_string(),197 || {198 let value = evaluate(context, expr)?;199 if matches!(value, Val::Null) {200 Ok(None)201 } else {202 Ok(Some(IStr::try_from(value)?))203 }204 },205 )?,206 })207}208209pub fn evaluate_comp(210 context: Context,211 specs: &[CompSpec],212 callback: &mut impl FnMut(Context) -> Result<()>,213) -> Result<()> {214 match specs.get(0) {215 None => callback(context)?,216 Some(CompSpec::IfSpec(IfSpecData(cond))) => {217 if bool::try_from(evaluate(context.clone(), cond)?)? {218 evaluate_comp(context, &specs[1..], callback)?219 }220 }221 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(context.clone(), expr)? {222 Val::Arr(list) => {223 for item in list.iter() {224 evaluate_comp(225 context.clone().with_var(var.clone(), item?.clone()),226 &specs[1..],227 callback,228 )?229 }230 }231 _ => throw!(InComprehensionCanOnlyIterateOverArray),232 },233 }234 Ok(())235}236237pub fn evaluate_member_list_object(context: Context, members: &[Member]) -> Result<ObjValue> {238 let new_bindings = FutureWrapper::new();239 let future_this = FutureWrapper::new();240 let context_creator = ContextCreator(context.clone(), new_bindings.clone());241 {242 let mut bindings: GcHashMap<IStr, LazyBinding> = GcHashMap::with_capacity(members.len());243 for (n, b) in members244 .iter()245 .filter_map(|m| match m {246 Member::BindStmt(b) => Some(b.clone()),247 _ => None,248 })249 .map(|b| evaluate_binding(&b, context_creator.clone()))250 {251 bindings.insert(n, b);252 }253 new_bindings.fill(bindings);254 }255256 let mut builder = ObjValueBuilder::new();257 for member in members.iter() {258 match member {259 Member::Field(FieldMember {260 name,261 plus,262 params: None,263 visibility,264 value,265 }) => {266 let name = evaluate_field_name(context.clone(), name)?;267 if name.is_none() {268 continue;269 }270 let name = name.unwrap();271272 #[derive(Trace)]273 struct ObjMemberBinding {274 context_creator: ContextCreator,275 value: LocExpr,276 name: IStr,277 }278 impl Bindable for ObjMemberBinding {279 fn bind(280 &self,281 this: Option<ObjValue>,282 super_obj: Option<ObjValue>,283 ) -> Result<LazyVal> {284 Ok(LazyVal::new_resolved(evaluate_named(285 self.context_creator.create(this, super_obj)?,286 &self.value,287 self.name.clone(),288 )?))289 }290 }291 builder292 .member(name.clone())293 .with_add(*plus)294 .with_visibility(*visibility)295 .with_location(value.1.clone())296 .bindable(TraceBox(Box::new(ObjMemberBinding {297 context_creator: context_creator.clone(),298 value: value.clone(),299 name,300 })));301 }302 Member::Field(FieldMember {303 name,304 params: Some(params),305 value,306 ..307 }) => {308 let name = evaluate_field_name(context.clone(), name)?;309 if name.is_none() {310 continue;311 }312 let name = name.unwrap();313 #[derive(Trace)]314 struct ObjMemberBinding {315 context_creator: ContextCreator,316 value: LocExpr,317 params: ParamsDesc,318 name: IStr,319 }320 impl Bindable for ObjMemberBinding {321 fn bind(322 &self,323 this: Option<ObjValue>,324 super_obj: Option<ObjValue>,325 ) -> Result<LazyVal> {326 Ok(LazyVal::new_resolved(evaluate_method(327 self.context_creator.create(this, super_obj)?,328 self.name.clone(),329 self.params.clone(),330 self.value.clone(),331 )))332 }333 }334 builder335 .member(name.clone())336 .hide()337 .with_location(value.1.clone())338 .bindable(TraceBox(Box::new(ObjMemberBinding {339 context_creator: context_creator.clone(),340 value: value.clone(),341 params: params.clone(),342 name,343 })));344 }345 Member::BindStmt(_) => {}346 Member::AssertStmt(stmt) => {347 #[derive(Trace)]348 struct ObjectAssert {349 context_creator: ContextCreator,350 assert: AssertStmt,351 }352 impl ObjectAssertion for ObjectAssert {353 fn run(354 &self,355 this: Option<ObjValue>,356 super_obj: Option<ObjValue>,357 ) -> Result<()> {358 let ctx = self.context_creator.create(this, super_obj)?;359 evaluate_assert(ctx, &self.assert)360 }361 }362 builder.assert(TraceBox(Box::new(ObjectAssert {363 context_creator: context_creator.clone(),364 assert: stmt.clone(),365 })));366 }367 }368 }369 let this = builder.build();370 future_this.fill(this.clone());371 Ok(this)372}373374pub fn evaluate_object(context: Context, object: &ObjBody) -> Result<ObjValue> {375 Ok(match object {376 ObjBody::MemberList(members) => evaluate_member_list_object(context, members)?,377 ObjBody::ObjComp(obj) => {378 let future_this = FutureWrapper::new();379 let mut builder = ObjValueBuilder::new();380 evaluate_comp(context.clone(), &obj.compspecs, &mut |ctx| {381 let new_bindings = FutureWrapper::new();382 let context_creator = ContextCreator(context.clone(), new_bindings.clone());383 let mut bindings: GcHashMap<IStr, LazyBinding> =384 GcHashMap::with_capacity(obj.pre_locals.len() + obj.post_locals.len());385 for (n, b) in obj386 .pre_locals387 .iter()388 .chain(obj.post_locals.iter())389 .map(|b| evaluate_binding(b, context_creator.clone()))390 {391 bindings.insert(n, b);392 }393 new_bindings.fill(bindings.clone());394 let ctx = ctx.extend_unbound(bindings, None, None, None)?;395 let key = evaluate(ctx.clone(), &obj.key)?;396397 match key {398 Val::Null => {}399 Val::Str(n) => {400 #[derive(Trace)]401 struct ObjCompBinding {402 context: Context,403 value: LocExpr,404 }405 impl Bindable for ObjCompBinding {406 fn bind(407 &self,408 this: Option<ObjValue>,409 _super_obj: Option<ObjValue>,410 ) -> Result<LazyVal> {411 Ok(LazyVal::new_resolved(evaluate(412 self.context413 .clone()414 .extend(GcHashMap::new(), None, this, None),415 &self.value,416 )?))417 }418 }419 builder420 .member(n)421 .with_location(obj.value.1.clone())422 .with_add(obj.plus)423 .bindable(TraceBox(Box::new(ObjCompBinding {424 context: ctx,425 value: obj.value.clone(),426 })));427 }428 v => throw!(FieldMustBeStringGot(v.value_type())),429 }430431 Ok(())432 })?;433434 let this = builder.build();435 future_this.fill(this.clone());436 this437 }438 })439}440441pub fn evaluate_apply(442 context: Context,443 value: &LocExpr,444 args: &ArgsDesc,445 loc: Option<&ExprLocation>,446 tailstrict: bool,447) -> Result<Val> {448 let value = evaluate(context.clone(), value)?;449 Ok(match value {450 Val::Func(f) => {451 let body = || f.evaluate(context, loc, args, tailstrict);452 if tailstrict {453 body()?454 } else {455 push_frame(loc, || format!("function <{}> call", f.name()), body)?456 }457 }458 v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),459 })460}461462pub fn evaluate_assert(context: Context, assertion: &AssertStmt) -> Result<()> {463 let value = &assertion.0;464 let msg = &assertion.1;465 let assertion_result = push_frame(466 Some(&value.1),467 || "assertion condition".to_owned(),468 || bool::try_from(evaluate(context.clone(), value)?),469 )?;470 if !assertion_result {471 push_frame(472 Some(&value.1),473 || "assertion failure".to_owned(),474 || {475 if let Some(msg) = msg {476 throw!(AssertionFailed(evaluate(context, msg)?.to_string()?));477 } else {478 throw!(AssertionFailed(Val::Null.to_string()?));479 }480 },481 )?482 }483 Ok(())484}485486pub fn evaluate_named(context: Context, lexpr: &LocExpr, name: IStr) -> Result<Val> {487 use Expr::*;488 let LocExpr(expr, _loc) = lexpr;489 Ok(match &**expr {490 Function(params, body) => evaluate_method(context, name, params.clone(), body.clone()),491 _ => evaluate(context, lexpr)?,492 })493}494495pub fn evaluate(context: Context, expr: &LocExpr) -> Result<Val> {496 use Expr::*;497 let LocExpr(expr, loc) = expr;498 // let bp = with_state(|s| s.0.stop_at.borrow().clone());499 Ok(match &**expr {500 Literal(LiteralType::This) => {501 Val::Obj(context.this().clone().ok_or(CantUseSelfOutsideOfObject)?)502 }503 Literal(LiteralType::Super) => Val::Obj(504 context505 .super_obj()506 .clone()507 .ok_or(NoSuperFound)?508 .with_this(context.this().clone().unwrap()),509 ),510 Literal(LiteralType::Dollar) => {511 Val::Obj(context.dollar().clone().ok_or(NoTopLevelObjectFound)?)512 }513 Literal(LiteralType::True) => Val::Bool(true),514 Literal(LiteralType::False) => Val::Bool(false),515 Literal(LiteralType::Null) => Val::Null,516 Parened(e) => evaluate(context, e)?,517 Str(v) => Val::Str(v.clone()),518 Num(v) => Val::new_checked_num(*v)?,519 BinaryOp(v1, o, v2) => evaluate_binary_op_special(context, v1, *o, v2)?,520 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?,521 Var(name) => push_frame(522 Some(loc),523 || format!("variable <{}> access", name),524 || context.binding(name.clone())?.evaluate(),525 )?,526 Index(value, index) => {527 match (evaluate(context.clone(), value)?, evaluate(context, index)?) {528 (Val::Obj(v), Val::Str(s)) => {529 let sn = s.clone();530 push_frame(531 Some(loc),532 || format!("field <{}> access", sn),533 || {534 if let Some(v) = v.get(s.clone())? {535 Ok(v)536 } else {537 throw!(NoSuchField(s))538 }539 },540 )?541 }542 (Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(543 ValType::Obj,544 ValType::Str,545 n.value_type(),546 )),547548 (Val::Arr(v), Val::Num(n)) => {549 if n.fract() > f64::EPSILON {550 throw!(FractionalIndex)551 }552 v.get(n as usize)?553 .ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?554 }555 (Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),556 (Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(557 ValType::Arr,558 ValType::Num,559 n.value_type(),560 )),561562 (Val::Str(s), Val::Num(n)) => Val::Str(563 s.chars()564 .skip(n as usize)565 .take(1)566 .collect::<String>()567 .into(),568 ),569 (Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(570 ValType::Str,571 ValType::Num,572 n.value_type(),573 )),574575 (v, _) => throw!(CantIndexInto(v.value_type())),576 }577 }578 LocalExpr(bindings, returned) => {579 let mut new_bindings: GcHashMap<IStr, LazyVal> =580 GcHashMap::with_capacity(bindings.len());581 let future_context = Context::new_future();582 for b in bindings {583 new_bindings.insert(584 b.name.clone(),585 evaluate_binding_in_future(b, future_context.clone()),586 );587 }588 let context = context589 .extend_bound(new_bindings)590 .into_future(future_context);591 evaluate(context, &returned.clone())?592 }593 Arr(items) => {594 let mut out = Vec::with_capacity(items.len());595 for item in items {596 // TODO: Implement ArrValue::Lazy with same context for every element?597 #[derive(Trace)]598 struct ArrayElement {599 context: Context,600 item: LocExpr,601 }602 impl LazyValValue for ArrayElement {603 fn get(self: Box<Self>) -> Result<Val> {604 evaluate(self.context, &self.item)605 }606 }607 out.push(LazyVal::new(TraceBox(Box::new(ArrayElement {608 context: context.clone(),609 item: item.clone(),610 }))));611 }612 Val::Arr(out.into())613 }614 ArrComp(expr, comp_specs) => {615 let mut out = Vec::new();616 evaluate_comp(context, comp_specs, &mut |ctx| {617 out.push(evaluate(ctx, expr)?);618 Ok(())619 })?;620 Val::Arr(ArrValue::Eager(Cc::new(out)))621 }622 Obj(body) => Val::Obj(evaluate_object(context, body)?),623 ObjExtend(s, t) => evaluate_add_op(624 &evaluate(context.clone(), s)?,625 &Val::Obj(evaluate_object(context, t)?),626 )?,627 Apply(value, args, tailstrict) => {628 evaluate_apply(context, value, args, Some(loc), *tailstrict)?629 }630 Function(params, body) => {631 evaluate_method(context, "anonymous".into(), params.clone(), body.clone())632 }633 Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(634 BUILTINS635 .with(|b| b.get(name).copied())636 .ok_or_else(|| IntrinsicNotFound(name.clone()))?,637 )),638 AssertExpr(assert, returned) => {639 evaluate_assert(context.clone(), assert)?;640 evaluate(context, returned)?641 }642 ErrorStmt(e) => push_frame(643 Some(loc),644 || "error statement".to_owned(),645 || throw!(RuntimeError(IStr::try_from(evaluate(context, e)?)?,)),646 )?,647 IfElse {648 cond,649 cond_then,650 cond_else,651 } => {652 if push_frame(653 Some(loc),654 || "if condition".to_owned(),655 || bool::try_from(evaluate(context.clone(), &cond.0)?),656 )? {657 evaluate(context, cond_then)?658 } else {659 match cond_else {660 Some(v) => evaluate(context, v)?,661 None => Val::Null,662 }663 }664 }665 Slice(value, desc) => {666 let indexable = evaluate(context.clone(), value)?;667668 fn parse_num(669 context: &Context,670 expr: Option<&LocExpr>,671 desc: &'static str,672 ) -> Result<Option<usize>> {673 Ok(match expr {674 Some(s) => evaluate(context.clone(), s)?675 .try_cast_nullable_num(desc)?676 .map(|v| v as usize),677 None => None,678 })679 }680681 let start = parse_num(&context, desc.start.as_ref(), "start")?;682 let end = parse_num(&context, desc.end.as_ref(), "end")?;683 let step = parse_num(&context, desc.step.as_ref(), "step")?;684685 std_slice(indexable.into_indexable()?, start, end, step)?686 }687 Import(path) => {688 let tmp = loc.clone().0;689 let mut import_location = tmp.to_path_buf();690 import_location.pop();691 push_frame(692 Some(loc),693 || format!("import {:?}", path),694 || with_state(|s| s.import_file(&import_location, path)),695 )?696 }697 ImportStr(path) => {698 let tmp = loc.clone().0;699 let mut import_location = tmp.to_path_buf();700 import_location.pop();701 Val::Str(with_state(|s| s.import_file_str(&import_location, path))?)702 }703 })704}crates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function.rs
+++ b/crates/jrsonnet-evaluator/src/function.rs
@@ -12,6 +12,19 @@
use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};
use std::{borrow::Cow, collections::HashMap, convert::TryFrom};
+#[derive(Clone, Copy)]
+pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);
+impl<'l> CallLocation<'l> {
+ pub fn new(loc: &'l ExprLocation) -> Self {
+ Self(Some(loc))
+ }
+}
+impl CallLocation<'static> {
+ pub fn native() -> Self {
+ Self(None)
+ }
+}
+
#[derive(Trace)]
struct EvaluateLazyVal {
context: Context,
@@ -383,12 +396,7 @@
pub trait Builtin: Trace {
fn name(&self) -> &str;
fn params(&self) -> &[BuiltinParam];
- fn call(
- &self,
- context: Context,
- loc: Option<&ExprLocation>,
- args: &dyn ArgsLike,
- ) -> Result<Val>;
+ fn call(&self, context: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;
}
pub trait StaticBuiltin: Builtin + Send + Sync
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -28,7 +28,7 @@
pub use dynamic::*;
use error::{Error::*, LocError, Result, StackTraceElement};
pub use evaluate::*;
-use function::{Builtin, TlaArg};
+use function::{Builtin, CallLocation, TlaArg};
use gc::{GcHashMap, TraceBox};
use gcmodule::{Cc, Trace, Weak};
pub use import::*;
@@ -173,6 +173,7 @@
/// Global state is fine here.
pub(crate) static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)
}
+
pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {
EVAL_STATE.with(|s| {
f(s.borrow().as_ref().expect(
@@ -181,7 +182,7 @@
})
}
pub fn push_frame<T>(
- e: Option<&ExprLocation>,
+ e: CallLocation,
frame_desc: impl FnOnce() -> String,
f: impl FnOnce() -> Result<T>,
) -> Result<T> {
@@ -345,7 +346,7 @@
/// Executes code creating a new stack frame
pub fn push<T>(
&self,
- e: Option<&ExprLocation>,
+ e: CallLocation,
frame_desc: impl FnOnce() -> String,
f: impl FnOnce() -> Result<T>,
) -> Result<T> {
@@ -368,7 +369,7 @@
}
if let Err(mut err) = result {
err.trace_mut().0.push(StackTraceElement {
- location: e.cloned(),
+ location: e.0.cloned(),
desc: frame_desc(),
});
return Err(err);
@@ -512,7 +513,7 @@
|| {
func.evaluate(
self.create_default_context(),
- None,
+ CallLocation::native(),
&self.settings().tla_vars,
true,
)
crates/jrsonnet-evaluator/src/native.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/native.rs
+++ b/crates/jrsonnet-evaluator/src/native.rs
@@ -1,11 +1,10 @@
#![allow(clippy::type_complexity)]
-use crate::function::{parse_builtin_call, ArgsLike, Builtin, BuiltinParam};
+use crate::function::{parse_builtin_call, ArgsLike, Builtin, BuiltinParam, CallLocation};
use crate::gc::TraceBox;
use crate::Context;
use crate::{error::Result, Val};
use gcmodule::Trace;
-use jrsonnet_parser::ExprLocation;
use std::path::Path;
use std::rc::Rc;
@@ -32,18 +31,13 @@
&self.params
}
- fn call(
- &self,
- context: Context,
- loc: Option<&ExprLocation>,
- args: &dyn ArgsLike,
- ) -> Result<Val> {
+ fn call(&self, context: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
let args = parse_builtin_call(context, &self.params, args, true)?;
let mut out_args = Vec::with_capacity(self.params.len());
for p in self.params.iter() {
out_args.push(args[&p.name].evaluate()?);
}
- self.handler.call(loc.map(|l| l.0.clone()), &out_args)
+ self.handler.call(loc.0.map(|l| l.0.clone()), &out_args)
}
}
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -1,5 +1,6 @@
use std::convert::{TryFrom, TryInto};
+use gcmodule::Cc;
use jrsonnet_interner::IStr;
pub use jrsonnet_macros::Typed;
use jrsonnet_types::{ComplexValType, ValType};
@@ -8,7 +9,7 @@
error::{Error::*, LocError, Result},
throw,
typed::CheckType,
- ArrValue, FuncVal, IndexableVal, ObjValue, ObjValueBuilder, Val,
+ ArrValue, FuncDesc, FuncVal, IndexableVal, ObjValue, ObjValueBuilder, Val,
};
pub trait TypedObj: Typed {
@@ -431,6 +432,30 @@
Ok(Self::Func(value))
}
}
+
+impl Typed for Cc<FuncDesc> {
+ const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);
+}
+impl TryFrom<Val> for Cc<FuncDesc> {
+ type Error = LocError;
+
+ fn try_from(value: Val) -> Result<Self, Self::Error> {
+ <Self as Typed>::TYPE.check(&value)?;
+ match value {
+ Val::Func(FuncVal::Normal(desc)) => Ok(desc.clone()),
+ Val::Func(_) => throw!(RuntimeError("expected normal function, not builtin".into())),
+ _ => unreachable!(),
+ }
+ }
+}
+impl TryFrom<Cc<FuncDesc>> for Val {
+ type Error = LocError;
+
+ fn try_from(value: Cc<FuncDesc>) -> Result<Self, Self::Error> {
+ Ok(Self::Func(FuncVal::Normal(value)))
+ }
+}
+
impl Typed for ObjValue {
const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj);
}
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -6,14 +6,15 @@
error::{Error::*, LocError},
evaluate,
function::{
- parse_default_function_call, parse_function_call, ArgsLike, Builtin, StaticBuiltin,
+ parse_default_function_call, parse_function_call, ArgsLike, Builtin, CallLocation,
+ StaticBuiltin,
},
gc::TraceBox,
throw, Context, ObjValue, Result,
};
use gcmodule::{Cc, Trace};
use jrsonnet_interner::IStr;
-use jrsonnet_parser::{ExprLocation, LocExpr, ParamsDesc};
+use jrsonnet_parser::{LocExpr, ParamsDesc};
use jrsonnet_types::ValType;
use std::{cell::RefCell, fmt::Debug, rc::Rc};
@@ -152,7 +153,7 @@
pub fn evaluate(
&self,
call_ctx: Context,
- loc: Option<&ExprLocation>,
+ loc: CallLocation,
args: &dyn ArgsLike,
tailstrict: bool,
) -> Result<Val> {
@@ -166,7 +167,7 @@
}
}
pub fn evaluate_simple(&self, args: &dyn ArgsLike) -> Result<Val> {
- self.evaluate(Context::default(), None, args, true)
+ self.evaluate(Context::default(), CallLocation::native(), args, true)
}
}
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -1,5 +1,5 @@
use proc_macro2::TokenStream;
-use quote::{quote, quote_spanned};
+use quote::quote;
use syn::{
parenthesized,
parse::{Parse, ParseStream},
@@ -7,8 +7,8 @@
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
- Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, PatType,
- Path, PathArguments, Result, Token, Type,
+ Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,
+ PathArguments, Result, ReturnType, Token, Type,
};
fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>
@@ -33,49 +33,42 @@
Ok(Some(attr))
}
-fn is_location_arg(t: &PatType) -> bool {
- t.attrs.iter().any(|a| a.path.is_ident("location"))
-}
-fn is_self_arg(t: &PatType) -> bool {
- t.attrs.iter().any(|a| a.path.is_ident("self"))
+fn path_is(path: &Path, needed: &str) -> bool {
+ path.leading_colon.is_none()
+ && path.segments.len() >= 1
+ && path.segments.iter().last().unwrap().ident == needed
}
-trait RetainHad<T> {
- fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool;
-}
-impl<T> RetainHad<T> for Vec<T> {
- fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool {
- let before = self.len();
- self.retain(h);
- let after = self.len();
- before != after
+fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {
+ match ty {
+ Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {
+ let args = &path.path.segments.iter().last().unwrap().arguments;
+ Some(args)
+ }
+ _ => None,
}
}
-fn extract_type_from_option(ty: &Type) -> Option<&Type> {
- fn path_is_option(path: &Path) -> bool {
- path.leading_colon.is_none()
- && path.segments.len() == 1
- && path.segments.iter().next().unwrap().ident == "Option"
- }
-
- match ty {
- Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => {
- // Get the first segment of the path (there is only one, in fact: "Option"):
- let type_params = &typepath.path.segments.iter().next().unwrap().arguments;
- // It should have only on angle-bracketed param ("<String>"):
- let generic_arg = match type_params {
- PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),
- _ => panic!("missing option generic"),
- };
- // This argument must be a type:
- match generic_arg {
- GenericArgument::Type(ty) => Some(ty),
- _ => panic!("option generic should be a type"),
+fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {
+ Ok(if let Some(args) = type_is_path(ty, "Option") {
+ // It should have only on angle-bracketed param ("<String>"):
+ let generic_arg = match args {
+ PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),
+ _ => return Err(Error::new(args.span(), "missing option generic")),
+ };
+ // This argument must be a type:
+ match generic_arg {
+ GenericArgument::Type(ty) => Some(ty),
+ _ => {
+ return Err(Error::new(
+ generic_arg.span(),
+ "option generic should be a type",
+ ))
}
}
- _ => None,
- }
+ } else {
+ None
+ })
}
struct Field {
@@ -101,7 +94,7 @@
struct EmptyAttr;
impl Parse for EmptyAttr {
- fn parse(input: ParseStream) -> Result<Self> {
+ fn parse(_input: ParseStream) -> Result<Self> {
Ok(Self)
}
}
@@ -124,107 +117,158 @@
}
}
+enum ArgInfo {
+ Normal {
+ ty: Type,
+ is_option: bool,
+ name: String,
+ // ident: Ident,
+ },
+ Lazy {
+ is_option: bool,
+ name: String,
+ },
+ Location,
+ This,
+}
+
+impl ArgInfo {
+ fn parse(arg: &FnArg) -> Result<Self> {
+ let typed = match arg {
+ FnArg::Receiver(_) => unreachable!(),
+ FnArg::Typed(a) => a,
+ };
+ let ident = match &typed.pat as &Pat {
+ Pat::Ident(i) => i.ident.clone(),
+ _ => {
+ return Err(Error::new(
+ typed.pat.span(),
+ "arg should be plain identifier",
+ ))
+ }
+ };
+ let ty = &typed.ty as &Type;
+ if type_is_path(&ty, "CallLocation").is_some() {
+ return Ok(Self::Location);
+ } else if type_is_path(&ty, "Self").is_some() {
+ return Ok(Self::This);
+ } else if type_is_path(&ty, "LazyVal").is_some() {
+ return Ok(Self::Lazy {
+ is_option: false,
+ name: ident.to_string(),
+ });
+ }
+
+ let (is_option, ty) = if let Some(ty) = extract_type_from_option(&ty)? {
+ if type_is_path(&ty, "LazyVal").is_some() {
+ return Ok(Self::Lazy {
+ is_option: true,
+ name: ident.to_string(),
+ });
+ }
+
+ (true, ty.clone())
+ } else {
+ (false, ty.clone())
+ };
+
+ Ok(Self::Normal {
+ ty,
+ is_option,
+ name: ident.to_string(),
+ // ident,
+ })
+ }
+}
+
#[proc_macro_attribute]
pub fn builtin(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
- let attrs = parse_macro_input!(attr as BuiltinAttrs);
- let mut fun: ItemFn = parse_macro_input!(item);
+ let attr = parse_macro_input!(attr as BuiltinAttrs);
+ let item: ItemFn = parse_macro_input!(item);
+
+ match builtin_inner(attr, item) {
+ Ok(v) => v.into(),
+ Err(e) => e.into_compile_error().into(),
+ }
+}
+fn builtin_inner(attr: BuiltinAttrs, fun: ItemFn) -> syn::Result<TokenStream> {
let result = match fun.sig.output {
- syn::ReturnType::Default => {
- return quote_spanned! { fun.sig.span() =>
- compile_error!("builtins should return something");
- }
- .into()
+ ReturnType::Default => {
+ return Err(Error::new(
+ fun.sig.span(),
+ "builtin should return something",
+ ))
}
- syn::ReturnType::Type(_, ref ty) => ty.clone(),
+ ReturnType::Type(_, ref ty) => ty.clone(),
};
- let params = fun
+ let args = fun
.sig
.inputs
.iter()
- .map(|i| match i {
- FnArg::Receiver(_) => unreachable!(),
- FnArg::Typed(t) => t,
- })
- .filter(|a| !is_location_arg(a) && !is_self_arg(a))
- .map(|t| {
- let ident = match &t.pat as &Pat {
- Pat::Ident(i) => i.ident.to_string(),
- _ => {
- return quote_spanned! { t.pat.span() =>
- compile_error!("args should be plain identifiers")
- }
- .into()
- }
- };
- let optional = extract_type_from_option(&t.ty).is_some();
- quote! {
- BuiltinParam {
- name: std::borrow::Cow::Borrowed(#ident),
- has_default: #optional,
- }
+ .map(|a| ArgInfo::parse(a))
+ .collect::<Result<Vec<_>>>()?;
+
+ let params_desc = args.iter().flat_map(|a| match a {
+ ArgInfo::Normal {
+ is_option, name, ..
+ }
+ | ArgInfo::Lazy { is_option, name } => Some(quote! {
+ BuiltinParam {
+ name: std::borrow::Cow::Borrowed(#name),
+ has_default: #is_option,
}
- })
- .collect::<Vec<_>>();
+ }),
+ ArgInfo::Location => None,
+ ArgInfo::This => None,
+ });
- let args = fun
- .sig
- .inputs
- .iter_mut()
- .map(|i| match i {
- FnArg::Receiver(_) => unreachable!(),
- FnArg::Typed(t) => t,
- })
- .map(|t| {
- if t.attrs.retain_had(|a| !a.path.is_ident("location")) {
- quote! {{
- loc
+ let pass = args.iter().map(|a| match a {
+ ArgInfo::Normal {
+ ty,
+ is_option,
+ name,
+ // ident,
+ } => {
+ let eval = quote! {::jrsonnet_evaluator::push_description_frame(
+ || format!("argument <{}> evaluation", #name),
+ || <#ty>::try_from(value.evaluate()?),
+ )?};
+ if *is_option {
+ quote! {if let Some(value) = parsed.get(#name) {
+ Some(#eval)
+ } else {
+ None
}}
- } else if t.attrs.retain_had(|a| !a.path.is_ident("self")) {
+ } else {
quote! {{
- self
+ let value = parsed.get(#name).expect("args shape is checked");
+ #eval
}}
- } else {
- let ident = match &t.pat as &Pat {
- Pat::Ident(i) => i.ident.to_string(),
- _ => {
- return quote_spanned! { t.pat.span() =>
- compile_error!("args should be plain identifiers")
- }
- .into()
- }
- };
- let ty = &t.ty;
- if let Some(opt_ty) = extract_type_from_option(&t.ty) {
- quote! {{
- if let Some(value) = parsed.get(#ident) {
- Some(::jrsonnet_evaluator::push_description_frame(
- || format!("argument <{}> evaluation", #ident),
- || <#opt_ty>::try_from(value.evaluate()?),
- )?)
- } else {
- None
- }
- }}
+ }
+ }
+ ArgInfo::Lazy { is_option, name } => {
+ if *is_option {
+ quote! {if let Some(value) = parsed.get(#name) {
+ Some(value.clone())
} else {
- quote! {{
- let value = parsed.get(#ident).unwrap();
-
- ::jrsonnet_evaluator::push_description_frame(
- || format!("argument <{}> evaluation", #ident),
- || <#ty>::try_from(value.evaluate()?),
- )?
- }}
+ None
+ }}
+ } else {
+ quote! {
+ parsed.get(#name).expect("args shape is correct").clone()
}
}
- })
- .collect::<Vec<_>>();
+ }
+ ArgInfo::Location => quote! {location},
+ ArgInfo::This => quote! {self},
+ });
- let fields = attrs.fields.iter().map(|field| {
+ let fields = attr.fields.iter().map(|field| {
let name = &field.name;
let ty = &field.ty;
quote! {
@@ -234,7 +278,7 @@
let name = &fun.sig.ident;
let vis = &fun.vis;
- let static_ext = if attrs.fields.is_empty() {
+ let static_ext = if attr.fields.is_empty() {
quote! {
impl #name {
pub const INST: &'static dyn StaticBuiltin = &#name {};
@@ -244,13 +288,13 @@
} else {
quote! {}
};
- let static_derive_copy = if attrs.fields.is_empty() {
+ let static_derive_copy = if attr.fields.is_empty() {
quote! {, Copy}
} else {
quote! {}
};
- (quote! {
+ Ok(quote! {
#fun
#[doc(hidden)]
#[allow(non_camel_case_types)]
@@ -260,12 +304,12 @@
}
const _: () = {
use ::jrsonnet_evaluator::{
- function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},
+ function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},
error::Result, Context,
parser::ExprLocation,
};
const PARAMS: &'static [BuiltinParam] = &[
- #(#params),*
+ #(#params_desc),*
];
#static_ext
@@ -279,17 +323,16 @@
fn params(&self) -> &[BuiltinParam] {
PARAMS
}
- fn call(&self, context: Context, loc: Option<&ExprLocation>, args: &dyn ArgsLike) -> Result<Val> {
+ fn call(&self, context: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
let parsed = parse_builtin_call(context, &PARAMS, args, false)?;
- let result: #result = #name(#(#args),*);
+ let result: #result = #name(#(#pass),*);
let result = result?;
result.try_into()
}
}
};
})
- .into()
}
#[derive(Default)]
@@ -366,15 +409,6 @@
)
}
- fn expand_shallow_field(&self) -> Option<TokenStream> {
- if self.is_option() {
- return None;
- }
- let name = self.name()?;
- Some(quote! {
- (#name, ComplexValType::Any)
- })
- }
fn expand_field(&self) -> Option<TokenStream> {
if self.is_option() {
return None;
@@ -450,7 +484,7 @@
}
fn as_option(&self) -> Option<&Type> {
- extract_type_from_option(&self.0.ty)
+ extract_type_from_option(&self.0.ty).unwrap()
}
fn is_option(&self) -> bool {
self.as_option().is_some()
@@ -460,25 +494,25 @@
#[proc_macro_derive(Typed, attributes(typed))]
pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(item as DeriveInput);
+
+ match derive_typed_inner(input) {
+ Ok(v) => v.into(),
+ Err(e) => e.to_compile_error().into(),
+ }
+}
+
+fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {
let data = match &input.data {
syn::Data::Struct(s) => s,
- _ => {
- return syn::Error::new(input.span(), "only structs supported")
- .to_compile_error()
- .into()
- }
+ _ => return Err(Error::new(input.span(), "only structs supported")),
};
let ident = &input.ident;
- let fields = match data
+ let fields = data
.fields
.iter()
.map(TypedField::try_new)
- .collect::<Result<Vec<_>>>()
- {
- Ok(v) => v,
- Err(e) => return e.to_compile_error().into(),
- };
+ .collect::<Result<Vec<_>>>()?;
let typed = {
let fields = fields
@@ -499,7 +533,7 @@
let fields_parse = fields.iter().map(TypedField::expand_parse);
let fields_serialize = fields.iter().map(TypedField::expand_serialize);
- quote! {
+ Ok(quote! {
const _: () = {
use ::jrsonnet_evaluator::{
typed::{ComplexValType, Typed, TypedObj, CheckType},
@@ -540,6 +574,5 @@
}
()
};
- }
- .into()
+ })
}