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}141142#[derive(Default)]143pub struct ContextBuilder {144 bindings: GcHashMap<IStr, Thunk<Val>>,145}146impl ContextBuilder {147 pub fn new() -> Self {148 Self::default()149 }150 pub fn with_capacity(capacity: usize) -> Self {151 Self {152 bindings: GcHashMap::with_capacity(capacity),153 }154 }155 pub fn bind(&mut self, name: IStr, value: Thunk<Val>) -> &mut Self {156 self.bindings.insert(name, value);157 self158 }159 pub fn build(self) -> Context {160 Context(Cc::new(ContextInternals {161 bindings: LayeredHashMap::new(self.bindings),162 dollar: None,163 sup: None,164 this: None,165 }))166 }167}