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 let params = params.clone();2728 #[derive(Trace)]29 struct LazyMethodBinding {30 fctx: FutureWrapper<Context>,31 name: IStr,32 params: ParamsDesc,33 value: LocExpr,34 }35 impl LazyValValue for LazyMethodBinding {36 fn get(self: Box<Self>, _: State) -> Result<Val> {37 Ok(evaluate_method(38 self.fctx.unwrap(),39 self.name,40 self.params,41 self.value,42 ))43 }44 }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}7172pub fn evaluate_binding(b: &BindSpec, cctx: ContextCreator) -> (IStr, LazyBinding) {73 let b = b.clone();74 if let Some(params) = &b.params {75 let params = params.clone();7677 #[derive(Trace)]78 struct BindableMethodLazyVal {79 this: Option<ObjValue>,80 super_obj: Option<ObjValue>,8182 cctx: ContextCreator,83 name: IStr,84 params: ParamsDesc,85 value: LocExpr,86 }87 impl LazyValValue for BindableMethodLazyVal {88 fn get(self: Box<Self>, s: State) -> Result<Val> {89 Ok(evaluate_method(90 self.cctx.create(s, self.this, self.super_obj)?,91 self.name,92 self.params,93 self.value,94 ))95 }96 }9798 #[derive(Trace)]99 struct BindableMethod {100 cctx: ContextCreator,101 name: IStr,102 params: ParamsDesc,103 value: LocExpr,104 }105 impl Bindable for BindableMethod {106 fn bind(107 &self,108 _: State,109 this: Option<ObjValue>,110 super_obj: Option<ObjValue>,111 ) -> Result<LazyVal> {112 Ok(LazyVal::new(TraceBox(Box::new(BindableMethodLazyVal {113 this,114 super_obj,115116 cctx: self.cctx.clone(),117 name: self.name.clone(),118 params: self.params.clone(),119 value: self.value.clone(),120 }))))121 }122 }123124 (125 b.name.clone(),126 LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableMethod {127 cctx,128 name: b.name.clone(),129 params,130 value: b.value.clone(),131 })))),132 )133 } else {134 #[derive(Trace)]135 struct BindableNamedLazyVal {136 this: Option<ObjValue>,137 super_obj: Option<ObjValue>,138139 cctx: ContextCreator,140 name: IStr,141 value: LocExpr,142 }143 impl LazyValValue for BindableNamedLazyVal {144 fn get(self: Box<Self>, s: State) -> Result<Val> {145 evaluate_named(146 s.clone(),147 self.cctx.create(s, self.this, self.super_obj)?,148 &self.value,149 self.name,150 )151 }152 }153154 #[derive(Trace)]155 struct BindableNamed {156 cctx: ContextCreator,157 name: IStr,158 value: LocExpr,159 }160 impl Bindable for BindableNamed {161 fn bind(162 &self,163 _: State,164 this: Option<ObjValue>,165 super_obj: Option<ObjValue>,166 ) -> Result<LazyVal> {167 Ok(LazyVal::new(TraceBox(Box::new(BindableNamedLazyVal {168 this,169 super_obj,170171 cctx: self.cctx.clone(),172 name: self.name.clone(),173 value: self.value.clone(),174 }))))175 }176 }177178 (179 b.name.clone(),180 LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableNamed {181 cctx,182 name: b.name.clone(),183 value: b.value.clone(),184 })))),185 )186 }187}188189pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {190 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {191 name,192 ctx,193 params,194 body,195 })))196}197198pub fn evaluate_field_name(199 s: State,200 ctx: Context,201 field_name: &jrsonnet_parser::FieldName,202) -> Result<Option<IStr>> {203 Ok(match field_name {204 jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),205 jrsonnet_parser::FieldName::Dyn(expr) => s.push(206 CallLocation::new(&expr.1),207 || "evaluating field name".to_string(),208 || {209 let value = evaluate(s.clone(), ctx, expr)?;210 if matches!(value, Val::Null) {211 Ok(None)212 } else {213 Ok(Some(IStr::from_untyped(value, s.clone())?))214 }215 },216 )?,217 })218}219220pub fn evaluate_comp(221 s: State,222 ctx: Context,223 specs: &[CompSpec],224 callback: &mut impl FnMut(Context) -> Result<()>,225) -> Result<()> {226 match specs.get(0) {227 None => callback(ctx)?,228 Some(CompSpec::IfSpec(IfSpecData(cond))) => {229 if bool::from_untyped(evaluate(s.clone(), ctx.clone(), cond)?, s.clone())? {230 evaluate_comp(s, ctx, &specs[1..], callback)?231 }232 }233 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {234 match evaluate(s.clone(), ctx.clone(), expr)? {235 Val::Arr(list) => {236 for item in list.iter(s.clone()) {237 evaluate_comp(238 s.clone(),239 ctx.clone().with_var(var.clone(), item?.clone()),240 &specs[1..],241 callback,242 )?243 }244 }245 _ => throw!(InComprehensionCanOnlyIterateOverArray),246 }247 }248 }249 Ok(())250}251252pub fn evaluate_member_list_object(s: State, ctx: Context, members: &[Member]) -> Result<ObjValue> {253 let new_bindings = FutureWrapper::new();254 let future_this = FutureWrapper::new();255 let cctx = ContextCreator(ctx.clone(), new_bindings.clone());256 {257 let mut bindings: GcHashMap<IStr, LazyBinding> = GcHashMap::with_capacity(members.len());258 for (n, b) in members259 .iter()260 .filter_map(|m| match m {261 Member::BindStmt(b) => Some(b.clone()),262 _ => None,263 })264 .map(|b| evaluate_binding(&b, cctx.clone()))265 {266 bindings.insert(n, b);267 }268 new_bindings.fill(bindings);269 }270271 let mut builder = ObjValueBuilder::new();272 for member in members.iter() {273 match member {274 Member::Field(FieldMember {275 name,276 plus,277 params: None,278 visibility,279 value,280 }) => {281 let name = evaluate_field_name(s.clone(), ctx.clone(), name)?;282 if name.is_none() {283 continue;284 }285 let name = name.unwrap();286287 #[derive(Trace)]288 struct ObjMemberBinding {289 cctx: ContextCreator,290 value: LocExpr,291 name: IStr,292 }293 impl Bindable for ObjMemberBinding {294 fn bind(295 &self,296 s: State,297 this: Option<ObjValue>,298 super_obj: Option<ObjValue>,299 ) -> Result<LazyVal> {300 Ok(LazyVal::new_resolved(evaluate_named(301 s.clone(),302 self.cctx.create(s, this, super_obj)?,303 &self.value,304 self.name.clone(),305 )?))306 }307 }308 builder309 .member(name.clone())310 .with_add(*plus)311 .with_visibility(*visibility)312 .with_location(value.1.clone())313 .bindable(314 s.clone(),315 TraceBox(Box::new(ObjMemberBinding {316 cctx: cctx.clone(),317 value: value.clone(),318 name,319 })),320 )?;321 }322 Member::Field(FieldMember {323 name,324 params: Some(params),325 value,326 ..327 }) => {328 let name = evaluate_field_name(s.clone(), ctx.clone(), name)?;329 if name.is_none() {330 continue;331 }332 let name = name.unwrap();333 #[derive(Trace)]334 struct ObjMemberBinding {335 cctx: ContextCreator,336 value: LocExpr,337 params: ParamsDesc,338 name: IStr,339 }340 impl Bindable for ObjMemberBinding {341 fn bind(342 &self,343 s: State,344 this: Option<ObjValue>,345 super_obj: Option<ObjValue>,346 ) -> Result<LazyVal> {347 Ok(LazyVal::new_resolved(evaluate_method(348 self.cctx.create(s, this, super_obj)?,349 self.name.clone(),350 self.params.clone(),351 self.value.clone(),352 )))353 }354 }355 builder356 .member(name.clone())357 .hide()358 .with_location(value.1.clone())359 .bindable(360 s.clone(),361 TraceBox(Box::new(ObjMemberBinding {362 cctx: cctx.clone(),363 value: value.clone(),364 params: params.clone(),365 name,366 })),367 )?;368 }369 Member::BindStmt(_) => {}370 Member::AssertStmt(stmt) => {371 #[derive(Trace)]372 struct ObjectAssert {373 cctx: ContextCreator,374 assert: AssertStmt,375 }376 impl ObjectAssertion for ObjectAssert {377 fn run(378 &self,379 s: State,380 this: Option<ObjValue>,381 super_obj: Option<ObjValue>,382 ) -> Result<()> {383 let ctx = self.cctx.create(s.clone(), this, super_obj)?;384 evaluate_assert(s, ctx, &self.assert)385 }386 }387 builder.assert(TraceBox(Box::new(ObjectAssert {388 cctx: cctx.clone(),389 assert: stmt.clone(),390 })));391 }392 }393 }394 let this = builder.build();395 future_this.fill(this.clone());396 Ok(this)397}398399pub fn evaluate_object(s: State, ctx: Context, object: &ObjBody) -> Result<ObjValue> {400 Ok(match object {401 ObjBody::MemberList(members) => evaluate_member_list_object(s, ctx, members)?,402 ObjBody::ObjComp(obj) => {403 let future_this = FutureWrapper::new();404 let mut builder = ObjValueBuilder::new();405 evaluate_comp(s.clone(), ctx, &obj.compspecs, &mut |ctx| {406 let new_bindings = FutureWrapper::new();407 let cctx = ContextCreator(ctx.clone(), new_bindings.clone());408 let mut bindings: GcHashMap<IStr, LazyBinding> =409 GcHashMap::with_capacity(obj.pre_locals.len() + obj.post_locals.len());410 for (n, b) in obj411 .pre_locals412 .iter()413 .chain(obj.post_locals.iter())414 .map(|b| evaluate_binding(b, cctx.clone()))415 {416 bindings.insert(n, b);417 }418 new_bindings.fill(bindings.clone());419 let ctx = ctx.extend_unbound(s.clone(), bindings, None, None, None)?;420 let key = evaluate(s.clone(), ctx.clone(), &obj.key)?;421422 match key {423 Val::Null => {}424 Val::Str(n) => {425 #[derive(Trace)]426 struct ObjCompBinding {427 ctx: Context,428 value: LocExpr,429 }430 impl Bindable for ObjCompBinding {431 fn bind(432 &self,433 s: State,434 this: Option<ObjValue>,435 _super_obj: Option<ObjValue>,436 ) -> Result<LazyVal> {437 Ok(LazyVal::new_resolved(evaluate(438 s,439 self.ctx.clone().extend(GcHashMap::new(), None, this, None),440 &self.value,441 )?))442 }443 }444 builder445 .member(n)446 .with_location(obj.value.1.clone())447 .with_add(obj.plus)448 .bindable(449 s.clone(),450 TraceBox(Box::new(ObjCompBinding {451 ctx,452 value: obj.value.clone(),453 })),454 )?;455 }456 v => throw!(FieldMustBeStringGot(v.value_type())),457 }458459 Ok(())460 })?;461462 let this = builder.build();463 future_this.fill(this.clone());464 this465 }466 })467}468469pub fn evaluate_apply(470 s: State,471 ctx: Context,472 value: &LocExpr,473 args: &ArgsDesc,474 loc: CallLocation,475 tailstrict: bool,476) -> Result<Val> {477 let value = evaluate(s.clone(), ctx.clone(), value)?;478 Ok(match value {479 Val::Func(f) => {480 let body = || f.evaluate(s.clone(), ctx, loc, args, tailstrict);481 if tailstrict {482 body()?483 } else {484 s.push(loc, || format!("function <{}> call", f.name()), body)?485 }486 }487 v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),488 })489}490491pub fn evaluate_assert(s: State, ctx: Context, assertion: &AssertStmt) -> Result<()> {492 let value = &assertion.0;493 let msg = &assertion.1;494 let assertion_result = s.push(495 CallLocation::new(&value.1),496 || "assertion condition".to_owned(),497 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),498 )?;499 if !assertion_result {500 s.push(501 CallLocation::new(&value.1),502 || "assertion failure".to_owned(),503 || {504 if let Some(msg) = msg {505 throw!(AssertionFailed(506 evaluate(s.clone(), ctx, msg)?.to_string(s.clone())?507 ));508 } else {509 throw!(AssertionFailed(Val::Null.to_string(s.clone())?));510 }511 },512 )?513 }514 Ok(())515}516517pub fn evaluate_named(s: State, ctx: Context, lexpr: &LocExpr, name: IStr) -> Result<Val> {518 use Expr::*;519 let LocExpr(expr, _loc) = lexpr;520 Ok(match &**expr {521 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),522 _ => evaluate(s, ctx, lexpr)?,523 })524}525526pub fn evaluate(s: State, ctx: Context, expr: &LocExpr) -> Result<Val> {527 use Expr::*;528 let LocExpr(expr, loc) = expr;529 530 Ok(match &**expr {531 Literal(LiteralType::This) => {532 Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)533 }534 Literal(LiteralType::Super) => Val::Obj(535 ctx.super_obj()536 .clone()537 .ok_or(NoSuperFound)?538 .with_this(ctx.this().clone().unwrap()),539 ),540 Literal(LiteralType::Dollar) => {541 Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)542 }543 Literal(LiteralType::True) => Val::Bool(true),544 Literal(LiteralType::False) => Val::Bool(false),545 Literal(LiteralType::Null) => Val::Null,546 Parened(e) => evaluate(s, ctx, e)?,547 Str(v) => Val::Str(v.clone()),548 Num(v) => Val::new_checked_num(*v)?,549 BinaryOp(v1, o, v2) => evaluate_binary_op_special(s, ctx, v1, *o, v2)?,550 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,551 Var(name) => s.push(552 CallLocation::new(loc),553 || format!("variable <{}> access", name),554 || ctx.binding(name.clone())?.evaluate(s.clone()),555 )?,556 Index(value, index) => {557 match (558 evaluate(s.clone(), ctx.clone(), value)?,559 evaluate(s.clone(), ctx, index)?,560 ) {561 (Val::Obj(v), Val::Str(key)) => s.push(562 CallLocation::new(loc),563 || format!("field <{}> access", key),564 || {565 if let Some(v) = v.get(s.clone(), key.clone())? {566 Ok(v)567 } else {568 throw!(NoSuchField(key.clone()))569 }570 },571 )?,572 (Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(573 ValType::Obj,574 ValType::Str,575 n.value_type(),576 )),577578 (Val::Arr(v), Val::Num(n)) => {579 if n.fract() > f64::EPSILON {580 throw!(FractionalIndex)581 }582 v.get(s, n as usize)?583 .ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?584 }585 (Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),586 (Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(587 ValType::Arr,588 ValType::Num,589 n.value_type(),590 )),591592 (Val::Str(s), Val::Num(n)) => Val::Str({593 let v: IStr = s594 .chars()595 .skip(n as usize)596 .take(1)597 .collect::<String>()598 .into();599 if v.is_empty() {600 let size = s.chars().count();601 throw!(StringBoundsError(n as usize, size))602 }603 v604 }),605 (Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(606 ValType::Str,607 ValType::Num,608 n.value_type(),609 )),610611 (v, _) => throw!(CantIndexInto(v.value_type())),612 }613 }614 LocalExpr(bindings, returned) => {615 let mut new_bindings: GcHashMap<IStr, LazyVal> =616 GcHashMap::with_capacity(bindings.len());617 let fctx = Context::new_future();618 for b in bindings {619 new_bindings.insert(b.name.clone(), evaluate_binding_in_future(b, fctx.clone()));620 }621 let ctx = ctx.extend_bound(new_bindings).into_future(fctx);622 evaluate(s, ctx, &returned.clone())?623 }624 Arr(items) => {625 let mut out = Vec::with_capacity(items.len());626 for item in items {627 628 #[derive(Trace)]629 struct ArrayElement {630 ctx: Context,631 item: LocExpr,632 }633 impl LazyValValue for ArrayElement {634 fn get(self: Box<Self>, s: State) -> Result<Val> {635 evaluate(s, self.ctx, &self.item)636 }637 }638 out.push(LazyVal::new(TraceBox(Box::new(ArrayElement {639 ctx: ctx.clone(),640 item: item.clone(),641 }))));642 }643 Val::Arr(out.into())644 }645 ArrComp(expr, comp_specs) => {646 let mut out = Vec::new();647 evaluate_comp(s.clone(), ctx, comp_specs, &mut |ctx| {648 out.push(evaluate(s.clone(), ctx, expr)?);649 Ok(())650 })?;651 Val::Arr(ArrValue::Eager(Cc::new(out)))652 }653 Obj(body) => Val::Obj(evaluate_object(s, ctx, body)?),654 ObjExtend(a, b) => evaluate_add_op(655 s.clone(),656 &evaluate(s.clone(), ctx.clone(), a)?,657 &Val::Obj(evaluate_object(s, ctx, b)?),658 )?,659 Apply(value, args, tailstrict) => {660 evaluate_apply(s, ctx, value, args, CallLocation::new(loc), *tailstrict)?661 }662 Function(params, body) => {663 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())664 }665 Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(666 BUILTINS667 .with(|b| b.get(name).copied())668 .ok_or_else(|| IntrinsicNotFound(name.clone()))?,669 )),670 AssertExpr(assert, returned) => {671 evaluate_assert(s.clone(), ctx.clone(), assert)?;672 evaluate(s, ctx, returned)?673 }674 ErrorStmt(e) => s.push(675 CallLocation::new(loc),676 || "error statement".to_owned(),677 || {678 throw!(RuntimeError(679 evaluate(s.clone(), ctx, e)?.to_string(s.clone())?,680 ))681 },682 )?,683 IfElse {684 cond,685 cond_then,686 cond_else,687 } => {688 if s.push(689 CallLocation::new(loc),690 || "if condition".to_owned(),691 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), &cond.0)?, s.clone()),692 )? {693 evaluate(s, ctx, cond_then)?694 } else {695 match cond_else {696 Some(v) => evaluate(s, ctx, v)?,697 None => Val::Null,698 }699 }700 }701 Slice(value, desc) => {702 let indexable = evaluate(s.clone(), ctx.clone(), value)?;703 let loc = CallLocation::new(loc);704705 fn parse_idx<T: Typed>(706 loc: CallLocation,707 s: State,708 ctx: &Context,709 expr: &Option<LocExpr>,710 desc: &'static str,711 ) -> Result<Option<T>> {712 if let Some(value) = expr {713 Ok(Some(s.push(714 loc,715 || format!("slice {}", desc),716 || T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),717 )?))718 } else {719 Ok(None)720 }721 }722723 let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?;724 let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?;725 let step = parse_idx(loc, s, &ctx, &desc.step, "step")?;726727 std_slice(indexable.into_indexable()?, start, end, step)?728 }729 Import(path) => {730 let tmp = loc.clone().0;731 let mut import_location = tmp.to_path_buf();732 import_location.pop();733 s.push(734 CallLocation::new(loc),735 || format!("import {:?}", path),736 || s.import_file(&import_location, path),737 )?738 }739 ImportStr(path) => {740 let tmp = loc.clone().0;741 let mut import_location = tmp.to_path_buf();742 import_location.pop();743 Val::Str(s.import_file_str(&import_location, path)?)744 }745 ImportBin(path) => {746 let tmp = loc.clone().0;747 let mut import_location = tmp.to_path_buf();748 import_location.pop();749 let bytes = s.import_file_bin(&import_location, path)?;750 Val::Arr(ArrValue::Bytes(bytes))751 }752 })753}