1use std::{cell::RefCell, fmt::Debug, rc::Rc};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::{IBytes, IStr};5use jrsonnet_types::ValType;67use crate::{8 error::{Error::*, LocError},9 function::FuncVal,10 gc::{GcHashMap, TraceBox},11 stdlib::manifest::{12 manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions,13 },14 throw,15 typed::BoundedUsize,16 ObjValue, Result, State, Unbound, WeakObjValue,17};1819pub trait ThunkValue: Trace {20 type Output;21 fn get(self: Box<Self>, s: State) -> Result<Self::Output>;22}2324#[derive(Trace)]25enum ThunkInner<T: Trace> {26 Computed(T),27 Errored(LocError),28 Waiting(TraceBox<dyn ThunkValue<Output = T>>),29 Pending,30}3132#[allow(clippy::module_name_repetitions)]33#[derive(Clone, Trace)]34pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);3536impl<T> Thunk<T>37where38 T: Clone + Trace,39{40 pub fn new(f: TraceBox<dyn ThunkValue<Output = T>>) -> Self {41 Self(Cc::new(RefCell::new(ThunkInner::Waiting(f))))42 }43 pub fn evaluated(val: T) -> Self {44 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))45 }46 pub fn force(&self, s: State) -> Result<()> {47 self.evaluate(s)?;48 Ok(())49 }50 pub fn evaluate(&self, s: State) -> Result<T> {51 match &*self.0.borrow() {52 ThunkInner::Computed(v) => return Ok(v.clone()),53 ThunkInner::Errored(e) => return Err(e.clone()),54 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),55 ThunkInner::Waiting(..) => (),56 };57 let value = if let ThunkInner::Waiting(value) =58 std::mem::replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)59 {60 value61 } else {62 unreachable!()63 };64 let new_value = match value.0.get(s) {65 Ok(v) => v,66 Err(e) => {67 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());68 return Err(e);69 }70 };71 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());72 Ok(new_value)73 }74}7576type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);7778#[derive(Trace, Clone)]79pub struct CachedUnbound<I, T>80where81 I: Unbound<Bound = T>,82 T: Trace,83{84 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,85 value: I,86}87impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {88 pub fn new(value: I) -> Self {89 Self {90 cache: Cc::new(RefCell::new(GcHashMap::new())),91 value,92 }93 }94}95impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {96 type Bound = T;97 fn bind(&self, s: State, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {98 let cache_key = (99 sup.as_ref().map(|s| s.clone().downgrade()),100 this.as_ref().map(|t| t.clone().downgrade()),101 );102 {103 if let Some(t) = self.cache.borrow().get(&cache_key) {104 return Ok(t.clone());105 }106 }107 let bound = self.value.bind(s, sup, this)?;108109 {110 let mut cache = self.cache.borrow_mut();111 cache.insert(cache_key, bound.clone());112 }113114 Ok(bound)115 }116}117118impl<T: Debug + Trace> Debug for Thunk<T> {119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {120 write!(f, "Lazy")121 }122}123impl<T: Trace> PartialEq for Thunk<T> {124 fn eq(&self, other: &Self) -> bool {125 Cc::ptr_eq(&self.0, &other.0)126 }127}128129#[derive(Clone)]130pub enum ManifestFormat {131 YamlStream(Box<ManifestFormat>),132 Yaml {133 padding: usize,134 #[cfg(feature = "exp-preserve-order")]135 preserve_order: bool,136 },137 Json {138 padding: usize,139 #[cfg(feature = "exp-preserve-order")]140 preserve_order: bool,141 },142 ToString,143 String,144}145impl ManifestFormat {146 #[cfg(feature = "exp-preserve-order")]147 fn preserve_order(&self) -> bool {148 match self {149 ManifestFormat::YamlStream(s) => s.preserve_order(),150 ManifestFormat::Yaml { preserve_order, .. } => *preserve_order,151 ManifestFormat::Json { preserve_order, .. } => *preserve_order,152 ManifestFormat::ToString => false,153 ManifestFormat::String => false,154 }155 }156}157158#[derive(Debug, Clone, Trace)]159pub struct Slice {160 pub(crate) inner: ArrValue,161 pub(crate) from: u32,162 pub(crate) to: u32,163 pub(crate) step: u32,164}165impl Slice {166 const fn from(&self) -> usize {167 self.from as usize168 }169 const fn to(&self) -> usize {170 self.to as usize171 }172 const fn step(&self) -> usize {173 self.step as usize174 }175 const fn len(&self) -> usize {176 177 let diff = self.to() - self.from();178 let rem = diff % self.step();179 let div = diff / self.step();180181 if rem == 0 {182 div183 } else {184 div + 1185 }186 }187}188189#[derive(Debug, Clone, Trace)]190191#[trace(tracking(force))]192pub enum ArrValue {193 Bytes(#[trace(skip)] IBytes),194 Lazy(Cc<Vec<Thunk<Val>>>),195 Eager(Cc<Vec<Val>>),196 Extended(Box<(Self, Self)>),197 Range(i32, i32),198 Slice(Box<Slice>),199 Reversed(Box<Self>),200}201202#[cfg(target_pointer_width = "64")]203static_assertions::assert_eq_size!(ArrValue, [u8; 16]);204205impl ArrValue {206 pub fn new_eager() -> Self {207 Self::Eager(Cc::new(Vec::new()))208 }209 pub fn empty() -> Self {210 Self::new_range(0, 0)211 }212213 214 215 #[inline]216 pub fn new_range(a: i32, b: i32) -> Self {217 assert!(a <= b);218 Self::Range(a, b)219 }220221 222 223 #[must_use]224 pub fn slice(self, from: Option<usize>, to: Option<usize>, step: Option<usize>) -> Self {225 let len = self.len();226 let from = from.unwrap_or(0);227 let to = to.unwrap_or(len).min(len);228 let step = step.unwrap_or(1);229 assert!(from < to);230 assert!(step > 0);231232 Self::Slice(Box::new(Slice {233 inner: self,234 from: from as u32,235 to: to as u32,236 step: step as u32,237 }))238 }239240 pub fn len(&self) -> usize {241 match self {242 Self::Bytes(i) => i.len(),243 Self::Lazy(l) => l.len(),244 Self::Eager(e) => e.len(),245 Self::Extended(v) => v.0.len() + v.1.len(),246 Self::Range(a, b) => a.abs_diff(*b) as usize + 1,247 Self::Reversed(i) => i.len(),248 Self::Slice(s) => s.len(),249 }250 }251252 pub fn is_empty(&self) -> bool {253 self.len() == 0254 }255256 pub fn get(&self, s: State, index: usize) -> Result<Option<Val>> {257 match self {258 Self::Bytes(i) => i259 .get(index)260 .map_or(Ok(None), |v| Ok(Some(Val::Num(f64::from(*v))))),261 Self::Lazy(vec) => {262 if let Some(v) = vec.get(index) {263 Ok(Some(v.evaluate(s)?))264 } else {265 Ok(None)266 }267 }268 Self::Eager(vec) => Ok(vec.get(index).cloned()),269 Self::Extended(v) => {270 let a_len = v.0.len();271 if a_len > index {272 v.0.get(s, index)273 } else {274 v.1.get(s, index - a_len)275 }276 }277 Self::Range(a, _) => {278 if index >= self.len() {279 return Ok(None);280 }281 Ok(Some(Val::Num(((*a as isize) + index as isize) as f64)))282 }283 Self::Reversed(v) => {284 let len = v.len();285 if index >= len {286 return Ok(None);287 }288 v.get(s, len - index - 1)289 }290 Self::Slice(v) => {291 let index = v.from() + index * v.step();292 if index >= v.to() {293 return Ok(None);294 }295 v.inner.get(s, index as usize)296 }297 }298 }299300 pub fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {301 match self {302 Self::Bytes(i) => i303 .get(index)304 .map(|b| Thunk::evaluated(Val::Num(f64::from(*b)))),305 Self::Lazy(vec) => vec.get(index).cloned(),306 Self::Eager(vec) => vec.get(index).cloned().map(Thunk::evaluated),307 Self::Extended(v) => {308 let a_len = v.0.len();309 if a_len > index {310 v.0.get_lazy(index)311 } else {312 v.1.get_lazy(index - a_len)313 }314 }315 Self::Range(a, _) => {316 if index >= self.len() {317 return None;318 }319 Some(Thunk::evaluated(Val::Num(320 ((*a as isize) + index as isize) as f64,321 )))322 }323 Self::Reversed(v) => {324 let len = v.len();325 if index >= len {326 return None;327 }328 v.get_lazy(len - index - 1)329 }330 Self::Slice(s) => {331 let index = s.from() + index * s.step();332 if index >= s.to() {333 return None;334 }335 s.inner.get_lazy(index as usize)336 }337 }338 }339340 pub fn evaluated(&self, s: State) -> Result<Cc<Vec<Val>>> {341 Ok(match self {342 Self::Bytes(i) => {343 let mut out = Vec::with_capacity(i.len());344 for v in i.iter() {345 out.push(Val::Num(f64::from(*v)));346 }347 Cc::new(out)348 }349 Self::Lazy(vec) => {350 let mut out = Vec::with_capacity(vec.len());351 for item in vec.iter() {352 out.push(item.evaluate(s.clone())?);353 }354 Cc::new(out)355 }356 Self::Eager(vec) => vec.clone(),357 Self::Extended(_v) => {358 let mut out = Vec::with_capacity(self.len());359 for item in self.iter(s) {360 out.push(item?);361 }362 Cc::new(out)363 }364 Self::Range(a, b) => {365 let mut out = Vec::with_capacity(self.len());366 for i in *a..*b {367 out.push(Val::Num(f64::from(i)));368 }369 Cc::new(out)370 }371 Self::Reversed(r) => {372 let mut r = r.evaluated(s)?;373 Cc::update_with(&mut r, |v| v.reverse());374 r375 }376 Self::Slice(v) => {377 let mut out = Vec::with_capacity(v.inner.len());378 for v in v379 .inner380 .iter_lazy()381 .skip(v.from())382 .take(v.to() - v.from())383 .step_by(v.step())384 {385 out.push(v.evaluate(s.clone())?);386 }387 Cc::new(out)388 }389 })390 }391392 pub fn iter(&self, s: State) -> impl DoubleEndedIterator<Item = Result<Val>> + '_ {393 (0..self.len()).map(move |idx| match self {394 Self::Bytes(b) => Ok(Val::Num(f64::from(b[idx]))),395 Self::Lazy(l) => l[idx].evaluate(s.clone()),396 Self::Eager(e) => Ok(e[idx].clone()),397 Self::Extended(..) | Self::Range(..) | Self::Reversed(..) | Self::Slice(..) => {398 self.get(s.clone(), idx).map(|e| e.expect("idx < len"))399 }400 })401 }402403 pub fn iter_lazy(&self) -> impl DoubleEndedIterator<Item = Thunk<Val>> + '_ {404 (0..self.len()).map(move |idx| match self {405 Self::Bytes(b) => Thunk::evaluated(Val::Num(f64::from(b[idx]))),406 Self::Lazy(l) => l[idx].clone(),407 Self::Eager(e) => Thunk::evaluated(e[idx].clone()),408 Self::Slice(..) | Self::Extended(..) | Self::Range(..) | Self::Reversed(..) => {409 self.get_lazy(idx).expect("idx < len")410 }411 })412 }413414 #[must_use]415 pub fn reversed(self) -> Self {416 Self::Reversed(Box::new(self))417 }418419 pub fn map(self, s: State, mapper: impl Fn(Val) -> Result<Val>) -> Result<Self> {420 let mut out = Vec::with_capacity(self.len());421422 for value in self.iter(s) {423 out.push(mapper(value?)?);424 }425426 Ok(Self::Eager(Cc::new(out)))427 }428429 pub fn filter(self, s: State, filter: impl Fn(&Val) -> Result<bool>) -> Result<Self> {430 let mut out = Vec::with_capacity(self.len());431432 for value in self.iter(s) {433 let value = value?;434 if filter(&value)? {435 out.push(value);436 }437 }438439 Ok(Self::Eager(Cc::new(out)))440 }441442 pub fn ptr_eq(a: &Self, b: &Self) -> bool {443 match (a, b) {444 (Self::Lazy(a), Self::Lazy(b)) => Cc::ptr_eq(a, b),445 (Self::Eager(a), Self::Eager(b)) => Cc::ptr_eq(a, b),446 _ => false,447 }448 }449}450451impl From<Vec<Thunk<Val>>> for ArrValue {452 fn from(v: Vec<Thunk<Val>>) -> Self {453 Self::Lazy(Cc::new(v))454 }455}456457impl From<Vec<Val>> for ArrValue {458 fn from(v: Vec<Val>) -> Self {459 Self::Eager(Cc::new(v))460 }461}462463#[allow(clippy::module_name_repetitions)]464pub enum IndexableVal {465 Str(IStr),466 Arr(ArrValue),467}468impl IndexableVal {469 pub fn slice(470 self,471 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,472 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,473 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,474 ) -> Result<Self> {475 match &self {476 IndexableVal::Str(s) => {477 let index = index.as_deref().copied().unwrap_or(0);478 let end = end.as_deref().copied().unwrap_or(usize::MAX);479 let step = step.as_deref().copied().unwrap_or(1);480481 if index >= end {482 return Ok(Self::Str("".into()));483 }484485 Ok(Self::Str(486 (s.chars()487 .skip(index)488 .take(end - index)489 .step_by(step)490 .collect::<String>())491 .into(),492 ))493 }494 IndexableVal::Arr(arr) => {495 let index = index.as_deref().copied().unwrap_or(0);496 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());497 let step = step.as_deref().copied().unwrap_or(1);498499 if index >= end {500 return Ok(Self::Arr(ArrValue::new_eager()));501 }502503 Ok(Self::Arr(ArrValue::Slice(Box::new(Slice {504 inner: arr.clone(),505 from: index as u32,506 to: end as u32,507 step: step as u32,508 }))))509 }510 }511 }512}513514#[derive(Debug, Clone, Trace)]515pub enum Val {516 Bool(bool),517 Null,518 Str(IStr),519 Num(f64),520 Arr(ArrValue),521 Obj(ObjValue),522 Func(FuncVal),523}524525impl From<IndexableVal> for Val {526 fn from(v: IndexableVal) -> Self {527 match v {528 IndexableVal::Str(s) => Self::Str(s),529 IndexableVal::Arr(a) => Self::Arr(a),530 }531 }532}533534#[cfg(target_pointer_width = "64")]535static_assertions::assert_eq_size!(Val, [u8; 32]);536537impl Val {538 pub const fn as_bool(&self) -> Option<bool> {539 match self {540 Self::Bool(v) => Some(*v),541 _ => None,542 }543 }544 pub const fn as_null(&self) -> Option<()> {545 match self {546 Self::Null => Some(()),547 _ => None,548 }549 }550 pub fn as_str(&self) -> Option<IStr> {551 match self {552 Self::Str(s) => Some(s.clone()),553 _ => None,554 }555 }556 pub const fn as_num(&self) -> Option<f64> {557 match self {558 Self::Num(n) => Some(*n),559 _ => None,560 }561 }562 pub fn as_arr(&self) -> Option<ArrValue> {563 match self {564 Self::Arr(a) => Some(a.clone()),565 _ => None,566 }567 }568 pub fn as_obj(&self) -> Option<ObjValue> {569 match self {570 Self::Obj(o) => Some(o.clone()),571 _ => None,572 }573 }574 pub fn as_func(&self) -> Option<FuncVal> {575 match self {576 Self::Func(f) => Some(f.clone()),577 _ => None,578 }579 }580581 582 583 pub fn new_checked_num(num: f64) -> Result<Self> {584 if num.is_finite() {585 Ok(Self::Num(num))586 } else {587 throw!(RuntimeError("overflow".into()))588 }589 }590591 pub const fn value_type(&self) -> ValType {592 match self {593 Self::Str(..) => ValType::Str,594 Self::Num(..) => ValType::Num,595 Self::Arr(..) => ValType::Arr,596 Self::Obj(..) => ValType::Obj,597 Self::Bool(_) => ValType::Bool,598 Self::Null => ValType::Null,599 Self::Func(..) => ValType::Func,600 }601 }602603 pub fn to_string(&self, s: State) -> Result<IStr> {604 Ok(match self {605 Self::Bool(true) => "true".into(),606 Self::Bool(false) => "false".into(),607 Self::Null => "null".into(),608 Self::Str(s) => s.clone(),609 v => manifest_json_ex(610 s,611 v,612 &ManifestJsonOptions {613 padding: "",614 mtype: ManifestType::ToString,615 newline: "\n",616 key_val_sep: ": ",617 #[cfg(feature = "exp-preserve-order")]618 preserve_order: false,619 },620 )?621 .into(),622 })623 }624625 626 pub fn manifest_multi(&self, s: State, ty: &ManifestFormat) -> Result<Vec<(IStr, IStr)>> {627 let obj = match self {628 Self::Obj(obj) => obj,629 _ => throw!(MultiManifestOutputIsNotAObject),630 };631 let keys = obj.fields(632 #[cfg(feature = "exp-preserve-order")]633 ty.preserve_order(),634 );635 let mut out = Vec::with_capacity(keys.len());636 for key in keys {637 let value = obj638 .get(s.clone(), key.clone())?639 .expect("item in object")640 .manifest(s.clone(), ty)?;641 out.push((key, value));642 }643 Ok(out)644 }645646 647 pub fn manifest_stream(&self, s: State, ty: &ManifestFormat) -> Result<Vec<IStr>> {648 let arr = match self {649 Self::Arr(a) => a,650 _ => throw!(StreamManifestOutputIsNotAArray),651 };652 let mut out = Vec::with_capacity(arr.len());653 for i in arr.iter(s.clone()) {654 out.push(i?.manifest(s.clone(), ty)?);655 }656 Ok(out)657 }658659 pub fn manifest(&self, s: State, ty: &ManifestFormat) -> Result<IStr> {660 Ok(match ty {661 ManifestFormat::YamlStream(format) => {662 let arr = match self {663 Self::Arr(a) => a,664 _ => throw!(StreamManifestOutputIsNotAArray),665 };666 let mut out = String::new();667668 match format as &ManifestFormat {669 ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),670 ManifestFormat::String => throw!(StreamManifestCannotNestString),671 _ => {}672 };673674 if !arr.is_empty() {675 for v in arr.iter(s.clone()) {676 out.push_str("---\n");677 out.push_str(&v?.manifest(s.clone(), format)?);678 out.push('\n');679 }680 out.push_str("...");681 }682683 out.into()684 }685 ManifestFormat::Yaml {686 padding,687 #[cfg(feature = "exp-preserve-order")]688 preserve_order,689 } => self.to_yaml(690 s,691 *padding,692 #[cfg(feature = "exp-preserve-order")]693 *preserve_order,694 )?,695 ManifestFormat::Json {696 padding,697 #[cfg(feature = "exp-preserve-order")]698 preserve_order,699 } => self.to_json(700 s,701 *padding,702 #[cfg(feature = "exp-preserve-order")]703 *preserve_order,704 )?,705 ManifestFormat::ToString => self.to_string(s)?,706 ManifestFormat::String => match self {707 Self::Str(s) => s.clone(),708 _ => throw!(StringManifestOutputIsNotAString),709 },710 })711 }712713 714 pub fn to_json(715 &self,716 s: State,717 padding: usize,718 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,719 ) -> Result<IStr> {720 manifest_json_ex(721 s,722 self,723 &ManifestJsonOptions {724 padding: &" ".repeat(padding),725 mtype: if padding == 0 {726 ManifestType::Minify727 } else {728 ManifestType::Manifest729 },730 newline: "\n",731 key_val_sep: ": ",732 #[cfg(feature = "exp-preserve-order")]733 preserve_order,734 },735 )736 .map(Into::into)737 }738739 740 pub fn to_std_json(741 &self,742 s: State,743 padding: usize,744 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,745 ) -> Result<Rc<str>> {746 manifest_json_ex(747 s,748 self,749 &ManifestJsonOptions {750 padding: &" ".repeat(padding),751 mtype: ManifestType::Std,752 newline: "\n",753 key_val_sep: ": ",754 #[cfg(feature = "exp-preserve-order")]755 preserve_order,756 },757 )758 .map(Into::into)759 }760761 pub fn to_yaml(762 &self,763 s: State,764 padding: usize,765 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,766 ) -> Result<IStr> {767 let padding = &" ".repeat(padding);768 manifest_yaml_ex(769 s,770 self,771 &ManifestYamlOptions {772 padding,773 arr_element_padding: padding,774 quote_keys: false,775 #[cfg(feature = "exp-preserve-order")]776 preserve_order,777 },778 )779 .map(Into::into)780 }781 pub fn into_indexable(self) -> Result<IndexableVal> {782 Ok(match self {783 Val::Str(s) => IndexableVal::Str(s),784 Val::Arr(arr) => IndexableVal::Arr(arr),785 _ => throw!(ValueIsNotIndexable(self.value_type())),786 })787 }788}789790const fn is_function_like(val: &Val) -> bool {791 matches!(val, Val::Func(_))792}793794795pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {796 Ok(match (val_a, val_b) {797 (Val::Bool(a), Val::Bool(b)) => a == b,798 (Val::Null, Val::Null) => true,799 (Val::Str(a), Val::Str(b)) => a == b,800 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,801 (Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(802 "primitiveEquals operates on primitive types, got array".into(),803 )),804 (Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(805 "primitiveEquals operates on primitive types, got object".into(),806 )),807 (a, b) if is_function_like(a) && is_function_like(b) => {808 throw!(RuntimeError("cannot test equality of functions".into()))809 }810 (_, _) => false,811 })812}813814815pub fn equals(s: State, val_a: &Val, val_b: &Val) -> Result<bool> {816 if val_a.value_type() != val_b.value_type() {817 return Ok(false);818 }819 match (val_a, val_b) {820 (Val::Arr(a), Val::Arr(b)) => {821 if ArrValue::ptr_eq(a, b) {822 return Ok(true);823 }824 if a.len() != b.len() {825 return Ok(false);826 }827 for (a, b) in a.iter(s.clone()).zip(b.iter(s.clone())) {828 if !equals(s.clone(), &a?, &b?)? {829 return Ok(false);830 }831 }832 Ok(true)833 }834 (Val::Obj(a), Val::Obj(b)) => {835 if ObjValue::ptr_eq(a, b) {836 return Ok(true);837 }838 let fields = a.fields(839 #[cfg(feature = "exp-preserve-order")]840 false,841 );842 if fields843 != b.fields(844 #[cfg(feature = "exp-preserve-order")]845 false,846 ) {847 return Ok(false);848 }849 for field in fields {850 if !equals(851 s.clone(),852 &a.get(s.clone(), field.clone())?.expect("field exists"),853 &b.get(s.clone(), field)?.expect("field exists"),854 )? {855 return Ok(false);856 }857 }858 Ok(true)859 }860 (a, b) => Ok(primitive_equals(a, b)?),861 }862}