The Jest Programming Language
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564//! Native JSON parser producing `Value` directly.//!//! Single-key objects are `Value::Object` with one entry; `eval_inner`//! dispatches them as procedure calls.
use crate::runtime::data::String as RStr;use crate::runtime::{Budget, Number, Value};use crate::{Error, Result};
// ── Public API ───────────────────────────────────────────────────────────────
/// Parse a single JSON value from `src`.pub fn parse_json(src: &str, budget: &Budget) -> Result<Value> { let mut p = Parser::new(src, budget); let val = p.parse_value()?; p.skip_ws(); if p.pos < p.src.len() { return Err(p.err("unexpected trailing content")); } Ok(val)}
/// Parse JSONL: one JSON value per non-empty, non-comment line.////// Lines starting with `//` (after optional whitespace) are treated as comments/// and skipped. Blank lines are skipped.pub fn parse_jsonl(src: &str, budget: &Budget) -> Result<Vec<Value>> { let mut values = Vec::new(); for line in src.lines() { let trimmed = line.trim(); if trimmed.is_empty() || trimmed.starts_with("//") { continue; } values.push(parse_json(trimmed, budget)?); } Ok(values)}
// ── Parser ───────────────────────────────────────────────────────────────────
struct Parser<'a> { src: &'a [u8], pos: usize, line: usize, column: usize, budget: &'a Budget,}
impl<'a> Parser<'a> { fn new(src: &'a str, budget: &'a Budget) -> Self { Self { src: src.as_bytes(), pos: 0, line: 1, column: 1, budget, } }
fn err(&self, message: &str) -> Error { Error::Parse { message: message.to_string(), line: self.line, column: self.column, } }
fn peek(&self) -> Option<u8> { self.src.get(self.pos).copied() }
fn advance(&mut self) -> Option<u8> { let b = self.src.get(self.pos).copied()?; self.pos += 1; if b == b'\n' { self.line += 1; self.column = 1; } else { self.column += 1; } Some(b) }
fn expect(&mut self, expected: u8) -> Result<()> { match self.advance() { Some(b) if b == expected => Ok(()), Some(b) => Err(self.err(&format!( "expected '{}', got '{}'", expected as char, b as char ))), None => Err(self.err(&format!("expected '{}', got EOF", expected as char))), } }
fn skip_ws(&mut self) { while let Some(b) = self.peek() { if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' { self.advance(); } else { break; } } }
// ── Value dispatch ───────────────────────────────────────────────────
fn parse_value(&mut self) -> Result<Value> { self.skip_ws(); match self.peek() { Some(b'"') => self.parse_string_value(), Some(b'{') => self.parse_object(), Some(b'[') => self.parse_array(), Some(b't') => self.parse_true(), Some(b'f') => self.parse_false(), Some(b'n') => self.parse_null(), Some(b) if b == b'-' || b.is_ascii_digit() => self.parse_number(), Some(b) => Err(self.err(&format!("unexpected character '{}'", b as char))), None => Err(self.err("unexpected end of input")), } }
// ── Null / Bool ──────────────────────────────────────────────────────
fn parse_null(&mut self) -> Result<Value> { self.expect_literal(b"null")?; Ok(Value::Null) }
fn parse_true(&mut self) -> Result<Value> { self.expect_literal(b"true")?; Ok(Value::Bool(true)) }
fn parse_false(&mut self) -> Result<Value> { self.expect_literal(b"false")?; Ok(Value::Bool(false)) }
fn expect_literal(&mut self, lit: &[u8]) -> Result<()> { for &expected in lit { match self.advance() { Some(b) if b == expected => {} _ => { return Err(self.err(&format!( "expected '{}'", std::str::from_utf8(lit).unwrap() ))) } } } Ok(()) }
// ── Number ───────────────────────────────────────────────────────────
fn parse_number(&mut self) -> Result<Value> { let start = self.pos; let _start_column = self.column;
// Optional minus if self.peek() == Some(b'-') { self.advance(); }
// Integer part match self.peek() { Some(b'0') => { self.advance(); } Some(b) if b.is_ascii_digit() => { while let Some(b) = self.peek() { if b.is_ascii_digit() { self.advance(); } else { break; } } } _ => return Err(self.err("expected digit")), }
let mut is_float = false;
// Fractional part if self.peek() == Some(b'.') { is_float = true; self.advance(); if !matches!(self.peek(), Some(b) if b.is_ascii_digit()) { return Err(self.err("expected digit after decimal point")); } while let Some(b) = self.peek() { if b.is_ascii_digit() { self.advance(); } else { break; } } }
// Exponent if matches!(self.peek(), Some(b'e' | b'E')) { is_float = true; self.advance(); if matches!(self.peek(), Some(b'+' | b'-')) { self.advance(); } if !matches!(self.peek(), Some(b) if b.is_ascii_digit()) { return Err(self.err("expected digit in exponent")); } while let Some(b) = self.peek() { if b.is_ascii_digit() { self.advance(); } else { break; } } }
// SAFETY: We only advanced through ASCII bytes, so this slice is valid UTF-8. let text = unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]) };
if is_float { let d: bigdecimal::BigDecimal = text .parse() .map_err(|_| self.err("invalid number"))?; Ok(Value::Number(Number::Float(d))) } else { let i: num_bigint::BigInt = text .parse() .map_err(|_| self.err("invalid integer"))?; Ok(Value::Number(Number::Integer(i))) } }
// ── String ───────────────────────────────────────────────────────────
fn parse_string_value(&mut self) -> Result<Value> { let s = self.parse_string_raw()?; Value::string(&s, self.budget) }
fn parse_string_raw(&mut self) -> Result<std::string::String> { self.expect(b'"')?; let mut buf = std::string::String::new(); loop { match self.advance() { None => return Err(self.err("unterminated string")), Some(b'"') => return Ok(buf), Some(b'\\') => { match self.advance() { Some(b'"') => buf.push('"'), Some(b'\\') => buf.push('\\'), Some(b'/') => buf.push('/'), Some(b'n') => buf.push('\n'), Some(b'r') => buf.push('\r'), Some(b't') => buf.push('\t'), Some(b'b') => buf.push('\u{0008}'), Some(b'f') => buf.push('\u{000C}'), Some(b'u') => { let cp = self.parse_hex4()?; // Handle surrogate pairs if (0xD800..=0xDBFF).contains(&cp) { // High surrogate — expect \uDxxx low surrogate if self.advance() != Some(b'\\') || self.advance() != Some(b'u') { return Err(self.err("expected low surrogate after high surrogate")); } let low = self.parse_hex4()?; if !(0xDC00..=0xDFFF).contains(&low) { return Err(self.err("invalid low surrogate")); } let codepoint = 0x10000 + ((cp as u32 - 0xD800) << 10) + (low as u32 - 0xDC00); let c = char::from_u32(codepoint) .ok_or_else(|| self.err("invalid unicode codepoint"))?; buf.push(c); } else if (0xDC00..=0xDFFF).contains(&cp) { return Err(self.err("unexpected low surrogate")); } else { let c = char::from_u32(cp as u32) .ok_or_else(|| self.err("invalid unicode codepoint"))?; buf.push(c); } } Some(b) => { return Err(self.err(&format!( "invalid escape character '{}'", b as char ))) } None => return Err(self.err("unterminated escape")), } } Some(b) => { // Multi-byte UTF-8: we need to read the full character. if b < 0x80 { buf.push(b as char); } else { // Determine byte count from leading byte let len = if b & 0xE0 == 0xC0 { 2 } else if b & 0xF0 == 0xE0 { 3 } else if b & 0xF8 == 0xF0 { 4 } else { return Err(self.err("invalid UTF-8")); }; let start = self.pos - 1; self.pos = start + len; // Multi-byte UTF-8 characters count as 1 column (one character position) // Note: We already advanced by 1 for the first byte, so advance by len-1 more self.column += len - 1; if self.pos > self.src.len() { return Err(self.err("truncated UTF-8")); } let s = std::str::from_utf8(&self.src[start..self.pos]) .map_err(|_| self.err("invalid UTF-8"))?; buf.push_str(s); } } } } }
fn parse_hex4(&mut self) -> Result<u16> { let mut val: u16 = 0; for _ in 0..4 { let b = self.advance().ok_or_else(|| self.err("expected hex digit"))?; let digit = match b { b'0'..=b'9' => b - b'0', b'a'..=b'f' => b - b'a' + 10, b'A'..=b'F' => b - b'A' + 10, _ => return Err(self.err("expected hex digit")), }; val = val * 16 + digit as u16; } Ok(val) }
// ── Array ────────────────────────────────────────────────────────────
fn parse_array(&mut self) -> Result<Value> { self.expect(b'[')?; self.skip_ws(); if self.peek() == Some(b']') { self.advance(); return Value::array(vec![], self.budget); } let mut items = Vec::new(); loop { items.push(self.parse_value()?); self.skip_ws(); match self.peek() { Some(b',') => { self.advance(); } Some(b']') => { self.advance(); return Value::array(items, self.budget); } _ => return Err(self.err("expected ',' or ']'")), } } }
// ── Object ───────────────────────────────────────────────────────────
fn parse_object(&mut self) -> Result<Value> { self.expect(b'{')?; self.skip_ws(); if self.peek() == Some(b'}') { self.advance(); return Value::object(vec![], self.budget); } let mut pairs: Vec<(std::string::String, Value)> = Vec::new(); loop { self.skip_ws(); let key = self.parse_string_raw()?; self.skip_ws(); self.expect(b':')?; let val = self.parse_value()?; pairs.push((key, val)); self.skip_ws(); match self.peek() { Some(b',') => { self.advance(); } Some(b'}') => { self.advance(); break; } _ => return Err(self.err("expected ',' or '}'")), } }
// Single-key objects → Call (procedure invocation), matching from_input. if pairs.len() == 1 { let (key, arg) = pairs.into_iter().next().unwrap(); Value::call(&key, arg, self.budget) } else { let rstr_pairs = pairs .into_iter() .map(|(k, v)| RStr::new(&k, self.budget).map(|rk| (rk, v))) .collect::<Result<Vec<_>>>()?; Value::object(rstr_pairs, self.budget) } }}
// ── Tests ────────────────────────────────────────────────────────────────────
#[cfg(test)]mod tests { use super::*;
fn budget() -> Budget { Budget::new(usize::MAX / 2) }
#[test] fn null() { let v = parse_json("null", &budget()).unwrap(); assert_eq!(v.to_string(), "null"); }
#[test] fn booleans() { assert_eq!(parse_json("true", &budget()).unwrap().to_string(), "true"); assert_eq!(parse_json("false", &budget()).unwrap().to_string(), "false"); }
#[test] fn integers() { assert_eq!(parse_json("0", &budget()).unwrap().to_string(), "0"); assert_eq!(parse_json("42", &budget()).unwrap().to_string(), "42"); assert_eq!(parse_json("-7", &budget()).unwrap().to_string(), "-7"); }
#[test] fn floats() { assert_eq!(parse_json("3.14", &budget()).unwrap().to_string(), "3.14"); assert_eq!(parse_json("1e10", &budget()).unwrap().to_string(), "10000000000.0"); assert_eq!(parse_json("2.5e2", &budget()).unwrap().to_string(), "250.0"); assert_eq!(parse_json("-0.5", &budget()).unwrap().to_string(), "-0.5"); }
#[test] fn strings() { assert_eq!( parse_json(r#""hello""#, &budget()).unwrap().to_string(), r#""hello""# ); assert_eq!( parse_json(r#""a\nb""#, &budget()).unwrap().to_string(), r#""a\nb""# ); assert_eq!( parse_json(r#""a\"b""#, &budget()).unwrap().to_string(), r#""a\"b""# ); }
#[test] fn unicode_escape() { // \u0041 = 'A' assert_eq!( parse_json(r#""\u0041""#, &budget()).unwrap().to_string(), r#""A""# ); }
#[test] fn surrogate_pair() { // U+1F600 = 😀 encoded as \uD83D\uDE00 let v = parse_json(r#""\uD83D\uDE00""#, &budget()).unwrap(); // Check the actual string value if let Value::String(s) = &v { assert_eq!(&**s, "😀"); } else { panic!("expected string"); } }
#[test] fn empty_array() { assert_eq!(parse_json("[]", &budget()).unwrap().to_string(), "[]"); }
#[test] fn array() { assert_eq!( parse_json("[1, 2, 3]", &budget()).unwrap().to_string(), "[1,2,3]" ); }
#[test] fn nested_array() { assert_eq!( parse_json("[[1], [2, 3]]", &budget()).unwrap().to_string(), "[[1],[2,3]]" ); }
#[test] fn empty_object() { assert_eq!(parse_json("{}", &budget()).unwrap().to_string(), "{}"); }
#[test] fn multi_key_object() { let v = parse_json(r#"{"a": 1, "b": 2}"#, &budget()).unwrap(); // Multi-key → Value::Object assert!(matches!(v, Value::Object(_))); assert_eq!(v.to_string(), r#"{"a":1,"b":2}"#); }
#[test] fn single_key_object_is_call() { let v = parse_json(r#"{"add": [1, 2]}"#, &budget()).unwrap(); // Single-key → Value::Object with one entry, recognized by as_call() assert!(v.as_call().is_some()); assert_eq!(v.to_string(), r#"{"add":[1,2]}"#); }
#[test] fn roundtrip_complex() { let src = r#"{"if":[true,{"+":[1,2]},"no"]}"#; let v = parse_json(src, &budget()).unwrap(); assert_eq!(v.to_string(), src); }
#[test] fn jsonl() { let src = "1\n2\n3"; let vals = parse_jsonl(src, &budget()).unwrap(); assert_eq!(vals.len(), 3); assert_eq!(vals[0].to_string(), "1"); assert_eq!(vals[2].to_string(), "3"); }
#[test] fn jsonl_skips_blanks_and_comments() { let src = "1\n\n// comment\n2"; let vals = parse_jsonl(src, &budget()).unwrap(); assert_eq!(vals.len(), 2); }
#[test] fn trailing_content_errors() { assert!(parse_json("1 2", &budget()).is_err()); }
#[test] fn unterminated_string_errors() { assert!(parse_json(r#""hello"#, &budget()).is_err()); }
#[test] fn invalid_literal_errors() { assert!(parse_json("tru", &budget()).is_err()); }}