scroll.server/crates/titan/src/json.rs
WiseDev d25a6de423 move all crates into crates/, glob members
pure move, no code touched. readme and protocol.md paths fixed up.
2026-08-23 08:15:45 +03:00

343 lines
11 KiB
Rust

use std::collections::BTreeMap;
use std::fmt::Write as _;
#[derive(Debug, Clone, PartialEq)]
pub enum JsonValue {
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec<JsonValue>),
Object(BTreeMap<String, JsonValue>),
}
#[derive(Debug, thiserror::Error)]
pub enum JsonError {
#[error("unexpected end of input at offset {0}")]
UnexpectedEnd(usize),
#[error("unexpected byte {byte:?} at offset {offset}")]
Unexpected { byte: char, offset: usize },
#[error("invalid number at offset {0}")]
InvalidNumber(usize),
#[error("invalid escape sequence at offset {0}")]
InvalidEscape(usize),
}
impl JsonValue {
pub fn as_i32(&self) -> Option<i32> {
match self {
JsonValue::Number(value) => Some(*value as i32),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
JsonValue::Bool(value) => Some(*value),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
JsonValue::String(value) => Some(value),
_ => None,
}
}
pub fn get(&self, key: &str) -> Option<&JsonValue> {
match self {
JsonValue::Object(map) => map.get(key),
_ => None,
}
}
pub fn int_or(&self, key: &str, fallback: i32) -> i32 {
self.get(key)
.and_then(JsonValue::as_i32)
.unwrap_or(fallback)
}
pub fn bool_or(&self, key: &str, fallback: bool) -> bool {
self.get(key)
.and_then(JsonValue::as_bool)
.unwrap_or(fallback)
}
pub fn str_or<'a>(&'a self, key: &str, fallback: &'a str) -> &'a str {
self.get(key)
.and_then(JsonValue::as_str)
.unwrap_or(fallback)
}
pub fn object(pairs: impl IntoIterator<Item = (&'static str, JsonValue)>) -> Self {
JsonValue::Object(
pairs
.into_iter()
.map(|(key, value)| (key.to_owned(), value))
.collect(),
)
}
fn write_into(&self, out: &mut String) {
match self {
JsonValue::Null => out.push_str("null"),
JsonValue::Bool(true) => out.push_str("true"),
JsonValue::Bool(false) => out.push_str("false"),
JsonValue::Number(value) => {
if value.fract() == 0.0 && value.is_finite() {
let _ = write!(out, "{}", *value as i64);
} else {
let _ = write!(out, "{value}");
}
}
JsonValue::String(value) => write_escaped(out, value),
JsonValue::Array(items) => {
out.push('[');
for (index, item) in items.iter().enumerate() {
if index > 0 {
out.push(',');
}
item.write_into(out);
}
out.push(']');
}
JsonValue::Object(map) => {
out.push('{');
for (index, (key, value)) in map.iter().enumerate() {
if index > 0 {
out.push(',');
}
write_escaped(out, key);
out.push(':');
value.write_into(out);
}
out.push('}');
}
}
}
pub fn parse(input: &str) -> Result<JsonValue, JsonError> {
let mut parser = Parser {
bytes: input.as_bytes(),
offset: 0,
};
parser.skip_whitespace();
let value = parser.parse_value()?;
parser.skip_whitespace();
Ok(value)
}
}
impl std::fmt::Display for JsonValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut out = String::new();
self.write_into(&mut out);
f.write_str(&out)
}
}
impl From<i32> for JsonValue {
fn from(value: i32) -> Self {
JsonValue::Number(value as f64)
}
}
impl From<bool> for JsonValue {
fn from(value: bool) -> Self {
JsonValue::Bool(value)
}
}
impl From<&str> for JsonValue {
fn from(value: &str) -> Self {
JsonValue::String(value.to_owned())
}
}
impl From<String> for JsonValue {
fn from(value: String) -> Self {
JsonValue::String(value)
}
}
fn write_escaped(out: &mut String, value: &str) {
out.push('"');
for ch in value.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
ch if (ch as u32) < 0x20 => {
let _ = write!(out, "\\u{:04x}", ch as u32);
}
ch => out.push(ch),
}
}
out.push('"');
}
struct Parser<'a> {
bytes: &'a [u8],
offset: usize,
}
impl<'a> Parser<'a> {
fn peek(&self) -> Result<u8, JsonError> {
self.bytes
.get(self.offset)
.copied()
.ok_or(JsonError::UnexpectedEnd(self.offset))
}
fn bump(&mut self) -> Result<u8, JsonError> {
let byte = self.peek()?;
self.offset += 1;
Ok(byte)
}
fn skip_whitespace(&mut self) {
while let Some(byte) = self.bytes.get(self.offset) {
if matches!(byte, b' ' | b'\t' | b'\n' | b'\r') {
self.offset += 1;
} else {
break;
}
}
}
fn expect(&mut self, expected: u8) -> Result<(), JsonError> {
let byte = self.bump()?;
if byte != expected {
return Err(JsonError::Unexpected {
byte: byte as char,
offset: self.offset - 1,
});
}
Ok(())
}
fn parse_value(&mut self) -> Result<JsonValue, JsonError> {
self.skip_whitespace();
match self.peek()? {
b'{' => self.parse_object(),
b'[' => self.parse_array(),
b'"' => Ok(JsonValue::String(self.parse_string()?)),
b't' => self.parse_literal("true", JsonValue::Bool(true)),
b'f' => self.parse_literal("false", JsonValue::Bool(false)),
b'n' => self.parse_literal("null", JsonValue::Null),
_ => self.parse_number(),
}
}
fn parse_literal(&mut self, literal: &str, value: JsonValue) -> Result<JsonValue, JsonError> {
if self.bytes[self.offset..].starts_with(literal.as_bytes()) {
self.offset += literal.len();
Ok(value)
} else {
Err(JsonError::Unexpected {
byte: self.peek()? as char,
offset: self.offset,
})
}
}
fn parse_number(&mut self) -> Result<JsonValue, JsonError> {
let start = self.offset;
while let Some(byte) = self.bytes.get(self.offset) {
if matches!(byte, b'-' | b'+' | b'.' | b'e' | b'E') || byte.is_ascii_digit() {
self.offset += 1;
} else {
break;
}
}
std::str::from_utf8(&self.bytes[start..self.offset])
.ok()
.and_then(|text| text.parse::<f64>().ok())
.map(JsonValue::Number)
.ok_or(JsonError::InvalidNumber(start))
}
fn parse_string(&mut self) -> Result<String, JsonError> {
self.expect(b'"')?;
let mut out = String::new();
loop {
let byte = self.bump()?;
match byte {
b'"' => break,
b'\\' => {
let escape = self.bump()?;
match escape {
b'"' => out.push('"'),
b'\\' => out.push('\\'),
b'/' => out.push('/'),
b'b' => out.push('\u{8}'),
b'f' => out.push('\u{c}'),
b'n' => out.push('\n'),
b'r' => out.push('\r'),
b't' => out.push('\t'),
b'u' => {
let hex = self
.bytes
.get(self.offset..self.offset + 4)
.ok_or(JsonError::UnexpectedEnd(self.offset))?;
let code = std::str::from_utf8(hex)
.ok()
.and_then(|text| u32::from_str_radix(text, 16).ok())
.ok_or(JsonError::InvalidEscape(self.offset))?;
self.offset += 4;
out.push(
char::from_u32(code)
.ok_or(JsonError::InvalidEscape(self.offset))?,
);
}
_ => return Err(JsonError::InvalidEscape(self.offset - 1)),
}
}
_ => {
let start = self.offset - 1;
let mut end = self.offset;
while end < self.bytes.len()
&& self.bytes[end] != b'"'
&& self.bytes[end] != b'\\'
{
end += 1;
}
let chunk = std::str::from_utf8(&self.bytes[start..end])
.map_err(|_| JsonError::InvalidEscape(start))?;
out.push_str(chunk);
self.offset = end;
}
}
}
Ok(out)
}
fn parse_array(&mut self) -> Result<JsonValue, JsonError> {
self.expect(b'[')?;
let mut items = Vec::new();
self.skip_whitespace();
if self.peek()? == b']' {
self.offset += 1;
return Ok(JsonValue::Array(items));
}
loop {
items.push(self.parse_value()?);
self.skip_whitespace();
match self.bump()? {
b',' => continue,
b']' => break,
byte => {
return Err(JsonError::Unexpected {
byte: byte as char,
offset: self.offset - 1,
})
}
}
}
Ok(JsonValue::Array(items))
}
fn parse_object(&mut self) -> Result<JsonValue, JsonError> {
self.expect(b'{')?;
let mut map = BTreeMap::new();
self.skip_whitespace();
if self.peek()? == b'}' {
self.offset += 1;
return Ok(JsonValue::Object(map));
}
loop {
self.skip_whitespace();
let key = self.parse_string()?;
self.skip_whitespace();
self.expect(b':')?;
let value = self.parse_value()?;
map.insert(key, value);
self.skip_whitespace();
match self.bump()? {
b',' => continue,
b'}' => break,
byte => {
return Err(JsonError::Unexpected {
byte: byte as char,
offset: self.offset - 1,
})
}
}
}
Ok(JsonValue::Object(map))
}
}