1use std::fmt::Debug;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;56use crate::{7 error::ErrorKind::*, gc::GcHashMap, map::LayeredHashMap, ObjValue, Pending, Result, State,8 Thunk, Val,9};1011#[derive(Trace)]12struct ContextInternals {13 state: Option<State>,14 dollar: Option<ObjValue>,15 sup: Option<ObjValue>,16 this: Option<ObjValue>,17 bindings: LayeredHashMap,18}19impl Debug for ContextInternals {20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {21 f.debug_struct("Context").finish()22 }23}2425262728#[derive(Debug, Clone, Trace)]29pub struct Context(Cc<ContextInternals>);30impl Context {31 pub fn new_future() -> Pending<Self> {32 Pending::new()33 }3435 pub fn state(&self) -> &State {36 self.037 .state38 .as_ref()39 .expect("used state from dummy context")40 }4142 pub fn dollar(&self) -> Option<&ObjValue> {43 self.0.dollar.as_ref()44 }4546 pub fn this(&self) -> Option<&ObjValue> {47 self.0.this.as_ref()48 }4950 pub fn super_obj(&self) -> Option<&ObjValue> {51 self.0.sup.as_ref()52 }5354 #[cfg(not(feature = "friendly-errors"))]55 pub fn binding(&self, name: IStr) -> Result<Thunk<Val>> {56 Ok(self57 .058 .bindings59 .get(&name)60 .cloned()61 .ok_or(VariableIsNotDefined(name, vec![]))?)62 }6364 #[cfg(feature = "friendly-errors")]65 pub fn binding(&self, name: IStr) -> Result<Thunk<Val>> {66 use std::cmp::Ordering;6768 use crate::throw;6970 if let Some(val) = self.0.bindings.get(&name).cloned() {71 return Ok(val);72 }7374 let mut heap = Vec::new();75 self.0.bindings.clone().iter_keys(|k| {76 let conf = strsim::jaro_winkler(&k as &str, &name as &str);77 if conf < 0.8 {78 return;79 }80 heap.push((conf, k));81 });82 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));8384 throw!(VariableIsNotDefined(85 name,86 heap.into_iter().map(|(_, k)| k).collect()87 ))88 }89 pub fn contains_binding(&self, name: IStr) -> bool {90 self.0.bindings.contains_key(&name)91 }92 #[must_use]93 pub fn into_future(self, ctx: Pending<Self>) -> Self {94 {95 ctx.0.borrow_mut().replace(self);96 }97 ctx.unwrap()98 }99100 #[must_use]101 pub fn with_var(self, name: IStr, value: Val) -> Self {102 let mut new_bindings = GcHashMap::with_capacity(1);103 new_bindings.insert(name, Thunk::evaluated(value));104 self.extend(new_bindings, None, None, None)105 }106107 #[must_use]108 pub fn extend(109 self,110 new_bindings: GcHashMap<IStr, Thunk<Val>>,111 new_dollar: Option<ObjValue>,112 new_sup: Option<ObjValue>,113 new_this: Option<ObjValue>,114 ) -> Self {115 let ctx = &self.0;116 let dollar = new_dollar.or_else(|| ctx.dollar.clone());117 let this = new_this.or_else(|| ctx.this.clone());118 let sup = new_sup.or_else(|| ctx.sup.clone());119 let bindings = if new_bindings.is_empty() {120 ctx.bindings.clone()121 } else {122 ctx.bindings.clone().extend(new_bindings)123 };124 Self(Cc::new(ContextInternals {125 state: ctx.state.clone(),126 dollar,127 sup,128 this,129 bindings,130 }))131 }132}133134impl PartialEq for Context {135 fn eq(&self, other: &Self) -> bool {136 Cc::ptr_eq(&self.0, &other.0)137 }138}139140pub struct ContextBuilder {141 state: Option<State>,142 bindings: GcHashMap<IStr, Thunk<Val>>,143 extend: Option<Context>,144}145146impl ContextBuilder {147 148 149 pub fn dangerous_empty_state() -> Self {150 Self {151 state: None,152 bindings: GcHashMap::new(),153 extend: None,154 }155 }156 pub fn new(state: State) -> Self {157 Self::with_capacity(state, 0)158 }159 pub fn with_capacity(state: State, capacity: usize) -> Self {160 Self {161 state: Some(state),162 bindings: GcHashMap::with_capacity(capacity),163 extend: None,164 }165 }166 pub fn extend(parent: Context) -> Self {167 Self {168 state: parent.0.state.clone(),169 bindings: GcHashMap::new(),170 extend: Some(parent),171 }172 }173 174 175 pub fn bind(&mut self, name: IStr, value: Thunk<Val>) -> &mut Self {176 let old = self.bindings.insert(name, value);177 assert!(old.is_none(), "variable bound twice in single context call");178 self179 }180 pub fn build(self) -> Context {181 if let Some(parent) = self.extend {182 183 parent.extend(self.bindings, None, None, None)184 } else {185 Context(Cc::new(ContextInternals {186 state: self.state,187 bindings: LayeredHashMap::new(self.bindings),188 dollar: None,189 sup: None,190 this: None,191 }))192 }193 }194}