#! GRAMMAR: #! * type ..... -> identifier #! * assignment -> :: | := #! * literal .. -> int | float | string #! * expr ..... -> identifier | literal #! * decl ..... -> identifier [type] assignment expr #! * stmt ..... -> decl import "@std/debug" import "@source/lexer" hide(file) TokenId :: alias lexer.TokenId NodeId :: distinct u32 ExtraId :: distinct u32 NO_ID_NODE :: maxval!(NodeId) NO_ID_EXTRA :: maxval!(ExtraId) # unsafe access on its own; NodeKind serves as a the tag # that determines the variant. NodeData :: union { token_id TokenId node_id NodeId extra_id ExtraId } Node :: struct { kind NodeKind main_token TokenId data0 NodeData = NodeData{ node_id = NO_ID_NODE } data1 NodeData = NodeData{ node_id = NO_ID_NODE } } NodeKind :: enum { # LITERALS: # * main_token: token literal literal_int literal_float literal_string # IDENTIFIER EXPR: # * main_token: token literal expr_identifier expr_unary expr_binary # DECL: # * main_token: symbol name (identifier) # * data0: ExtraId # + extra[data0]: NodeId - type identifier node # + extra[data0 + 1]: NodeId - initializer expression # * data1: TokenId of either `::` or `:=` indicating mutability stmt_decl invalid } node_id proc(idx uint) NodeId { debug.assert(u64(idx) < u64(NO_ID_NODE)) return NodeId(idx) } extra_id proc(idx uint) ExtraId { debug.assert(u64(idx) < u64(NO_ID_EXTRA)) return ExtraId(idx) }