jasn 0.2.0

A Rust library for parsing and formatting JASN (Just Another Serialization Notation)
Documentation
// JASN Grammar for Pest Parser
// Whitespace and comments are automatically skipped between tokens
// JASN is completely whitespace-agnostic (all whitespace is interchangeable)

WHITESPACE = _{ " " | "\t" | "\r" | "\n" }

COMMENT = _{ block_comment }
block_comment = { "/*" ~ (!"*/" ~ ANY)* ~ "*/" }

// Root value
// Note: Float before integer to correctly parse trailing-dot syntax like "5."
value = { null | boolean | float | integer | string | binary | timestamp | list | map }

// Primitives
null = { "null" }
boolean = { "true" | "false" }

// Numbers - Float must be tried before integer to handle cases like "5."
integer = @{ sign? ~ (hex_integer | binary_integer | octal_integer | decimal_integer) }

decimal_integer = { digit ~ ("_"* ~ digit)* }
hex_integer = { ^"0x" ~ hex_digit ~ ("_"* ~ hex_digit)* }
binary_integer = { ^"0b" ~ binary_digit ~ ("_"* ~ binary_digit)* }
octal_integer = { ^"0o" ~ octal_digit ~ ("_"* ~ octal_digit)* }

float = @{ sign? ~ (infinity | nan | decimal_float | special_float) }

decimal_float = {
    (int_part ~ frac_part ~ exp_part?)
  | (int_part ~ exp_part)
  | (frac_part ~ exp_part?)
}
special_float = { int_part ~ "." }
int_part = { digit+ }
frac_part = { "." ~ digit+ }
exp_part = { ^"e" ~ sign? ~ digit+ }

infinity = { "inf" }
nan = { "nan" }
sign = { "+" | "-" }

// Character classes
digit = { '0'..'9' }
binary_digit = { '0'..'1' }
octal_digit = { '0'..'7' }
hex_digit = { '0'..'9' | 'a'..'f' | 'A'..'F' }

// Strings
string = ${ double_quoted_string | single_quoted_string }
double_quoted_string = { "\"" ~ double_string_content ~ "\"" }
single_quoted_string = { "'" ~ single_string_content ~ "'" }

double_string_content = @{ (escape_sequence | double_string_char)* }
single_string_content = @{ (escape_sequence | single_string_char)* }

double_string_char = { !("\"" | "\\" | NEWLINE) ~ ANY }
single_string_char = { !("'" | "\\" | NEWLINE) ~ ANY }

escape_sequence = {
    "\\" ~ (
        "\"" | "'" | "\\" | "/" | "b" | "f" | "n" | "r" | "t"
      | unicode_escape
    )
}
unicode_escape = { "u" ~ hex_digit ~ hex_digit ~ hex_digit ~ hex_digit }

// Binary data
binary = { base64_binary | hex_binary }
base64_binary = ${ "b64\"" ~ base64_content ~ "\"" }
hex_binary = ${ "hex\"" ~ hex_content ~ "\"" }

base64_content = @{ base64_char* }
hex_content = @{ hex_digit* }
base64_char = { 'A'..'Z' | 'a'..'z' | '0'..'9' | "+" | "/" | "=" }

// Timestamp data (ISO8601/RFC3339)
timestamp = ${ "ts\"" ~ timestamp_content ~ "\"" }
timestamp_content = @{
    digit{4} ~ "-" ~ digit{2} ~ "-" ~ digit{2} ~
    "T" ~
    digit{2} ~ ":" ~ digit{2} ~ ":" ~ digit{2} ~
    ("." ~ digit{1,9})? ~
    ("Z" | (("+" | "-") ~ digit{2} ~ ":" ~ digit{2}))
}

// Lists (arrays)
list = { "[" ~ (value ~ ("," ~ value)* ~ ","?)? ~ "]" }

// Maps (objects)
map = { "{" ~ (member ~ ("," ~ member)* ~ ","?)? ~ "}" }
member = { key ~ ":" ~ value }

key = { string | identifier }
identifier = @{ id_start ~ id_continue* }
id_start = { 'a'..'z' | 'A'..'Z' | "_" }
id_continue = { id_start | digit }

// Top-level rule
jasn = { SOI ~ value ~ EOI }