git.delta.rocks / jrsonnet / refs/commits / d0fb5f4781e5

difftreelog

feat exp-bigint

Yaroslav Bolyukin2023-04-17parent: #205090d.patch.diff
in: master

16 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -298,6 +298,7 @@
  "jrsonnet-macros",
  "jrsonnet-parser",
  "jrsonnet-types",
+ "num-bigint",
  "pathdiff",
  "rustc-hash",
  "serde",
@@ -370,6 +371,7 @@
  "jrsonnet-macros",
  "jrsonnet-parser",
  "md5",
+ "num-bigint",
  "serde",
  "serde_json",
  "serde_yaml_with_quirks",
@@ -449,6 +451,37 @@
 ]
 
 [[package]]
+name = "num-bigint"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+ "serde",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
 name = "once_cell"
 version = "1.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
 [workspace]
-package.version = "0.5.0"
+package.version = "0.5.0-pre7"
 members = ["crates/*", "bindings/jsonnet", "cmds/jrsonnet", "tests"]
 default-members = ["cmds/jrsonnet"]
 
modifiedcmds/jrsonnet/Cargo.tomldiffbeforeafterboth
--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -19,6 +19,8 @@
 exp-destruct = ["jrsonnet-evaluator/exp-destruct"]
 # Iteration over objects yields [key, value] elements
 exp-object-iteration = ["jrsonnet-evaluator/exp-object-iteration"]
+# Bigint type
+exp-bigint = ["jrsonnet-evaluator/exp-bigint", "jrsonnet-cli/exp-bigint"]
 
 # std.thisFile support
 legacy-this-file = ["jrsonnet-cli/legacy-this-file"]
modifiedcrates/jrsonnet-cli/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-cli/Cargo.toml
+++ b/crates/jrsonnet-cli/Cargo.toml
@@ -11,6 +11,10 @@
     "jrsonnet-evaluator/exp-preserve-order",
     "jrsonnet-stdlib/exp-preserve-order",
 ]
+exp-bigint = [
+    "jrsonnet-evaluator/exp-bigint",
+    "jrsonnet-stdlib/exp-bigint",
+]
 legacy-this-file = ["jrsonnet-stdlib/legacy-this-file"]
 
 [dependencies]
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -24,6 +24,8 @@
 exp-destruct = ["jrsonnet-parser/exp-destruct"]
 # Iteration over objects yields [key, value] elements
 exp-object-iteration = []
+# Bigint type
+exp-bigint = ["num-bigint"]
 
 # Improves performance, and implements some useful things using nightly-only features
 nightly = ["hashbrown/nightly"]
@@ -54,3 +56,5 @@
 annotate-snippets = { version = "0.9.1", features = ["color"], optional = true }
 # Async imports
 async-trait = { version = "0.1.60", optional = true }
+# Bigint
+num-bigint = { version = "0.4.3", features = ["serde"], optional = true }
modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -47,6 +47,8 @@
 		(Arr(a), Arr(b)) => Val::Arr(ArrValue::extended(a.clone(), b.clone())),
 
 		(Num(v1), Num(v2)) => Val::new_checked_num(v1 + v2)?,
+		#[cfg(feature = "exp-bigint")]
+		(BigInt(a), BigInt(b)) => BigInt(Box::new((&**a).clone() + (&**b).clone())),
 		_ => throw!(BinaryOperatorDoesNotOperateOnValues(
 			BinaryOpType::Add,
 			a.value_type(),
@@ -95,6 +97,8 @@
 	Ok(match (a, b) {
 		(Str(a), Str(b)) => a.cmp(b),
 		(Num(a), Num(b)) => a.partial_cmp(b).expect("jsonnet numbers are non NaN"),
+		#[cfg(feature = "exp-bigint")]
+		(BigInt(a), BigInt(b)) => a.cmp(b),
 		(Arr(a), Arr(b)) => {
 			if let (Some(ai), Some(bi)) = (a.iter_cheap(), b.iter_cheap()) {
 				for (a, b) in ai.zip(bi) {
@@ -174,6 +178,12 @@
 			Num(f64::from((*v1 as i32) >> (*v2 as i32)))
 		}
 
+		// Bigint X Bigint
+		#[cfg(feature = "exp-bigint")]
+		(BigInt(a), Mul, BigInt(b)) => BigInt(Box::new((&**a).clone() * (&**b).clone())),
+		#[cfg(feature = "exp-bigint")]
+		(BigInt(a), Sub, BigInt(b)) => BigInt(Box::new((&**a).clone() - (&**b).clone())),
+
 		_ => throw!(BinaryOperatorDoesNotOperateOnValues(
 			op,
 			a.value_type(),
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -162,6 +162,8 @@
 			Val::Null => serializer.serialize_none(),
 			Val::Str(s) => serializer.serialize_str(&s.clone().into_flat()),
 			Val::Num(n) => serializer.serialize_f64(*n),
+			#[cfg(feature = "exp-bigint")]
+			Val::BigInt(b) => b.serialize(serializer),
 			Val::Arr(arr) => {
 				let mut seq = serializer.serialize_seq(Some(arr.len()))?;
 				for (i, element) in arr.iter().enumerate() {
modifiedcrates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -149,6 +149,8 @@
 		Val::Null => buf.push_str("null"),
 		Val::Str(s) => escape_string_json_buf(&s.clone().into_flat(), buf),
 		Val::Num(n) => write!(buf, "{n}").unwrap(),
+		#[cfg(feature = "exp-bigint")]
+		Val::BigInt(n) => write!(buf, "{n}").unwrap(),
 		Val::Arr(items) => {
 			buf.push('[');
 			if !items.is_empty() {
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	fmt::{self, Debug, Display},4	mem::replace,5	rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::ArrValue;13use crate::{14	error::{Error, ErrorKind::*},15	function::FuncVal,16	gc::{GcHashMap, TraceBox},17	manifest::{ManifestFormat, ToStringFormat},18	tb, throw,19	typed::BoundedUsize,20	ObjValue, Result, Unbound, WeakObjValue,21};2223pub trait ThunkValue: Trace {24	type Output;25	fn get(self: Box<Self>) -> Result<Self::Output>;26}2728#[derive(Trace)]29enum ThunkInner<T: Trace> {30	Computed(T),31	Errored(Error),32	Waiting(TraceBox<dyn ThunkValue<Output = T>>),33	Pending,34}3536/// Lazily evaluated value37#[allow(clippy::module_name_repetitions)]38#[derive(Clone, Trace)]39pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4041impl<T: Trace> Thunk<T> {42	pub fn evaluated(val: T) -> Self {43		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))44	}45	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {46		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))47	}48	pub fn errored(e: Error) -> Self {49		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))50	}51}5253impl<T> Thunk<T>54where55	T: Clone + Trace,56{57	pub fn force(&self) -> Result<()> {58		self.evaluate()?;59		Ok(())60	}6162	/// Evaluate thunk, or return cached value63	///64	/// # Errors65	///66	/// - Lazy value evaluation returned error67	/// - This method was called during inner value evaluation68	pub fn evaluate(&self) -> Result<T> {69		match &*self.0.borrow() {70			ThunkInner::Computed(v) => return Ok(v.clone()),71			ThunkInner::Errored(e) => return Err(e.clone()),72			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),73			ThunkInner::Waiting(..) => (),74		};75		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else {76			unreachable!();77		};78		let new_value = match value.0.get() {79			Ok(v) => v,80			Err(e) => {81				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());82				return Err(e);83			}84		};85		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());86		Ok(new_value)87	}88}8990type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);9192#[derive(Trace, Clone)]93pub struct CachedUnbound<I, T>94where95	I: Unbound<Bound = T>,96	T: Trace,97{98	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,99	value: I,100}101impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {102	pub fn new(value: I) -> Self {103		Self {104			cache: Cc::new(RefCell::new(GcHashMap::new())),105			value,106		}107	}108}109impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {110	type Bound = T;111	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {112		let cache_key = (113			sup.as_ref().map(|s| s.clone().downgrade()),114			this.as_ref().map(|t| t.clone().downgrade()),115		);116		{117			if let Some(t) = self.cache.borrow().get(&cache_key) {118				return Ok(t.clone());119			}120		}121		let bound = self.value.bind(sup, this)?;122123		{124			let mut cache = self.cache.borrow_mut();125			cache.insert(cache_key, bound.clone());126		}127128		Ok(bound)129	}130}131132impl<T: Debug + Trace> Debug for Thunk<T> {133	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {134		write!(f, "Lazy")135	}136}137impl<T: Trace> PartialEq for Thunk<T> {138	fn eq(&self, other: &Self) -> bool {139		Cc::ptr_eq(&self.0, &other.0)140	}141}142143/// Represents a Jsonnet value, which can be sliced or indexed (string or array).144#[allow(clippy::module_name_repetitions)]145pub enum IndexableVal {146	/// String.147	Str(IStr),148	/// Array.149	Arr(ArrValue),150}151impl IndexableVal {152	/// Slice the value.153	///154	/// # Implementation155	///156	/// For strings, will create a copy of specified interval.157	///158	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.159	pub fn slice(160		self,161		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,162		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,163		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,164	) -> Result<Self> {165		match &self {166			IndexableVal::Str(s) => {167				let index = index.as_deref().copied().unwrap_or(0);168				let end = end.as_deref().copied().unwrap_or(usize::MAX);169				let step = step.as_deref().copied().unwrap_or(1);170171				if index >= end {172					return Ok(Self::Str("".into()));173				}174175				Ok(Self::Str(176					(s.chars()177						.skip(index)178						.take(end - index)179						.step_by(step)180						.collect::<String>())181					.into(),182				))183			}184			IndexableVal::Arr(arr) => {185				let index = index.as_deref().copied().unwrap_or(0);186				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());187				let step = step.as_deref().copied().unwrap_or(1);188189				if index >= end {190					return Ok(Self::Arr(ArrValue::empty()));191				}192193				Ok(Self::Arr(194					arr.clone()195						.slice(Some(index), Some(end), Some(step))196						.expect("arguments checked"),197				))198			}199		}200	}201}202203#[derive(Debug, Clone, Trace)]204pub enum StrValue {205	Flat(IStr),206	Tree(Rc<(StrValue, StrValue, usize)>),207}208impl StrValue {209	pub fn concat(a: StrValue, b: StrValue) -> Self {210		// TODO: benchmark for an optimal value, currently just a arbitrary choice211		const STRING_EXTEND_THRESHOLD: usize = 100;212213		if a.is_empty() {214			b215		} else if b.is_empty() {216			a217		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {218			Self::Flat(format!("{a}{b}").into())219		} else {220			let len = a.len() + b.len();221			Self::Tree(Rc::new((a, b, len)))222		}223	}224	pub fn into_flat(self) -> IStr {225		#[cold]226		fn write_buf(s: &StrValue, out: &mut String) {227			match s {228				StrValue::Flat(f) => out.push_str(f),229				StrValue::Tree(t) => {230					write_buf(&t.0, out);231					write_buf(&t.1, out);232				}233			}234		}235		match self {236			StrValue::Flat(f) => f,237			StrValue::Tree(_) => {238				let mut buf = String::with_capacity(self.len());239				write_buf(&self, &mut buf);240				buf.into()241			}242		}243	}244	pub fn len(&self) -> usize {245		match self {246			StrValue::Flat(v) => v.len(),247			StrValue::Tree(t) => t.2,248		}249	}250	pub fn is_empty(&self) -> bool {251		match self {252			Self::Flat(v) => v.is_empty(),253			// Can't create non-flat empty string254			Self::Tree(_) => false,255		}256	}257}258impl From<&str> for StrValue {259	fn from(value: &str) -> Self {260		Self::Flat(value.into())261	}262}263impl From<String> for StrValue {264	fn from(value: String) -> Self {265		Self::Flat(value.into())266	}267}268impl Display for StrValue {269	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {270		match self {271			StrValue::Flat(v) => write!(f, "{v}"),272			StrValue::Tree(t) => {273				write!(f, "{}", t.0)?;274				write!(f, "{}", t.1)275			}276		}277	}278}279impl PartialEq for StrValue {280	fn eq(&self, other: &Self) -> bool {281		let a = self.clone().into_flat();282		let b = other.clone().into_flat();283		a == b284	}285}286impl Eq for StrValue {}287impl PartialOrd for StrValue {288	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {289		let a = self.clone().into_flat();290		let b = other.clone().into_flat();291		Some(a.cmp(&b))292	}293}294impl Ord for StrValue {295	fn cmp(&self, other: &Self) -> std::cmp::Ordering {296		self.partial_cmp(other)297			.expect("partial_cmp always returns Some")298	}299}300301/// Represents any valid Jsonnet value.302#[derive(Debug, Clone, Trace)]303pub enum Val {304	/// Represents a Jsonnet boolean.305	Bool(bool),306	/// Represents a Jsonnet null value.307	Null,308	/// Represents a Jsonnet string.309	Str(StrValue),310	/// Represents a Jsonnet number.311	/// Should be finite, and not NaN312	/// This restriction isn't enforced by enum, as enum field can't be marked as private313	Num(f64),314	/// Represents a Jsonnet array.315	Arr(ArrValue),316	/// Represents a Jsonnet object.317	Obj(ObjValue),318	/// Represents a Jsonnet function.319	Func(FuncVal),320}321322#[cfg(target_pointer_width = "64")]323static_assertions::assert_eq_size!(Val, [u8; 24]);324325impl From<IndexableVal> for Val {326	fn from(v: IndexableVal) -> Self {327		match v {328			IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),329			IndexableVal::Arr(a) => Self::Arr(a),330		}331	}332}333334impl Val {335	pub const fn as_bool(&self) -> Option<bool> {336		match self {337			Self::Bool(v) => Some(*v),338			_ => None,339		}340	}341	pub const fn as_null(&self) -> Option<()> {342		match self {343			Self::Null => Some(()),344			_ => None,345		}346	}347	pub fn as_str(&self) -> Option<IStr> {348		match self {349			Self::Str(s) => Some(s.clone().into_flat()),350			_ => None,351		}352	}353	pub const fn as_num(&self) -> Option<f64> {354		match self {355			Self::Num(n) => Some(*n),356			_ => None,357		}358	}359	pub fn as_arr(&self) -> Option<ArrValue> {360		match self {361			Self::Arr(a) => Some(a.clone()),362			_ => None,363		}364	}365	pub fn as_obj(&self) -> Option<ObjValue> {366		match self {367			Self::Obj(o) => Some(o.clone()),368			_ => None,369		}370	}371	pub fn as_func(&self) -> Option<FuncVal> {372		match self {373			Self::Func(f) => Some(f.clone()),374			_ => None,375		}376	}377378	/// Creates `Val::Num` after checking for numeric overflow.379	/// As numbers are `f64`, we can just check for their finity.380	pub fn new_checked_num(num: f64) -> Result<Self> {381		if num.is_finite() {382			Ok(Self::Num(num))383		} else {384			throw!("overflow")385		}386	}387388	pub const fn value_type(&self) -> ValType {389		match self {390			Self::Str(..) => ValType::Str,391			Self::Num(..) => ValType::Num,392			Self::Arr(..) => ValType::Arr,393			Self::Obj(..) => ValType::Obj,394			Self::Bool(_) => ValType::Bool,395			Self::Null => ValType::Null,396			Self::Func(..) => ValType::Func,397		}398	}399400	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {401		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {402			manifest.manifest(val.clone())403		}404		manifest_dyn(self, &format)405	}406407	pub fn to_string(&self) -> Result<IStr> {408		Ok(match self {409			Self::Bool(true) => "true".into(),410			Self::Bool(false) => "false".into(),411			Self::Null => "null".into(),412			Self::Str(s) => s.clone().into_flat(),413			_ => self.manifest(ToStringFormat).map(IStr::from)?,414		})415	}416417	pub fn into_indexable(self) -> Result<IndexableVal> {418		Ok(match self {419			Val::Str(s) => IndexableVal::Str(s.into_flat()),420			Val::Arr(arr) => IndexableVal::Arr(arr),421			_ => throw!(ValueIsNotIndexable(self.value_type())),422		})423	}424}425426const fn is_function_like(val: &Val) -> bool {427	matches!(val, Val::Func(_))428}429430/// Native implementation of `std.primitiveEquals`431pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {432	Ok(match (val_a, val_b) {433		(Val::Bool(a), Val::Bool(b)) => a == b,434		(Val::Null, Val::Null) => true,435		(Val::Str(a), Val::Str(b)) => a == b,436		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,437		(Val::Arr(_), Val::Arr(_)) => {438			throw!("primitiveEquals operates on primitive types, got array")439		}440		(Val::Obj(_), Val::Obj(_)) => {441			throw!("primitiveEquals operates on primitive types, got object")442		}443		(a, b) if is_function_like(a) && is_function_like(b) => {444			throw!("cannot test equality of functions")445		}446		(_, _) => false,447	})448}449450/// Native implementation of `std.equals`451pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {452	if val_a.value_type() != val_b.value_type() {453		return Ok(false);454	}455	match (val_a, val_b) {456		(Val::Arr(a), Val::Arr(b)) => {457			if ArrValue::ptr_eq(a, b) {458				return Ok(true);459			}460			if a.len() != b.len() {461				return Ok(false);462			}463			for (a, b) in a.iter().zip(b.iter()) {464				if !equals(&a?, &b?)? {465					return Ok(false);466				}467			}468			Ok(true)469		}470		(Val::Obj(a), Val::Obj(b)) => {471			if ObjValue::ptr_eq(a, b) {472				return Ok(true);473			}474			let fields = a.fields(475				#[cfg(feature = "exp-preserve-order")]476				false,477			);478			if fields479				!= b.fields(480					#[cfg(feature = "exp-preserve-order")]481					false,482				) {483				return Ok(false);484			}485			for field in fields {486				if !equals(487					&a.get(field.clone())?.expect("field exists"),488					&b.get(field)?.expect("field exists"),489				)? {490					return Ok(false);491				}492			}493			Ok(true)494		}495		(a, b) => Ok(primitive_equals(a, b)?),496	}497}
modifiedcrates/jrsonnet-stdlib/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/Cargo.toml
+++ b/crates/jrsonnet-stdlib/Cargo.toml
@@ -17,6 +17,8 @@
 exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"]
 # Add nonstandard `std.sha256` function
 exp-more-hashes = ["dep:sha2"]
+# Bigint type
+exp-bigint = ["num-bigint", "jrsonnet-evaluator/exp-bigint"]
 
 [dependencies]
 jrsonnet-evaluator.workspace = true
@@ -39,6 +41,7 @@
 serde_yaml_with_quirks = "0.8.24"
 
 sha2 = { version = "0.10.6", optional = true }
+num-bigint = { version = "0.4.3", optional = true }
 
 [build-dependencies]
 jrsonnet-parser.workspace = true
modifiedcrates/jrsonnet-stdlib/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/expr.rs
+++ b/crates/jrsonnet-stdlib/src/expr.rs
@@ -83,7 +83,7 @@
 			pub(super) use std::{option::Option, rc::Rc, vec};
 
 			pub(super) use jrsonnet_parser::*;
-		};
+		}
 
 		include!(concat!(env!("OUT_DIR"), "/stdlib.rs"))
 	}
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -143,6 +143,8 @@
 		("asciiLower", builtin_ascii_lower::INST),
 		("findSubstr", builtin_find_substr::INST),
 		("parseInt", builtin_parse_int::INST),
+		#[cfg(feature = "exp-bigint")]
+		("bigint", builtin_bigint::INST),
 		("parseOctal", builtin_parse_octal::INST),
 		("parseHex", builtin_parse_hex::INST),
 		// Misc
modifiedcrates/jrsonnet-stdlib/src/manifest/toml.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest/toml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/toml.rs
@@ -103,6 +103,8 @@
 			escape_string_json_buf(&s.clone().into_flat(), buf);
 		}
 		Val::Num(n) => write!(buf, "{n}").unwrap(),
+		#[cfg(feature = "exp-bigint")]
+		Val::BigInt(n) => write!(buf, "{n}").unwrap(),
 		Val::Arr(a) => {
 			if a.is_empty() {
 				buf.push_str("[]");
modifiedcrates/jrsonnet-stdlib/src/manifest/yaml.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest/yaml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/yaml.rs
@@ -140,6 +140,8 @@
 			}
 		}
 		Val::Num(n) => write!(buf, "{}", *n).unwrap(),
+		#[cfg(feature = "exp-bigint")]
+		Val::BigInt(n) => write!(buf, "{}", *n).unwrap(),
 		Val::Arr(a) => {
 			if a.is_empty() {
 				buf.push_str("[]");
modifiedcrates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -151,6 +151,20 @@
 	})
 }
 
+#[cfg(feature = "exp-bigint")]
+#[builtin]
+pub fn builtin_bigint(v: Either![f64, IStr]) -> Result<Val> {
+	use Either2::*;
+	Ok(match v {
+		A(a) => Val::BigInt(Box::new((a as i64).into())),
+		B(b) => Val::BigInt(Box::new(
+			b.as_str()
+				.parse()
+				.map_err(|e| RuntimeError(format!("bad bigint: {e}").into()))?,
+		)),
+	})
+}
+
 #[cfg(test)]
 mod tests {
 	use super::*;
modifiedcrates/jrsonnet-types/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-types/src/lib.rs
+++ b/crates/jrsonnet-types/src/lib.rs
@@ -88,6 +88,8 @@
 	Null,
 	Str,
 	Num,
+	#[cfg(feature = "exp-bigint")]
+	BigInt,
 	Arr,
 	Obj,
 	Func,
@@ -101,6 +103,8 @@
 			Null => "null",
 			Str => "string",
 			Num => "number",
+			#[cfg(feature = "exp-bigint")]
+			BigInt => "bigint",
 			Arr => "array",
 			Obj => "object",
 			Func => "function",