#! 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 @hide:file NO_TOKEN :: alias lexer.NO_TOKEN NodeId :: distinct u32 ExtraId :: distinct u32 NO_NODE :: maxval!(NodeId) NO_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 = NO_TOKEN data0 NodeData = NodeData{ node_id = NO_NODE } data1 NodeData = NodeData{ node_id = NO_NODE } } NodeKind :: enum { # LITERALS: # * main_token: token literal literal_int literal_float literal_string # IDENTIFIER EXPR: # * main_token: token literal expr_identifier # UNARY: # * main_token: operator # * data0: NodeId - expression expr_unary # BINARY: # * main_token: operator # * data0: NodeId - left-hand side # * data1: NodeId - right-hand side 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 # PACKAGE: # * data0: ExtraId # * data1: ExtraId # # extra[data0..data1]: NodeId - all statement node ids in package package invalid } node_id proc(idx uint) NodeId { debug.assert(u64(idx) < u64(NO_NODE)) return NodeId(idx) } maybe_node_id proc(idx uint) ?NodeId { if (u64(idx) >= u64(NO_NODE)) return null return NodeId(idx) } extra_id proc(idx uint) ExtraId { debug.assert(u64(idx) < u64(NO_EXTRA)) return ExtraId(idx) }