na x is na extended with support for types and functions.
na's data types may be extended with types and functions, inspired by edn's tagged elements.
Types may be defined by both users and parsers, while functions can only be defined by parsers.
Types and functions must be defined with a valid name. Types must be prefixed with a #
sigil.
When applied to a value, a type or function must appear before the value, on the same line, separated by space.
Parsers may allow clients to register handlers for custom types and functions, for example to transform na values into data types of the target language. Client-defined handlers should be pure functions without side effects.
Security is paramount
A parser's built-in handlers must be pure functions with no side effects. Further, parsers must by default disallow clients to register handlers. To enable client-defined handlers, a parser must be explicitly instructed to run in unsafe mode, which should raise a warning.
If a parser encounters a type or function for which no handler is registered, it may ignore it and use the value verbatim instead.
Parsers must be able to read any syntactically valid na data without causing errors. Errors may, however, be raised if the parser is run in strict mode.
#any: #none | #some
#some: #truth | #number | #text | #block
#number: #decimal | #integer | #natural | #ratio
#list: [ #natural: #any ] -- key signature
#record: [ #name: #any ] -- key signature
Where:
#none
denotes the concept ofnothing
/null
/nil
/void
/undefined
#name
denotes a valid name#number
denotes an arbitrary-precision number, with the following subtypes:#decimal
denotes a decimal approximation of a real number (ℝ)#integer
denotes an integer number (ℤ)#natural
denotes a natural number (ℕ), a non-negative integer
#ratio
denotes a rational number (ℚ), the ratio of two positive integers
In standard na x, #name
may only be used as the key of a key signature, and keys may only be of the type #natural
or #name
.
Support for fixed-precision numbers are implementation/platform dependent.
Implementations should use type names from the following list:
- Signed integers
#i8
denotes an 8-bit signed integer#i16
denotes a 16-bit signed integer#i32
denotes a 32-bit signed integer#i64
denotes a 64-bit signed integer#i128
denotes a 128-bit signed integer- …
- Unsigned integers
#u8
denotes an 8-bit unsigned integer#u16
denotes a 16-bit unsigned integer#u32
denotes a 32-bit unsigned integer#u64
denotes a 64-bit unsigned integer#u128
denotes a 128-bit unsigned integer- …
- Floating-point
#f32
denotes an IEEE 754 single-precision binary floating-point number (binary32)#f64
denotes an IEEE 754 double-precision binary floating-point number (binary64)- …
#person: [ -- type definition
name: #text -- type annotation
friends: #persons | #none -- optional item
]
#persons: [#natural: #person] -- list type (key signature)
joe: #person [ -- typed record
name: 'Joe'
]
area: square[7m, 6m] -- applying a function to a block of values
timestamp: instant '1985-04-12T23:20:50.52Z' -- "casting" a string to an RFC 3339 timestamp
id: uuid 'f81d4fae7dec11d0a76500a0c91e6bf6' -- "casting" a string to an RFC 4122 UUID
na x may itself be used as a subset of other data formats.