JAML - Just Another Markup Language
A Rust library for parsing and formatting JAML, a YAML-inspired data serialization format with explicit integer and binary types.
JAML shares the same data model as JASN but uses indentation-based syntax for a cleaner, more readable appearance. Think "YAML done right" - no implicit type conversions, no ambiguous syntax, explicit types.
Why JAML?
- Clean YAML-like syntax without the gotchas (no "Norway Problem")
- Explicit types:
42is an integer,42.0is a float - Quoted strings: No implicit type conversion, all string values must be quoted
- Native binary data:
b64"..."andhex"..."literals - Timestamps: First-class
ts"..."support - Flexible indentation: First indent defines base unit (2 spaces, 4 spaces, tabs, etc.)
- Full serde support: Serialize/deserialize any Rust type
Quick Start
Parsing JAML
use parse;
Serde Integration
use ;
let config = Config ;
// Serialize to JAML
let jaml = to_string?;
// Deserialize from JAML
let parsed: Config = from_str?;
Example
A comprehensive example showing all supported types:
# Null
nothing: null
# Booleans
active: true
disabled: false
# Integers (distinct from floats)
count: 42
negative: -123
hex_value: 0xFF
binary_value: 0b1010
octal_value: 0o755
with_underscores: 1_000_000
# Floats
pi: 3.14159
scientific: 1.5e10
infinity: inf
not_a_number: nan
# Strings (must be quoted)
name: "Alice"
message: "Hello, World!"
unicode: "Hello \u4E16\u754C"
# Binary data
data_b64: b64"SGVsbG8gV29ybGQh"
data_hex: hex"48656c6c6f"
# Timestamps
created: ts"2024-01-15T12:30:45Z"
updated: ts"2024-01-15T12:30:45.123Z"
# Lists (block syntax)
numbers:
- 1
- 2
- 3
# Nested lists
matrix:
-
- 1
- 2
-
- 3
- 4
# Maps (nested structures)
person:
name: "Bob"
age: 25
address:
street: "123 Main St"
city: "Portland"
zip: "97201"
# Inline lists and maps (compact single-line syntax)
inline_list: [1, 2, 3]
inline_map: {x: 10, y: 20}
# Lists of maps
users:
- name: "Alice"
role: "admin"
- name: "Bob"
role: "user"
Features
Data Types
- Null:
null - Boolean:
true,false - Integer (i64):
42,-123,0xFF,0b1010,0o755 - Float (f64):
3.14,1e10,.5,5.,inf,nan - String:
"quoted"or'quoted'(always quoted) - Binary:
b64"base64..."orhex"hexdigits..." - Timestamp:
ts"2024-01-15T12:30:45Z" - List: Block syntax or inline
[...] - Map: Block syntax or inline
{...}
Syntax Features
- Indentation-based: Clean structure without braces
- Flexible indentation: First indent (2 spaces, 4 spaces, tabs) defines base unit
- Line comments:
#starts a comment - Unquoted keys: Map keys can be identifiers (
name: "value") - Quoted keys: For special characters (
"key-with-dash": "value") - Inline syntax: Compact
[...]and{...}for single-line structures - Trailing commas: Allowed in inline lists/maps
No YAML Gotchas
JAML avoids common YAML pitfalls:
# YAML 1.1 (problematic)
NO: Norway # NO becomes false!
yes: Yemen # yes becomes true!
# JAML (explicit)
NO: "Norway" # NO is an identifier key, "Norway" is a quoted string
yes: "Yemen" # yes is an identifier key, "Yemen" is a quoted string
All string values must be quoted - no implicit type conversion!
Installation
[]
= "0.2"
For serde support (enabled by default):
[]
= { = "0.2", = ["serde"] }
API Overview
Parsing
use ;
// Parse to AST
let value: Value = parse?;
// With serde
let config: MyStruct = from_str?;
Formatting
use ;
// Format a value
let text = format;
// With custom options
let opts = new
.with_sort_keys
.with_quote_style;
let text = format_with_opts;
// With serde
let text = to_string?;
Documentation
- Grammar Specification: Complete EBNF grammar and rules
- Examples: Valid and invalid example files
- JASN: Alternative JSON5-like syntax with same data model
Differences from YAML
- Explicit string values: All string values must be quoted
- Integer/Float distinction:
42and42.0are different types - Binary type: Native
b64"..."andhex"..."literals - Timestamp type: Native
ts"..."literals - No implicit conversions: No yes/no/on/off boolean conversion
- Flexible indentation: First indent defines base unit
- Single document: No multi-document support
- No anchors/aliases: No
&anchoror*alias - No tags: No
!!typesupport - Simpler, explicit syntax: Focused subset with clear rules
Differences from JASN
Important: JAML is not a superset of JASN. Unlike YAML/JSON (where JASN is a superset of JSON), JAML and JASN use intentionally disjoint syntax styles.
Both share the same data model (jasn-core::Value) and can represent identical data, but use different syntax:
| Feature | JASN | JAML |
|---|---|---|
| Structure | Braces {} and brackets [] |
Indentation-based |
| Multi-line structures | Allowed in {} and [] |
Only via indentation (block syntax) |
| Inline structures | [...] and {...} with newlines allowed |
[...] and {...} must be single-line |
| Comments | Block /* */ |
Line # only |
| String values | Optional quotes | Must be quoted |
| Trailing commas | Everywhere | Only in inline [] and {} |
Design Philosophy: The syntax incompatibility is intentional. Each format has its own consistent style:
- JASN: Braces/brackets everywhere, block comments, flexible quoting
- JAML: Indentation for structure, line comments, explicit quoting
- Compatibility: Via the shared
Valuetype - convert between formats through the AST
// JASN text -> Value -> JAML text
let jasn = r#"{ name: "Alice", age: 30 }"#;
let value = parse?;
let jaml = format;
// JAML text -> Value -> JASN text
let jaml = "name: \"Alice\"\nage: 30";
let value = parse?;
let jasn = format;
License
MIT License - see LICENSE file for details.