///|
/// JSON value types
pub(all) enum JsonValue {
Null
Bool(Bool)
Number(Double)
String(String)
Array(Array[JsonValue])
Object(Map[String, JsonValue])
} derive(Show, Eq)
///|
/// Position in the input for error reporting
struct Position {
line : Int
column : Int
} derive(Show, Eq)
///|
/// Parser errors
pub(all) suberror ParseError {
UnexpectedChar(Position, Char)
UnexpectedEnd
InvalidNumber(Position, String)
InvalidEscape(Position, Char)
InvalidUnicode(Position, String)
Expected(Position, String)
} derive(Show, Eq)
///|
/// Token types for the lexer
priv enum Token {
LeftBrace
RightBrace
LeftBracket
RightBracket
Colon
Comma
Null
True
False
Number(Double)
TString(String)
Eof
} derive(Eq)
///|
/// Lexer state
priv struct Lexer {
input : String
mut pos : Int
mut line : Int
mut column : Int
}
///|
/// Create a new lexer
fn Lexer::new(input : String) -> Lexer {
{ input, pos: 0, line: 1, column: 1 }
}
///|
/// Get current position
fn Lexer::current_position(self : Lexer) -> Position {
{ line: self.line, column: self.column }
}
///|
/// Peek at current character
fn Lexer::peek(self : Lexer) -> Char? {
if self.pos >= self.input.length() {
None
} else {
self.input.get_char(self.pos)
}
}
///|
/// Advance to next character
fn Lexer::advance(self : Lexer) -> Unit {
if self.pos < self.input.length() {
match self.input.get_char(self.pos) {
Some('\n') => {
self.line += 1
self.column = 1
}
Some(_) => self.column += 1
None => ()
}
self.pos += 1
}
}
///|
/// Skip whitespace
fn Lexer::skip_whitespace(self : Lexer) -> Unit {
while true {
match self.peek() {
Some(' ') | Some('\t') | Some('\n') | Some('\r') => self.advance()
_ => break
}
}
}
///|
/// Parse a number
fn Lexer::parse_number(self : Lexer) -> Token raise ParseError {
let start = self.pos
let pos = self.current_position()
// Handle optional minus
if self.peek() is Some('-') {
self.advance()
}
// Parse integer part
match self.peek() {
Some('0') => self.advance()
Some(c) if c >= '1' && c <= '9' => {
self.advance()
while self.peek() is Some(c) && c >= '0' && c <= '9' {
self.advance()
}
}
_ => raise ParseError::InvalidNumber(pos, "Expected digit")
}
// Parse optional fraction
if self.peek() is Some('.') {
self.advance()
let mut has_digit = false
while self.peek() is Some(c) && c >= '0' && c <= '9' {
has_digit = true
self.advance()
}
if not(has_digit) {
raise ParseError::InvalidNumber(pos, "Expected digit after decimal point")
}
}
// Parse optional exponent
if self.peek() is Some('e') || self.peek() is Some('E') {
self.advance()
if self.peek() is Some('+') || self.peek() is Some('-') {
self.advance()
}
let mut has_digit = false
while self.peek() is Some(c) && c >= '0' && c <= '9' {
has_digit = true
self.advance()
}
if not(has_digit) {
raise ParseError::InvalidNumber(pos, "Expected digit in exponent")
}
}
let num_str = try! self.input[start:self.pos]
let n = @strconv.parse_double(num_str) catch {
_ => raise ParseError::InvalidNumber(pos, "Invalid number format")
}
Number(n)
}
///|
/// Parse a string
fn Lexer::parse_string(self : Lexer) -> Token raise ParseError {
let pos = self.current_position()
self.advance() // Skip opening quote
let result = @buffer.new()
let ret : Token = while true {
match self.peek() {
None => raise ParseError::UnexpectedEnd
Some('"') => {
self.advance()
break TString(result.to_string())
}
Some('\\') => {
self.advance()
match self.peek() {
Some('"') => {
result.write_char('"')
self.advance()
}
Some('\\') => {
result.write_char('\\')
self.advance()
}
Some('/') => {
result.write_char('/')
self.advance()
}
Some('b') => {
result.write_char('\b')
self.advance()
}
Some('f') => {
result.write_char('\u000C')
self.advance()
}
Some('n') => {
result.write_char('\n')
self.advance()
}
Some('r') => {
result.write_char('\r')
self.advance()
}
Some('t') => {
result.write_char('\t')
self.advance()
}
Some('u') => {
self.advance()
let hex = @buffer.new()
for i = 0; i < 4; i = i + 1 {
match self.peek() {
Some(c) if (c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F') => {
hex.write_char(c)
self.advance()
}
_ =>
raise ParseError::InvalidUnicode(
pos, "Invalid unicode escape",
)
}
}
let hex_str = hex.to_string()
let code = @strconv.parse_int(hex_str, base=16) catch {
_ =>
raise ParseError::InvalidUnicode(pos, "Invalid unicode value")
}
result.write_char(code.unsafe_to_char())
}
Some(c) => raise ParseError::InvalidEscape(pos, c)
None => raise ParseError::UnexpectedEnd
}
}
Some(c) if c.to_int() < 0x20 => raise ParseError::UnexpectedChar(pos, c)
Some(c) => {
result.write_char(c)
self.advance()
}
}
} else {
TString(result.to_string())
}
ret
}
///|
/// Parse a keyword
fn Lexer::parse_keyword(
self : Lexer,
keyword : String,
token : Token,
) -> Token raise ParseError {
let pos = self.current_position()
for i = 0; i < keyword.length(); i = i + 1 {
match (self.peek(), keyword.get_char(i)) {
(Some(c1), Some(c2)) if c1 == c2 => self.advance()
_ => raise ParseError::Expected(pos, keyword)
}
}
token
}
///|
/// Get next token
fn Lexer::next_token(self : Lexer) -> Token raise ParseError {
self.skip_whitespace()
match self.peek() {
None => Eof
Some('{') => {
self.advance()
LeftBrace
}
Some('}') => {
self.advance()
RightBrace
}
Some('[') => {
self.advance()
LeftBracket
}
Some(']') => {
self.advance()
RightBracket
}
Some(':') => {
self.advance()
Colon
}
Some(',') => {
self.advance()
Comma
}
Some('"') => self.parse_string()
Some('-') => self.parse_number()
Some(c) if c >= '0' && c <= '9' => self.parse_number()
Some('t') => self.parse_keyword("true", Token::True)
Some('f') => self.parse_keyword("false", Token::False)
Some('n') => self.parse_keyword("null", Token::Null)
Some(c) => raise ParseError::UnexpectedChar(self.current_position(), c)
}
}
///|
/// Parser state
priv struct Parser {
lexer : Lexer
mut current : Token
}
///|
/// Create a new parser
fn Parser::new(input : String) -> Parser raise ParseError {
let lexer = Lexer::new(input)
let current = lexer.next_token()
{ lexer, current }
}
///|
/// Advance to next token
fn Parser::advance(self : Parser) -> Unit raise ParseError {
self.current = self.lexer.next_token()
}
///|
/// Parse a JSON value
fn Parser::parse_value(self : Parser) -> JsonValue raise ParseError {
match self.current {
Token::Null => {
self.advance()
JsonValue::Null
}
Token::True => {
self.advance()
JsonValue::Bool(true)
}
Token::False => {
self.advance()
JsonValue::Bool(false)
}
Number(n) => {
self.advance()
JsonValue::Number(n)
}
TString(s) => {
self.advance()
JsonValue::String(s)
}
LeftBracket => self.parse_array()
LeftBrace => self.parse_object()
_ => raise ParseError::Expected(self.lexer.current_position(), "value")
}
}
///|
/// Parse a JSON array
fn Parser::parse_array(self : Parser) -> JsonValue raise ParseError {
self.advance() // Skip '['
let elements : Array[JsonValue] = []
if self.current == RightBracket {
self.advance()
return JsonValue::Array(elements)
}
let result : JsonValue = while true {
elements.push(self.parse_value())
match self.current {
Comma => {
self.advance()
continue
}
RightBracket => {
self.advance()
break JsonValue::Array(elements)
}
_ =>
raise ParseError::Expected(self.lexer.current_position(), "',' or ']'")
}
} else {
JsonValue::Array(elements)
}
result
}
///|
/// Parse a JSON object
fn Parser::parse_object(self : Parser) -> JsonValue raise ParseError {
self.advance() // Skip '{'
let members : Map[String, JsonValue] = {}
if self.current == RightBrace {
self.advance()
return JsonValue::Object(members)
}
let result : JsonValue = while true {
// Parse key
let key = match self.current {
TString(s) => {
self.advance()
s
}
_ =>
raise ParseError::Expected(self.lexer.current_position(), "string key")
}
// Expect colon
if self.current != Colon {
raise ParseError::Expected(self.lexer.current_position(), "':'")
}
self.advance()
// Parse value
let value = self.parse_value()
members[key] = value
match self.current {
Comma => {
self.advance()
continue
}
RightBrace => {
self.advance()
break JsonValue::Object(members)
}
_ =>
raise ParseError::Expected(self.lexer.current_position(), "',' or '}'")
}
} else {
JsonValue::Object(members)
}
result
}
///|
/// Parse JSON from a string
pub fn parse(input : String) -> JsonValue raise ParseError {
let parser = Parser::new(input)
let value = parser.parse_value()
// Ensure we've consumed all input
if parser.current != Eof {
raise ParseError::Expected(parser.lexer.current_position(), "end of input")
}
value
}
///|
/// Convert JsonValue to a formatted string
pub fn JsonValue::to_json_string(self : JsonValue, indent : Int) -> String {
let spaces = " ".repeat(indent)
let inner_spaces = " ".repeat(indent + 2)
match self {
Null => "null"
Bool(true) => "true"
Bool(false) => "false"
Number(n) => n.to_string()
String(s) => {
let escaped = @buffer.new()
escaped.write_char('"')
for ch in s {
match ch {
'"' => escaped.write_string("\\\"")
'\\' => escaped.write_string("\\\\")
'\b' => escaped.write_string("\\b")
'\u000C' => escaped.write_string("\\f")
'\n' => escaped.write_string("\\n")
'\r' => escaped.write_string("\\r")
'\t' => escaped.write_string("\\t")
c if c.to_int() < 0x20 => {
escaped.write_string("\\u")
let code = c.to_int()
// Format as 4-digit hex
let digits = "0123456789abcdef"
escaped.write_char(digits.get_char((code >> 12) & 0xF).unwrap())
escaped.write_char(digits.get_char((code >> 8) & 0xF).unwrap())
escaped.write_char(digits.get_char((code >> 4) & 0xF).unwrap())
escaped.write_char(digits.get_char(code & 0xF).unwrap())
}
c => escaped.write_char(c)
}
}
escaped.write_char('"')
escaped.to_string()
}
Array(arr) =>
if arr.is_empty() {
"[]"
} else {
let result = @buffer.new()
result.write_string("[\n")
for i = 0; i < arr.length(); i = i + 1 {
result.write_string(inner_spaces)
result.write_string(arr[i].to_json_string(indent + 2))
if i < arr.length() - 1 {
result.write_char(',')
}
result.write_char('\n')
}
result.write_string(spaces)
result.write_char(']')
result.to_string()
}
Object(obj) =>
if obj.is_empty() {
"{}"
} else {
let result = @buffer.new()
result.write_string("{\n")
let mut first = true
for key, value in obj {
if not(first) {
result.write_string(",\n")
}
first = false
result.write_string(inner_spaces)
result.write_string(JsonValue::String(key).to_json_string(0))
result.write_string(": ")
result.write_string(value.to_json_string(indent + 2))
}
result.write_char('\n')
result.write_string(spaces)
result.write_char('}')
result.to_string()
}
}
}