@@ -672,6 +672,168 @@ pm_parser_warn_node(pm_parser_t *parser, const pm_node_t *node, pm_diagnostic_id
672
672
#define PM_PARSER_WARN_NODE_FORMAT(parser, node, diag_id, ...) \
673
673
PM_PARSER_WARN_FORMAT(parser, (node)->location.start, (node)->location.end, diag_id, __VA_ARGS__)
674
674
675
+ /******************************************************************************/
676
+ /* Local variable-related functions */
677
+ /******************************************************************************/
678
+
679
+ static void
680
+ pm_locals_free(pm_locals_t *locals) {
681
+ if (locals->capacity > 0) {
682
+ xfree(locals->locals);
683
+ }
684
+ }
685
+
686
+ /**
687
+ * Use the mid-square method to hash the given constant id.
688
+ */
689
+ static uint32_t
690
+ pm_locals_hash(pm_constant_id_t name) {
691
+ uint64_t square = (uint64_t) name * (uint64_t) name;
692
+
693
+ uint32_t num_digits = (uint32_t) floor(log10(square) + 1);
694
+ uint32_t start = num_digits / 2;
695
+ uint32_t end = start + 1;
696
+
697
+ return (uint32_t) (((uint64_t) ((square / pow(10, start))) % (uint64_t) pow(10, end)));
698
+ }
699
+
700
+ static void
701
+ pm_locals_rehash(pm_locals_t *locals) {
702
+ uint32_t next_capacity = locals->capacity == 0 ? 8 : (locals->capacity * 2);
703
+ assert(next_capacity > locals->capacity);
704
+
705
+ pm_local_t *next_locals = xcalloc(next_capacity, sizeof(pm_local_t));
706
+ if (next_locals == NULL) abort();
707
+
708
+ uint32_t mask = next_capacity - 1;
709
+ for (uint32_t index = 0; index < locals->capacity; index++) {
710
+ pm_local_t *local = &locals->locals[index];
711
+
712
+ if (local->name != PM_CONSTANT_ID_UNSET) {
713
+ uint32_t hash = local->hash;
714
+
715
+ while (next_locals[hash & mask].name != PM_CONSTANT_ID_UNSET) hash++;
716
+ next_locals[hash & mask] = *local;
717
+ }
718
+ }
719
+
720
+ pm_locals_free(locals);
721
+ locals->locals = next_locals;
722
+ locals->capacity = next_capacity;
723
+ }
724
+
725
+ /**
726
+ * Add a new local to the set of locals. This will automatically rehash the
727
+ * locals if the size is greater than 3/4 of the capacity.
728
+ *
729
+ * Returns true if the local was added, and false if the local already exists.
730
+ */
731
+ static bool
732
+ pm_locals_write(pm_locals_t *locals, pm_constant_id_t name) {
733
+ if (locals->size >= (locals->capacity / 4 * 3)) {
734
+ pm_locals_rehash(locals);
735
+ }
736
+
737
+ uint32_t mask = locals->capacity - 1;
738
+ uint32_t hash = pm_locals_hash(name);
739
+ uint32_t initial_hash = hash;
740
+
741
+ do {
742
+ pm_local_t *local = &locals->locals[hash & mask];
743
+
744
+ if (local->name == PM_CONSTANT_ID_UNSET) {
745
+ *local = (pm_local_t) {
746
+ .name = name,
747
+ .index = locals->size++,
748
+ .reads = 0,
749
+ .hash = hash
750
+ };
751
+ return true;
752
+ } else if (local->name == name) {
753
+ return false;
754
+ } else {
755
+ hash++;
756
+ }
757
+ } while ((hash & mask) != initial_hash);
758
+
759
+ assert(false && "unreachable");
760
+ }
761
+
762
+ /**
763
+ * Finds the index of a local variable in the locals set. If it is not found,
764
+ * this returns UINT32_MAX.
765
+ */
766
+ static uint32_t
767
+ pm_locals_find(pm_locals_t *locals, pm_constant_id_t name) {
768
+ if (locals->capacity == 0) return UINT32_MAX;
769
+
770
+ uint32_t mask = locals->capacity - 1;
771
+ uint32_t hash = pm_locals_hash(name);
772
+ uint32_t initial_hash = hash & mask;
773
+
774
+ do {
775
+ pm_local_t *local = &locals->locals[hash & mask];
776
+
777
+ if (local->name == PM_CONSTANT_ID_UNSET) {
778
+ return UINT32_MAX;
779
+ } else if (local->name == name) {
780
+ return hash & mask;
781
+ } else {
782
+ hash++;
783
+ }
784
+ } while ((hash & mask) != initial_hash);
785
+
786
+ return UINT32_MAX;
787
+ }
788
+
789
+ /**
790
+ * Called when a variable is read in a certain lexical context. Tracks the read
791
+ * by adding to the reads count.
792
+ */
793
+ // static void
794
+ // pm_locals_read(pm_locals_t *locals, pm_constant_id_t name) {
795
+ // uint32_t index = pm_locals_find(locals, name);
796
+ // assert(index != UINT32_MAX);
797
+
798
+ // pm_local_t *local = &locals->locals[index];
799
+ // assert(local->reads < UINT32_MAX);
800
+
801
+ // local->reads++;
802
+ // }
803
+
804
+ /**
805
+ * Called when a variable read is transformed into a variable write, because a
806
+ * write operator is found after the variable name.
807
+ */
808
+ // static void
809
+ // pm_locals_unread(pm_locals_t *locals, pm_constant_id_t name) {
810
+ // uint32_t index = pm_locals_find(locals, name);
811
+ // assert(index != UINT32_MAX);
812
+
813
+ // pm_local_t *local = &locals->locals[index];
814
+ // assert(local->reads > 0);
815
+
816
+ // local->reads--;
817
+ // }
818
+
819
+ /**
820
+ * Write out the locals into the given list of constant ids in the correct
821
+ * order. This is used to set the list of locals on the nodes in the tree once
822
+ * we're sure no additional locals will be added to the set.
823
+ */
824
+ static void
825
+ pm_locals_order(pm_locals_t *locals, pm_constant_id_list_t *list) {
826
+ pm_constant_id_list_init_capacity(list, locals->size);
827
+
828
+ for (uint32_t index = 0; index < locals->capacity; index++) {
829
+ pm_local_t *local = &locals->locals[index];
830
+
831
+ if (local->name != PM_CONSTANT_ID_UNSET) {
832
+ pm_constant_id_list_insert(list, (size_t) local->index, local->name);
833
+ }
834
+ }
835
+ }
836
+
675
837
/******************************************************************************/
676
838
/* Node-related functions */
677
839
/******************************************************************************/
@@ -7045,7 +7207,7 @@ pm_parser_local_depth_constant_id(pm_parser_t *parser, pm_constant_id_t constant
7045
7207
int depth = 0;
7046
7208
7047
7209
while (scope != NULL) {
7048
- if (pm_constant_id_list_includes (&scope->locals, constant_id)) return depth;
7210
+ if (pm_locals_find (&scope->locals, constant_id) != UINT32_MAX ) return depth;
7049
7211
if (scope->closed) break;
7050
7212
7051
7213
scope = scope->previous;
@@ -7070,9 +7232,7 @@ pm_parser_local_depth(pm_parser_t *parser, pm_token_t *token) {
7070
7232
*/
7071
7233
static inline void
7072
7234
pm_parser_local_add(pm_parser_t *parser, pm_constant_id_t constant_id) {
7073
- if (!pm_constant_id_list_includes(&parser->current_scope->locals, constant_id)) {
7074
- pm_constant_id_list_append(&parser->current_scope->locals, constant_id);
7075
- }
7235
+ pm_locals_write(&parser->current_scope->locals, constant_id);
7076
7236
}
7077
7237
7078
7238
/**
@@ -7175,7 +7335,7 @@ pm_parser_parameter_name_check(pm_parser_t *parser, const pm_token_t *name) {
7175
7335
// whether it's already in the current scope.
7176
7336
pm_constant_id_t constant_id = pm_parser_constant_id_token(parser, name);
7177
7337
7178
- if (pm_constant_id_list_includes (&parser->current_scope->locals, constant_id)) {
7338
+ if (pm_locals_find (&parser->current_scope->locals, constant_id) != UINT32_MAX ) {
7179
7339
// Add an error if the parameter doesn't start with _ and has been seen before
7180
7340
if ((name->start < name->end) && (*name->start != '_')) {
7181
7341
pm_parser_err_token(parser, name, PM_ERR_PARAMETER_NAME_DUPLICATED);
@@ -7186,14 +7346,13 @@ pm_parser_parameter_name_check(pm_parser_t *parser, const pm_token_t *name) {
7186
7346
}
7187
7347
7188
7348
/**
7189
- * Pop the current scope off the scope stack. Note that we specifically do not
7190
- * free the associated constant list because we assume that we have already
7191
- * transferred ownership of the list to the AST somewhere.
7349
+ * Pop the current scope off the scope stack.
7192
7350
*/
7193
7351
static void
7194
7352
pm_parser_scope_pop(pm_parser_t *parser) {
7195
7353
pm_scope_t *scope = parser->current_scope;
7196
7354
parser->current_scope = scope->previous;
7355
+ pm_locals_free(&scope->locals);
7197
7356
xfree(scope);
7198
7357
}
7199
7358
@@ -13849,7 +14008,8 @@ parse_block(pm_parser_t *parser) {
13849
14008
expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_BLOCK_TERM_END);
13850
14009
}
13851
14010
13852
- pm_constant_id_list_t locals = parser->current_scope->locals;
14011
+ pm_constant_id_list_t locals;
14012
+ pm_locals_order(&parser->current_scope->locals, &locals);
13853
14013
pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &opening, &parser->previous);
13854
14014
13855
14015
pm_parser_scope_pop(parser);
@@ -17173,7 +17333,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
17173
17333
}
17174
17334
17175
17335
expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM);
17176
- pm_constant_id_list_t locals = parser->current_scope->locals;
17336
+
17337
+ pm_constant_id_list_t locals;
17338
+ pm_locals_order(&parser->current_scope->locals, &locals);
17177
17339
17178
17340
pm_parser_scope_pop(parser);
17179
17341
pm_do_loop_stack_pop(parser);
@@ -17234,7 +17396,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
17234
17396
pm_parser_err_token(parser, &class_keyword, PM_ERR_CLASS_IN_METHOD);
17235
17397
}
17236
17398
17237
- pm_constant_id_list_t locals = parser->current_scope->locals;
17399
+ pm_constant_id_list_t locals;
17400
+ pm_locals_order(&parser->current_scope->locals, &locals);
17238
17401
17239
17402
pm_parser_scope_pop(parser);
17240
17403
pm_do_loop_stack_pop(parser);
@@ -17509,7 +17672,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
17509
17672
end_keyword = parser->previous;
17510
17673
}
17511
17674
17512
- pm_constant_id_list_t locals = parser->current_scope->locals;
17675
+ pm_constant_id_list_t locals;
17676
+ pm_locals_order(&parser->current_scope->locals, &locals);
17513
17677
17514
17678
pm_parser_scope_pop(parser);
17515
17679
pm_parser_current_param_name_restore(parser, saved_param_name);
@@ -17774,7 +17938,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
17774
17938
statements = (pm_node_t *) parse_rescues_implicit_begin(parser, module_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_MODULE);
17775
17939
}
17776
17940
17777
- pm_constant_id_list_t locals = parser->current_scope->locals;
17941
+ pm_constant_id_list_t locals;
17942
+ pm_locals_order(&parser->current_scope->locals, &locals);
17943
+
17778
17944
pm_parser_scope_pop(parser);
17779
17945
pm_parser_current_param_name_restore(parser, saved_param_name);
17780
17946
@@ -18541,7 +18707,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
18541
18707
expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_LAMBDA_TERM_END);
18542
18708
}
18543
18709
18544
- pm_constant_id_list_t locals = parser->current_scope->locals;
18710
+ pm_constant_id_list_t locals;
18711
+ pm_locals_order(&parser->current_scope->locals, &locals);
18545
18712
pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &operator, &parser->previous);
18546
18713
18547
18714
pm_parser_scope_pop(parser);
@@ -19815,7 +19982,9 @@ parse_program(pm_parser_t *parser) {
19815
19982
if (!statements) {
19816
19983
statements = pm_statements_node_create(parser);
19817
19984
}
19818
- pm_constant_id_list_t locals = parser->current_scope->locals;
19985
+
19986
+ pm_constant_id_list_t locals;
19987
+ pm_locals_order(&parser->current_scope->locals, &locals);
19819
19988
pm_parser_scope_pop(parser);
19820
19989
19821
19990
// If this is an empty file, then we're still going to parse all of the
@@ -20140,7 +20309,6 @@ pm_parser_free(pm_parser_t *parser) {
20140
20309
// assumed that ownership has transferred to the AST. However if we have
20141
20310
// scopes while we're freeing the parser, it's likely they came from
20142
20311
// eval scopes and we need to free them explicitly here.
20143
- pm_constant_id_list_free(&parser->current_scope->locals);
20144
20312
pm_parser_scope_pop(parser);
20145
20313
}
20146
20314
0 commit comments