* Parameterized types Types should be instantiable at decoding time, the example use-case being the endianness field in the ELF file header. - The syntax currently knows "parameterized" types ~array~, ~i~ and ~u~, where the type parameters must be constants, or, with ~array~, a field reference for the length is permissible. - For the ELF use-case, we need to be able to parameterize ~i~ and ~u~ also with an endianness, e.g. ~(u 32 le)~. The default should stay ~be~, and add ~native~ for good measure. Shorthand versions like ~(u32 native)~ should be available. In the decoding phase, each field's type is concrete, i.e. its type is only parameterized by constants. Before decoding a field, its type must be evaluated: - For nullary types, this is essentially an identity transform. - For parameterized types, each argument is evaluated with access to the field environment, and a ~lexpr::Value~ produced. This ~lexpr::Value~ is then fed to the type's constructor, which produces concrete type. The instantiated types need to be accumulated in the decoding context, and can be cached, as type constructors are required to be pure. This implies that concrete types can by just a ~TypeId~, which refers to a ~DecodedType~. - For user-defined parameterized types, the type of its fields (potentially) depends on the type parameters, so these must be available when evaluating a field's type. To deal with the ELF endianness: - Allow putting "properties" on nodes, which are a set of symbol to value associations. - Allow expressing references to these properties (of already-parsed fields) in type parameter expressions. Proposed built-in parameterized types: - ~i~ *width* [*endianness*] - ~u~ *width* [*endianness*] - ~array~ *length* *element-type* ** Custom types Each custom type (i.e. root node) may be potentially parameterized. Each parameter is positional, and given a name at the node definition, which may be referenced in type parameter expressions. * The various ~Type~ | Level | Fields | Custom Types | Type Parameters | |-----------+--------+--------------+------------------| | Syntactic | Named | Named | Expression | | Forest | Named | NodeId | Expression | | Tree | Index | ExecNodeId | Expression | | FieldEnv | - | TypeId ? | - | #+begin_src rust enum DecodedType { U(u8, Endianness), I(u8, Endianness), Array(usize, TypeId), Shaped(ExecNodeId, Vec), } #+end_src * Scoping - References to type parameters are resolved lexically. - References to field names are resolved when preparing a parse tree. The actual field they reference depends on the path the node is reached through, so this has a certain "dynamically scoped" feel to it, but the field references are known *before* decoding, so they are not actually dynamically scoped.