Semantic Values
If successful, a parser produces a semantic value, which describes the input in some way useful to the application invoking the parser. In addition, semantic values may be used to control how other parts of the input are to be parsed. DaeDaLus has a number of built-in semantic values types, and allows for user-defined record and union types.
Booleans
The type bool classifies the usual boolean values true and false.
The operator ! may be used to negate a boolean value.
The operators || and && are for (short-circuiting) “or” and “and”
respectively.
Boolean values may be compared for equality using == and are ordered
with false < true.
Decisions on a boolean may be made either using If-then-else, by using Guards, or by using Case.
Numeric Types
DaeDaLus supports the following numeric types:
Type |
Description |
|---|---|
|
Integers of arbitrary size |
|
Unsigned integers represented in |
|
Signed integers represented in |
|
IEEE 754 single-precision (32-bit) floating-point numbers |
|
IEEE 754 double-precision (64-bit) floating-point numbers |
Numeric Literals
Integer literals may be written in decimal, hexadecimal (0x),
octal (0o), or binary (0b) notation (e.g., 10, 0xA,
0o12, 0b1010). The type of a literal can be inferred from the
context (e.g., 10 can be used as both int and uint 8).
There is also a byte-string numeric literal written as 0s"...", which
interprets each character as a byte (8 bits) and concatenates them into
a single integer. For example, 0s"scnr" is a uint 32 with value
0x73636E72. The width of the resulting type is the number of
characters times 8.
Floating-point literals are written with a decimal point (e.g., 3.14).
The constant pi is also available at both float and double types.
Comparisons
Numeric types can also be compared for equality using == and !=,
and ordered using <, <=, >, and >=.
Basic Arithmetic
All numeric types support addition, subtraction, multiplication, and
division using the operators +, -, *, and /.
The modulus operator % is available only for integer types.
For floating-point types, arithmetic follows IEEE 754 semantics and never raises an exception—operations that overflow or are undefined produce infinity or NaN instead.
Arithmetic Exceptions
Some arithmetic operations can raise exceptions on integer types:
+,-,*, and unary negation raise an exception if the result overflows foruint Norsint N. Operations onintnever overflow./and%raise an exception on division by zero. They can also raise an exception onsint Nwhen dividing the minimum value by -1 (as the result overflows).<<raises an exception if the shift amount exceeds 4095 (when the first operand is of typeint).
Arithmetic exceptions cannot be caught and will result in the parser terminating with a parse error.
Bitwise Operations
DaeDaLus also supports shift operations << and >>.
These operations are overloaded and can be used on all numeric types,
with the restriction that the inputs and the outputs must be of the
same type. The shift amount is a value of type uint 64.
Unsigned integers may also be treated as bit-vectors, and support various bitwise operations:
complement:
~bitwise exclusive-or
.^.bitwise-or
.|.bitwise-and
.&..
Unsigned numbers can also be appended to other numbers via the
# and <# operator. To see the difference between the two,
consider two bitvectors (x : uint A) and (y : uint B).
The result of x # y is a bitvector of type A + B with
x in the more significant bits, and y in the less significant bits.
The result of x <# y is a bitvector of type A that contains
x # y but truncated to the A less significant bits.
Floating-Point Operations
Comparisons on floating-point types follow IEEE 754 semantics:
comparisons involving NaN return false, except for != which
returns true when either operand is NaN.
The following predicates test for special IEEE 754 values:
Predicate |
Description |
|---|---|
|
True if the value is NaN |
|
True if the value is positive or negative infinity |
|
True if the value is denormalized |
|
True if the value is negative zero |
The following operations reinterpret the bits of an integer as a floating-point value (a bitcast, not a numeric conversion):
Operation |
Description |
|---|---|
|
Reinterpret 32 bits as a |
|
Reinterpret 64 bits as a |
Coercions Involving Floating-Point Types
Integer types may be coerced to floating-point types. The coercion
is lossless (as) only if the integer is exactly representable;
otherwise use as! (which may round) or as? (which fails if
rounding would occur).
Coercion from float to double is always exact. Coercion from
double to float may lose precision; use as? to detect this.
When coercing from a floating-point type to an integer type using as!,
the value is truncated toward zero and clamped to the target range:
Value |
|
|
|
|---|---|---|---|
NaN |
0 |
0 |
0 |
Positive infinity |
0 |
2^N - 1 |
max representable |
Negative infinity |
0 |
0 |
min representable |
Normal values |
truncated |
clamped to [0, 2^N-1] |
clamped to range |
The as? coercion succeeds only when the conversion is exact (i.e.,
the floating-point value is an integer that fits in the target type).
maybe type
DaeDaLus supports the special polymorphic type maybe A, which has possible
values nothing and just x, for some value, x of type A.
The parser Optional P will try to parse the input using P and produce
a maybe value. If P succeeds with result x then
Optional P will succeed with just x, and if P fails, then
Optional P will succeed with nothing.
def MaybeLetter = Optional $[ 'A'..'Z' ]
To examine values of the maybe type you may use
Guards or Case.
Arrays
The type of arrays containg elements of type T is [T].
-- Array literals
[] -- empty array
[1,2,3] -- array with 3 elements
"Hello" -- array with 5 elements
-- Get the element at the given array index
-- This is a parser, which fails if the index is out of bounds
Index (a : [?a]) (i : uint 64) : ?a
-- Length of an array
length (a : [?a]) : uint 64
To visit all elements in array you may use a for loop for loops.
Numeric Ranges
The rangeUp and rangeDown operations produce arrays of numbers:
rangeUp end -- [0, 1, ..., end-1]
rangeUp start end -- [start, start+1, ..., end-1]
rangeUp start end step -- [start, start+step, ...] while < end
rangeDown start -- [start, start-1, ..., 1]
rangeDown start end -- [start, start-1, ..., end+1]
rangeDown start end step -- [start, start-step, ...] while > end
The step must be a positive number; a non-positive step raises an exception. Some examples:
rangeUp 5 -- [0, 1, 2, 3, 4]
rangeUp 10 20 -- [10, 11, 12, ..., 19]
rangeUp 10 20 3 -- [10, 13, 16, 19]
rangeDown 5 -- [5, 4, 3, 2, 1]
rangeDown 20 10 -- [20, 19, 18, ..., 11]
rangeDown 20 10 3 -- [20, 17, 14, 11]
Warning
These operations construct the entire array in memory. They are not lazy iterators, so avoid using them for very large ranges.
Array Builders
A builder is a datastructure that helps build arrays.
To build an array, start with the empty builder builder and use
emit to add elements to the builder. Once all elements have been
added, you may use build to convert the builder to an array.
-- empty builder
builder : builder ?a
emit (front : builder ?a) (back : ?a) : builder ?a
-- Add an array of element to the end of the builder
emitArray (front : builder ?a) (back : [?a]) : builder ?a
-- Add a builder at the end of another builder
emitBuilder (front : builder ?a) (back : builder ?a) : builder ?a
-- Turn a builder into an array
build (b : builder ?a) : [?a]
Association Maps
The type of association maps with keys of type K and elements of type
T is [ K -> T ].
-- An empty map
empty : [ ?k -> ?v ]
-- Insert an element in a map.
-- The element is replaced, if it was already present.
insert (key : ?k) (value : ?v) (m : [ ?k -> ?v ]) : [ ?k -> ?v ]
-- Insert an element in a map.
-- This is a parser which fails if the element was already in the map.
Insert (key : ?k) (value : ?v) (m : [ ?k -> ?v ]) : [ ?k -> ?v ]
-- Look up an element in the map.
lookup (key : ?k) (m : [ ?k -> ?v ]) : maybe ?v
-- Look up an element in the map.
-- This is a parser which fails if the element is not in the map.
Lookup (key : ?k) (m : [ ?k -> ?v ]) : ?v
To visit all elements of an association map you may use a
for loop for loops.
Streams
The type stream is for values representing streams of data that
can be parserd by a parser. See Stream Manipulation for more examples
of how to manipualte the parser’s stream.
-- Get the current stream for the parser
GetStream : stream
-- Restrict a stream to the first `n` bytes.
-- Will fail if the stream does not have enough bytes
Take (n : uint 64) (s : stream) : stream
-- Restrict a stream to at most `n` bytes.
-- The resulting stream might be shorter if there are not
-- enough bytes
take (n : uint 64) (s : stream) : stream
-- Advance a stream by `n` bytes.
-- Will fail if the stream does not have enough bytes
Drop (n : uint 64) (s : stream) : stream
-- Make a stream with the given name and bytes to parser
arrayStream (name : [uint 8]) (data : [uint 8]) : stream
-- Get the bytes associates with a stream as an array
bytesOfStream (s : stream) : [uint 8]