diff --git a/include/prism/internal/constant_pool.h b/include/prism/internal/constant_pool.h index fa2be783f5..459da08056 100644 --- a/include/prism/internal/constant_pool.h +++ b/include/prism/internal/constant_pool.h @@ -4,6 +4,7 @@ #include "prism/constant_pool.h" #include "prism/arena.h" +#include "prism/compiler/inline.h" #include @@ -80,8 +81,42 @@ void pm_constant_id_list_init_capacity(pm_arena_t *arena, pm_constant_id_list_t /* Insert a constant id into a list of constant ids at the specified index. */ void pm_constant_id_list_insert(pm_constant_id_list_t *list, size_t index, pm_constant_id_t id); -/* Checks if the current constant id list includes the given constant id. */ -bool pm_constant_id_list_includes(pm_constant_id_list_t *list, pm_constant_id_t id); +/* + * Mix the bits of a constant id so that ids which are close together, as + * sequentially assigned ids are, land in unrelated slots of a hash table. + */ +static PRISM_INLINE uint32_t +pm_constant_id_hash(pm_constant_id_t id) { + id = ((id >> 16) ^ id) * 0x45d9f3b; + id = ((id >> 16) ^ id) * 0x45d9f3b; + id = (id >> 16) ^ id; + return id; +} + +/* + * A set of constant ids, for where the parser only needs to know whether it has + * already seen a name, as with the variables a pattern captures or the named + * capture groups of a regular expression. + */ +typedef struct { + /* The number of ids in the set. */ + size_t size; + + /* The number of slots in `ids`, either zero or a power of two. */ + size_t capacity; + + /* + * An open-addressed table of the ids in the set, where PM_CONSTANT_ID_UNSET + * marks an empty slot. It is NULL until the first id is added. + */ + pm_constant_id_t *ids; +} pm_constant_id_set_t; + +/* + * Add a constant id, which must not be PM_CONSTANT_ID_UNSET, to the set. + * Returns whether the set did not already contain it. + */ +bool pm_constant_id_set_insert(pm_arena_t *arena, pm_constant_id_set_t *set, pm_constant_id_t id); /* Initialize a new constant pool with a given capacity. */ void pm_constant_pool_init(pm_arena_t *arena, pm_constant_pool_t *pool, uint32_t capacity); diff --git a/include/prism/internal/regexp.h b/include/prism/internal/regexp.h index 3710c984fc..3057b9c858 100644 --- a/include/prism/internal/regexp.h +++ b/include/prism/internal/regexp.h @@ -2,6 +2,7 @@ #define PRISM_INTERNAL_REGEXP_H #include "prism/ast.h" +#include "prism/internal/constant_pool.h" #include "prism/parser.h" /* @@ -17,8 +18,8 @@ typedef struct { /* The match write node being built, or NULL if no captures found yet. */ pm_match_write_node_t *match; - /* The list of capture names found so far (for deduplication). */ - pm_constant_id_list_t names; + /* The set of capture names found so far (for deduplication). */ + pm_constant_id_set_t names; } pm_regexp_name_data_t; /* diff --git a/snapshots/pattern_many_captures.txt b/snapshots/pattern_many_captures.txt new file mode 100644 index 0000000000..accdd38f14 --- /dev/null +++ b/snapshots/pattern_many_captures.txt @@ -0,0 +1,459 @@ +@ ProgramNode (location: (1,0)-(9,138)) +├── flags: ∅ +├── locals: [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o, :a1, :b1, :c1, :d1, :e1, :f1, :g1, :h1, :i1, :j1, :k1, :l1, :m1, :p1, :p2, :p3, :p4, :p5, :p6, :p7, :p8, :p9, :p10, :p11, :p12, :p13, :p14, :p15] +└── statements: + @ StatementsNode (location: (1,0)-(9,138)) + ├── flags: ∅ + └── body: (length: 3) + ├── @ CaseMatchNode (location: (1,0)-(3,3)) + │ ├── flags: newline + │ ├── predicate: + │ │ @ CallNode (location: (1,5)-(1,10)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :value + │ │ ├── message_loc: (1,5)-(1,10) = "value" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── conditions: (length: 1) + │ │ └── @ InNode (location: (2,0)-(2,48)) + │ │ ├── flags: ∅ + │ │ ├── pattern: + │ │ │ @ ArrayPatternNode (location: (2,3)-(2,48)) + │ │ │ ├── flags: ∅ + │ │ │ ├── constant: ∅ + │ │ │ ├── requireds: (length: 15) + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,4)-(2,5)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :a + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,7)-(2,8)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :b + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,10)-(2,11)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :c + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,13)-(2,14)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :d + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,16)-(2,17)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :e + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,19)-(2,20)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :f + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,22)-(2,23)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :g + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,25)-(2,26)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :h + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,28)-(2,29)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :i + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,31)-(2,32)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :j + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,34)-(2,35)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :k + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,37)-(2,38)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :l + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,40)-(2,41)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :m + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── @ LocalVariableTargetNode (location: (2,43)-(2,44)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :n + │ │ │ │ │ └── depth: 0 + │ │ │ │ └── @ LocalVariableTargetNode (location: (2,46)-(2,47)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :o + │ │ │ │ └── depth: 0 + │ │ │ ├── rest: ∅ + │ │ │ ├── posts: (length: 0) + │ │ │ ├── opening_loc: (2,3)-(2,4) = "[" + │ │ │ └── closing_loc: (2,47)-(2,48) = "]" + │ │ ├── statements: ∅ + │ │ ├── in_keyword_loc: (2,0)-(2,2) = "in" + │ │ └── then_keyword_loc: ∅ + │ ├── else_clause: ∅ + │ ├── case_keyword_loc: (1,0)-(1,4) = "case" + │ └── end_keyword_loc: (3,0)-(3,3) = "end" + ├── @ CaseMatchNode (location: (5,0)-(7,3)) + │ ├── flags: newline + │ ├── predicate: + │ │ @ CallNode (location: (5,5)-(5,10)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :value + │ │ ├── message_loc: (5,5)-(5,10) = "value" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── conditions: (length: 1) + │ │ └── @ InNode (location: (6,0)-(6,68)) + │ │ ├── flags: ∅ + │ │ ├── pattern: + │ │ │ @ HashPatternNode (location: (6,3)-(6,68)) + │ │ │ ├── flags: ∅ + │ │ │ ├── constant: ∅ + │ │ │ ├── elements: (length: 13) + │ │ │ │ ├── @ AssocNode (location: (6,4)-(6,7)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,4)-(6,7)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,4)-(6,6) = "a1" + │ │ │ │ │ │ ├── closing_loc: (6,6)-(6,7) = ":" + │ │ │ │ │ │ └── unescaped: "a1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,4)-(6,6)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,4)-(6,6)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :a1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── @ AssocNode (location: (6,9)-(6,12)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,9)-(6,12)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,9)-(6,11) = "b1" + │ │ │ │ │ │ ├── closing_loc: (6,11)-(6,12) = ":" + │ │ │ │ │ │ └── unescaped: "b1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,9)-(6,11)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,9)-(6,11)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :b1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── @ AssocNode (location: (6,14)-(6,17)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,14)-(6,17)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,14)-(6,16) = "c1" + │ │ │ │ │ │ ├── closing_loc: (6,16)-(6,17) = ":" + │ │ │ │ │ │ └── unescaped: "c1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,14)-(6,16)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,14)-(6,16)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :c1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── @ AssocNode (location: (6,19)-(6,22)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,19)-(6,22)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,19)-(6,21) = "d1" + │ │ │ │ │ │ ├── closing_loc: (6,21)-(6,22) = ":" + │ │ │ │ │ │ └── unescaped: "d1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,19)-(6,21)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,19)-(6,21)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :d1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── @ AssocNode (location: (6,24)-(6,27)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,24)-(6,27)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,24)-(6,26) = "e1" + │ │ │ │ │ │ ├── closing_loc: (6,26)-(6,27) = ":" + │ │ │ │ │ │ └── unescaped: "e1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,24)-(6,26)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,24)-(6,26)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :e1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── @ AssocNode (location: (6,29)-(6,32)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,29)-(6,32)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,29)-(6,31) = "f1" + │ │ │ │ │ │ ├── closing_loc: (6,31)-(6,32) = ":" + │ │ │ │ │ │ └── unescaped: "f1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,29)-(6,31)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,29)-(6,31)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :f1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── @ AssocNode (location: (6,34)-(6,37)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,34)-(6,37)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,34)-(6,36) = "g1" + │ │ │ │ │ │ ├── closing_loc: (6,36)-(6,37) = ":" + │ │ │ │ │ │ └── unescaped: "g1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,34)-(6,36)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,34)-(6,36)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :g1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── @ AssocNode (location: (6,39)-(6,42)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,39)-(6,42)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,39)-(6,41) = "h1" + │ │ │ │ │ │ ├── closing_loc: (6,41)-(6,42) = ":" + │ │ │ │ │ │ └── unescaped: "h1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,39)-(6,41)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,39)-(6,41)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :h1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── @ AssocNode (location: (6,44)-(6,47)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,44)-(6,47)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,44)-(6,46) = "i1" + │ │ │ │ │ │ ├── closing_loc: (6,46)-(6,47) = ":" + │ │ │ │ │ │ └── unescaped: "i1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,44)-(6,46)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,44)-(6,46)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :i1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── @ AssocNode (location: (6,49)-(6,52)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,49)-(6,52)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,49)-(6,51) = "j1" + │ │ │ │ │ │ ├── closing_loc: (6,51)-(6,52) = ":" + │ │ │ │ │ │ └── unescaped: "j1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,49)-(6,51)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,49)-(6,51)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :j1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── @ AssocNode (location: (6,54)-(6,57)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,54)-(6,57)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,54)-(6,56) = "k1" + │ │ │ │ │ │ ├── closing_loc: (6,56)-(6,57) = ":" + │ │ │ │ │ │ └── unescaped: "k1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,54)-(6,56)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,54)-(6,56)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :k1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── @ AssocNode (location: (6,59)-(6,62)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (6,59)-(6,62)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── content_loc: (6,59)-(6,61) = "l1" + │ │ │ │ │ │ ├── closing_loc: (6,61)-(6,62) = ":" + │ │ │ │ │ │ └── unescaped: "l1" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (6,59)-(6,61)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (6,59)-(6,61)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :l1 + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ └── @ AssocNode (location: (6,64)-(6,67)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── key: + │ │ │ │ │ @ SymbolNode (location: (6,64)-(6,67)) + │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ ├── content_loc: (6,64)-(6,66) = "m1" + │ │ │ │ │ ├── closing_loc: (6,66)-(6,67) = ":" + │ │ │ │ │ └── unescaped: "m1" + │ │ │ │ ├── value: + │ │ │ │ │ @ ImplicitNode (location: (6,64)-(6,66)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ └── value: + │ │ │ │ │ @ LocalVariableTargetNode (location: (6,64)-(6,66)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :m1 + │ │ │ │ │ └── depth: 0 + │ │ │ │ └── operator_loc: ∅ + │ │ │ ├── rest: ∅ + │ │ │ ├── opening_loc: (6,3)-(6,4) = "{" + │ │ │ └── closing_loc: (6,67)-(6,68) = "}" + │ │ ├── statements: ∅ + │ │ ├── in_keyword_loc: (6,0)-(6,2) = "in" + │ │ └── then_keyword_loc: ∅ + │ ├── else_clause: ∅ + │ ├── case_keyword_loc: (5,0)-(5,4) = "case" + │ └── end_keyword_loc: (7,0)-(7,3) = "end" + └── @ MatchWriteNode (location: (9,0)-(9,138)) + ├── flags: newline + ├── call: + │ @ CallNode (location: (9,0)-(9,138)) + │ ├── flags: ∅ + │ ├── receiver: + │ │ @ RegularExpressionNode (location: (9,0)-(9,128)) + │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ ├── opening_loc: (9,0)-(9,1) = "/" + │ │ ├── content_loc: (9,1)-(9,127) = "(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)" + │ │ ├── closing_loc: (9,127)-(9,128) = "/" + │ │ └── unescaped: "(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)" + │ ├── call_operator_loc: ∅ + │ ├── name: :=~ + │ ├── message_loc: (9,129)-(9,131) = "=~" + │ ├── opening_loc: ∅ + │ ├── arguments: + │ │ @ ArgumentsNode (location: (9,132)-(9,138)) + │ │ ├── flags: ∅ + │ │ └── arguments: (length: 1) + │ │ └── @ CallNode (location: (9,132)-(9,138)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :string + │ │ ├── message_loc: (9,132)-(9,138) = "string" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + └── targets: (length: 15) + ├── @ LocalVariableTargetNode (location: (9,4)-(9,6)) + │ ├── flags: ∅ + │ ├── name: :p1 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,12)-(9,14)) + │ ├── flags: ∅ + │ ├── name: :p2 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,20)-(9,22)) + │ ├── flags: ∅ + │ ├── name: :p3 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,28)-(9,30)) + │ ├── flags: ∅ + │ ├── name: :p4 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,36)-(9,38)) + │ ├── flags: ∅ + │ ├── name: :p5 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,44)-(9,46)) + │ ├── flags: ∅ + │ ├── name: :p6 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,52)-(9,54)) + │ ├── flags: ∅ + │ ├── name: :p7 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,60)-(9,62)) + │ ├── flags: ∅ + │ ├── name: :p8 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,68)-(9,70)) + │ ├── flags: ∅ + │ ├── name: :p9 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,76)-(9,79)) + │ ├── flags: ∅ + │ ├── name: :p10 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,85)-(9,88)) + │ ├── flags: ∅ + │ ├── name: :p11 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,94)-(9,97)) + │ ├── flags: ∅ + │ ├── name: :p12 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,103)-(9,106)) + │ ├── flags: ∅ + │ ├── name: :p13 + │ └── depth: 0 + ├── @ LocalVariableTargetNode (location: (9,112)-(9,115)) + │ ├── flags: ∅ + │ ├── name: :p14 + │ └── depth: 0 + └── @ LocalVariableTargetNode (location: (9,121)-(9,124)) + ├── flags: ∅ + ├── name: :p15 + └── depth: 0 diff --git a/src/constant_pool.c b/src/constant_pool.c index 6e9c5c6eba..c1e723063a 100644 --- a/src/constant_pool.c +++ b/src/constant_pool.c @@ -65,14 +65,57 @@ pm_constant_id_list_insert(pm_constant_id_list_t *list, size_t index, pm_constan } /** - * Checks if the current constant id list includes the given constant id. + * The slot that holds the given id, or the empty slot it would occupy. The + * table is never full, so this terminates. + */ +static PRISM_INLINE size_t +pm_constant_id_set_slot(const pm_constant_id_set_t *set, pm_constant_id_t id) { + assert(set->capacity != 0); + + size_t mask = set->capacity - 1; + size_t index = ((size_t) pm_constant_id_hash(id)) & mask; + + while (set->ids[index] != PM_CONSTANT_ID_UNSET && set->ids[index] != id) { + index = (index + 1) & mask; + } + + return index; +} + +/** + * Add a constant id to the set, reporting whether it was not already there. */ bool -pm_constant_id_list_includes(pm_constant_id_list_t *list, pm_constant_id_t id) { - for (size_t index = 0; index < list->size; index++) { - if (list->ids[index] == id) return true; +pm_constant_id_set_insert(pm_arena_t *arena, pm_constant_id_set_t *set, pm_constant_id_t id) { + /* PM_CONSTANT_ID_UNSET marks an empty slot, so it cannot also be a member. */ + assert(id != PM_CONSTANT_ID_UNSET); + + /* + * Grow at the same load factor the locals table and the constant pool use. + * An empty set has no slots at all, so this also builds the first table. + */ + if (set->size >= (set->capacity / 4 * 3)) { + size_t capacity = set->capacity == 0 ? 8 : set->capacity * 2; + pm_constant_id_set_t grown = { + .size = set->size, + .capacity = capacity, + .ids = (pm_constant_id_t *) pm_arena_zalloc(arena, capacity * sizeof(pm_constant_id_t), PRISM_ALIGNOF(pm_constant_id_t)) + }; + + for (size_t index = 0; index < set->capacity; index++) { + pm_constant_id_t moved = set->ids[index]; + if (moved != PM_CONSTANT_ID_UNSET) grown.ids[pm_constant_id_set_slot(&grown, moved)] = moved; + } + + *set = grown; } - return false; + + size_t index = pm_constant_id_set_slot(set, id); + if (set->ids[index] == id) return false; + + set->ids[index] = id; + set->size++; + return true; } /** diff --git a/src/prism.c b/src/prism.c index c3189f607c..e6b7e64f2d 100644 --- a/src/prism.c +++ b/src/prism.c @@ -883,18 +883,6 @@ pm_locals_free(pm_locals_t *locals) { } } -/** - * Use as simple and fast a hash function as we can that still properly mixes - * the bits. - */ -static uint32_t -pm_locals_hash(pm_constant_id_t name) { - name = ((name >> 16) ^ name) * 0x45d9f3b; - name = ((name >> 16) ^ name) * 0x45d9f3b; - name = (name >> 16) ^ name; - return name; -} - /** * Resize the locals list to be twice its current size. If the next capacity is * above the threshold for switching to a hash, then we'll switch to a hash. @@ -921,7 +909,7 @@ pm_locals_resize(pm_locals_t *locals) { pm_local_t *local = &locals->locals[index]; if (local->name != PM_CONSTANT_ID_UNSET) { - if (hash_needed) local->hash = pm_locals_hash(local->name); + if (hash_needed) local->hash = pm_constant_id_hash(local->name); uint32_t hash = local->hash; while (next_locals[hash & mask].name != PM_CONSTANT_ID_UNSET) hash++; @@ -977,7 +965,7 @@ pm_locals_write(pm_locals_t *locals, pm_constant_id_t name, uint32_t start, uint } } else { uint32_t mask = locals->capacity - 1; - uint32_t hash = pm_locals_hash(name); + uint32_t hash = pm_constant_id_hash(name); uint32_t initial_hash = hash; do { @@ -1019,7 +1007,7 @@ pm_locals_find(pm_locals_t *locals, pm_constant_id_t name) { } } else { uint32_t mask = locals->capacity - 1; - uint32_t hash = pm_locals_hash(name); + uint32_t hash = pm_constant_id_hash(name); uint32_t initial_hash = hash & mask; do { @@ -16938,22 +16926,20 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 #define PM_PARSE_PATTERN_MULTI 2 static pm_node_t * -parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flags, pm_diagnostic_id_t diag_id, uint16_t depth); +parse_pattern(pm_parser_t *parser, pm_constant_id_set_t *captures, uint8_t flags, pm_diagnostic_id_t diag_id, uint16_t depth); /** - * Add the newly created local to the list of captures for this pattern matching + * Add the newly created local to the set of captures for this pattern matching * expression. If it is duplicated from a previous local, then we'll need to add * an error to the parser. */ static void -parse_pattern_capture(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_constant_id_t capture, const pm_location_t *location) { +parse_pattern_capture(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_constant_id_t capture, const pm_location_t *location) { // Skip this capture if it starts with an underscore. if (peek_at(parser, parser->start + location->start) == '_') return; - if (pm_constant_id_list_includes(captures, capture)) { + if (!pm_constant_id_set_insert(parser->arena, captures, capture)) { pm_parser_err(parser, location->start, location->length, PM_ERR_PATTERN_CAPTURE_DUPLICATE); - } else { - pm_constant_id_list_append(parser->arena, captures, capture); } } @@ -16961,7 +16947,7 @@ parse_pattern_capture(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_c * Accept any number of constants joined by :: delimiters. */ static pm_node_t * -parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node_t *node, uint16_t depth) { +parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_node_t *node, uint16_t depth) { // Now, if there are any :: operators that follow, parse them as constant // path nodes. while (accept1(parser, PM_TOKEN_COLON_COLON)) { @@ -17081,7 +17067,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures * Parse a rest pattern. */ static pm_splat_node_t * -parse_pattern_rest(pm_parser_t *parser, pm_constant_id_list_t *captures) { +parse_pattern_rest(pm_parser_t *parser, pm_constant_id_set_t *captures) { assert(parser->previous.type == PM_TOKEN_USTAR); pm_token_t operator = parser->previous; pm_node_t *name = NULL; @@ -17115,7 +17101,7 @@ parse_pattern_rest(pm_parser_t *parser, pm_constant_id_list_t *captures) { * Parse a keyword rest node. */ static pm_node_t * -parse_pattern_keyword_rest(pm_parser_t *parser, pm_constant_id_list_t *captures) { +parse_pattern_keyword_rest(pm_parser_t *parser, pm_constant_id_set_t *captures) { assert(parser->current.type == PM_TOKEN_USTAR_STAR); parser_lex(parser); @@ -17179,7 +17165,7 @@ pm_slice_is_valid_local(const pm_parser_t *parser, const uint8_t *start, const u * value. This will use an implicit local variable target. */ static pm_node_t * -parse_pattern_hash_implicit_value(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_symbol_node_t *key) { +parse_pattern_hash_implicit_value(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_symbol_node_t *key) { const pm_location_t *value_loc = &((pm_symbol_node_t *) key)->content_loc; const uint8_t *start = parser->start + PM_LOCATION_START(value_loc); const uint8_t *end = parser->start + PM_LOCATION_END(value_loc); @@ -17227,7 +17213,7 @@ parse_pattern_hash_key(pm_parser_t *parser, pm_static_literals_t *keys, pm_node_ * Parse a hash pattern. */ static pm_hash_pattern_node_t * -parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node_t *first_node, uint16_t depth) { +parse_pattern_hash(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_node_t *first_node, uint16_t depth) { pm_node_list_t assocs = { 0 }; pm_static_literals_t keys = { 0 }; pm_node_t *rest = NULL; @@ -17370,7 +17356,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node * Parse a pattern expression primitive. */ static pm_node_t * -parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_diagnostic_id_t diag_id, uint16_t depth) { +parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_diagnostic_id_t diag_id, uint16_t depth) { switch (parser->current.type) { case PM_TOKEN_IDENTIFIER: case PM_TOKEN_METHOD_NAME: { @@ -17677,7 +17663,7 @@ parse_pattern_alternation_error(pm_parser_t *parser, const pm_node_t *node) { * assignment. */ static pm_node_t * -parse_pattern_primitives(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node_t *first_node, pm_diagnostic_id_t diag_id, uint16_t depth) { +parse_pattern_primitives(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_node_t *first_node, pm_diagnostic_id_t diag_id, uint16_t depth) { pm_node_t *node = first_node; bool alternation = false; @@ -17776,7 +17762,7 @@ parse_pattern_primitives(pm_parser_t *parser, pm_constant_id_list_t *captures, p * Parse a pattern matching expression. */ static pm_node_t * -parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flags, pm_diagnostic_id_t diag_id, uint16_t depth) { +parse_pattern(pm_parser_t *parser, pm_constant_id_set_t *captures, uint8_t flags, pm_diagnostic_id_t diag_id, uint16_t depth) { pm_node_t *node = NULL; bool leading_rest = false; @@ -18310,7 +18296,7 @@ parse_case(pm_parser_t *parser, uint8_t flags, uint16_t depth) { pm_token_t in_keyword = parser->previous; - pm_constant_id_list_t captures = { 0 }; + pm_constant_id_set_t captures = { 0 }; pm_node_t *pattern = parse_pattern(parser, &captures, PM_PARSE_PATTERN_TOP | PM_PARSE_PATTERN_MULTI, PM_ERR_PATTERN_EXPRESSION_AFTER_IN, (uint16_t) (depth + 1)); parser->pattern_matching_newlines = previous_pattern_matching_newlines; @@ -21459,7 +21445,7 @@ pm_named_capture_escape(pm_parser_t *parser, pm_buffer_t *unescaped, const uint8 static void parse_regular_expression_named_capture(pm_parser_t *parser, const pm_string_t *capture, bool shared, pm_regexp_name_data_t *callback_data) { pm_call_node_t *call = callback_data->call; - pm_constant_id_list_t *names = &callback_data->names; + pm_constant_id_set_t *names = &callback_data->names; const uint8_t *source = pm_string_source(capture); size_t length = pm_string_length(capture); @@ -21508,10 +21494,11 @@ parse_regular_expression_named_capture(pm_parser_t *parser, const pm_string_t *c name = pm_parser_constant_id_owned(parser, memory, length); } - // Add this name to the list of constants if it is valid, not duplicated, - // and not a keyword. - if (name != 0 && !pm_constant_id_list_includes(names, name)) { - pm_constant_id_list_append(parser->arena, names, name); + /* + * Add this name to the set of constants if it is valid, not duplicated, + * and not a keyword. + */ + if (name != 0 && pm_constant_id_set_insert(parser->arena, names, name)) { int depth; if ((depth = pm_parser_local_depth_constant_id(parser, name)) == -1) { @@ -22458,7 +22445,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t lex_state_set(parser, PM_LEX_STATE_BEG | PM_LEX_STATE_LABEL); parser_lex(parser); - pm_constant_id_list_t captures = { 0 }; + pm_constant_id_set_t captures = { 0 }; pm_node_t *pattern = parse_pattern(parser, &captures, PM_PARSE_PATTERN_TOP | PM_PARSE_PATTERN_MULTI, PM_ERR_PATTERN_EXPRESSION_AFTER_IN, (uint16_t) (depth + 1)); parser->pattern_matching_newlines = previous_pattern_matching_newlines; @@ -22474,7 +22461,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t lex_state_set(parser, PM_LEX_STATE_BEG | PM_LEX_STATE_LABEL); parser_lex(parser); - pm_constant_id_list_t captures = { 0 }; + pm_constant_id_set_t captures = { 0 }; pm_node_t *pattern = parse_pattern(parser, &captures, PM_PARSE_PATTERN_TOP | PM_PARSE_PATTERN_MULTI, PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET, (uint16_t) (depth + 1)); parser->pattern_matching_newlines = previous_pattern_matching_newlines; diff --git a/test/prism/errors/duplicate_pattern_capture_after_growth.txt b/test/prism/errors/duplicate_pattern_capture_after_growth.txt new file mode 100644 index 0000000000..92faa9d07d --- /dev/null +++ b/test/prism/errors/duplicate_pattern_capture_after_growth.txt @@ -0,0 +1,5 @@ +case value +in [a, b, c, d, e, f, g, h, i, j, k, l, m, n, a] + ^ duplicated variable name +end + diff --git a/test/prism/fixtures/pattern_many_captures.txt b/test/prism/fixtures/pattern_many_captures.txt new file mode 100644 index 0000000000..e662282aee --- /dev/null +++ b/test/prism/fixtures/pattern_many_captures.txt @@ -0,0 +1,9 @@ +case value +in [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o] +end + +case value +in {a1:, b1:, c1:, d1:, e1:, f1:, g1:, h1:, i1:, j1:, k1:, l1:, m1:} +end + +/(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)(?.)/ =~ string