jasn 0.2.0

A Rust library for parsing and formatting JASN (Just Another Serialization Notation)
Documentation
# JASN Grammar Specification

> **Note:** This specification is still under active development and may be subject to change.

JASN (Just Another Serialization Notation) extends JSON with explicit integer and binary types, plus convenient JSON5-inspired syntax features.

## Overview

- **Integers**: Distinct 64-bit signed integer type, supporting decimal, hexadecimal, binary, and octal notation
- **Binary**: Byte array type with base64 (`b64"..."`) and hex (`hex"..."`) encoding
- **Timestamps**: ISO8601/RFC3339 timestamp literals with `ts"..."` syntax
- **JSON5 Features**: Trailing commas, single quotes, unquoted keys, liberal number parsing, comments
- **Comments**: Block comments (`/* */`) only
- **Whitespace-Agnostic**: All whitespace characters are completely interchangeable

## EBNF Grammar

> **Note:** Whitespace is implicitly allowed between all tokens and is automatically skipped during parsing. All whitespace characters (space, tab, newline, carriage return) are completely interchangeable.

```ebnf
(* Root *)
(* 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 *)
integer = [ sign ] , ( decimal_integer | hex_integer | binary_integer | octal_integer ) ;
decimal_integer = digit , { { "_" } , digit } ;
hex_integer = ( "0x" | "0X" ) , hex_digit , { { "_" } , hex_digit } ;
binary_integer = ( "0b" | "0B" ) , binary_digit , { { "_" } , binary_digit } ;
octal_integer = ( "0o" | "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 ] ) ;
int_part = digit , { digit } ;
frac_part = "." , digit , { digit } ;
exp_part = ( "e" | "E" ) , [ sign ] , digit , { digit } ;
special_float = int_part , "." ;  (* Trailing dot: "5." *)
infinity = "inf" ;
nan = "nan" ;

sign = "+" | "-" ;
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
binary_digit = "0" | "1" ;
octal_digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" ;
hex_digit = digit | "a" | "b" | "c" | "d" | "e" | "f"
                  | "A" | "B" | "C" | "D" | "E" | "F" ;

(* Strings *)
string = double_quoted_string | single_quoted_string ;
double_quoted_string = '"' , { string_char_double | escape_sequence } , '"' ;
single_quoted_string = "'" , { string_char_single | escape_sequence } , "'" ;
string_char_double = ? any Unicode character except '"', '\', or control characters ? ;
string_char_single = ? any Unicode character except "'", '\', or control characters ? ;

escape_sequence = "\\" , ( '"' | "'" | "\\" | "/" | "b" | "f" | "n" | "r" | "t"
                         | unicode_escape ) ;
unicode_escape = "u" , hex_digit , hex_digit , hex_digit , hex_digit ;

(* Binary *)
binary = base64_binary | hex_binary ;
base64_binary = "b64" , '"' , { base64_char } , '"' ;
hex_binary = "hex" , '"' , { hex_digit } , '"' ;
base64_char = ? ASCII letter (A-Z, a-z) ? | digit | "+" | "/" | "=" ;

(* Timestamps *)
timestamp = "ts" , '"' , iso8601_datetime , '"' ;
iso8601_datetime = ? ISO 8601 / RFC 3339 formatted datetime string ? ;

(* Lists *)
list = "[" , [ value_list ] , [ "," ] , "]" ;
value_list = value , { "," , value } ;

(* Maps *)
map = "{" , [ member_list ] , [ "," ] , "}" ;
member_list = member , { "," , member } ;
member = key , ":" , value ;

key = string | identifier ;
identifier = id_start , { id_continue } ;
id_start = ? ASCII letter (A-Z, a-z) ? | "_" ;
id_continue = id_start | digit ;

(* Comments *)
comment = "/*" , { ? any character ? - ( "*/" ) } , "*/" ;
```

## Type Resolution Rules

JASN distinguishes between integers and floats at parse time:

### Integer Type (64-bit signed integer)
- Decimal digits only: `42`, `-123`, `+99`, `1_000_000`
- Hexadecimal notation: `0xFF`, `0x10`, `-0xDEAD_BEEF`
- Binary notation: `0b1010`, `0b1111_1111`, `-0b1000`
- Octal notation: `0o755`, `0o644`, `+0o777`
- Underscores allowed between digits for readability (including multiple consecutive: `1__000`, `1___000`)
- Underscores not allowed at the start or end of the number
- No decimal point, no exponent

### Float Type (IEEE 754 binary64)
- Contains decimal point: `42.0`, `.5`, `5.`
- Contains exponent: `1e10`, `2.5e-3`, `5E+2`
- Special values: `inf`, `+inf`, `-inf`, `nan` (lowercase only)

## Examples

### Integers
```jasn
42
-123
+99
1_000_000
0xFF
0x10
-0xDEAD_BEEF
0b1010
0b1111_1111
-0b1000
0o755
0o644
+0o777
0o100_000
```

### Floats
```jasn
42.0
3.14159
-2.5
.5
5.
1e10
2.5e-3
5E+2
inf
+inf
-inf
nan
```

### Binary
```jasn
b64"SGVsbG8gV29ybGQh"
b64"AQIDBA=="
b64""
hex"48656c6c6f20576f726c6421"
hex"01020304"
hex"DEADBEEF"
hex""
```

### Timestamps
```jasn
ts"2024-01-15T12:30:45.123Z"
ts"2024-01-15T12:30:45Z"
ts"2024-01-15T12:30:45-05:00"
ts"2024-12-31T23:59:59.999999999Z"
```

### Strings
```jasn
"double quotes"
'single quotes'
"escaped: \"quote\" and \n newline"
'also escaped: \' and \\'
"unicode: \u0041\u0042\u0043"
```

### Lists (with trailing commas)
```jasn
[1, 2, 3]
[1, 2, 3,]
[
  42,
  "hello",
  true,
]
[]
```

### Maps (with unquoted keys and trailing commas)
```jasn
{
  "quoted": 1,
  'single': 2,
  unquoted: 3,
  _private_123: 4,
}

{
  name: "Alice",
  age: 30,
  data: b64"YmluYXJ5",
}
```

### Complex Example
```jasn
/* Configuration file example */
{
  /* Version information */
  version: 1,
  count: 0x100,  /* Hex integer */
  ratio: 3.14,
  name: "JASN Example",
  active: true,
  metadata: null,
  
  /* Binary data can be encoded
     in multiple formats */
  binary_data: b64"SGVsbG8=",
  
  items: [
    { id: 1, value: 10.5, },  /* First item */
    { id: 2, value: 20.0, },  /* Second item */
    { id: 3, value: .5, },    /* Third item */
  ],
  
  config: {
    timeout: 30,           /* seconds */
    'max-retries': 5,      /* quoted key with dash */
    enabled: true,
  },
}
```

## Differences from JSON

1. **Integer type**: Numbers without decimal point/exponent are 64-bit signed integers, not double-precision floats
2. **Binary type**: New `b64"..."` and `hex"..."` literals for binary data
3. **Timestamp type**: New `ts"..."` literals for ISO8601/RFC3339 timestamps
4. **Trailing commas**: Allowed in lists and maps
4. **Single quotes**: Strings can use `'...'` or `"..."`
5. **Unquoted keys**: Map keys can be identifiers, including reserved words (`null`, `true`, `false`, `inf`, `nan`)
6. **Duplicate keys**: Not allowed in maps (parse error)
7. **Multiple radix integers**: `0x` (hex), `0b` (binary), `0o` (octal) prefixes (case-insensitive)
7. **Liberal numbers**: Leading/trailing decimal points (`.5`, `5.`), explicit sign (`+42`), underscores in integers (`1_000`, `1__000`)
8. **Special floats**: `inf`, `nan` with signs (lowercase only)
9. **Comments**: Block comments `/* */` only (whitespace-agnostic design)

## JSON Compatibility

JASN is designed to accept most valid JSON with the following important limitations:

### Integer Range Restriction

**Numbers without decimal points or exponents are parsed as 64-bit signed integers** with range:
- Minimum: `-9,223,372,036,854,775,808` (-2^63)
- Maximum: `9,223,372,036,854,775,807` (2^63 - 1)

**Valid JSON documents containing integers outside this range will be rejected** as parse errors.

Examples:
```jasn
9223372036854775807   /* ✓ Valid (max i64) */
9223372036854775808   /* ✗ Parse error (overflow) */
-9223372036854775808  /* ✓ Valid (min i64) */
-9223372036854775809  /* ✗ Parse error (underflow) */
```

**Workaround:** Use float notation for numbers outside the i64 range:
```jasn
9223372036854775808.0   /* ✓ Valid as float */
1e20                     /* ✓ Valid as float */
```

### Type Distinction

Unlike JSON (where all numbers are typically implemented as doubles), JASN distinguishes:
- `42` → 64-bit signed integer
- `42.0` → IEEE 754 binary64 float

This means `42` and `42.0` are **different types** in JASN, though mathematically equivalent.

### Compatibility Summary

- ✓ All valid JSON strings, booleans, null
- ✓ All JSON objects (maps) and arrays (lists)
- ✓ All JSON whitespace and escape sequences
- ✓ JSON numbers within i64 range (may become integer type)
- ✗ JSON integers outside ±2^63-1 (rejected)
- ✓ More permissive: allows leading zeros, trailing commas (JSON forbids these)

## Differences from JSON5

1. **Integer/Float split**: Explicit type distinction based on syntax
2. **Binary literals**: New `b64"..."` and `hex"..."` types
3. **Multi-line strings**: Not supported (standard JSON escaping only)
4. **Infinity/NaN**: Supported with simpler syntax (`inf`, `nan` vs `Infinity`, `NaN`)
5. **Additional integer radixes**: Binary (`0b`) and octal (`0o`) literals beyond JSON5

## Future Considerations

- Additional binary encodings: `b"..."` for Python-style b-strings
- Multi-line strings with proper indentation handling