1use gcmodule::{Cc, Trace};2use jrsonnet_interner::IStr;3use jrsonnet_parser::{4 ArgsDesc, AssertStmt, BindSpec, CompSpec, Destruct, Expr, FieldMember, ForSpecData, IfSpecData,5 LiteralType, LocExpr, Member, ObjBody, ParamsDesc,6};7use jrsonnet_types::ValType;89use crate::{10 destructure::evaluate_dest,11 error::Error::*,12 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},13 function::{CallLocation, FuncDesc, FuncVal},14 stdlib::{std_slice, BUILTINS},15 tb, throw,16 typed::Typed,17 val::{ArrValue, Thunk, ThunkValue},18 Bindable, Context, ContextCreator, GcHashMap, LazyBinding, ObjValue, ObjValueBuilder,19 ObjectAssertion, Pending, Result, State, Val,20};21pub mod destructure;22pub mod operator;2324#[allow(clippy::too_many_lines)]25pub fn evaluate_binding(b: BindSpec, cctx: ContextCreator) -> Result<(IStr, LazyBinding)> {26 match b {27 BindSpec::Field {28 into: Destruct::Full(name),29 value,30 } => {31 #[derive(Trace)]32 struct BindableNamedThunk {33 this: Option<ObjValue>,34 super_obj: Option<ObjValue>,3536 cctx: ContextCreator,37 name: IStr,38 value: LocExpr,39 }40 impl ThunkValue for BindableNamedThunk {41 type Output = Val;42 fn get(self: Box<Self>, s: State) -> Result<Val> {43 evaluate_named(44 s.clone(),45 self.cctx.create(s, self.this, self.super_obj)?,46 &self.value,47 self.name,48 )49 }50 }5152 #[derive(Trace)]53 struct BindableNamed {54 cctx: ContextCreator,55 name: IStr,56 value: LocExpr,57 }58 impl Bindable for BindableNamed {59 fn bind(60 &self,61 _: State,62 this: Option<ObjValue>,63 super_obj: Option<ObjValue>,64 ) -> Result<Thunk<Val>> {65 Ok(Thunk::new(tb!(BindableNamedThunk {66 this,67 super_obj,6869 cctx: self.cctx.clone(),70 name: self.name.clone(),71 value: self.value.clone(),72 })))73 }74 }7576 Ok((77 name.clone(),78 LazyBinding::Bindable(Cc::new(tb!(BindableNamed {79 cctx,80 name: name.clone(),81 value: value.clone(),82 }))),83 ))84 }85 #[cfg(feature = "exp-destruct")]86 BindSpec::Field { into: _, .. } => {87 use crate::throw_runtime;88 throw_runtime!("destructuring is not yet supported here")89 }90 BindSpec::Function {91 name,92 params,93 value,94 } => {95 #[derive(Trace)]96 struct BindableMethodThunk {97 this: Option<ObjValue>,98 super_obj: Option<ObjValue>,99100 cctx: ContextCreator,101 name: IStr,102 params: ParamsDesc,103 value: LocExpr,104 }105 impl ThunkValue for BindableMethodThunk {106 type Output = Val;107 fn get(self: Box<Self>, s: State) -> Result<Val> {108 Ok(evaluate_method(109 self.cctx.create(s, self.this, self.super_obj)?,110 self.name,111 self.params,112 self.value,113 ))114 }115 }116117 #[derive(Trace)]118 struct BindableMethod {119 cctx: ContextCreator,120 name: IStr,121 params: ParamsDesc,122 value: LocExpr,123 }124 impl Bindable for BindableMethod {125 fn bind(126 &self,127 _: State,128 this: Option<ObjValue>,129 super_obj: Option<ObjValue>,130 ) -> Result<Thunk<Val>> {131 Ok(Thunk::<Val>::new(tb!(BindableMethodThunk {132 this,133 super_obj,134135 cctx: self.cctx.clone(),136 name: self.name.clone(),137 params: self.params.clone(),138 value: self.value.clone(),139 })))140 }141 }142143 let params = params.clone();144145 Ok((146 name.clone(),147 LazyBinding::Bindable(Cc::new(tb!(BindableMethod {148 cctx,149 name: name.clone(),150 params,151 value,152 }))),153 ))154 }155 }156}157158pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {159 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {160 name,161 ctx,162 params,163 body,164 })))165}166167pub fn evaluate_field_name(168 s: State,169 ctx: Context,170 field_name: &jrsonnet_parser::FieldName,171) -> Result<Option<IStr>> {172 Ok(match field_name {173 jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),174 jrsonnet_parser::FieldName::Dyn(expr) => s.push(175 CallLocation::new(&expr.1),176 || "evaluating field name".to_string(),177 || {178 let value = evaluate(s.clone(), ctx, expr)?;179 if matches!(value, Val::Null) {180 Ok(None)181 } else {182 Ok(Some(IStr::from_untyped(value, s.clone())?))183 }184 },185 )?,186 })187}188189pub fn evaluate_comp(190 s: State,191 ctx: Context,192 specs: &[CompSpec],193 callback: &mut impl FnMut(Context) -> Result<()>,194) -> Result<()> {195 match specs.get(0) {196 None => callback(ctx)?,197 Some(CompSpec::IfSpec(IfSpecData(cond))) => {198 if bool::from_untyped(evaluate(s.clone(), ctx.clone(), cond)?, s.clone())? {199 evaluate_comp(s, ctx, &specs[1..], callback)?;200 }201 }202 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {203 match evaluate(s.clone(), ctx.clone(), expr)? {204 Val::Arr(list) => {205 for item in list.iter(s.clone()) {206 evaluate_comp(207 s.clone(),208 ctx.clone().with_var(var.clone(), item?.clone()),209 &specs[1..],210 callback,211 )?;212 }213 }214 _ => throw!(InComprehensionCanOnlyIterateOverArray),215 }216 }217 }218 Ok(())219}220221#[allow(clippy::too_many_lines)]222pub fn evaluate_member_list_object(s: State, ctx: Context, members: &[Member]) -> Result<ObjValue> {223 let new_bindings = Pending::new();224 let future_this = Pending::new();225 let cctx = ContextCreator(ctx.clone(), new_bindings.clone());226 {227 let mut bindings: GcHashMap<IStr, LazyBinding> = GcHashMap::with_capacity(members.len());228 for r in members229 .iter()230 .filter_map(|m| match m {231 Member::BindStmt(b) => Some(b.clone()),232 _ => None,233 })234 .map(|b| evaluate_binding(b.clone(), cctx.clone()))235 {236 let (n, b) = r?;237 bindings.insert(n, b);238 }239 new_bindings.fill(bindings);240 }241242 let mut builder = ObjValueBuilder::new();243 for member in members.iter() {244 match member {245 Member::Field(FieldMember {246 name,247 plus,248 params: None,249 visibility,250 value,251 }) => {252 #[derive(Trace)]253 struct ObjMemberBinding {254 cctx: ContextCreator,255 value: LocExpr,256 name: IStr,257 }258 impl Bindable for ObjMemberBinding {259 fn bind(260 &self,261 s: State,262 this: Option<ObjValue>,263 super_obj: Option<ObjValue>,264 ) -> Result<Thunk<Val>> {265 Ok(Thunk::evaluated(evaluate_named(266 s.clone(),267 self.cctx.create(s, this, super_obj)?,268 &self.value,269 self.name.clone(),270 )?))271 }272 }273274 let name = evaluate_field_name(s.clone(), ctx.clone(), name)?;275 let name = if let Some(name) = name {276 name277 } else {278 continue;279 };280281 builder282 .member(name.clone())283 .with_add(*plus)284 .with_visibility(*visibility)285 .with_location(value.1.clone())286 .bindable(287 s.clone(),288 tb!(ObjMemberBinding {289 cctx: cctx.clone(),290 value: value.clone(),291 name,292 }),293 )?;294 }295 Member::Field(FieldMember {296 name,297 params: Some(params),298 value,299 ..300 }) => {301 #[derive(Trace)]302 struct ObjMemberBinding {303 cctx: ContextCreator,304 value: LocExpr,305 params: ParamsDesc,306 name: IStr,307 }308 impl Bindable for ObjMemberBinding {309 fn bind(310 &self,311 s: State,312 this: Option<ObjValue>,313 super_obj: Option<ObjValue>,314 ) -> Result<Thunk<Val>> {315 Ok(Thunk::evaluated(evaluate_method(316 self.cctx.create(s, this, super_obj)?,317 self.name.clone(),318 self.params.clone(),319 self.value.clone(),320 )))321 }322 }323324 let name = if let Some(name) = evaluate_field_name(s.clone(), ctx.clone(), name)? {325 name326 } else {327 continue;328 };329330 builder331 .member(name.clone())332 .hide()333 .with_location(value.1.clone())334 .bindable(335 s.clone(),336 tb!(ObjMemberBinding {337 cctx: cctx.clone(),338 value: value.clone(),339 params: params.clone(),340 name,341 }),342 )?;343 }344 Member::BindStmt(_) => {}345 Member::AssertStmt(stmt) => {346 #[derive(Trace)]347 struct ObjectAssert {348 cctx: ContextCreator,349 assert: AssertStmt,350 }351 impl ObjectAssertion for ObjectAssert {352 fn run(353 &self,354 s: State,355 this: Option<ObjValue>,356 super_obj: Option<ObjValue>,357 ) -> Result<()> {358 let ctx = self.cctx.create(s.clone(), this, super_obj)?;359 evaluate_assert(s, ctx, &self.assert)360 }361 }362 builder.assert(tb!(ObjectAssert {363 cctx: cctx.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(s: State, ctx: Context, object: &ObjBody) -> Result<ObjValue> {375 Ok(match object {376 ObjBody::MemberList(members) => evaluate_member_list_object(s, ctx, members)?,377 ObjBody::ObjComp(obj) => {378 let future_this = Pending::new();379 let mut builder = ObjValueBuilder::new();380 evaluate_comp(s.clone(), ctx, &obj.compspecs, &mut |ctx| {381 let new_bindings = Pending::new();382 let cctx = ContextCreator(ctx.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 r in obj386 .pre_locals387 .iter()388 .chain(obj.post_locals.iter())389 .map(|b| evaluate_binding(b.clone(), cctx.clone()))390 {391 let (n, b) = r?;392 bindings.insert(n, b);393 }394 new_bindings.fill(bindings.clone());395 let ctx = ctx.extend_unbound(s.clone(), bindings, None, None, None)?;396 let key = evaluate(s.clone(), ctx.clone(), &obj.key)?;397398 match key {399 Val::Null => {}400 Val::Str(n) => {401 #[derive(Trace)]402 struct ObjCompBinding {403 ctx: Context,404 value: LocExpr,405 }406 impl Bindable for ObjCompBinding {407 fn bind(408 &self,409 s: State,410 this: Option<ObjValue>,411 _super_obj: Option<ObjValue>,412 ) -> Result<Thunk<Val>> {413 Ok(Thunk::evaluated(evaluate(414 s,415 self.ctx.clone().extend(GcHashMap::new(), None, this, None),416 &self.value,417 )?))418 }419 }420 builder421 .member(n)422 .with_location(obj.value.1.clone())423 .with_add(obj.plus)424 .bindable(425 s.clone(),426 tb!(ObjCompBinding {427 ctx,428 value: obj.value.clone(),429 }),430 )?;431 }432 v => throw!(FieldMustBeStringGot(v.value_type())),433 }434435 Ok(())436 })?;437438 let this = builder.build();439 future_this.fill(this.clone());440 this441 }442 })443}444445pub fn evaluate_apply(446 s: State,447 ctx: Context,448 value: &LocExpr,449 args: &ArgsDesc,450 loc: CallLocation,451 tailstrict: bool,452) -> Result<Val> {453 let value = evaluate(s.clone(), ctx.clone(), value)?;454 Ok(match value {455 Val::Func(f) => {456 let body = || f.evaluate(s.clone(), ctx, loc, args, tailstrict);457 if tailstrict {458 body()?459 } else {460 s.push(loc, || format!("function <{}> call", f.name()), body)?461 }462 }463 v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),464 })465}466467pub fn evaluate_assert(s: State, ctx: Context, assertion: &AssertStmt) -> Result<()> {468 let value = &assertion.0;469 let msg = &assertion.1;470 let assertion_result = s.push(471 CallLocation::new(&value.1),472 || "assertion condition".to_owned(),473 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),474 )?;475 if !assertion_result {476 s.push(477 CallLocation::new(&value.1),478 || "assertion failure".to_owned(),479 || {480 if let Some(msg) = msg {481 throw!(AssertionFailed(482 evaluate(s.clone(), ctx, msg)?.to_string(s.clone())?483 ));484 }485 throw!(AssertionFailed(Val::Null.to_string(s.clone())?));486 },487 )?;488 }489 Ok(())490}491492pub fn evaluate_named(s: State, ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {493 use Expr::*;494 let LocExpr(raw_expr, _loc) = expr;495 Ok(match &**raw_expr {496 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),497 _ => evaluate(s, ctx, expr)?,498 })499}500501#[allow(clippy::too_many_lines)]502pub fn evaluate(s: State, ctx: Context, expr: &LocExpr) -> Result<Val> {503 use Expr::*;504 let LocExpr(expr, loc) = expr;505 506 Ok(match &**expr {507 Literal(LiteralType::This) => {508 Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)509 }510 Literal(LiteralType::Super) => Val::Obj(511 ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(512 ctx.this()513 .clone()514 .expect("if super exists - then this should to"),515 ),516 ),517 Literal(LiteralType::Dollar) => {518 Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)519 }520 Literal(LiteralType::True) => Val::Bool(true),521 Literal(LiteralType::False) => Val::Bool(false),522 Literal(LiteralType::Null) => Val::Null,523 Parened(e) => evaluate(s, ctx, e)?,524 Str(v) => Val::Str(v.clone()),525 Num(v) => Val::new_checked_num(*v)?,526 BinaryOp(v1, o, v2) => evaluate_binary_op_special(s, ctx, v1, *o, v2)?,527 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,528 Var(name) => s.push(529 CallLocation::new(loc),530 || format!("variable <{}> access", name),531 || ctx.binding(name.clone())?.evaluate(s.clone()),532 )?,533 Index(value, index) => {534 match (535 evaluate(s.clone(), ctx.clone(), value)?,536 evaluate(s.clone(), ctx, index)?,537 ) {538 (Val::Obj(v), Val::Str(key)) => s.push(539 CallLocation::new(loc),540 || format!("field <{}> access", key),541 || match v.get(s.clone(), key.clone()) {542 Ok(Some(v)) => Ok(v),543 Ok(None) => throw!(NoSuchField(key.clone())),544 Err(e) if matches!(e.error(), MagicThisFileUsed) => {545 Ok(Val::Str(loc.0.to_string_lossy().into()))546 }547 Err(e) => Err(e),548 },549 )?,550 (Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(551 ValType::Obj,552 ValType::Str,553 n.value_type(),554 )),555556 (Val::Arr(v), Val::Num(n)) => {557 if n.fract() > f64::EPSILON {558 throw!(FractionalIndex)559 }560 v.get(s, n as usize)?561 .ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?562 }563 (Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),564 (Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(565 ValType::Arr,566 ValType::Num,567 n.value_type(),568 )),569570 (Val::Str(s), Val::Num(n)) => Val::Str({571 let v: IStr = s572 .chars()573 .skip(n as usize)574 .take(1)575 .collect::<String>()576 .into();577 if v.is_empty() {578 let size = s.chars().count();579 throw!(StringBoundsError(n as usize, size))580 }581 v582 }),583 (Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(584 ValType::Str,585 ValType::Num,586 n.value_type(),587 )),588589 (v, _) => throw!(CantIndexInto(v.value_type())),590 }591 }592 LocalExpr(bindings, returned) => {593 let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =594 GcHashMap::with_capacity(bindings.len());595 let fctx = Context::new_future();596 for b in bindings {597 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;598 }599 let ctx = ctx.extend_bound(new_bindings).into_future(fctx);600 evaluate(s, ctx, &returned.clone())?601 }602 Arr(items) => {603 let mut out = Vec::with_capacity(items.len());604 for item in items {605 606 #[derive(Trace)]607 struct ArrayElement {608 ctx: Context,609 item: LocExpr,610 }611 impl ThunkValue for ArrayElement {612 type Output = Val;613 fn get(self: Box<Self>, s: State) -> Result<Val> {614 evaluate(s, self.ctx, &self.item)615 }616 }617 out.push(Thunk::new(tb!(ArrayElement {618 ctx: ctx.clone(),619 item: item.clone(),620 })));621 }622 Val::Arr(out.into())623 }624 ArrComp(expr, comp_specs) => {625 let mut out = Vec::new();626 evaluate_comp(s.clone(), ctx, comp_specs, &mut |ctx| {627 out.push(evaluate(s.clone(), ctx, expr)?);628 Ok(())629 })?;630 Val::Arr(ArrValue::Eager(Cc::new(out)))631 }632 Obj(body) => Val::Obj(evaluate_object(s, ctx, body)?),633 ObjExtend(a, b) => evaluate_add_op(634 s.clone(),635 &evaluate(s.clone(), ctx.clone(), a)?,636 &Val::Obj(evaluate_object(s, ctx, b)?),637 )?,638 Apply(value, args, tailstrict) => {639 evaluate_apply(s, ctx, value, args, CallLocation::new(loc), *tailstrict)?640 }641 Function(params, body) => {642 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())643 }644 Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(645 BUILTINS646 .with(|b| b.get(name).copied())647 .ok_or_else(|| IntrinsicNotFound(name.clone()))?,648 )),649 IntrinsicThisFile => return Err(MagicThisFileUsed.into()),650 IntrinsicId => Val::Func(FuncVal::identity()),651 AssertExpr(assert, returned) => {652 evaluate_assert(s.clone(), ctx.clone(), assert)?;653 evaluate(s, ctx, returned)?654 }655 ErrorStmt(e) => s.push(656 CallLocation::new(loc),657 || "error statement".to_owned(),658 || {659 throw!(RuntimeError(660 evaluate(s.clone(), ctx, e)?.to_string(s.clone())?,661 ))662 },663 )?,664 IfElse {665 cond,666 cond_then,667 cond_else,668 } => {669 if s.push(670 CallLocation::new(loc),671 || "if condition".to_owned(),672 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), &cond.0)?, s.clone()),673 )? {674 evaluate(s, ctx, cond_then)?675 } else {676 match cond_else {677 Some(v) => evaluate(s, ctx, v)?,678 None => Val::Null,679 }680 }681 }682 Slice(value, desc) => {683 fn parse_idx<T: Typed>(684 loc: CallLocation,685 s: State,686 ctx: &Context,687 expr: &Option<LocExpr>,688 desc: &'static str,689 ) -> Result<Option<T>> {690 if let Some(value) = expr {691 Ok(Some(s.push(692 loc,693 || format!("slice {}", desc),694 || T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),695 )?))696 } else {697 Ok(None)698 }699 }700701 let indexable = evaluate(s.clone(), ctx.clone(), value)?;702 let loc = CallLocation::new(loc);703704 let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?;705 let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?;706 let step = parse_idx(loc, s, &ctx, &desc.step, "step")?;707708 std_slice(indexable.into_indexable()?, start, end, step)?709 }710 Import(path) => {711 let tmp = loc.clone().0;712 let mut import_location = tmp.to_path_buf();713 import_location.pop();714 s.push(715 CallLocation::new(loc),716 || format!("import {:?}", path),717 || s.import_file(&import_location, path),718 )?719 }720 ImportStr(path) => {721 let tmp = loc.clone().0;722 let mut import_location = tmp.to_path_buf();723 import_location.pop();724 Val::Str(s.import_file_str(&import_location, path)?)725 }726 ImportBin(path) => {727 let tmp = loc.clone().0;728 let mut import_location = tmp.to_path_buf();729 import_location.pop();730 let bytes = s.import_file_bin(&import_location, path)?;731 Val::Arr(ArrValue::Bytes(bytes))732 }733 })734}