# Filespec compiler # Takes in fspec source code and outputs bytecode for further processing include 'expr.lm' context fspec enum_counter:int void enum_inc() { enum_counter = enum_counter + 1 } void enum_set(v:int) { enum_counter = v } int enum_get() { return enum_counter } context primitive token TYPE_SIGN / [us] / token TYPE_BITS / [1-9][0-9]* / int strtoull(a:str, b:int) = c_strtoull def type signed:bool bits:int [TYPE_SIGN TYPE_BITS] { lhs.signed = ($r1 == 's') lhs.bits = strtoull($r2, 10) } end context container context enum lex ignore / '//' [^\n]* '\n' | space+ / literal `= `, `{ `} end literal `enum int const_int_expr(expr:collapser::collapsed) { if (!expr || !expr.result.value || !expr.result.value.number) reject return expr.result.value.number.value } def declaration value:int [name:reference::variable::type `= expr::enum::type] { lhs.value = const_int_expr(r3.collapsed) enum_set(lhs.value) enum_inc() } | [name:reference::variable::type] { lhs.value = enum_get() enum_inc() } def item [decl:declaration `, item] | [decl:declaration] def type name:str items:item+ select:expr::paren::type [type:`enum WS+ reference::variable::type? `{ item+ `}] { for n:reference::variable::type in child(r3) lhs.name = $n lhs.items = r5 enum_set(0) } end context strukt # <- struct is taken :( lex ignore / '//' [^\n]* '\n' | space+ / literal `{ `} end literal `struct def item [data:declaration::type] def type name:str items:item+ select:expr::paren::type [type:`struct WS+ reference::variable::type? `{ item+ `}] { for n:reference::variable::type in child(r3) lhs.name = $n lhs.items = r5 } end context select lex ignore / '//' [^\n]* '\n' | space+ / literal `( `) `{ `} `* end literal `select def item [`* `) data:declaration::type] | [expr:expr::paren::type `) data:declaration::type] def type name:str items:item+ # BUG: marking item+ with items: in the match below causes weird behaviour select:expr::paren::type [type:`select `( expr::paren::type `) `{ item+ `}] { lhs.select = r3 lhs.items = r6 } end def type [data:enum::type] | [data:strukt::type] | [data:select::type] end context declaration lex ignore / '//' [^\n]* '\n' | space+ / literal `: `[ `] `| `; token VISUAL / 'nul' | 'dec' | 'hex' | 'str' / token ENDIANESS / 'le' | 'be' / end literal `enum `struct def endianess [name:ENDIANESS] def visual [name:VISUAL] def filter [`| function:reference::function::type] def subscript [`[ expr:expr::bracket::type `: slice:expr::bracket::type `]] | [`[ expr:expr::bracket::type `]] def extra # if set, this field has trivial length, otherwise need to read subscripts length:collapser::collapsed [subscript:subscript* filter:filter* visual:visual? endianess:endianess?] { f:str = '' has_slice:bool for l:subscript in repeat(r1) { if (l.slice) { f = '' has_slice = true break } if (f != '') f = f + '*' if (l.expr.collapsed.result.value) { f = f + '(' + $l.expr.collapsed.result.value + ')' } else { f = f + '(' + $l.expr.collapsed + ')' } } if (f == '' && !has_slice) f = '1' if (f != '') lhs.length = collapser::stream(f) } def type # enum name name ; [cref:`enum WS+ parent:reference::variable::type WS+ primitive:primitive::type WS+ name:reference::variable::type extra:extra `;] # struct name name ; | [cref:`struct WS+ parent:reference::variable::type WS+ name:reference::variable::type extra:extra `;] # name ; | [primitive:primitive::type WS+ name:reference::variable::type extra:extra `;] # select ((thing)) { ... } ; INVALID | [container::select::type extra `;] commit { reject } # select ((thing)) { ... } name ; INVALID | [container::select::type primitive::type WS+ reference::variable::type extra `;] commit { reject } # struct (optional) { ... } name ; INVALID | [container::strukt::type primitive::type WS+ reference::variable::type extra `;] commit { reject } # enum (optional) { ... } name ; | [container:container::type primitive:primitive::type WS+ name:reference::variable::type extra:extra `;] # select ((expr)) { ... } name ; # struct (optional) { ... } name ; | [container:container::type name:reference::variable::type extra:extra `;] # (enum|struct) name { ... } <(le|be)>; | [container:container::type endianess:endianess? `;] end def source [items:declaration::type*] commit source stream(s:stream) { c:fspec = new fspec() c->enum_counter = 0 return parse source(c)[s] } end source:fspec::source = fspec::stream(stdin) if (!source) { print(error) exit(1) } struct scope endianess:str names:map> end global g_scopes:list = new list() void push_scope(endianess:str) { s:scope = new scope() s->names = new map>() s->endianess = endianess g_scopes->push_head(s) } void pop_scope() { g_scopes->pop_head() } any lookup_no_error(type:str, name:str) { for s:scope in g_scopes { cmap:map = s->names->find(type) if (cmap) { var:any = cmap->find(name) if (var) return var } } return nil } any insert(type:str, name:str, var:any) { if (!name) return var # if (type != 'variable' && lookup_no_error(type, name)) { print('`', type, ' ', name, '` is already declared as a `', type, '` in current scope!\n') exit(1) } cmap:map = g_scopes->top->names->find(type) if (!cmap) { cmap = new map() } else if (cmap->find(name)) { print('`', type, ' ', name, '` is already declared as a `', type, '` in current scope!\n') exit(1) } cmap->insert(name, var) g_scopes->top->names->insert(type, cmap) return var } any lookup(type:str, name:str) { r:any = lookup_no_error(type, name) if (!r) { print('`', type, ' ', name, '` is not declared in this or outer scope!\n') exit(1) } return r } str container_name_str(s:str) { if (!s) return '' return s } str signed_str(s:bool) { if (s) return 'signed' return 'unsigned' } str endianess_str(s:str) { if (s == 'be') return 'big-endian' else if (s == 'le') return 'little-endian' print('something went wrong!\n') exit(1) } str endianess_from_decl(d:fspec::declaration::type) { endianess:str = g_scopes->top->endianess if (d.endianess) for e:fspec::declaration::endianess in child(d.endianess) endianess = $e if (d.extra && d.extra.endianess) for e:fspec::declaration::endianess in child(d.extra.endianess) endianess = $e return endianess } void print_declaration(d:fspec::declaration::type) { insert('variable', $d.name, d) print('variable `', $d.name, '` is ') c:fspec::container::type if (d.cref) c = lookup($d.cref, $d.parent) else c = d.container if (c) { print('`', c.data.type, ' ', container_name_str(c.data.name), '` ') if (c.data.select) print('with expression `', $c.data.select, '` ') } if (d.primitive) print(d.primitive.bits, ' bits and ', signed_str(d.primitive.signed)) print('\n') if (d.extra) { if (d.extra.length) { if (!d.extra.length.result.value || d.extra.length.result.value.reference) { print(' it has a variable length that needs to be computed with formula `', $d.extra.length, '`\n') } else { if (d.extra.length.result.value.number) { print(' it has a constant length of ', $d.extra.length.result.value, '\n') } else if (d.extra.length.result.value.string) { print(' its length will increase until pattern `', $d.extra.length.result.value.string.raw, '` has been read from stream\n') } } } else { print(' the subscripts contain slices, and thus needs some runtime loops to be computed\n') } for f:fspec::declaration::filter in repeat(d.extra.filter) print(' it needs to be filtered with `', $f.function, '`\n') for v:fspec::declaration::visual in child(d.extra.visual) print(' and it should be visualized as `', $v.name, '`\n') print(' the field is in ', endianess_str(endianess_from_decl(d)), ' byte order\n') } } void walk(d:fspec::declaration::type) { if (!d.container) { print('something went wrong!\n') exit(1) } s:fspec::container::type = d.container insert($s.data.type, s.data.name, s) if ($s.data.type == 'enum') { for i:fspec::container::enum::item in repeat(s.data.items) { print('constant `', $i.decl.name, '` is ', $i.decl.value, '\n') insert('variable', $i.decl.name, i) } } else if ($s.data.type == 'struct') { push_scope(endianess_from_decl(d)) for i:fspec::container::strukt::item in repeat(s.data.items) { if (i.data.container) walk(i.data) else print_declaration(i.data) } pop_scope() } else if ($s.data.type == 'select') { push_scope(endianess_from_decl(d)) print('━━━━ start of (', $s.data.select, ') ━━━━\n') for i:fspec::container::select::item in repeat(s.data.items) { if (i.expr) print('━━━━ (', $i.expr, ')\n') else print('━━━━ DEFAULT\n') if (i.data.container) walk(i.data) else print_declaration(i.data) } print('━━━━ end of (', $s.data.select, ') ━━━━\n') pop_scope() } } push_scope('le') for d:fspec::declaration::type in repeat(source.items) walk(d) pop_scope()