Scalar Interpretation
How opaque scalars are interpreted as typed values during deserialization.
Styx scalars are opaque text at parse time — the parser assigns no type. Interpretation happens during deserialization, when a schema or target type requests a specific type. This page specifies the standard interpretation rules that all conforming implementations MUST follow.
Strings
interp[interp.string] Any scalar MAY be interpreted as a string. The scalar's text content becomes the string value.
name Alice // bare scalar → "Alice" name "Alice" // quoted scalar → "Alice" name r#"Alice"# // raw scalar → "Alice"
Booleans
interp[interp.bool.true] A scalar is interpreted as boolean
trueif its text content is exactlytrue(case-sensitive).
interp[interp.bool.false] A scalar is interpreted as boolean
falseif its text content is exactlyfalse(case-sensitive).
interp[interp.bool.error] Any other scalar text MUST produce an error when boolean interpretation is requested.
enabled true // → true enabled false // → false enabled yes // ERROR: not a valid boolean enabled TRUE // ERROR: case-sensitive
Integers
interp[interp.int.decimal] A decimal integer is an optional sign (
+or-) followed by one or more digits0-9. Leading zeros are permitted. Underscores MAY appear between digits for readability and are ignored.port 8080 // → 8080 offset -42 // → -42 big 1_000_000 // → 1000000
interp[interp.int.hex] A hexadecimal integer starts with
0xor0Xfollowed by one or more hex digits0-9,a-f,A-F. Underscores MAY appear between digits.color 0xff5500 // → 16733440 mask 0xFF_FF // → 65535
interp[interp.int.octal] An octal integer starts with
0oor0Ofollowed by one or more digits0-7. Underscores MAY appear between digits.mode 0o755 // → 493
interp[interp.int.binary] A binary integer starts with
0bor0Bfollowed by one or more digits0-1. Underscores MAY appear between digits.flags 0b1010 // → 10 mask 0b1111_0000 // → 240
interp[interp.int.range] Implementations MUST reject integers that overflow the target type's range. The error message SHOULD include the valid range.
Floating-point
interp[interp.float.syntax] A floating-point number consists of:
- An optional sign (
+or-)- An integer part (one or more digits)
- An optional fractional part (
.followed by one or more digits)- An optional exponent (
eorE, optional sign, one or more digits)At least one of the fractional parts or exponent MUST be present to distinguish from integer. Underscores MAY appear between digits.
pi 3.14159 avogadro 6.022e23 small 1.5e-10 precise 3.141_592_653
interp[interp.float.special] The following case-sensitive literals represent special floating-point values:
infor+inf— positive infinity-inf— negative infinitynan— not a number (quiet NaN)max inf min -inf undefined nan
Durations
interp[interp.duration.syntax] A duration is a sequence of one or more
<number><unit>pairs, where:
<number>is a non-negative integer or floating-point number<unit>is one of the following (case-sensitive):
Unit Meaning nsnanoseconds usorµsmicroseconds msmilliseconds sseconds mminutes hhours ddays (24 hours) Multiple pairs are summed. No whitespace is allowed between pairs.
timeout 30s // 30 seconds interval 1h30m // 1 hour + 30 minutes = 5400 seconds precise 1.5s // 1500 milliseconds delay 500ms // 500 milliseconds ttl 7d // 7 days
interp[interp.duration.order] Units MAY appear in any order, but for readability SHOULD appear largest to smallest. The same unit MAY appear multiple times; values are summed.
weird 30s1h // valid: 1 hour 30 seconds (not recommended) also 1h1h // valid: 2 hours (not recommended)
Dates and times
interp[interp.datetime.iso8601] Date and time values follow RFC 3339 format. Implementations MUST support at minimum:
Format Example Meaning YYYY-MM-DD2024-03-15Date only YYYY-MM-DDTHH:MM:SS2024-03-15T14:30:00Local datetime YYYY-MM-DDTHH:MM:SSZ2024-03-15T14:30:00ZUTC datetime YYYY-MM-DDTHH:MM:SS±HH:MM2024-03-15T14:30:00+01:00Datetime with offset The
Tseparator MAY be replaced with a space when the scalar is quoted.created 2024-03-15 updated 2024-03-15T14:30:00Z local "2024-03-15 14:30:00"
interp[interp.datetime.subsec] Fractional seconds MAY be included with up to nanosecond precision:
precise 2024-03-15T14:30:00.123456789Z
Bytes
interp[interp.bytes.hex] A byte sequence MAY be represented as a hexadecimal string: an even number of hex digits
0-9,a-f,A-F. Each pair of digits represents one byte. Underscores MAY appear between byte pairs for readability.hash deadbeef key 00_11_22_33 empty "" // zero bytes
interp[interp.bytes.base64] Implementations MAY support base64-encoded bytes, indicated by a
base64:prefix or schema annotation. Standard base64 alphabet with+/and=padding MUST be supported. URL-safe alphabet with-_SHOULD be supported.data base64:SGVsbG8gV29ybGQ=
Null and unit
interp[interp.null] The unit value
@MAY be interpreted as null/nil/None in languages that support it. Unit indicates absence when deserializing to an optional type.value @ // null / None / nil
interp[interp.unit.field] When a field's value is unit and the target type is not optional, implementations MUST produce an error.
Type coercion
interp[interp.coerce.none] Implementations MUST NOT perform implicit type coercion. A scalar that matches integer syntax MUST NOT automatically become a string, or vice versa. The target type determines interpretation.
// If schema says port is @int: port 8080 // OK: interpreted as integer // If schema says port is @string: port 8080 // OK: interpreted as string "8080" // If schema says port is @int: port localhost // ERROR: not a valid integer
interp[interp.error.context] When interpretation fails, the error MUST include:
- The scalar's source location (file, line, column)
- The scalar's text content (or a prefix if very long)
- The expected type
- Why the interpretation failed