├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── examples └── skill_tree.jai └── module.jai /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.build 2 | *.exe 3 | *.pdb 4 | *.dot 5 | *.exp 6 | *.db 7 | *.db-shm 8 | *.db-wal 9 | *.dll 10 | !lib/**/*.dll 11 | *.lib 12 | !lib/**/*.lib 13 | .vs 14 | *.rdbg 15 | **/bin 16 | *.rdi 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Adam Rezich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Enumerated Array module 2 | ======================= 3 | 4 | A generic fixed-size array that can be indexed into with an `enum`. 5 | 6 | ```jai 7 | Bird :: enum { DOVE; FALCON; HAWK; } 8 | bird_counts: Enumerated_Array(Bird, int); 9 | ``` 10 | 11 | The size of the array will be set such that it can fit all possible unique 12 | values of the provided `enum`. 13 | 14 | You can index into an `Enumerated_Array` using its `enum`, but only if you fully 15 | qualify it (due to language restrictions): 16 | ```jai 17 | number_of_falcons := bird_counts[Bird.FALCON]; 18 | bird_counts[Bird.HAWK] += 1; 19 | ``` 20 | For very verbose `enum` names, though, this can become unwieldy. To help with 21 | this, `Enumerated_Array`'s backing array is `#place`d "onto" a `using`'d anonymous 22 | `struct` containing members matching each `enum` name -- allowing you to access 23 | the array elements more concisely: 24 | ```jai 25 | number_of_falcons := bird_counts.FALCON; 26 | bird_counts.HAWK += 1; 27 | ``` 28 | Unlike other implementations, `Enumerated_Array` correctly handles `enum`s with 29 | more than one name corresponding to a single value, as well as `enum`s that 30 | have "holes" between named values, and `enum`s whose lowest named value is 31 | nonzero. 32 | 33 | If more than one `enum` name corresponds to a single value, then the (`using`'d) 34 | values `struct` will contain a nested `union` containing all names for that value 35 | -- so you can access that element of the array "by member name" using any of 36 | the possible names that map to that value. 37 | 38 | For example, given the following `enum`: 39 | ```jai 40 | Thing :: enum { 41 | ZERO; 42 | FIRST :: ZERO; 43 | TWO :: 2; 44 | SIX :: 6; 45 | SEVEN; 46 | THREE :: 3; 47 | SPECIAL :: 420; 48 | EIGHT :: 8; 49 | NINE; 50 | LAST :: SPECIAL; 51 | FIRST_AGAIN :: FIRST; 52 | } 53 | ``` 54 | The contents of an `Enumerated_Array` using this `enum`, e.g. `Enumerated_Array(Thing, int)` will look like this: 55 | ```jai 56 | count :: 8; 57 | using values: struct { 58 | /* 0 */ union { ZERO, FIRST, FIRST_AGAIN: int; } 59 | /* 1 */ TWO: int; 60 | /* 2 */ THREE: int; 61 | /* 3 */ SIX: int; 62 | /* 4 */ SEVEN: int; 63 | /* 5 */ EIGHT: int; 64 | /* 6 */ NINE: int; 65 | /* 7 */ union { SPECIAL, LAST: int; } 66 | } 67 | #place values; 68 | data: [8] int; 69 | ``` 70 | 71 | A use-case for `Enumerated_Array` that many may find useful is for defining a constant “table” of constant data that is indexed into with an `enum`: 72 | ```jai 73 | Powerup_Kind :: enum { 74 | DOUBLE_DAMAGE; 75 | HASTE; 76 | INVISIBILITY; 77 | } 78 | Powerup_Definition :: struct { 79 | name: string; 80 | color: RGB; 81 | on_collect: (*Hero); 82 | } 83 | POWERUP_DEFINITIONS :: Enumerated_Array(Powerup_Kind, Powerup_Definition).{ 84 | 85 | DOUBLE_DAMAGE=.{ name="Double Damage", 86 | color=.{0,0,1}, 87 | on_collect=(using hero: *Hero) { 88 | stats.damage *= 2; 89 | } 90 | }, 91 | 92 | HASTE=.{ name="Haste", 93 | color=.{1,0,0}, 94 | on_collect=(using hero: *Hero) { 95 | stats.move_speed = MOVE_SPEED_MAX; 96 | } 97 | }, 98 | 99 | INVISIBILITY=.{ name="Invisibility", 100 | color=.{1,1,0}, 101 | on_collect=(using hero: *Hero) { 102 | is_invisible = true; 103 | } 104 | }, 105 | 106 | }; 107 | ``` 108 | In some other languages, one may be tempted to reach for some kind of hash table for this sort of lookup, even though it's a table of constant data. Here, though, `Enumerated_Array` is a natural fit for just such a situation. 109 | 110 | For more information, see the comments in [the module itself](module.jai). 111 | 112 | For an example use of the module, see [the “skill tree” example](examples/skill_tree.jai). 113 | -------------------------------------------------------------------------------- /examples/skill_tree.jai: -------------------------------------------------------------------------------- 1 | // Whether we're demoing a skill tree where different levels of the same skill 2 | // can have differing costs (true) or whether all levels of a given skill have 3 | // the same cost (false). 4 | SKILL_LEVELS_CAN_HAVE_DIFFERENT_COSTS :: true; 5 | 6 | Currency :: enum { CASH; SKULLS; } 7 | Currencies :: #type Enumerated_Array(Currency, int); 8 | 9 | Skill_Kind :: enum { 10 | TRIGGER_FINGER; 11 | BADASS; 12 | } 13 | Skill_Definition :: struct { 14 | name: string; 15 | description: string; 16 | 17 | #if SKILL_LEVELS_CAN_HAVE_DIFFERENT_COSTS { 18 | levels: [] Currencies; 19 | } else { 20 | levels: int; 21 | cost: Currencies; 22 | } 23 | 24 | level_up: (level: int); 25 | unlocks: [] Skill_Kind; 26 | initially_unlocked: bool; 27 | } 28 | 29 | #if SKILL_LEVELS_CAN_HAVE_DIFFERENT_COSTS { 30 | 31 | SKILL_DEFINITIONS :: Enumerated_Array(Skill_Kind, Skill_Definition).{ 32 | TRIGGER_FINGER=.{ name="Trigger Finger", 33 | description="Shoot 10\% faster per level.", 34 | levels=.[ 35 | .{ CASH=15 }, 36 | .{ CASH=30 }, 37 | .{ CASH=45 }, 38 | ], 39 | level_up=(level: int) { using state.stats; 40 | attack_speed_multiplier += .1; 41 | }, 42 | unlocks=.[ .BADASS ], 43 | initially_unlocked=true, 44 | }, 45 | BADASS=.{ name="Badass", 46 | description="Deal 10\% more damage per level.", 47 | levels=.[ 48 | .{ CASH=15 }, 49 | ], 50 | level_up=(level: int) { using state.stats; 51 | damage_multiplier += .1; 52 | } 53 | } 54 | }; 55 | 56 | } else { 57 | 58 | SKILL_DEFINITIONS :: Enumerated_Array(Skill_Kind, Skill_Definition).{ 59 | TRIGGER_FINGER=.{ name="Trigger Finger", 60 | description="Shoot 10\% faster per level.", 61 | levels=3, 62 | cost=.{ CASH=15 }, 63 | level_up=(level: int) { using state.stats; 64 | attack_speed_multiplier += .1; 65 | }, 66 | unlocks=.[ .BADASS ], 67 | initially_unlocked=true, 68 | }, 69 | BADASS=.{ name="Badass", 70 | description="Deal 10\% more damage per level.", 71 | levels=1, 72 | cost=.{ CASH=15 }, 73 | level_up=(level: int) { using state.stats; 74 | damage_multiplier += .1; 75 | } 76 | } 77 | }; 78 | 79 | } 80 | 81 | // Compile-time skill definition checks 82 | #run { for SKILL_DEFINITIONS { 83 | assert(!!it.level_up, 84 | "You forgot to include a level_up() procedure for %!", 85 | it_index 86 | ); 87 | }} 88 | 89 | // Always check the result of an invocation of this before invoking level_up() 90 | can_level_up :: (skill_kind: Skill_Kind) -> bool { 91 | // Can't level up if it's not unlocked 92 | if state.skill_levels[skill_kind] == -1 then return false; 93 | 94 | def := SKILL_DEFINITIONS[skill_kind]; 95 | 96 | // Can't level up if it's already max level 97 | #if SKILL_LEVELS_CAN_HAVE_DIFFERENT_COSTS { 98 | if state.skill_levels[skill_kind] == def.levels.count then return false; 99 | } else { 100 | if state.skill_levels[skill_kind] == def.levels then return false; 101 | } 102 | 103 | // Can't level up if you don't have the currencies necessary 104 | #if SKILL_LEVELS_CAN_HAVE_DIFFERENT_COSTS { 105 | cost := def.levels[state.skill_levels[skill_kind]]; // No +1 because levels is zero-indexed 106 | for state.currencies if it < cost[it_index] then return false; 107 | } else { 108 | for state.currencies if it < def.cost[it_index] then return false; 109 | } 110 | 111 | return true; 112 | } 113 | 114 | // Always check can_level_up() before invoking this 115 | level_up :: (skill_kind: Skill_Kind) { 116 | def := SKILL_DEFINITIONS[skill_kind]; 117 | 118 | #if SKILL_LEVELS_CAN_HAVE_DIFFERENT_COSTS { 119 | for def.levels[state.skill_levels[skill_kind]] state.currencies[it_index] -= it; // No +1 because levels is zero-indexed 120 | } else { 121 | for def.cost state.currencies[it_index] -= it; 122 | } 123 | 124 | if state.skill_levels[skill_kind] == 0 then for def.unlocks state.skill_levels[it] = max(state.skill_levels[it], 0); 125 | 126 | state.skill_levels[skill_kind] += 1; 127 | def.level_up(state.skill_levels[skill_kind]); 128 | } 129 | 130 | 131 | // Global game state 132 | state: struct { 133 | // -1 = locked, 0 = unlocked but unleveled, 1+ = unlocked and leveled 134 | skill_levels: Enumerated_Array(Skill_Kind, int); 135 | currencies: Currencies; 136 | stats: struct { 137 | attack_speed_multiplier: float = 1.0; 138 | damage_multiplier: float = 1.0; 139 | } 140 | } 141 | 142 | init :: () { using state; 143 | for SKILL_DEFINITIONS skill_levels[it_index] = ifx it.initially_unlocked then 0 else -1; 144 | } 145 | 146 | give :: (currency: Currency, amount: int) { state.currencies[currency] += amount; } 147 | take :: (currency: Currency, amount: int) { state.currencies[currency] -= amount; } 148 | 149 | #run { (#import "Compiler").set_build_options_dc(.{do_output=false}); 150 | 151 | init(); 152 | assert(!can_level_up(.TRIGGER_FINGER)); 153 | assert(!can_level_up(.BADASS)); 154 | 155 | give(.CASH, 100); 156 | assert( can_level_up(.TRIGGER_FINGER)); 157 | assert(!can_level_up(.BADASS)); 158 | 159 | if can_level_up(.TRIGGER_FINGER) then level_up(.TRIGGER_FINGER); 160 | 161 | assert( can_level_up(.TRIGGER_FINGER)); 162 | assert( can_level_up(.BADASS)); 163 | 164 | } 165 | 166 | #import "Basic"; 167 | #import,file "../module.jai"; // Enumerated_Array 168 | -------------------------------------------------------------------------------- /module.jai: -------------------------------------------------------------------------------- 1 | // @NOTE: This module has NOT been EXHAUSTIVELY tested -- there MAY be bugs. 2 | 3 | // A generic fixed-size array that can be indexed into with an enum. 4 | // 5 | // Bird :: enum { DOVE; FALCON; HAWK; } 6 | // bird_counts: Enumerated_Array(Bird, int); 7 | // 8 | // The size of the array will be set such that it can fit all possible unique 9 | // values of the provided enum. 10 | // 11 | // You can index into an Enumerated_Array using its enum, but only if you fully 12 | // qualify it (due to language restrictions): 13 | // 14 | // number_of_falcons := bird_counts[Bird.FALCON]; 15 | // bird_counts[Bird.HAWK] += 1; 16 | // 17 | // For very verbose enum names, though, this can become unwieldy. To help with 18 | // this, Enumerated_Array's backing array is #placed "onto" a using'd anonymous 19 | // struct containing members matching each enum name -- allowing you to access 20 | // the array elements more concisely: 21 | // 22 | // number_of_falcons := bird_counts.FALCON; 23 | // bird_counts.HAWK += 1; 24 | // 25 | // Unlike other implementations, Enumerated_Array correctly handles enums with 26 | // more than one name corresponding to a single value, as well as enums that 27 | // have "holes" between named values, and enums whose lowest named value is 28 | // nonzero. 29 | // 30 | // If more than one enum name corresponds to a single value, then the (using'd) 31 | // values struct will contain a nested union containing all names for that value 32 | // -- so you can access that element of the array "by member name" using any of 33 | // the possible names that map to that value. 34 | 35 | 36 | // Example/Tests -- set false to true below and compile this file to run 37 | #if false #run { (#import "Compiler").set_build_options_dc(.{do_output=false}); 38 | Thing :: enum { 39 | ZERO; 40 | FIRST :: ZERO; 41 | TWO :: 2; 42 | SIX :: 6; 43 | SEVEN; 44 | THREE :: 3; 45 | SPECIAL :: 420; 46 | EIGHT :: 8; 47 | NINE; 48 | LAST :: SPECIAL; 49 | FIRST_AGAIN :: FIRST; 50 | } 51 | stuff: Enumerated_Array(Thing, int); 52 | // The interface you will use to interact with stuff will look like this: 53 | // count :: 8; 54 | // 55 | // using values: struct { 56 | // /* 0 */ union { ZERO, FIRST, FIRST_AGAIN: int; } 57 | // /* 1 */ TWO: int; 58 | // /* 2 */ THREE: int; 59 | // /* 3 */ SIX: int; 60 | // /* 4 */ SEVEN: int; 61 | // /* 5 */ EIGHT: int; 62 | // /* 6 */ NINE: int; 63 | // /* 7 */ union { SPECIAL, LAST: int; } 64 | // } 65 | // #place values; 66 | // data: [8] int; 67 | 68 | // Assignment inside of a for-loop 69 | for * stuff it.* = 1; 70 | for 0..stuff.count-1 assert(stuff.data[it] == 1); 71 | 72 | // Assignment using subscripted qualified enum 73 | stuff[Thing.TWO] = 22; 74 | assert(stuff.data[1] == 22); 75 | 76 | // Assignment using member 77 | stuff.SEVEN = 7777777; 78 | assert(stuff.data[4] == 7777777); 79 | 80 | // Assignment using subscripted enum from an array of said enum 81 | NICE_THINGS :: Thing.[ .SIX, .NINE ]; 82 | for NICE_THINGS stuff[it] = 69; 83 | assert(stuff.data[3] == 69); 84 | assert(stuff.data[6] == 69); 85 | 86 | // Assignment using member, with multiple members unioned to the same value 87 | stuff.SPECIAL = 420420; 88 | assert(stuff.LAST == 420420); 89 | assert(stuff.data[7] == 420420); 90 | 91 | // Ditto 92 | stuff.FIRST_AGAIN = 108; 93 | assert(stuff.ZERO == 108); 94 | assert(stuff.data[0] == 108); 95 | 96 | for stuff log("% %", it_index, it); 97 | log("---"); 98 | 99 | // Assignment using a values literal 100 | other_stuff := Enumerated_Array(Thing, float).{ 101 | FIRST =.1, 102 | TWO =.2, 103 | SEVEN =.7, 104 | SPECIAL=.420, 105 | }; 106 | for other_stuff log("% %", it_index, it); 107 | log("---"); 108 | 109 | // Assignment using an data literal 110 | more_stuff := Enumerated_Array(Thing, string).{data=.[ 111 | "Hello", "Sailor", "This", "Is", "Just", "A", "Test", "Goodbye" 112 | ]}; 113 | for more_stuff log("% %", it_index, it); 114 | log("---"); 115 | 116 | // Offset test 117 | even_more_stuff := Enumerated_Array(enum { FOO :: 47; BAR :: 48; }, int).{ 118 | FOO=100, 119 | BAR=10000 120 | }; 121 | for even_more_stuff log("% %", it_index, it); 122 | log("---"); 123 | 124 | // Empty enum test 125 | Empty :: enum {} 126 | nothing: Enumerated_Array(Empty, int); 127 | for nothing assert(false, "This should not assert."); 128 | log("---"); 129 | 130 | // Non-s64 enum test 131 | Elf_Type :: enum u16 { 132 | NONE :: 0x00; 133 | REL :: 0x01; 134 | EXEC :: 0x02; 135 | DYN :: 0x03; 136 | CORE :: 0x04; 137 | LOOS :: 0xFE00; 138 | HIOS :: 0xFEFF; 139 | LOPROC :: 0xFF00; 140 | HIPROC :: 0xFFFF; 141 | } 142 | elves := Enumerated_Array(Elf_Type, string).{data=.[ 143 | "Hermey", "Buddy", "Legolas", "Drizzt", "Dobby", "Ernie", "Tyrande", "Tanis", "Puck" 144 | ]}; 145 | for elves log("% %", it_index, it); 146 | log("---"); 147 | 148 | // Just for fun 149 | Currency :: enum { OLD_COINS; BUCKS; SIMOLEANS; } 150 | Loot_Bag :: Enumerated_Array(Currency, int); 151 | my_sack_of_loot := Loot_Bag.{ 152 | OLD_COINS = 69, 153 | BUCKS = 420, 154 | SIMOLEANS = 108, 155 | }; 156 | CURRENCY_TO_GOLD_EXCHANGE_RATES :: Enumerated_Array(Currency, float).{ 157 | OLD_COINS = 0.5 , 158 | BUCKS = 1 , 159 | SIMOLEANS = 4.25 160 | }; 161 | gold_value_of_my_sack_of_loot := 0; 162 | for my_sack_of_loot 163 | gold_value_of_my_sack_of_loot += xx (cast(float) it * CURRENCY_TO_GOLD_EXCHANGE_RATES[it_index]); 164 | assert(gold_value_of_my_sack_of_loot == 913); 165 | } 166 | 167 | 168 | Enumerated_Array :: struct( 169 | ENUM: Type, // The enum type 170 | T: Type, // The value type 171 | USE_INDEX_LOOKUP_TABLE := true // Whether or not to use a lookup table when 172 | // indexing into the array. If true (default), a constant array sized to fit 173 | // the highest value in ENUM will be generated, to provide speedy lookups. 174 | // 175 | // For very large, very sparse ENUMs, you might want to set this to false 176 | // instead. 177 | // 178 | // If ENUM is zero-indexed and contiguous, then this setting is ignored, as 179 | // no lookup table is required, and ENUM itself can be used to directly 180 | // index into the array. 181 | ) #modify { 182 | info := (cast(*Type_Info_Enum) ENUM); 183 | if info.type != .ENUM then return false, 184 | "Enumerated_Array's ENUM must be an enum."; 185 | if info.enum_type_flags & .FLAGS then return false, 186 | "Enumerated_Array's ENUM must be an enum (not an enum_flags)."; 187 | return true; 188 | } { 189 | // Main interface: 190 | // count: Constant integer representing the number of elements in data. 191 | // This is the same as the number of unique values in ENUM. 192 | // data: Fixed-size array of T, sized to fit all unique ENUM values. 193 | // (values:) Anonymous struct containing T members and/or unions 194 | // containing only T members -- one member corresponding with 195 | // each ENUM name. data is #placed "onto" this struct, providing 196 | // an alternate way of accessing the same T values. The struct 197 | // is also using'd, so you don't need to qualify access to it 198 | // with "values." 199 | 200 | // Common array convention: "count" 201 | #if IS_CONTIGUOUS 202 | then count :: #run type_info(ENUM).values.count; 203 | else count :: VALUE_MAPPINGS.count; 204 | 205 | // Alternate way to access data: a member for each ENUM value 206 | #if count then using values: struct { #insert -> string { builder: String_Builder; 207 | #if IS_CONTIGUOUS { 208 | for type_info(ENUM).names print(*builder, "%1%2", 209 | it, ifx it_index < count-1 then ", " 210 | ); 211 | append(*builder, ": T;\n"); 212 | } else for mapping: VALUE_MAPPINGS { 213 | if mapping.names.count > 1 then append(*builder, "union {\n"); 214 | for mapping.names print(*builder, "%1%2", 215 | it, ifx it_index < mapping.names.count-1 then ", " 216 | ); 217 | append(*builder, ": T;\n"); 218 | if mapping.names.count > 1 then append(*builder, "}\n"); 219 | } 220 | return builder_to_string(*builder); 221 | }} 222 | 223 | // Common array convention: "data" 224 | #if count 225 | then #overlay (values) data: [count] T; 226 | else data: [count] T; 227 | 228 | 229 | // Internal interface: 230 | // IS_CONTIGUOUS: True if all ENUM values are contiguous; false 231 | // otherwise. Only used at compile time. 232 | // INDEX_OFFSET: First value of ENUM. Used to optimize contiguous ENUM 233 | // values when the first value is nonzero. Used at runtime 234 | // if nonzero. 235 | // Value_Mapping: struct mapping the internal type of ENUM to an array of 236 | // names. Only exists if ENUM's values are not contiguous. 237 | // Used in VALUE_MAPPINGS (below). Only used at compile 238 | // time. 239 | // VALUE_MAPPINGS: Constant fixed-size array containing one Value_Mapping 240 | // for each unique value in ENUM. Only exists if ENUM's 241 | // values are not contiguous. Used at compile time to 242 | // build the values struct. Used at runtime to linear- 243 | // search index into data, only if USE_INDEX_LOOKUP_TABLE 244 | // is false (which it is *not* by default). 245 | // INDEX_MAPPINGS: Constant fixed-size array sized to fit the highest 246 | // ENUM value. Only exists if ENUM's values are not 247 | // contiguous *and* USE_INDEX_LOOKUP_TABLE is true (which 248 | // it is by default). May become very large for incredibly 249 | // spare ENUMs. Used at runtime. 250 | 251 | IS_CONTIGUOUS :: #run -> bool { 252 | using _ := type_info(ENUM); 253 | for 0..values.count-2 if values[it+1] != values[it]+1 then return false; 254 | return true; 255 | } 256 | INDEX_OFFSET :: #run -> s64 { 257 | using _ := type_info(ENUM); 258 | return ifx count then values[0]; 259 | } 260 | 261 | #if !IS_CONTIGUOUS { 262 | Value_Mapping :: struct { 263 | VALUE_TYPE :: #run,stallable -> Type { 264 | return (cast(*Type) *(cast(*Type_Info) type_info(ENUM).internal_type)).*; 265 | }; 266 | value: VALUE_TYPE; 267 | names: [] string; 268 | } 269 | VALUE_MAPPINGS :: #run,stallable -> [] Value_Mapping { 270 | mappings: [..] Value_Mapping; 271 | names, values := type_info(ENUM).names, type_info(ENUM).values; 272 | for values { 273 | value := cast(Value_Mapping.VALUE_TYPE) it; 274 | mapping_exists := false; 275 | for mapping: mappings if mapping.value == value { mapping_exists = true; break; } 276 | if mapping_exists then continue; 277 | mapping_names: [..] string; 278 | for names if values[it_index] == value then array_add(*mapping_names, it); 279 | array_add(*mappings, .{ value, mapping_names }); 280 | } 281 | quick_sort(mappings, (a,b) => a.value-b.value); 282 | return mappings; 283 | } 284 | #if USE_INDEX_LOOKUP_TABLE then INDEX_MAPPINGS :: #run,stallable -> [] int { 285 | COUNT :: #run enum_highest_value(ENUM)+1; 286 | mappings: [COUNT] int; 287 | for 0..COUNT-1 mappings[it] = -1; 288 | for VALUE_MAPPINGS mappings[it.value] = it_index; 289 | return mappings; 290 | } 291 | } 292 | } 293 | 294 | for_expansion :: ( 295 | array: *Enumerated_Array, 296 | body: Code, // No remove 297 | flags: For_Flags // .REVERSE, .POINTER 298 | ) #expand { 299 | for <= cast(bool)(flags & .REVERSE) index: 0..array.count-1 { 300 | #if array.IS_CONTIGUOUS && array.INDEX_OFFSET == 0 then // Direct index 301 | `it_index := cast(array.ENUM) index; 302 | else #if array.IS_CONTIGUOUS then // Offset index 303 | `it_index := cast(array.ENUM) (index + array.INDEX_OFFSET); 304 | else // Lookup table 305 | `it_index := cast(array.ENUM) array.VALUE_MAPPINGS[index].value; 306 | 307 | `it := #ifx flags & .POINTER 308 | then *array.data[index] 309 | else array.data[index] 310 | ; 311 | 312 | #insert(remove=#assert false "This for_expansion does not support remove.") body; 313 | } 314 | } 315 | 316 | operator *[] :: ( 317 | using array: *Enumerated_Array, 318 | index: array.ENUM 319 | ) -> *array.T { 320 | actual_index := -1; 321 | 322 | #if IS_CONTIGUOUS && INDEX_OFFSET == 0 then // Direct index 323 | actual_index = xx index; 324 | else #if IS_CONTIGUOUS then // Offset index 325 | actual_index = xx index - INDEX_OFFSET; 326 | else #if USE_INDEX_LOOKUP_TABLE then // Lookup table 327 | actual_index = ifx index < INDEX_MAPPINGS.count then INDEX_MAPPINGS[index] else -1; 328 | else for VALUE_MAPPINGS if it.value == xx index { // Linear search 329 | actual_index = it_index; break; 330 | } 331 | 332 | assert(actual_index != -1, 333 | "Invalid enum index % in %", 334 | index, type_of(array.*) 335 | ); 336 | 337 | return *data[actual_index]; 338 | } 339 | 340 | // Helper procedure -- only makes sense if T can be summed 341 | sum :: ( 342 | using array: Enumerated_Array 343 | ) -> array.T { 344 | total: T; 345 | for data total += it; 346 | return total; 347 | } 348 | 349 | #scope_file //====================================================================================== 350 | 351 | #import "Basic"; 352 | #import "Sort"; 353 | --------------------------------------------------------------------------------