<%# encoding: ASCII -%>
#include "prism/prettyprint.h"

// We optionally support pretty printing nodes. For systems that don't want or
// need this functionality, it can be turned off with the
// PRISM_EXCLUDE_PRETTYPRINT define.
#ifdef PRISM_EXCLUDE_PRETTYPRINT

void pm_prettyprint(void) {}

#else

static inline void
prettyprint_location(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm_location_t *location) {
    pm_line_column_t start = pm_newline_list_line_column(&parser->newline_list, location->start, parser->start_line);
    pm_line_column_t end = pm_newline_list_line_column(&parser->newline_list, location->end, parser->start_line);
    pm_buffer_append_format(output_buffer, "(%" PRIi32 ",%" PRIu32 ")-(%" PRIi32 ",%" PRIu32 ")", start.line, start.column, end.line, end.column);
}

static inline void
prettyprint_constant(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm_constant_id_t constant_id) {
    pm_constant_t *constant = pm_constant_pool_id_to_constant(&parser->constant_pool, constant_id);
    pm_buffer_append_format(output_buffer, ":%.*s", (int) constant->length, constant->start);
}

static void
prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm_node_t *node, pm_buffer_t *prefix_buffer) {
    switch (PM_NODE_TYPE(node)) {
        case PM_SCOPE_NODE:
            // We do not need to print a ScopeNode as it's not part of the AST.
            return;
        <%- nodes.each do |node| -%>
        case <%= node.type %>: {
            <%- if !node.flags.nil? || node.fields.any? -%>
            pm_<%= node.human %>_t *cast = (pm_<%= node.human %>_t *) node;
            <%- end -%>
            pm_buffer_append_string(output_buffer, "@ <%= node.name %> (location: ", <%= node.name.length + 14 %>);
            prettyprint_location(output_buffer, parser, &node->location);
            pm_buffer_append_string(output_buffer, ")\n", 2);
            <%- (fields = [*node.flags, *node.fields]).each_with_index do |field, index| -%>
            <%- preadd = index == fields.length - 1 ? "    " : "|   " -%>

            // <%= field.name %>
            {
                pm_buffer_concat(output_buffer, prefix_buffer);
                pm_buffer_append_string(output_buffer, "+-- <%= field.name %>:", <%= 4 + field.name.length + 1 %>);
            <%- case field -%>
            <%- when Prism::Template::NodeField -%>
                pm_buffer_append_byte(output_buffer, '\n');

                size_t prefix_length = prefix_buffer->length;
                pm_buffer_append_string(prefix_buffer, "<%= preadd %>", 4);
                pm_buffer_concat(output_buffer, prefix_buffer);
                prettyprint_node(output_buffer, parser, (pm_node_t *) cast-><%= field.name %>, prefix_buffer);
                prefix_buffer->length = prefix_length;
            <%- when Prism::Template::OptionalNodeField -%>
                if (cast-><%= field.name %> == NULL) {
                    pm_buffer_append_string(output_buffer, " nil\n", 5);
                } else {
                    pm_buffer_append_byte(output_buffer, '\n');

                    size_t prefix_length = prefix_buffer->length;
                    pm_buffer_append_string(prefix_buffer, "<%= preadd %>", 4);
                    pm_buffer_concat(output_buffer, prefix_buffer);
                    prettyprint_node(output_buffer, parser, (pm_node_t *) cast-><%= field.name %>, prefix_buffer);
                    prefix_buffer->length = prefix_length;
                }
            <%- when Prism::Template::StringField -%>
                pm_buffer_append_string(output_buffer, " \"", 2);
                pm_buffer_append_source(output_buffer, pm_string_source(&cast-><%= field.name %>), pm_string_length(&cast-><%= field.name %>), PM_BUFFER_ESCAPING_RUBY);
                pm_buffer_append_string(output_buffer, "\"\n", 2);
            <%- when Prism::Template::NodeListField -%>
                pm_buffer_append_format(output_buffer, " (length: %lu)\n", (unsigned long) (cast-><%= field.name %>.size));

                size_t last_index = cast-><%= field.name %>.size;
                for (uint32_t index = 0; index < last_index; index++) {
                    size_t prefix_length = prefix_buffer->length;
                    pm_buffer_append_string(prefix_buffer, "<%= preadd %>", 4);
                    pm_buffer_concat(output_buffer, prefix_buffer);
                    pm_buffer_append_string(output_buffer, "+-- ", 4);
                    pm_buffer_append_string(prefix_buffer, (index == last_index - 1) ? "    " : "|   ", 4);
                    prettyprint_node(output_buffer, parser, (pm_node_t *) cast-><%= field.name %>.nodes[index], prefix_buffer);
                    prefix_buffer->length = prefix_length;
                }
            <%- when Prism::Template::ConstantField -%>
                pm_buffer_append_byte(output_buffer, ' ');
                prettyprint_constant(output_buffer, parser, cast-><%= field.name %>);
                pm_buffer_append_byte(output_buffer, '\n');
            <%- when Prism::Template::OptionalConstantField -%>
                if (cast-><%= field.name %> == 0) {
                    pm_buffer_append_string(output_buffer, " nil\n", 5);
                } else {
                    pm_buffer_append_byte(output_buffer, ' ');
                    prettyprint_constant(output_buffer, parser, cast-><%= field.name %>);
                    pm_buffer_append_byte(output_buffer, '\n');
                }
            <%- when Prism::Template::ConstantListField -%>
                pm_buffer_append_string(output_buffer, " [", 2);
                for (uint32_t index = 0; index < cast-><%= field.name %>.size; index++) {
                    if (index != 0) pm_buffer_append_string(output_buffer, ", ", 2);
                    prettyprint_constant(output_buffer, parser, cast-><%= field.name %>.ids[index]);
                }
                pm_buffer_append_string(output_buffer, "]\n", 2);
            <%- when Prism::Template::LocationField -%>
                pm_location_t *location = &cast-><%= field.name %>;
                pm_buffer_append_byte(output_buffer, ' ');
                prettyprint_location(output_buffer, parser, location);
                pm_buffer_append_string(output_buffer, " = \"", 4);
                pm_buffer_append_source(output_buffer, location->start, (size_t) (location->end - location->start), PM_BUFFER_ESCAPING_RUBY);
                pm_buffer_append_string(output_buffer, "\"\n", 2);
            <%- when Prism::Template::OptionalLocationField -%>
                pm_location_t *location = &cast-><%= field.name %>;
                if (location->start == NULL) {
                    pm_buffer_append_string(output_buffer, " nil\n", 5);
                } else {
                    pm_buffer_append_byte(output_buffer, ' ');
                    prettyprint_location(output_buffer, parser, location);
                    pm_buffer_append_string(output_buffer, " = \"", 4);
                    pm_buffer_append_source(output_buffer, location->start, (size_t) (location->end - location->start), PM_BUFFER_ESCAPING_RUBY);
                    pm_buffer_append_string(output_buffer, "\"\n", 2);
                }
            <%- when Prism::Template::UInt8Field -%>
                pm_buffer_append_format(output_buffer, " %" PRIu8 "\n", cast-><%= field.name %>);
            <%- when Prism::Template::UInt32Field -%>
                pm_buffer_append_format(output_buffer, " %" PRIu32 "\n", cast-><%= field.name %>);
            <%- when Prism::Template::Flags -%>
                bool found = false;
                <%- field.values.each do |value| -%>
                if (cast->base.flags & PM_<%= field.human.upcase %>_<%= value.name %>) {
                    if (found) pm_buffer_append_byte(output_buffer, ',');
                    pm_buffer_append_string(output_buffer, " <%= value.name.downcase %>", <%= value.name.bytesize + 1 %>);
                    found = true;
                }
                <%- end -%>
                if (!found) pm_buffer_append_string(output_buffer, " nil", 4);
                pm_buffer_append_byte(output_buffer, '\n');
            <%- when Prism::Template::IntegerField -%>
                const pm_integer_t *integer = &cast-><%= field.name %>;
                pm_buffer_append_byte(output_buffer, ' ');
                pm_integer_string(output_buffer, integer);
                pm_buffer_append_byte(output_buffer, '\n');
            <%- when Prism::Template::DoubleField -%>
                pm_buffer_append_format(output_buffer, " %f\n", cast-><%= field.name %>);
            <%- else -%>
            <%- raise -%>
            <%- end -%>
            }
            <%- end -%>

            break;
        }
        <%- end -%>
    }
}

/**
 * Pretty-prints the AST represented by the given node to the given buffer.
 */
PRISM_EXPORTED_FUNCTION void
pm_prettyprint(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm_node_t *node) {
    pm_buffer_t prefix_buffer = { 0 };
    prettyprint_node(output_buffer, parser, node, &prefix_buffer);
    pm_buffer_free(&prefix_buffer);
}

#endif
