1use std::fmt::Debug;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;56use crate::{7 error::Error::*, gc::GcHashMap, map::LayeredHashMap, ObjValue, Pending, Result, Thunk, Val,8};910#[derive(Trace)]11struct ContextInternals {12 dollar: Option<ObjValue>,13 sup: Option<ObjValue>,14 this: Option<ObjValue>,15 bindings: LayeredHashMap,16}17impl Debug for ContextInternals {18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {19 f.debug_struct("Context").finish()20 }21}2223#[derive(Debug, Clone, Trace)]24pub struct Context(Cc<ContextInternals>);25impl Context {26 pub fn new_future() -> Pending<Self> {27 Pending::new()28 }2930 pub fn dollar(&self) -> &Option<ObjValue> {31 &self.0.dollar32 }3334 pub fn this(&self) -> &Option<ObjValue> {35 &self.0.this36 }3738 pub fn super_obj(&self) -> &Option<ObjValue> {39 &self.0.sup40 }4142 pub fn new() -> Self {43 Self(Cc::new(ContextInternals {44 dollar: None,45 this: None,46 sup: None,47 bindings: LayeredHashMap::default(),48 }))49 }5051 #[cfg(not(feature = "friendly-errors"))]52 pub fn binding(&self, name: IStr) -> Result<Thunk<Val>> {53 Ok(self54 .055 .bindings56 .get(&name)57 .cloned()58 .ok_or(VariableIsNotDefined(name, vec![]))?)59 }6061 #[cfg(feature = "friendly-errors")]62 pub fn binding(&self, name: IStr) -> Result<Thunk<Val>> {63 use std::cmp::Ordering;6465 use crate::throw;6667 if let Some(val) = self.0.bindings.get(&name).cloned() {68 return Ok(val);69 }7071 let mut heap = Vec::new();72 self.0.bindings.clone().iter_keys(|k| {73 let conf = strsim::jaro_winkler(&k as &str, &name as &str);74 if conf < 0.8 {75 return;76 }77 heap.push((conf, k));78 });79 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));8081 throw!(VariableIsNotDefined(82 name,83 heap.into_iter().map(|(_, k)| k).collect()84 ))85 }86 pub fn contains_binding(&self, name: IStr) -> bool {87 self.0.bindings.contains_key(&name)88 }89 #[must_use]90 pub fn into_future(self, ctx: Pending<Self>) -> Self {91 {92 ctx.0.borrow_mut().replace(self);93 }94 ctx.unwrap()95 }9697 #[must_use]98 pub fn with_var(self, name: IStr, value: Val) -> Self {99 let mut new_bindings = GcHashMap::with_capacity(1);100 new_bindings.insert(name, Thunk::evaluated(value));101 self.extend(new_bindings, None, None, None)102 }103104 #[must_use]105 pub fn extend(106 self,107 new_bindings: GcHashMap<IStr, Thunk<Val>>,108 new_dollar: Option<ObjValue>,109 new_sup: Option<ObjValue>,110 new_this: Option<ObjValue>,111 ) -> Self {112 let ctx = &self.0;113 let dollar = new_dollar.or_else(|| ctx.dollar.clone());114 let this = new_this.or_else(|| ctx.this.clone());115 let sup = new_sup.or_else(|| ctx.sup.clone());116 let bindings = if new_bindings.is_empty() {117 ctx.bindings.clone()118 } else {119 ctx.bindings.clone().extend(new_bindings)120 };121 Self(Cc::new(ContextInternals {122 dollar,123 sup,124 this,125 bindings,126 }))127 }128}129130impl Default for Context {131 fn default() -> Self {132 Self::new()133 }134}135136impl PartialEq for Context {137 fn eq(&self, other: &Self) -> bool {138 Cc::ptr_eq(&self.0, &other.0)139 }140}141142pub struct ContextBuilder {143 bindings: GcHashMap<IStr, Thunk<Val>>,144 extend: Option<Context>,145}146147impl ContextBuilder {148 pub fn new() -> Self {149 Self::with_capacity(0)150 }151 pub fn with_capacity(capacity: usize) -> Self {152 Self {153 bindings: GcHashMap::with_capacity(capacity),154 extend: None,155 }156 }157 pub fn extend(parent: Context) -> Self {158 Self {159 bindings: GcHashMap::new(),160 extend: Some(parent),161 }162 }163 pub fn bind(&mut self, name: IStr, value: Thunk<Val>) -> &mut Self {164 self.bindings.insert(name, value);165 self166 }167 pub fn build(self) -> Context {168 if let Some(parent) = self.extend {169 parent.extend(self.bindings, None, None, None)170 } else {171 Context(Cc::new(ContextInternals {172 bindings: LayeredHashMap::new(self.bindings),173 dollar: None,174 sup: None,175 this: None,176 }))177 }178 }179}180181impl Default for ContextBuilder {182 fn default() -> Self {183 Self::new()184 }185}