1use gcmodule::{Cc, Trace};2use jrsonnet_interner::IStr;3use jrsonnet_parser::{4 ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, ForSpecData, IfSpecData,5 LiteralType, LocExpr, Member, ObjBody, ParamsDesc,6};7use jrsonnet_types::ValType;89use crate::{10 builtin::{std_slice, BUILTINS},11 error::Error::*,12 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},13 function::CallLocation,14 gc::TraceBox,15 throw,16 typed::Typed,17 val::{ArrValue, FuncDesc, FuncVal, LazyValValue},18 Bindable, Context, ContextCreator, FutureWrapper, GcHashMap, LazyBinding, LazyVal, ObjValue,19 ObjValueBuilder, ObjectAssertion, Result, State, Val,20};21pub mod operator;2223pub fn evaluate_binding_in_future(b: &BindSpec, fctx: FutureWrapper<Context>) -> LazyVal {24 let b = b.clone();25 if let Some(params) = &b.params {26 #[derive(Trace)]27 struct LazyMethodBinding {28 fctx: FutureWrapper<Context>,29 name: IStr,30 params: ParamsDesc,31 value: LocExpr,32 }33 impl LazyValValue for LazyMethodBinding {34 fn get(self: Box<Self>, _: State) -> Result<Val> {35 Ok(evaluate_method(36 self.fctx.unwrap(),37 self.name,38 self.params,39 self.value,40 ))41 }42 }4344 let params = params.clone();4546 LazyVal::new(TraceBox(Box::new(LazyMethodBinding {47 fctx,48 name: b.name.clone(),49 params,50 value: b.value.clone(),51 })))52 } else {53 #[derive(Trace)]54 struct LazyNamedBinding {55 fctx: FutureWrapper<Context>,56 name: IStr,57 value: LocExpr,58 }59 impl LazyValValue for LazyNamedBinding {60 fn get(self: Box<Self>, s: State) -> Result<Val> {61 evaluate_named(s, self.fctx.unwrap(), &self.value, self.name)62 }63 }64 LazyVal::new(TraceBox(Box::new(LazyNamedBinding {65 fctx,66 name: b.name.clone(),67 value: b.value,68 })))69 }70}7172#[allow(clippy::too_many_lines)]73pub fn evaluate_binding(b: &BindSpec, cctx: ContextCreator) -> (IStr, LazyBinding) {74 let b = b.clone();75 if let Some(params) = &b.params {76 #[derive(Trace)]77 struct BindableMethodLazyVal {78 this: Option<ObjValue>,79 super_obj: Option<ObjValue>,8081 cctx: ContextCreator,82 name: IStr,83 params: ParamsDesc,84 value: LocExpr,85 }86 impl LazyValValue for BindableMethodLazyVal {87 fn get(self: Box<Self>, s: State) -> Result<Val> {88 Ok(evaluate_method(89 self.cctx.create(s, self.this, self.super_obj)?,90 self.name,91 self.params,92 self.value,93 ))94 }95 }9697 #[derive(Trace)]98 struct BindableMethod {99 cctx: ContextCreator,100 name: IStr,101 params: ParamsDesc,102 value: LocExpr,103 }104 impl Bindable for BindableMethod {105 fn bind(106 &self,107 _: State,108 this: Option<ObjValue>,109 super_obj: Option<ObjValue>,110 ) -> Result<LazyVal> {111 Ok(LazyVal::new(TraceBox(Box::new(BindableMethodLazyVal {112 this,113 super_obj,114115 cctx: self.cctx.clone(),116 name: self.name.clone(),117 params: self.params.clone(),118 value: self.value.clone(),119 }))))120 }121 }122123 let params = params.clone();124125 (126 b.name.clone(),127 LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableMethod {128 cctx,129 name: b.name.clone(),130 params,131 value: b.value.clone(),132 })))),133 )134 } else {135 #[derive(Trace)]136 struct BindableNamedLazyVal {137 this: Option<ObjValue>,138 super_obj: Option<ObjValue>,139140 cctx: ContextCreator,141 name: IStr,142 value: LocExpr,143 }144 impl LazyValValue for BindableNamedLazyVal {145 fn get(self: Box<Self>, s: State) -> Result<Val> {146 evaluate_named(147 s.clone(),148 self.cctx.create(s, self.this, self.super_obj)?,149 &self.value,150 self.name,151 )152 }153 }154155 #[derive(Trace)]156 struct BindableNamed {157 cctx: ContextCreator,158 name: IStr,159 value: LocExpr,160 }161 impl Bindable for BindableNamed {162 fn bind(163 &self,164 _: State,165 this: Option<ObjValue>,166 super_obj: Option<ObjValue>,167 ) -> Result<LazyVal> {168 Ok(LazyVal::new(TraceBox(Box::new(BindableNamedLazyVal {169 this,170 super_obj,171172 cctx: self.cctx.clone(),173 name: self.name.clone(),174 value: self.value.clone(),175 }))))176 }177 }178179 (180 b.name.clone(),181 LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableNamed {182 cctx,183 name: b.name.clone(),184 value: b.value.clone(),185 })))),186 )187 }188}189190pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {191 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {192 name,193 ctx,194 params,195 body,196 })))197}198199pub fn evaluate_field_name(200 s: State,201 ctx: Context,202 field_name: &jrsonnet_parser::FieldName,203) -> Result<Option<IStr>> {204 Ok(match field_name {205 jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),206 jrsonnet_parser::FieldName::Dyn(expr) => s.push(207 CallLocation::new(&expr.1),208 || "evaluating field name".to_string(),209 || {210 let value = evaluate(s.clone(), ctx, expr)?;211 if matches!(value, Val::Null) {212 Ok(None)213 } else {214 Ok(Some(IStr::from_untyped(value, s.clone())?))215 }216 },217 )?,218 })219}220221pub fn evaluate_comp(222 s: State,223 ctx: Context,224 specs: &[CompSpec],225 callback: &mut impl FnMut(Context) -> Result<()>,226) -> Result<()> {227 match specs.get(0) {228 None => callback(ctx)?,229 Some(CompSpec::IfSpec(IfSpecData(cond))) => {230 if bool::from_untyped(evaluate(s.clone(), ctx.clone(), cond)?, s.clone())? {231 evaluate_comp(s, ctx, &specs[1..], callback)?;232 }233 }234 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {235 match evaluate(s.clone(), ctx.clone(), expr)? {236 Val::Arr(list) => {237 for item in list.iter(s.clone()) {238 evaluate_comp(239 s.clone(),240 ctx.clone().with_var(var.clone(), item?.clone()),241 &specs[1..],242 callback,243 )?;244 }245 }246 _ => throw!(InComprehensionCanOnlyIterateOverArray),247 }248 }249 }250 Ok(())251}252253#[allow(clippy::too_many_lines)]254pub fn evaluate_member_list_object(s: State, ctx: Context, members: &[Member]) -> Result<ObjValue> {255 let new_bindings = FutureWrapper::new();256 let future_this = FutureWrapper::new();257 let cctx = ContextCreator(ctx.clone(), new_bindings.clone());258 {259 let mut bindings: GcHashMap<IStr, LazyBinding> = GcHashMap::with_capacity(members.len());260 for (n, b) in members261 .iter()262 .filter_map(|m| match m {263 Member::BindStmt(b) => Some(b.clone()),264 _ => None,265 })266 .map(|b| evaluate_binding(&b, cctx.clone()))267 {268 bindings.insert(n, b);269 }270 new_bindings.fill(bindings);271 }272273 let mut builder = ObjValueBuilder::new();274 for member in members.iter() {275 match member {276 Member::Field(FieldMember {277 name,278 plus,279 params: None,280 visibility,281 value,282 }) => {283 #[derive(Trace)]284 struct ObjMemberBinding {285 cctx: ContextCreator,286 value: LocExpr,287 name: IStr,288 }289 impl Bindable for ObjMemberBinding {290 fn bind(291 &self,292 s: State,293 this: Option<ObjValue>,294 super_obj: Option<ObjValue>,295 ) -> Result<LazyVal> {296 Ok(LazyVal::new_resolved(evaluate_named(297 s.clone(),298 self.cctx.create(s, this, super_obj)?,299 &self.value,300 self.name.clone(),301 )?))302 }303 }304305 let name = evaluate_field_name(s.clone(), ctx.clone(), name)?;306 let name = if let Some(name) = name {307 name308 } else {309 continue;310 };311312 builder313 .member(name.clone())314 .with_add(*plus)315 .with_visibility(*visibility)316 .with_location(value.1.clone())317 .bindable(318 s.clone(),319 TraceBox(Box::new(ObjMemberBinding {320 cctx: cctx.clone(),321 value: value.clone(),322 name,323 })),324 )?;325 }326 Member::Field(FieldMember {327 name,328 params: Some(params),329 value,330 ..331 }) => {332 #[derive(Trace)]333 struct ObjMemberBinding {334 cctx: ContextCreator,335 value: LocExpr,336 params: ParamsDesc,337 name: IStr,338 }339 impl Bindable for ObjMemberBinding {340 fn bind(341 &self,342 s: State,343 this: Option<ObjValue>,344 super_obj: Option<ObjValue>,345 ) -> Result<LazyVal> {346 Ok(LazyVal::new_resolved(evaluate_method(347 self.cctx.create(s, this, super_obj)?,348 self.name.clone(),349 self.params.clone(),350 self.value.clone(),351 )))352 }353 }354355 let name = if let Some(name) = evaluate_field_name(s.clone(), ctx.clone(), name)? {356 name357 } else {358 continue;359 };360361 builder362 .member(name.clone())363 .hide()364 .with_location(value.1.clone())365 .bindable(366 s.clone(),367 TraceBox(Box::new(ObjMemberBinding {368 cctx: cctx.clone(),369 value: value.clone(),370 params: params.clone(),371 name,372 })),373 )?;374 }375 Member::BindStmt(_) => {}376 Member::AssertStmt(stmt) => {377 #[derive(Trace)]378 struct ObjectAssert {379 cctx: ContextCreator,380 assert: AssertStmt,381 }382 impl ObjectAssertion for ObjectAssert {383 fn run(384 &self,385 s: State,386 this: Option<ObjValue>,387 super_obj: Option<ObjValue>,388 ) -> Result<()> {389 let ctx = self.cctx.create(s.clone(), this, super_obj)?;390 evaluate_assert(s, ctx, &self.assert)391 }392 }393 builder.assert(TraceBox(Box::new(ObjectAssert {394 cctx: cctx.clone(),395 assert: stmt.clone(),396 })));397 }398 }399 }400 let this = builder.build();401 future_this.fill(this.clone());402 Ok(this)403}404405pub fn evaluate_object(s: State, ctx: Context, object: &ObjBody) -> Result<ObjValue> {406 Ok(match object {407 ObjBody::MemberList(members) => evaluate_member_list_object(s, ctx, members)?,408 ObjBody::ObjComp(obj) => {409 let future_this = FutureWrapper::new();410 let mut builder = ObjValueBuilder::new();411 evaluate_comp(s.clone(), ctx, &obj.compspecs, &mut |ctx| {412 let new_bindings = FutureWrapper::new();413 let cctx = ContextCreator(ctx.clone(), new_bindings.clone());414 let mut bindings: GcHashMap<IStr, LazyBinding> =415 GcHashMap::with_capacity(obj.pre_locals.len() + obj.post_locals.len());416 for (n, b) in obj417 .pre_locals418 .iter()419 .chain(obj.post_locals.iter())420 .map(|b| evaluate_binding(b, cctx.clone()))421 {422 bindings.insert(n, b);423 }424 new_bindings.fill(bindings.clone());425 let ctx = ctx.extend_unbound(s.clone(), bindings, None, None, None)?;426 let key = evaluate(s.clone(), ctx.clone(), &obj.key)?;427428 match key {429 Val::Null => {}430 Val::Str(n) => {431 #[derive(Trace)]432 struct ObjCompBinding {433 ctx: Context,434 value: LocExpr,435 }436 impl Bindable for ObjCompBinding {437 fn bind(438 &self,439 s: State,440 this: Option<ObjValue>,441 _super_obj: Option<ObjValue>,442 ) -> Result<LazyVal> {443 Ok(LazyVal::new_resolved(evaluate(444 s,445 self.ctx.clone().extend(GcHashMap::new(), None, this, None),446 &self.value,447 )?))448 }449 }450 builder451 .member(n)452 .with_location(obj.value.1.clone())453 .with_add(obj.plus)454 .bindable(455 s.clone(),456 TraceBox(Box::new(ObjCompBinding {457 ctx,458 value: obj.value.clone(),459 })),460 )?;461 }462 v => throw!(FieldMustBeStringGot(v.value_type())),463 }464465 Ok(())466 })?;467468 let this = builder.build();469 future_this.fill(this.clone());470 this471 }472 })473}474475pub fn evaluate_apply(476 s: State,477 ctx: Context,478 value: &LocExpr,479 args: &ArgsDesc,480 loc: CallLocation,481 tailstrict: bool,482) -> Result<Val> {483 let value = evaluate(s.clone(), ctx.clone(), value)?;484 Ok(match value {485 Val::Func(f) => {486 let body = || f.evaluate(s.clone(), ctx, loc, args, tailstrict);487 if tailstrict {488 body()?489 } else {490 s.push(loc, || format!("function <{}> call", f.name()), body)?491 }492 }493 v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),494 })495}496497pub fn evaluate_assert(s: State, ctx: Context, assertion: &AssertStmt) -> Result<()> {498 let value = &assertion.0;499 let msg = &assertion.1;500 let assertion_result = s.push(501 CallLocation::new(&value.1),502 || "assertion condition".to_owned(),503 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),504 )?;505 if !assertion_result {506 s.push(507 CallLocation::new(&value.1),508 || "assertion failure".to_owned(),509 || {510 if let Some(msg) = msg {511 throw!(AssertionFailed(512 evaluate(s.clone(), ctx, msg)?.to_string(s.clone())?513 ));514 }515 throw!(AssertionFailed(Val::Null.to_string(s.clone())?));516 },517 )?;518 }519 Ok(())520}521522pub fn evaluate_named(s: State, ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {523 use Expr::*;524 let LocExpr(raw_expr, _loc) = expr;525 Ok(match &**raw_expr {526 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),527 _ => evaluate(s, ctx, expr)?,528 })529}530531#[allow(clippy::too_many_lines)]532pub fn evaluate(s: State, ctx: Context, expr: &LocExpr) -> Result<Val> {533 use Expr::*;534 let LocExpr(expr, loc) = expr;535 536 Ok(match &**expr {537 Literal(LiteralType::This) => {538 Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)539 }540 Literal(LiteralType::Super) => Val::Obj(541 ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(542 ctx.this()543 .clone()544 .expect("if super exists - then this should to"),545 ),546 ),547 Literal(LiteralType::Dollar) => {548 Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)549 }550 Literal(LiteralType::True) => Val::Bool(true),551 Literal(LiteralType::False) => Val::Bool(false),552 Literal(LiteralType::Null) => Val::Null,553 Parened(e) => evaluate(s, ctx, e)?,554 Str(v) => Val::Str(v.clone()),555 Num(v) => Val::new_checked_num(*v)?,556 BinaryOp(v1, o, v2) => evaluate_binary_op_special(s, ctx, v1, *o, v2)?,557 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,558 Var(name) => s.push(559 CallLocation::new(loc),560 || format!("variable <{}> access", name),561 || ctx.binding(name.clone())?.evaluate(s.clone()),562 )?,563 Index(value, index) => {564 match (565 evaluate(s.clone(), ctx.clone(), value)?,566 evaluate(s.clone(), ctx, index)?,567 ) {568 (Val::Obj(v), Val::Str(key)) => s.push(569 CallLocation::new(loc),570 || format!("field <{}> access", key),571 || {572 if let Some(v) = v.get(s.clone(), key.clone())? {573 Ok(v)574 } else {575 throw!(NoSuchField(key.clone()))576 }577 },578 )?,579 (Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(580 ValType::Obj,581 ValType::Str,582 n.value_type(),583 )),584585 (Val::Arr(v), Val::Num(n)) => {586 if n.fract() > f64::EPSILON {587 throw!(FractionalIndex)588 }589 v.get(s, n as usize)?590 .ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?591 }592 (Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),593 (Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(594 ValType::Arr,595 ValType::Num,596 n.value_type(),597 )),598599 (Val::Str(s), Val::Num(n)) => Val::Str({600 let v: IStr = s601 .chars()602 .skip(n as usize)603 .take(1)604 .collect::<String>()605 .into();606 if v.is_empty() {607 let size = s.chars().count();608 throw!(StringBoundsError(n as usize, size))609 }610 v611 }),612 (Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(613 ValType::Str,614 ValType::Num,615 n.value_type(),616 )),617618 (v, _) => throw!(CantIndexInto(v.value_type())),619 }620 }621 LocalExpr(bindings, returned) => {622 let mut new_bindings: GcHashMap<IStr, LazyVal> =623 GcHashMap::with_capacity(bindings.len());624 let fctx = Context::new_future();625 for b in bindings {626 new_bindings.insert(b.name.clone(), evaluate_binding_in_future(b, fctx.clone()));627 }628 let ctx = ctx.extend_bound(new_bindings).into_future(fctx);629 evaluate(s, ctx, &returned.clone())?630 }631 Arr(items) => {632 let mut out = Vec::with_capacity(items.len());633 for item in items {634 635 #[derive(Trace)]636 struct ArrayElement {637 ctx: Context,638 item: LocExpr,639 }640 impl LazyValValue for ArrayElement {641 fn get(self: Box<Self>, s: State) -> Result<Val> {642 evaluate(s, self.ctx, &self.item)643 }644 }645 out.push(LazyVal::new(TraceBox(Box::new(ArrayElement {646 ctx: ctx.clone(),647 item: item.clone(),648 }))));649 }650 Val::Arr(out.into())651 }652 ArrComp(expr, comp_specs) => {653 let mut out = Vec::new();654 evaluate_comp(s.clone(), ctx, comp_specs, &mut |ctx| {655 out.push(evaluate(s.clone(), ctx, expr)?);656 Ok(())657 })?;658 Val::Arr(ArrValue::Eager(Cc::new(out)))659 }660 Obj(body) => Val::Obj(evaluate_object(s, ctx, body)?),661 ObjExtend(a, b) => evaluate_add_op(662 s.clone(),663 &evaluate(s.clone(), ctx.clone(), a)?,664 &Val::Obj(evaluate_object(s, ctx, b)?),665 )?,666 Apply(value, args, tailstrict) => {667 evaluate_apply(s, ctx, value, args, CallLocation::new(loc), *tailstrict)?668 }669 Function(params, body) => {670 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())671 }672 Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(673 BUILTINS674 .with(|b| b.get(name).copied())675 .ok_or_else(|| IntrinsicNotFound(name.clone()))?,676 )),677 AssertExpr(assert, returned) => {678 evaluate_assert(s.clone(), ctx.clone(), assert)?;679 evaluate(s, ctx, returned)?680 }681 ErrorStmt(e) => s.push(682 CallLocation::new(loc),683 || "error statement".to_owned(),684 || {685 throw!(RuntimeError(686 evaluate(s.clone(), ctx, e)?.to_string(s.clone())?,687 ))688 },689 )?,690 IfElse {691 cond,692 cond_then,693 cond_else,694 } => {695 if s.push(696 CallLocation::new(loc),697 || "if condition".to_owned(),698 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), &cond.0)?, s.clone()),699 )? {700 evaluate(s, ctx, cond_then)?701 } else {702 match cond_else {703 Some(v) => evaluate(s, ctx, v)?,704 None => Val::Null,705 }706 }707 }708 Slice(value, desc) => {709 fn parse_idx<T: Typed>(710 loc: CallLocation,711 s: State,712 ctx: &Context,713 expr: &Option<LocExpr>,714 desc: &'static str,715 ) -> Result<Option<T>> {716 if let Some(value) = expr {717 Ok(Some(s.push(718 loc,719 || format!("slice {}", desc),720 || T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),721 )?))722 } else {723 Ok(None)724 }725 }726727 let indexable = evaluate(s.clone(), ctx.clone(), value)?;728 let loc = CallLocation::new(loc);729730 let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?;731 let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?;732 let step = parse_idx(loc, s, &ctx, &desc.step, "step")?;733734 std_slice(indexable.into_indexable()?, start, end, step)?735 }736 Import(path) => {737 let tmp = loc.clone().0;738 let mut import_location = tmp.to_path_buf();739 import_location.pop();740 s.push(741 CallLocation::new(loc),742 || format!("import {:?}", path),743 || s.import_file(&import_location, path),744 )?745 }746 ImportStr(path) => {747 let tmp = loc.clone().0;748 let mut import_location = tmp.to_path_buf();749 import_location.pop();750 Val::Str(s.import_file_str(&import_location, path)?)751 }752 ImportBin(path) => {753 let tmp = loc.clone().0;754 let mut import_location = tmp.to_path_buf();755 import_location.pop();756 let bytes = s.import_file_bin(&import_location, path)?;757 Val::Arr(ArrValue::Bytes(bytes))758 }759 })760}