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 pub fn binding(&self, name: IStr) -> Result<Thunk<Val>> {55 use std::cmp::Ordering;5657 use crate::bail;5859 if let Some(val) = self.0.bindings.get(&name).cloned() {60 return Ok(val);61 }6263 let mut heap = Vec::new();64 self.0.bindings.clone().iter_keys(|k| {65 let conf = strsim::jaro_winkler(&k as &str, &name as &str);66 if conf < 0.8 {67 return;68 }69 heap.push((conf, k));70 });71 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));7273 bail!(VariableIsNotDefined(74 name,75 heap.into_iter().map(|(_, k)| k).collect()76 ))77 }78 pub fn contains_binding(&self, name: IStr) -> bool {79 self.0.bindings.contains_key(&name)80 }81 #[must_use]82 pub fn into_future(self, ctx: Pending<Self>) -> Self {83 {84 ctx.clone().fill(self);85 }86 ctx.unwrap()87 }8889 #[must_use]90 pub fn with_var(self, name: impl Into<IStr>, value: Val) -> Self {91 let mut new_bindings = GcHashMap::with_capacity(1);92 new_bindings.insert(name.into(), Thunk::evaluated(value));93 self.extend(new_bindings, None, None, None)94 }9596 #[must_use]97 pub fn extend(98 self,99 new_bindings: GcHashMap<IStr, Thunk<Val>>,100 new_dollar: Option<ObjValue>,101 new_sup: Option<ObjValue>,102 new_this: Option<ObjValue>,103 ) -> Self {104 let ctx = &self.0;105 let dollar = new_dollar.or_else(|| ctx.dollar.clone());106 let this = new_this.or_else(|| ctx.this.clone());107 let sup = new_sup.or_else(|| ctx.sup.clone());108 let bindings = if new_bindings.is_empty() {109 ctx.bindings.clone()110 } else {111 ctx.bindings.clone().extend(new_bindings)112 };113 Self(Cc::new(ContextInternals {114 state: ctx.state.clone(),115 dollar,116 sup,117 this,118 bindings,119 }))120 }121}122123impl PartialEq for Context {124 fn eq(&self, other: &Self) -> bool {125 Cc::ptr_eq(&self.0, &other.0)126 }127}128129pub struct ContextBuilder {130 state: Option<State>,131 bindings: GcHashMap<IStr, Thunk<Val>>,132 extend: Option<Context>,133}134135impl ContextBuilder {136 137 138 pub fn dangerous_empty_state() -> Self {139 Self {140 state: None,141 bindings: GcHashMap::new(),142 extend: None,143 }144 }145 pub fn new(state: State) -> Self {146 Self::with_capacity(state, 0)147 }148 pub fn with_capacity(state: State, capacity: usize) -> Self {149 Self {150 state: Some(state),151 bindings: GcHashMap::with_capacity(capacity),152 extend: None,153 }154 }155 pub fn extend(parent: Context) -> Self {156 Self {157 state: parent.0.state.clone(),158 bindings: GcHashMap::new(),159 extend: Some(parent),160 }161 }162 163 164 pub fn bind(&mut self, name: impl Into<IStr>, value: Thunk<Val>) -> &mut Self {165 let old = self.bindings.insert(name.into(), value);166 assert!(old.is_none(), "variable bound twice in single context call");167 self168 }169 pub fn build(self) -> Context {170 if let Some(parent) = self.extend {171 172 parent.extend(self.bindings, None, None, None)173 } else {174 Context(Cc::new(ContextInternals {175 state: self.state,176 bindings: LayeredHashMap::new(self.bindings),177 dollar: None,178 sup: None,179 this: None,180 }))181 }182 }183}