natalie_parser 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +22 -0
  3. data/Dockerfile +26 -0
  4. data/Gemfile +10 -0
  5. data/LICENSE +21 -0
  6. data/README.md +55 -0
  7. data/Rakefile +242 -0
  8. data/ext/natalie_parser/extconf.rb +9 -0
  9. data/ext/natalie_parser/mri_creator.hpp +139 -0
  10. data/ext/natalie_parser/natalie_parser.cpp +144 -0
  11. data/include/natalie_parser/creator/debug_creator.hpp +113 -0
  12. data/include/natalie_parser/creator.hpp +108 -0
  13. data/include/natalie_parser/lexer/interpolated_string_lexer.hpp +64 -0
  14. data/include/natalie_parser/lexer/regexp_lexer.hpp +37 -0
  15. data/include/natalie_parser/lexer/word_array_lexer.hpp +57 -0
  16. data/include/natalie_parser/lexer.hpp +135 -0
  17. data/include/natalie_parser/node/alias_node.hpp +35 -0
  18. data/include/natalie_parser/node/arg_node.hpp +74 -0
  19. data/include/natalie_parser/node/array_node.hpp +34 -0
  20. data/include/natalie_parser/node/array_pattern_node.hpp +28 -0
  21. data/include/natalie_parser/node/assignment_node.hpp +34 -0
  22. data/include/natalie_parser/node/back_ref_node.hpp +28 -0
  23. data/include/natalie_parser/node/begin_block_node.hpp +25 -0
  24. data/include/natalie_parser/node/begin_node.hpp +52 -0
  25. data/include/natalie_parser/node/begin_rescue_node.hpp +47 -0
  26. data/include/natalie_parser/node/bignum_node.hpp +37 -0
  27. data/include/natalie_parser/node/block_node.hpp +55 -0
  28. data/include/natalie_parser/node/block_pass_node.hpp +33 -0
  29. data/include/natalie_parser/node/break_node.hpp +32 -0
  30. data/include/natalie_parser/node/call_node.hpp +85 -0
  31. data/include/natalie_parser/node/case_in_node.hpp +40 -0
  32. data/include/natalie_parser/node/case_node.hpp +52 -0
  33. data/include/natalie_parser/node/case_when_node.hpp +43 -0
  34. data/include/natalie_parser/node/class_node.hpp +39 -0
  35. data/include/natalie_parser/node/colon2_node.hpp +44 -0
  36. data/include/natalie_parser/node/colon3_node.hpp +34 -0
  37. data/include/natalie_parser/node/constant_node.hpp +26 -0
  38. data/include/natalie_parser/node/def_node.hpp +55 -0
  39. data/include/natalie_parser/node/defined_node.hpp +33 -0
  40. data/include/natalie_parser/node/encoding_node.hpp +26 -0
  41. data/include/natalie_parser/node/end_block_node.hpp +25 -0
  42. data/include/natalie_parser/node/evaluate_to_string_node.hpp +37 -0
  43. data/include/natalie_parser/node/false_node.hpp +23 -0
  44. data/include/natalie_parser/node/fixnum_node.hpp +36 -0
  45. data/include/natalie_parser/node/float_node.hpp +36 -0
  46. data/include/natalie_parser/node/hash_node.hpp +34 -0
  47. data/include/natalie_parser/node/hash_pattern_node.hpp +27 -0
  48. data/include/natalie_parser/node/identifier_node.hpp +123 -0
  49. data/include/natalie_parser/node/if_node.hpp +43 -0
  50. data/include/natalie_parser/node/infix_op_node.hpp +46 -0
  51. data/include/natalie_parser/node/interpolated_node.hpp +33 -0
  52. data/include/natalie_parser/node/interpolated_regexp_node.hpp +28 -0
  53. data/include/natalie_parser/node/interpolated_shell_node.hpp +22 -0
  54. data/include/natalie_parser/node/interpolated_string_node.hpp +31 -0
  55. data/include/natalie_parser/node/interpolated_symbol_key_node.hpp +18 -0
  56. data/include/natalie_parser/node/interpolated_symbol_node.hpp +28 -0
  57. data/include/natalie_parser/node/iter_node.hpp +45 -0
  58. data/include/natalie_parser/node/keyword_arg_node.hpp +25 -0
  59. data/include/natalie_parser/node/keyword_splat_node.hpp +38 -0
  60. data/include/natalie_parser/node/logical_and_node.hpp +40 -0
  61. data/include/natalie_parser/node/logical_or_node.hpp +40 -0
  62. data/include/natalie_parser/node/match_node.hpp +38 -0
  63. data/include/natalie_parser/node/module_node.hpp +32 -0
  64. data/include/natalie_parser/node/multiple_assignment_arg_node.hpp +32 -0
  65. data/include/natalie_parser/node/multiple_assignment_node.hpp +37 -0
  66. data/include/natalie_parser/node/next_node.hpp +37 -0
  67. data/include/natalie_parser/node/nil_node.hpp +23 -0
  68. data/include/natalie_parser/node/nil_sexp_node.hpp +23 -0
  69. data/include/natalie_parser/node/node.hpp +155 -0
  70. data/include/natalie_parser/node/node_with_args.hpp +47 -0
  71. data/include/natalie_parser/node/not_match_node.hpp +35 -0
  72. data/include/natalie_parser/node/not_node.hpp +37 -0
  73. data/include/natalie_parser/node/nth_ref_node.hpp +27 -0
  74. data/include/natalie_parser/node/op_assign_accessor_node.hpp +74 -0
  75. data/include/natalie_parser/node/op_assign_and_node.hpp +34 -0
  76. data/include/natalie_parser/node/op_assign_node.hpp +47 -0
  77. data/include/natalie_parser/node/op_assign_or_node.hpp +34 -0
  78. data/include/natalie_parser/node/pin_node.hpp +33 -0
  79. data/include/natalie_parser/node/range_node.hpp +52 -0
  80. data/include/natalie_parser/node/redo_node.hpp +20 -0
  81. data/include/natalie_parser/node/regexp_node.hpp +36 -0
  82. data/include/natalie_parser/node/retry_node.hpp +20 -0
  83. data/include/natalie_parser/node/return_node.hpp +34 -0
  84. data/include/natalie_parser/node/safe_call_node.hpp +31 -0
  85. data/include/natalie_parser/node/sclass_node.hpp +37 -0
  86. data/include/natalie_parser/node/self_node.hpp +23 -0
  87. data/include/natalie_parser/node/shadow_arg_node.hpp +40 -0
  88. data/include/natalie_parser/node/shell_node.hpp +32 -0
  89. data/include/natalie_parser/node/splat_node.hpp +39 -0
  90. data/include/natalie_parser/node/splat_value_node.hpp +32 -0
  91. data/include/natalie_parser/node/stabby_proc_node.hpp +29 -0
  92. data/include/natalie_parser/node/string_node.hpp +42 -0
  93. data/include/natalie_parser/node/super_node.hpp +44 -0
  94. data/include/natalie_parser/node/symbol_key_node.hpp +19 -0
  95. data/include/natalie_parser/node/symbol_node.hpp +30 -0
  96. data/include/natalie_parser/node/to_array_node.hpp +33 -0
  97. data/include/natalie_parser/node/true_node.hpp +23 -0
  98. data/include/natalie_parser/node/unary_op_node.hpp +41 -0
  99. data/include/natalie_parser/node/undef_node.hpp +31 -0
  100. data/include/natalie_parser/node/until_node.hpp +21 -0
  101. data/include/natalie_parser/node/while_node.hpp +52 -0
  102. data/include/natalie_parser/node/yield_node.hpp +29 -0
  103. data/include/natalie_parser/node.hpp +89 -0
  104. data/include/natalie_parser/parser.hpp +218 -0
  105. data/include/natalie_parser/token.hpp +842 -0
  106. data/include/tm/defer.hpp +34 -0
  107. data/include/tm/hashmap.hpp +826 -0
  108. data/include/tm/macros.hpp +16 -0
  109. data/include/tm/optional.hpp +223 -0
  110. data/include/tm/owned_ptr.hpp +186 -0
  111. data/include/tm/recursion_guard.hpp +156 -0
  112. data/include/tm/shared_ptr.hpp +259 -0
  113. data/include/tm/string.hpp +1447 -0
  114. data/include/tm/tests.hpp +78 -0
  115. data/include/tm/vector.hpp +796 -0
  116. data/lib/natalie_parser/sexp.rb +36 -0
  117. data/lib/natalie_parser/version.rb +5 -0
  118. data/lib/natalie_parser.rb +3 -0
  119. data/natalie_parser.gemspec +23 -0
  120. data/src/lexer/interpolated_string_lexer.cpp +88 -0
  121. data/src/lexer/regexp_lexer.cpp +95 -0
  122. data/src/lexer/word_array_lexer.cpp +134 -0
  123. data/src/lexer.cpp +1703 -0
  124. data/src/node/alias_node.cpp +11 -0
  125. data/src/node/assignment_node.cpp +33 -0
  126. data/src/node/begin_node.cpp +29 -0
  127. data/src/node/begin_rescue_node.cpp +33 -0
  128. data/src/node/class_node.cpp +22 -0
  129. data/src/node/interpolated_regexp_node.cpp +19 -0
  130. data/src/node/interpolated_shell_node.cpp +25 -0
  131. data/src/node/interpolated_string_node.cpp +111 -0
  132. data/src/node/interpolated_symbol_node.cpp +25 -0
  133. data/src/node/match_node.cpp +14 -0
  134. data/src/node/module_node.cpp +21 -0
  135. data/src/node/multiple_assignment_node.cpp +37 -0
  136. data/src/node/node.cpp +10 -0
  137. data/src/node/node_with_args.cpp +35 -0
  138. data/src/node/op_assign_node.cpp +36 -0
  139. data/src/node/string_node.cpp +33 -0
  140. data/src/parser.cpp +2972 -0
  141. data/src/token.cpp +27 -0
  142. metadata +186 -0
@@ -0,0 +1,16 @@
1
+ #pragma once
2
+
3
+ #define TM_UNREACHABLE() \
4
+ { \
5
+ fprintf(stderr, "panic: unreachable in %s#%d\n", __FILE__, __LINE__); \
6
+ abort(); \
7
+ }
8
+
9
+ #define TM_NOT_YET_IMPLEMENTED(msg, ...) \
10
+ { \
11
+ fprintf(stderr, "NOT YET IMPLEMENTED in %s#%d: " msg, __FILE__, __LINE__, ##__VA_ARGS__); \
12
+ fprintf(stderr, "\n"); \
13
+ abort(); \
14
+ }
15
+
16
+ #define TM_UNUSED(var) (void)var
@@ -0,0 +1,223 @@
1
+ #pragma once
2
+
3
+ #include <assert.h>
4
+ #include <stdio.h>
5
+
6
+ namespace TM {
7
+
8
+ template <typename T>
9
+ class Optional {
10
+ public:
11
+ /**
12
+ * Constructs a new Optional with a value.
13
+ *
14
+ * ```
15
+ * auto obj = Thing(1);
16
+ * auto opt = Optional<Thing>(obj);
17
+ * assert(opt);
18
+ * ```
19
+ */
20
+ Optional(T value)
21
+ : m_present { true }
22
+ , m_value { value } { }
23
+
24
+ /**
25
+ * Constructs a new Optional without a value.
26
+ *
27
+ * ```
28
+ * auto opt = Optional<Thing>();
29
+ * assert_not(opt);
30
+ * ```
31
+ */
32
+ Optional()
33
+ : m_present { false } { }
34
+
35
+ /**
36
+ * Copies the given Optional.
37
+ *
38
+ * ```
39
+ * auto obj = Thing(1);
40
+ * auto opt1 = Optional<Thing>(obj);
41
+ * auto opt2 = Optional<Thing>(opt1);
42
+ * assert_eq(obj, opt1.value());
43
+ * assert_eq(obj, opt2.value());
44
+ * ```
45
+ */
46
+ Optional(const Optional &other)
47
+ : m_present { other.m_present } {
48
+ if (m_present)
49
+ m_value = other.m_value;
50
+ }
51
+
52
+ ~Optional() {
53
+ clear();
54
+ }
55
+
56
+ /**
57
+ * Overwrites this Optional with the given one.
58
+ *
59
+ * ```
60
+ * auto obj1 = Thing(1);
61
+ * auto obj2 = Thing(2);
62
+ * auto opt1 = Optional<Thing>(obj1);
63
+ * auto opt2 = Optional<Thing>(obj2);
64
+ * opt1 = opt2;
65
+ * assert_eq(obj2, opt1.value());
66
+ * assert_eq(obj2, opt2.value());
67
+ * ```
68
+ */
69
+ Optional<T> &operator=(const Optional<T> &other) {
70
+ m_present = other.m_present;
71
+ if (m_present)
72
+ m_value = other.m_value;
73
+ return *this;
74
+ }
75
+
76
+ /**
77
+ * Overwrites this Optional with the given moved value.
78
+ *
79
+ * ```
80
+ * auto obj1 = Thing(1);
81
+ * auto obj2 = Thing(2);
82
+ * auto opt = Optional<Thing>(obj1);
83
+ * opt = std::move(obj2);
84
+ * assert_eq(obj2, opt.value());
85
+ * ```
86
+ */
87
+ Optional<T> &operator=(T &&value) {
88
+ m_present = true;
89
+ m_value = value;
90
+ return *this;
91
+ }
92
+
93
+ /**
94
+ * Returns a reference to the underlying value.
95
+ *
96
+ * ```
97
+ * auto obj = Thing(1);
98
+ * auto opt = Optional<Thing>(obj);
99
+ * assert_eq(obj, opt.value());
100
+ * ```
101
+ *
102
+ * This method aborts if the value not present.
103
+ *
104
+ * ```should_abort
105
+ * auto opt = Optional<Thing>();
106
+ * opt.value();
107
+ * ```
108
+ */
109
+ T &value() {
110
+ assert(m_present);
111
+ return m_value;
112
+ }
113
+
114
+ /**
115
+ * Returns a reference to the underlying value.
116
+ *
117
+ * ```
118
+ * auto obj = Thing(1);
119
+ * auto opt = Optional<Thing>(obj);
120
+ * assert_eq(obj, opt.value());
121
+ * ```
122
+ *
123
+ * This method aborts if the value not present.
124
+ *
125
+ * ```should_abort
126
+ * auto opt = Optional<Thing>();
127
+ * opt.value();
128
+ * ```
129
+ */
130
+ T const &value() const {
131
+ assert(m_present);
132
+ return m_value;
133
+ }
134
+
135
+ /**
136
+ * Returns a reference to the underlying value or
137
+ * the given fallback value.
138
+ *
139
+ * ```
140
+ * auto obj = Thing(1);
141
+ * auto opt = Optional<Thing>(obj);
142
+ * assert_eq(Thing(1), opt.value_or(Thing(2)));
143
+ * ```
144
+ *
145
+ * If we don't have a value present, then the fallback
146
+ * is returned.
147
+ *
148
+ * ```
149
+ * auto opt = Optional<Thing>();
150
+ * assert_eq(Thing(2), opt.value_or(Thing(2)));
151
+ * ```
152
+ */
153
+ T const &value_or(const T &fallback) const {
154
+ if (present())
155
+ return value();
156
+ else
157
+ return fallback;
158
+ }
159
+
160
+ /**
161
+ * Returns a reference to the underlying value.
162
+ *
163
+ * ```
164
+ * auto obj = Thing(1);
165
+ * auto opt = Optional<Thing>(obj);
166
+ * assert_eq(obj, *opt);
167
+ * ```
168
+ *
169
+ * This method aborts if the value not present.
170
+ *
171
+ * ```should_abort
172
+ * auto opt = Optional<Thing>();
173
+ * *opt;
174
+ * ```
175
+ */
176
+ T operator*() {
177
+ assert(m_present);
178
+ return m_value;
179
+ }
180
+
181
+ /**
182
+ * Sets the Optional to not present.
183
+ *
184
+ * ```
185
+ * auto opt = Optional<Thing>(Thing(1));
186
+ * assert(opt);
187
+ * opt.clear();
188
+ * assert_not(opt);
189
+ * ```
190
+ */
191
+ void clear() { m_present = false; }
192
+
193
+ /**
194
+ * Returns true if the Optional contains a value.
195
+ *
196
+ * ```
197
+ * auto opt1 = Optional<Thing>(Thing(1));
198
+ * auto opt2 = Optional<Thing>();
199
+ * assert(opt1);
200
+ * assert_not(opt2);
201
+ * ```
202
+ */
203
+ operator bool() const { return m_present; }
204
+
205
+ /**
206
+ * Returns true if the Optional contains a value.
207
+ *
208
+ * ```
209
+ * auto opt1 = Optional<Thing>(Thing(1));
210
+ * auto opt2 = Optional<Thing>();
211
+ * assert(opt1.present());
212
+ * assert_not(opt2.present());
213
+ * ```
214
+ */
215
+ bool present() const { return m_present; }
216
+
217
+ private:
218
+ bool m_present;
219
+
220
+ T m_value {};
221
+ };
222
+
223
+ }
@@ -0,0 +1,186 @@
1
+ #pragma once
2
+
3
+ #include <assert.h>
4
+
5
+ namespace TM {
6
+
7
+ template <typename T>
8
+ class OwnedPtr {
9
+ public:
10
+ /**
11
+ * Constructs a null OwnedPtr.
12
+ *
13
+ * ```
14
+ * auto ptr = OwnedPtr<Thing>();
15
+ * assert(!ptr);
16
+ * ```
17
+ */
18
+ OwnedPtr()
19
+ : m_ptr { nullptr } { }
20
+
21
+ /**
22
+ * Constructs an OwnedPtr with the given raw pointer.
23
+ *
24
+ * ```
25
+ * auto ptr = OwnedPtr<Thing>(new Thing(1));
26
+ * assert(ptr);
27
+ * ```
28
+ *
29
+ * Alternative syntax:
30
+ *
31
+ * ```
32
+ * OwnedPtr<Thing> ptr = new Thing(1);
33
+ * assert(ptr);
34
+ * ```
35
+ */
36
+ OwnedPtr(T *ptr)
37
+ : m_ptr { ptr } { }
38
+
39
+ ~OwnedPtr() {
40
+ delete m_ptr;
41
+ }
42
+
43
+ OwnedPtr(const OwnedPtr &other) = delete;
44
+
45
+ /**
46
+ * Moves the given OwnedPtr's raw pointer into a
47
+ * new OwnedPtr and invalidates the old one
48
+ * (sets it to nullptr).
49
+ *
50
+ * ```
51
+ * OwnedPtr<Thing> obj1 = new Thing(1);
52
+ * OwnedPtr<Thing> obj2 = std::move(obj1);
53
+ * assert(!obj1);
54
+ * assert(obj2);
55
+ * ```
56
+ */
57
+ OwnedPtr(OwnedPtr &&other)
58
+ : m_ptr { other.m_ptr } {
59
+ other.m_ptr = nullptr;
60
+ }
61
+
62
+ /**
63
+ * Overwrites this OwnedPtr with a new raw pointer,
64
+ * destroying our existing held object, if any.
65
+ *
66
+ * ```
67
+ * auto obj1 = new Thing(1);
68
+ * auto ptr = OwnedPtr<Thing>(obj1);
69
+ * ptr = new Thing(2);
70
+ * // obj1 has been destroyed
71
+ * ```
72
+ */
73
+ OwnedPtr<T> &operator=(T *ptr) {
74
+ if (ptr != m_ptr)
75
+ delete m_ptr;
76
+ m_ptr = ptr;
77
+ return *this;
78
+ }
79
+
80
+ OwnedPtr<T> &operator=(const OwnedPtr<T> &other) = delete;
81
+
82
+ /**
83
+ * Returns true if this OwnedPtr is not null.
84
+ *
85
+ * ```
86
+ * auto ptr1 = OwnedPtr<Thing>(new Thing(1));
87
+ * auto ptr2 = OwnedPtr<Thing>();
88
+ * assert(ptr1);
89
+ * assert(!ptr2);
90
+ * ```
91
+ */
92
+ operator bool() const {
93
+ return !!m_ptr;
94
+ }
95
+
96
+ /**
97
+ * Returns a reference to the underlying raw pointer.
98
+ *
99
+ * ```
100
+ * auto raw = new Thing(1);
101
+ * auto ptr = OwnedPtr<Thing>(raw);
102
+ * assert_eq(*raw, *ptr);
103
+ * ```
104
+ *
105
+ * This method aborts if the pointer is null.
106
+ *
107
+ * ```should_abort
108
+ * auto ptr = OwnedPtr<Thing>();
109
+ * *ptr;
110
+ * ```
111
+ */
112
+ const T &operator*() const {
113
+ assert(m_ptr);
114
+ return *m_ptr;
115
+ }
116
+
117
+ /**
118
+ * Returns a reference to the underlying raw pointer.
119
+ *
120
+ * ```
121
+ * auto raw = new Thing(1);
122
+ * auto ptr = OwnedPtr<Thing>(raw);
123
+ * assert_eq(*raw, ptr.ref());
124
+ * ```
125
+ *
126
+ * This method aborts if the pointer is null.
127
+ *
128
+ * ```should_abort
129
+ * auto ptr = OwnedPtr<Thing>();
130
+ * ptr.ref();
131
+ * ```
132
+ */
133
+ T &ref() const {
134
+ assert(m_ptr);
135
+ return *m_ptr;
136
+ }
137
+
138
+ /**
139
+ * Dereferences the underlying raw pointer for
140
+ * chained member reference.
141
+ *
142
+ * ```
143
+ * auto raw = new Thing(1);
144
+ * auto ptr = OwnedPtr<Thing>(raw);
145
+ * assert_eq(1, ptr->value());
146
+ * ```
147
+ *
148
+ * This method aborts if the pointer is null.
149
+ *
150
+ * ```should_abort
151
+ * auto ptr = OwnedPtr<Thing>();
152
+ * ptr->value();
153
+ * ```
154
+ */
155
+ T *operator->() const {
156
+ assert(m_ptr);
157
+ return m_ptr;
158
+ }
159
+
160
+ /**
161
+ * Returns the underlying raw pointer and
162
+ * releases ownership. When this OwnedPtr is
163
+ * destroyed later, the underlying pointer will
164
+ * not be deleted.
165
+ *
166
+ * ```
167
+ * Thing *ptr;
168
+ * {
169
+ * OwnedPtr<Thing> op = new Thing(1);
170
+ * ptr = op.release();
171
+ * }
172
+ * assert(ptr);
173
+ * delete ptr;
174
+ * ```
175
+ */
176
+ T *release() {
177
+ auto ptr = m_ptr;
178
+ m_ptr = nullptr;
179
+ return ptr;
180
+ }
181
+
182
+ private:
183
+ T *m_ptr;
184
+ };
185
+
186
+ }
@@ -0,0 +1,156 @@
1
+ #pragma once
2
+
3
+ #include "tm/defer.hpp"
4
+ #include "tm/hashmap.hpp"
5
+ #include <assert.h>
6
+
7
+ namespace TM {
8
+
9
+ class RecursionGuard {
10
+ public:
11
+ /**
12
+ * Constructs a new RecursionGuard that guards against
13
+ * calling the same code recursively in relation to the given
14
+ * object pointer.
15
+ *
16
+ * ```
17
+ * auto obj = Thing(1);
18
+ * RecursionGuard guard { &obj };
19
+ * ```
20
+ */
21
+ RecursionGuard(void *instance)
22
+ : m_instance { instance } {
23
+ assert(m_instance);
24
+ }
25
+
26
+ /**
27
+ * Runs the given lambda or callable type, passing boolean true
28
+ * if the object was seen already.
29
+ *
30
+ * ```
31
+ * auto obj = Thing(1);
32
+ * RecursionGuard guard { &obj };
33
+ *
34
+ * guard.run([&](bool is_recursive) {
35
+ * if (is_recursive) {
36
+ * printf("recursive!\n");
37
+ * } else {
38
+ * printf("normal work here, possibly recursive\n");
39
+ * }
40
+ * });
41
+ * ```
42
+ */
43
+ template <typename F>
44
+ auto run(F callback) {
45
+ if (seen())
46
+ return callback(true);
47
+
48
+ mark();
49
+ Defer on_close([&]() { clear(); });
50
+ return callback(false);
51
+ }
52
+
53
+ private:
54
+ void *m_instance { nullptr };
55
+
56
+ bool seen() {
57
+ return s_did_run.get(m_instance) != nullptr;
58
+ }
59
+
60
+ void mark() {
61
+ s_did_run.set(m_instance);
62
+ }
63
+
64
+ void clear() {
65
+ s_did_run.remove(m_instance);
66
+ }
67
+
68
+ static inline TM::Hashmap<void *> s_did_run {};
69
+ };
70
+
71
+ class PairedRecursionGuard {
72
+ public:
73
+ /**
74
+ * Constructs a new PairedRecursionGuard that guards against
75
+ * calling the same code recursively in relation to the given
76
+ * two object pointers. This guards against mutual recursion.
77
+ *
78
+ * ```
79
+ * auto obj1 = Thing(1);
80
+ * auto obj2 = Thing(2);
81
+ * PairedRecursionGuard guard { &obj1, &obj2 };
82
+ * ```
83
+ */
84
+ PairedRecursionGuard(void *instance, void *other_instance)
85
+ : m_instance { instance }
86
+ , m_other_instance { other_instance } {
87
+ assert(m_instance);
88
+ assert(m_other_instance);
89
+ }
90
+
91
+ /**
92
+ * Runs the given lambda or callable type, passing boolean true
93
+ * if the object pair was seen already.
94
+ *
95
+ * ```
96
+ * auto obj1 = Thing(1);
97
+ * auto obj2 = Thing(2);
98
+ * PairedRecursionGuard guard { &obj1, &obj2 };
99
+ *
100
+ * guard.run([&](bool is_recursive) {
101
+ * if (is_recursive) {
102
+ * printf("recursive!\n");
103
+ * } else {
104
+ * printf("normal work here, possibly recursive\n");
105
+ * }
106
+ * });
107
+ * ```
108
+ */
109
+ template <typename F>
110
+ auto run(F callback) {
111
+ if (seen())
112
+ return callback(true);
113
+
114
+ mark();
115
+ Defer on_close([&]() { clear(); });
116
+ return callback(false);
117
+ }
118
+
119
+ private:
120
+ void *m_instance { nullptr };
121
+ void *m_other_instance { nullptr };
122
+
123
+ bool seen() {
124
+ auto companions = s_did_run.get(m_instance);
125
+ if (!companions)
126
+ return false;
127
+ return companions->get(m_other_instance) != nullptr;
128
+ }
129
+
130
+ void mark() {
131
+ auto companions = s_did_run.get(m_instance);
132
+ if (!companions) {
133
+ companions = new TM::Hashmap<void *> {};
134
+ s_did_run.put(m_instance, companions);
135
+ }
136
+ companions->set(m_other_instance);
137
+ return;
138
+ }
139
+
140
+ void clear() {
141
+ auto companions = s_did_run.get(m_instance);
142
+ if (!companions) {
143
+ s_did_run.remove(m_instance);
144
+ return;
145
+ }
146
+ companions->remove(m_other_instance);
147
+ if (companions->is_empty()) {
148
+ delete companions;
149
+ s_did_run.remove(m_instance);
150
+ }
151
+ }
152
+
153
+ static inline TM::Hashmap<void *, TM::Hashmap<void *> *> s_did_run {};
154
+ };
155
+
156
+ }