summaryrefslogtreecommitdiff
path: root/prism/templates/src/node.c.erb
blob: e1c35f5a456f042ea52d99ccb76e1f64c65c9c8c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
#line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>"
#include "prism/node.h"

static void
pm_node_memsize_node(pm_node_t *node, pm_memsize_t *memsize);

/**
 * Calculate the size of the node list in bytes.
 */
static size_t
pm_node_list_memsize(pm_node_list_t *node_list, pm_memsize_t *memsize) {
    pm_node_t *node;
    PM_NODE_LIST_FOREACH(node_list, index, node) pm_node_memsize_node(node, memsize);
    return sizeof(pm_node_list_t) + (node_list->capacity * sizeof(pm_node_t *));
}

/**
 * Attempts to grow the node list to the next size. If there is already
 * capacity in the list, this function does nothing. Otherwise it reallocates
 * the list to be twice as large as it was before. If the reallocation fails,
 * this function returns false, otherwise it returns true.
 */
static bool
pm_node_list_grow(pm_node_list_t *list, size_t size) {
    size_t requested_size = list->size + size;

    // If the requested size caused overflow, return false.
    if (requested_size < list->size) return false;

    // If the requested size is within the existing capacity, return true.
    if (requested_size < list->capacity) return true;

    // Otherwise, reallocate the list to be twice as large as it was before.
    size_t next_capacity = list->capacity == 0 ? 4 : list->capacity * 2;

    // If multiplying by 2 caused overflow, return false.
    if (next_capacity < list->capacity) return false;

    // If we didn't get enough by doubling, keep doubling until we do.
    while (requested_size > next_capacity) {
        size_t double_capacity = next_capacity * 2;

        // Ensure we didn't overflow by multiplying by 2.
        if (double_capacity < next_capacity) return false;
        next_capacity = double_capacity;
    }

    pm_node_t **nodes = (pm_node_t **) xrealloc(list->nodes, sizeof(pm_node_t *) * next_capacity);
    if (nodes == NULL) return false;

    list->nodes = nodes;
    list->capacity = next_capacity;
    return true;
}

/**
 * Append a new node onto the end of the node list.
 */
void
pm_node_list_append(pm_node_list_t *list, pm_node_t *node) {
    if (pm_node_list_grow(list, 1)) {
        list->nodes[list->size++] = node;
    }
}

/**
 * Prepend a new node onto the beginning of the node list.
 */
void
pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node) {
    if (pm_node_list_grow(list, 1)) {
        memmove(list->nodes + 1, list->nodes, list->size * sizeof(pm_node_t *));
        list->nodes[0] = node;
        list->size++;
    }
}

/**
 * Concatenate the given node list onto the end of the other node list.
 */
void
pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other) {
    if (other->size > 0 && pm_node_list_grow(list, other->size)) {
        memcpy(list->nodes + list->size, other->nodes, other->size * sizeof(pm_node_t *));
        list->size += other->size;
    }
}

/**
 * Free the internal memory associated with the given node list.
 */
void
pm_node_list_free(pm_node_list_t *list) {
    if (list->capacity > 0) {
        xfree(list->nodes);
        *list = (pm_node_list_t) { 0 };
    }
}

PRISM_EXPORTED_FUNCTION void
pm_node_destroy(pm_parser_t *parser, pm_node_t *node);

/**
 * Destroy the nodes that are contained within the given node list.
 */
static void
pm_node_list_destroy(pm_parser_t *parser, pm_node_list_t *list) {
    pm_node_t *node;
    PM_NODE_LIST_FOREACH(list, index, node) pm_node_destroy(parser, node);
    pm_node_list_free(list);
}

/**
 * Deallocate the space for a pm_node_t. Similarly to pm_node_alloc, we're not
 * using the parser argument, but it's there to allow for the future possibility
 * of pre-allocating larger memory pools.
 */
PRISM_EXPORTED_FUNCTION void
pm_node_destroy(pm_parser_t *parser, pm_node_t *node) {
    switch (PM_NODE_TYPE(node)) {
        <%- nodes.each do |node| -%>
#line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>"
        case <%= node.type %>: {
            <%- if node.fields.any? { |field| ![Prism::Template::LocationField, Prism::Template::OptionalLocationField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::FlagsField, Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::DoubleField].include?(field.class) } -%>
            pm_<%= node.human %>_t *cast = (pm_<%= node.human %>_t *) node;
            <%- end -%>
            <%- node.fields.each do |field| -%>
            <%- case field -%>
            <%- when Prism::Template::LocationField, Prism::Template::OptionalLocationField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::FlagsField, Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::DoubleField -%>
            <%- when Prism::Template::NodeField -%>
            pm_node_destroy(parser, (pm_node_t *)cast-><%= field.name %>);
            <%- when Prism::Template::OptionalNodeField -%>
            if (cast-><%= field.name %> != NULL) {
                pm_node_destroy(parser, (pm_node_t *)cast-><%= field.name %>);
            }
            <%- when Prism::Template::StringField -%>
            pm_string_free(&cast-><%= field.name %>);
            <%- when Prism::Template::NodeListField -%>
            pm_node_list_destroy(parser, &cast-><%= field.name %>);
            <%- when Prism::Template::ConstantListField -%>
            pm_constant_id_list_free(&cast-><%= field.name %>);
            <%- when Prism::Template::IntegerField -%>
            pm_integer_free(&cast-><%= field.name %>);
            <%- else -%>
            <%- raise -%>
            <%- end -%>
            <%- end -%>
            break;
        }
        <%- end -%>
#line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>"
        default:
            assert(false && "unreachable");
            break;
    }
    xfree(node);
}

static void
pm_node_memsize_node(pm_node_t *node, pm_memsize_t *memsize) {
    memsize->node_count++;

    switch (PM_NODE_TYPE(node)) {
        // We do not calculate memsize of a ScopeNode
        // as it should never be generated
        case PM_SCOPE_NODE:
            return;
        <%- nodes.each do |node| -%>
#line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>"
        case <%= node.type %>: {
            pm_<%= node.human %>_t *cast = (pm_<%= node.human %>_t *) node;
            memsize->memsize += sizeof(*cast);
            <%- node.fields.each do |field| -%>
            <%- case field -%>
            <%- when Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::FlagsField, Prism::Template::LocationField, Prism::Template::OptionalLocationField, Prism::Template::DoubleField -%>
            <%- when Prism::Template::NodeField -%>
            pm_node_memsize_node((pm_node_t *)cast-><%= field.name %>, memsize);
            <%- when Prism::Template::OptionalNodeField -%>
            if (cast-><%= field.name %> != NULL) {
                pm_node_memsize_node((pm_node_t *)cast-><%= field.name %>, memsize);
            }
            <%- when Prism::Template::StringField -%>
            memsize->memsize += (pm_string_memsize(&cast-><%= field.name %>) - sizeof(pm_string_t));
            <%- when Prism::Template::NodeListField -%>
            memsize->memsize += (pm_node_list_memsize(&cast-><%= field.name %>, memsize) - sizeof(pm_node_list_t));
            <%- when Prism::Template::ConstantListField -%>
            memsize->memsize += (pm_constant_id_list_memsize(&cast-><%= field.name %>) - sizeof(pm_constant_id_list_t));
            <%- when Prism::Template::IntegerField -%>
            memsize->memsize += (pm_integer_memsize(&cast-><%= field.name %>) - sizeof(pm_integer_t));
            <%- else -%>
            <%- raise -%>
            <%- end -%>
            <%- end -%>
            break;
        }
        <%- end -%>
#line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>"
    }
}

/**
 * Calculates the memory footprint of a given node.
 */
PRISM_EXPORTED_FUNCTION void
pm_node_memsize(pm_node_t *node, pm_memsize_t *memsize) {
    *memsize = (pm_memsize_t) { .memsize = 0, .node_count = 0 };
    pm_node_memsize_node(node, memsize);
}

/**
 * Returns a string representation of the given node type.
 */
PRISM_EXPORTED_FUNCTION const char *
pm_node_type_to_str(pm_node_type_t node_type)
{
    switch (node_type) {
<%- nodes.each do |node| -%>
        case <%= node.type %>:
            return "<%= node.type %>";
<%- end -%>
    }
    return "";
}

/**
 * Visit each of the nodes in this subtree using the given visitor callback. The
 * callback function will be called for each node in the subtree. If it returns
 * false, then that node's children will not be visited. If it returns true,
 * then the children will be visited. The data parameter is treated as an opaque
 * pointer and is passed to the visitor callback for consumers to use as they
 * see fit.
 */
PRISM_EXPORTED_FUNCTION void
pm_visit_node(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void *data), void *data) {
    if (visitor(node, data)) pm_visit_child_nodes(node, visitor, data);
}

/**
 * Visit the children of the given node with the given callback. This is the
 * default behavior for walking the tree that is called from pm_visit_node if
 * the callback returns true.
 */
PRISM_EXPORTED_FUNCTION void
pm_visit_child_nodes(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void *data), void *data) {
    switch (PM_NODE_TYPE(node)) {
        <%- nodes.each do |node| -%>
        <%- if (fields = node.fields.select { |field| field.is_a?(Prism::Template::NodeField) || field.is_a?(Prism::Template::OptionalNodeField) || field.is_a?(Prism::Template::NodeListField) }).any? -%>
        case <%= node.type %>: {
            const pm_<%= node.human %>_t *cast = (const pm_<%= node.human %>_t *) node;
            <%- fields.each do |field| -%>

            // Visit the <%= field.name %> field
            <%- case field -%>
            <%- when Prism::Template::NodeField -%>
            pm_visit_node((const pm_node_t *) cast-><%= field.name %>, visitor, data);
            <%- when Prism::Template::OptionalNodeField -%>
            if (cast-><%= field.name %> != NULL) {
                pm_visit_node((const pm_node_t *) cast-><%= field.name %>, visitor, data);
            }
            <%- when Prism::Template::NodeListField -%>
            const pm_node_list_t *<%= field.name %> = &cast-><%= field.name %>;
            for (size_t index = 0; index < <%= field.name %>->size; index++) {
                pm_visit_node(<%= field.name %>->nodes[index], visitor, data);
            }
            <%- end -%>
            <%- end -%>

            break;
        }
        <%- else -%>
        case <%= node.type %>:
            break;
        <%- end -%>
        <%- end -%>
        case PM_SCOPE_NODE:
            break;
    }
}

// We optionally support dumping to JSON. For systems that don't want or need
// this functionality, it can be turned off with the PRISM_EXCLUDE_JSON define.
#ifndef PRISM_EXCLUDE_JSON

static void
pm_dump_json_constant(pm_buffer_t *buffer, const pm_parser_t *parser, pm_constant_id_t constant_id) {
    const pm_constant_t *constant = pm_constant_pool_id_to_constant(&parser->constant_pool, constant_id);
    pm_buffer_append_byte(buffer, '"');
    pm_buffer_append_source(buffer, constant->start, constant->length, PM_BUFFER_ESCAPING_JSON);
    pm_buffer_append_byte(buffer, '"');
}

static void
pm_dump_json_location(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_location_t *location) {
    uint32_t start = (uint32_t) (location->start - parser->start);
    uint32_t end = (uint32_t) (location->end - parser->start);
    pm_buffer_append_format(buffer, "{\"start\":%" PRIu32 ",\"end\":%" PRIu32 "}", start, end);
}

/**
 * Dump JSON to the given buffer.
 */
PRISM_EXPORTED_FUNCTION void
pm_dump_json(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *node) {
    switch (PM_NODE_TYPE(node)) {
        <%- nodes.each do |node| -%>
        case <%= node.type %>: {
            pm_buffer_append_string(buffer, "{\"type\":\"<%= node.name %>\",\"location\":", <%= node.name.bytesize + 22 %>);

            const pm_<%= node.human %>_t *cast = (const pm_<%= node.human %>_t *) node;
            pm_dump_json_location(buffer, parser, &cast->base.location);
            <%- node.fields.each_with_index do |field, index| -%>

            // Dump the <%= field.name %> field
            pm_buffer_append_byte(buffer, ',');
            pm_buffer_append_string(buffer, "\"<%= field.name %>\":", <%= field.name.bytesize + 3 %>);
            <%- case field -%>
            <%- when Prism::Template::NodeField -%>
            pm_dump_json(buffer, parser, (const pm_node_t *) cast-><%= field.name %>);
            <%- when Prism::Template::OptionalNodeField -%>
            if (cast-><%= field.name %> != NULL) {
                pm_dump_json(buffer, parser, (const pm_node_t *) cast-><%= field.name %>);
            } else {
                pm_buffer_append_string(buffer, "null", 4);
            }
            <%- when Prism::Template::NodeListField -%>
            const pm_node_list_t *<%= field.name %> = &cast-><%= field.name %>;
            pm_buffer_append_byte(buffer, '[');

            for (size_t index = 0; index < <%= field.name %>->size; index++) {
                if (index != 0) pm_buffer_append_byte(buffer, ',');
                pm_dump_json(buffer, parser, <%= field.name %>->nodes[index]);
            }
            pm_buffer_append_byte(buffer, ']');
            <%- when Prism::Template::StringField -%>
            const pm_string_t *<%= field.name %> = &cast-><%= field.name %>;
            pm_buffer_append_byte(buffer, '"');
            pm_buffer_append_source(buffer, pm_string_source(<%= field.name %>), pm_string_length(<%= field.name %>), PM_BUFFER_ESCAPING_JSON);
            pm_buffer_append_byte(buffer, '"');
            <%- when Prism::Template::ConstantField -%>
            pm_dump_json_constant(buffer, parser, cast-><%= field.name %>);
            <%- when Prism::Template::OptionalConstantField -%>
            if (cast-><%= field.name %> != PM_CONSTANT_ID_UNSET) {
                pm_dump_json_constant(buffer, parser, cast-><%= field.name %>);
            } else {
                pm_buffer_append_string(buffer, "null", 4);
            }
            <%- when Prism::Template::ConstantListField -%>
            const pm_constant_id_list_t *<%= field.name %> = &cast-><%= field.name %>;
            pm_buffer_append_byte(buffer, '[');

            for (size_t index = 0; index < <%= field.name %>->size; index++) {
                if (index != 0) pm_buffer_append_byte(buffer, ',');
                pm_dump_json_constant(buffer, parser, <%= field.name %>->ids[index]);
            }
            pm_buffer_append_byte(buffer, ']');
            <%- when Prism::Template::LocationField -%>
            pm_dump_json_location(buffer, parser, &cast-><%= field.name %>);
            <%- when Prism::Template::OptionalLocationField -%>
            if (cast-><%= field.name %>.start != NULL) {
                pm_dump_json_location(buffer, parser, &cast-><%= field.name %>);
            } else {
                pm_buffer_append_string(buffer, "null", 4);
            }
            <%- when Prism::Template::UInt8Field -%>
            pm_buffer_append_format(buffer, "%" PRIu8, cast-><%= field.name %>);
            <%- when Prism::Template::UInt32Field -%>
            pm_buffer_append_format(buffer, "%" PRIu32, cast-><%= field.name %>);
            <%- when Prism::Template::FlagsField -%>
            size_t flags = 0;
            pm_buffer_append_byte(buffer, '[');
            <%- found = flags.find { |flag| flag.name == field.kind }.tap { |found| raise "Expected to find #{field.kind}" unless found } -%>
            <%- found.values.each_with_index do |value, index| -%>
            if (PM_NODE_FLAG_P(cast, PM_<%= found.human.upcase %>_<%= value.name %>)) {
                if (flags != 0) pm_buffer_append_byte(buffer, ',');
                pm_buffer_append_string(buffer, "\"<%= value.name %>\"", <%= value.name.bytesize + 2 %>);
                flags++;
            }
            <%- end -%>
            pm_buffer_append_byte(buffer, ']');
            <%- when Prism::Template::IntegerField -%>
            pm_integer_string(buffer, &cast-><%= field.name %>);
            <%- when Prism::Template::DoubleField -%>
            pm_buffer_append_format(buffer, "%f", cast-><%= field.name %>);
            <%- else -%>
            <%- raise %>
            <%- end -%>
            <%- end -%>

            pm_buffer_append_byte(buffer, '}');
            break;
        }
        <%- end -%>
        case PM_SCOPE_NODE:
            break;
    }
}

#endif