diff options
Diffstat (limited to 'prism/templates')
21 files changed, 1467 insertions, 947 deletions
diff --git a/prism/templates/ext/prism/api_node.c.erb b/prism/templates/ext/prism/api_node.c.erb index 23af8886a7..41d7165930 100644 --- a/prism/templates/ext/prism/api_node.c.erb +++ b/prism/templates/ext/prism/api_node.c.erb @@ -1,5 +1,9 @@ #line <%= __LINE__ + 1 %> "prism/templates/ext/prism/<%= File.basename(__FILE__) %>" #include "prism/extension.h" +#include "prism/internal/allocator.h" +#include "prism/internal/arena.h" + +#include <assert.h> extern VALUE rb_cPrism; extern VALUE rb_cPrismNode; @@ -12,25 +16,20 @@ static VALUE rb_cPrism<%= node.name %>; <%- end -%> static VALUE -pm_location_new(const pm_parser_t *parser, const uint8_t *start, const uint8_t *end, VALUE source, bool freeze) { +pm_location_new(const uint32_t start, const uint32_t length, VALUE source, bool freeze) { if (freeze) { - VALUE location_argv[] = { - source, - LONG2FIX(start - parser->start), - LONG2FIX(end - start) - }; - + VALUE location_argv[] = { source, LONG2FIX(start), LONG2FIX(length) }; return rb_obj_freeze(rb_class_new_instance(3, location_argv, rb_cPrismLocation)); } else { - uint64_t value = ((((uint64_t) (start - parser->start)) << 32) | ((uint32_t) (end - start))); + uint64_t value = ((((uint64_t) start) << 32) | ((uint64_t) length)); return ULL2NUM(value); } } VALUE pm_token_new(const pm_parser_t *parser, const pm_token_t *token, rb_encoding *encoding, VALUE source, bool freeze) { - ID type = rb_intern(pm_token_type_name(token->type)); - VALUE location = pm_location_new(parser, token->start, token->end, source, freeze); + ID type = rb_intern(pm_token_type(token->type)); + VALUE location = pm_location_new((uint32_t) (token->start - pm_parser_start(parser)), (uint32_t) (token->end - token->start), source, freeze); VALUE slice = rb_enc_str_new((const char *) token->start, token->end - token->start, encoding); if (freeze) rb_obj_freeze(slice); @@ -79,19 +78,25 @@ pm_integer_new(const pm_integer_t *integer) { // Create a Prism::Source object from the given parser, after pm_parse() was called. VALUE pm_source_new(const pm_parser_t *parser, rb_encoding *encoding, bool freeze) { - VALUE source_string = rb_enc_str_new((const char *) parser->start, parser->end - parser->start, encoding); + const uint8_t *start = pm_parser_start(parser); + VALUE source_string = rb_enc_str_new((const char *) start, pm_parser_end(parser) - start, encoding); - VALUE offsets = rb_ary_new_capa(parser->newline_list.size); - for (size_t index = 0; index < parser->newline_list.size; index++) { - rb_ary_push(offsets, ULONG2NUM(parser->newline_list.offsets[index])); - } + const pm_line_offset_list_t *line_offsets = pm_parser_line_offsets(parser); + VALUE offsets; if (freeze) { + offsets = rb_ary_new_capa(line_offsets->size); + for (size_t index = 0; index < line_offsets->size; index++) { + rb_ary_push(offsets, ULONG2NUM(line_offsets->offsets[index])); + } + rb_obj_freeze(source_string); rb_obj_freeze(offsets); + } else { + offsets = rb_str_new((const char *) line_offsets->offsets, line_offsets->size * sizeof(uint32_t)); } - VALUE source = rb_funcall(rb_cPrismSource, rb_intern("for"), 3, source_string, LONG2NUM(parser->start_line), offsets); + VALUE source = rb_funcall(rb_cPrismSource, rb_intern("for"), 3, source_string, LONG2NUM(pm_parser_start_line(parser)), offsets); if (freeze) rb_obj_freeze(source); return source; @@ -104,8 +109,8 @@ typedef struct pm_node_stack_node { } pm_node_stack_node_t; static void -pm_node_stack_push(pm_node_stack_node_t **stack, const pm_node_t *visit) { - pm_node_stack_node_t *node = xmalloc(sizeof(pm_node_stack_node_t)); +pm_node_stack_push(pm_arena_t *arena, pm_node_stack_node_t **stack, const pm_node_t *visit) { + pm_node_stack_node_t *node = (pm_node_stack_node_t *) pm_arena_alloc(arena, sizeof(pm_node_stack_node_t), PRISM_ALIGNOF(pm_node_stack_node_t)); node->prev = *stack; node->visit = visit; node->visited = false; @@ -118,32 +123,40 @@ pm_node_stack_pop(pm_node_stack_node_t **stack) { const pm_node_t *visit = current->visit; *stack = current->prev; - xfree(current); return visit; } -VALUE -pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encoding, VALUE source, bool freeze) { - VALUE constants = rb_ary_new_capa(parser->constant_pool.size); - - for (uint32_t index = 0; index < parser->constant_pool.size; index++) { - pm_constant_t *constant = &parser->constant_pool.constants[index]; - int state = 0; +typedef struct { + VALUE constants; + rb_encoding *encoding; +} pm_ast_constants_each_data_t; - VALUE string = rb_enc_str_new((const char *) constant->start, constant->length, encoding); - VALUE value = rb_protect(rb_str_intern, string, &state); +static void +pm_ast_constants_each(const pm_constant_t *constant, void *data) { + pm_ast_constants_each_data_t *constants_data = (pm_ast_constants_each_data_t *) data; + int state = 0; - if (state != 0) { - value = ID2SYM(rb_intern_const("?")); - rb_set_errinfo(Qnil); - } + VALUE string = rb_enc_str_new((const char *) pm_constant_start(constant), pm_constant_length(constant), constants_data->encoding); + VALUE value = rb_protect(rb_str_intern, string, &state); - rb_ary_push(constants, value); + if (state != 0) { + value = ID2SYM(rb_intern_const("?")); + rb_set_errinfo(Qnil); } + rb_ary_push(constants_data->constants, value); +} + +VALUE +pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encoding, VALUE source, bool freeze) { + VALUE constants = rb_ary_new_capa(pm_parser_constants_size(parser)); + pm_ast_constants_each_data_t constants_data = { .constants = constants, .encoding = encoding }; + pm_parser_constants_each(parser, pm_ast_constants_each, &constants_data); + + pm_arena_t *node_arena = pm_arena_new(); pm_node_stack_node_t *node_stack = NULL; - pm_node_stack_push(&node_stack, node); + pm_node_stack_push(node_arena, &node_stack, node); VALUE value_stack = rb_ary_new(); while (node_stack != NULL) { @@ -166,10 +179,10 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi <%- node.fields.each do |field| -%> <%- case field -%> <%- when Prism::Template::NodeField, Prism::Template::OptionalNodeField -%> - pm_node_stack_push(&node_stack, (pm_node_t *) cast-><%= field.name %>); + pm_node_stack_push(node_arena, &node_stack, (pm_node_t *) cast-><%= field.name %>); <%- when Prism::Template::NodeListField -%> for (size_t index = 0; index < cast-><%= field.name %>.size; index++) { - pm_node_stack_push(&node_stack, (pm_node_t *) cast-><%= field.name %>.nodes[index]); + pm_node_stack_push(node_arena, &node_stack, (pm_node_t *) cast-><%= field.name %>.nodes[index]); } <%- end -%> <%- end -%> @@ -200,7 +213,7 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi argv[1] = ULONG2NUM(node->node_id); // location - argv[2] = pm_location_new(parser, node->location.start, node->location.end, source, freeze); + argv[2] = pm_location_new(node->location.start, node->location.length, source, freeze); // flags argv[3] = ULONG2NUM(node->flags); @@ -237,10 +250,10 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi if (freeze) rb_obj_freeze(argv[<%= index %>]); <%- when Prism::Template::LocationField -%> #line <%= __LINE__ + 1 %> "prism/templates/ext/prism/<%= File.basename(__FILE__) %>" - argv[<%= index %>] = pm_location_new(parser, cast-><%= field.name %>.start, cast-><%= field.name %>.end, source, freeze); + argv[<%= index %>] = pm_location_new(cast-><%= field.name %>.start, cast-><%= field.name %>.length, source, freeze); <%- when Prism::Template::OptionalLocationField -%> #line <%= __LINE__ + 1 %> "prism/templates/ext/prism/<%= File.basename(__FILE__) %>" - argv[<%= index %>] = cast-><%= field.name %>.start == NULL ? Qnil : pm_location_new(parser, cast-><%= field.name %>.start, cast-><%= field.name %>.end, source, freeze); + argv[<%= index %>] = cast-><%= field.name %>.length == 0 ? Qnil : pm_location_new(cast-><%= field.name %>.start, cast-><%= field.name %>.length, source, freeze); <%- when Prism::Template::UInt8Field -%> #line <%= __LINE__ + 1 %> "prism/templates/ext/prism/<%= File.basename(__FILE__) %>" argv[<%= index %>] = UINT2NUM(cast-><%= field.name %>); @@ -271,6 +284,7 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi } } + pm_arena_free(node_arena); return rb_ary_pop(value_stack); } diff --git a/prism/templates/include/prism/ast.h.erb b/prism/templates/include/prism/ast.h.erb index 751c0b43c2..3b3be25e76 100644 --- a/prism/templates/include/prism/ast.h.erb +++ b/prism/templates/include/prism/ast.h.erb @@ -2,16 +2,20 @@ * @file ast.h * * The abstract syntax tree. + * + * -- */ #ifndef PRISM_AST_H #define PRISM_AST_H -#include "prism/defines.h" -#include "prism/util/pm_constant_pool.h" -#include "prism/util/pm_integer.h" -#include "prism/util/pm_string.h" +#include "prism/compiler/align.h" +#include "prism/compiler/exported.h" + +#include "prism/arena.h" +#include "prism/constant_pool.h" +#include "prism/integer.h" +#include "prism/stringy.h" -#include <assert.h> #include <stddef.h> #include <stdint.h> @@ -20,7 +24,7 @@ */ typedef enum pm_token_type { <%- tokens.each do |token| -%> - /** <%= token.comment %> */ + /** <%= Prism::Template::Doxygen.verbatim(token.comment) %> */ PM_TOKEN_<%= token.name %><%= " = #{token.value}" if token.value %>, <%- end -%> @@ -44,15 +48,28 @@ typedef struct { } pm_token_t; /** - * This represents a range of bytes in the source string to which a node or - * token corresponds. + * Returns a string representation of the given token type. + * + * @param token_type The type of the token to get the string representation of. + * @returns A string representation of the given token type. This is meant for + * debugging purposes and is not guaranteed to be stable across versions. + */ +PRISM_EXPORTED_FUNCTION const char * pm_token_type(pm_token_type_t token_type); + +/** + * This struct represents a slice in the source code, defined by an offset and + * a length. Note that we have confirmation that we can represent all locations + * within Ruby source files using 32-bit integers per: + * + * https://bugs.ruby-lang.org/issues/20488#note-1 + * */ typedef struct { - /** A pointer to the start location of the range in the source. */ - const uint8_t *start; + /** The offset of the location from the start of the source. */ + uint32_t start; - /** A pointer to the end location of the range in the source. */ - const uint8_t *end; + /** The length of the location. */ + uint32_t length; } pm_location_t; struct pm_node; @@ -104,29 +121,13 @@ static const pm_node_flags_t PM_NODE_FLAG_NEWLINE = 0x1; static const pm_node_flags_t PM_NODE_FLAG_STATIC_LITERAL = 0x2; /** - * Cast the type to an enum to allow the compiler to provide exhaustiveness - * checking. - */ -#define PM_NODE_TYPE(node) ((enum pm_node_type) (node)->type) - -/** - * Return true if the type of the given node matches the given type. - */ -#define PM_NODE_TYPE_P(node, type) (PM_NODE_TYPE(node) == (type)) - -/** - * Return true if the given flag is set on the given node. - */ -#define PM_NODE_FLAG_P(node, flag) ((((pm_node_t *)(node))->flags & (flag)) != 0) - -/** * This is the base structure that represents a node in the syntax tree. It is * embedded into every node type. */ typedef struct pm_node { /** * This represents the type of the node. It somewhat maps to the nodes that - * existed in the original grammar and ripper, but it's not a 1:1 mapping. + * existed in the original grammar and ripper, but it is not a 1:1 mapping. */ pm_node_type_t type; @@ -143,11 +144,46 @@ typedef struct pm_node { uint32_t node_id; /** - * This is the location of the node in the source. It's a range of bytes + * This is the location of the node in the source. It is a range of bytes * containing a start and an end. */ pm_location_t location; } pm_node_t; + +/** + * Cast the given node to the base pm_node_t type. + */ +#define PM_NODE_UPCAST(node_) ((pm_node_t *) (node_)) + +/** + * Cast the type to an enum to allow the compiler to provide exhaustiveness + * checking. + */ +#define PM_NODE_TYPE(node_) ((enum pm_node_type) (node_)->type) + +/** + * Return true if the type of the given node matches the given type. + */ +#define PM_NODE_TYPE_P(node_, type_) (PM_NODE_TYPE(node_) == (type_)) + +/** + * Return the flags associated with the given node. + */ +#define PM_NODE_FLAGS(node_) (PM_NODE_UPCAST(node_)->flags) + +/** + * Return true if the given flag is set on the given node. + */ +#define PM_NODE_FLAG_P(node_, flag_) ((PM_NODE_FLAGS(node_) & (flag_)) != 0) + +/** + * The alignment required for a child node within a parent node. + */ +#ifdef _MSC_VER +#define PM_NODE_ALIGNAS __declspec(align(8)) +#else +#define PM_NODE_ALIGNAS PRISM_ALIGNAS(PRISM_ALIGNOF(void *)) +#endif <%- nodes.each do |node| -%> /** @@ -170,7 +206,6 @@ typedef struct pm_node { typedef struct pm_<%= node.human %> { /** The embedded base node. */ pm_node_t base; - <%- node.fields.each do |field| -%> /** @@ -183,7 +218,7 @@ typedef struct pm_<%= node.human %> { <%- end -%> */ <%= case field - when Prism::Template::NodeField, Prism::Template::OptionalNodeField then "struct #{field.c_type} *#{field.name}" + when Prism::Template::NodeField, Prism::Template::OptionalNodeField then "PM_NODE_ALIGNAS struct #{field.c_type} *#{field.name}" when Prism::Template::NodeListField then "struct pm_node_list #{field.name}" when Prism::Template::ConstantField, Prism::Template::OptionalConstantField then "pm_constant_id_t #{field.name}" when Prism::Template::ConstantListField then "pm_constant_id_list_t #{field.name}" @@ -210,8 +245,27 @@ typedef enum pm_<%= flag.human %> { /** <%= value.comment %> */ PM_<%= flag.human.upcase %>_<%= value.name %> = <%= 1 << (index + Prism::Template::COMMON_FLAGS_COUNT) %>, <%- end -%> + + PM_<%= flag.human.upcase %>_LAST, } pm_<%= flag.human %>_t; <%- end -%> +<%- nodes.each do |node| -%> + +<%- params = node.fields.map(&:c_param) -%> +/** + * Allocate and initialize a new <%= node.name %> node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. +<%- node.fields.each do |field| -%> + * @param <%= field.name %> <%= field.comment ? Prism::Template::Doxygen.verbatim(field.comment.lines.first.strip) : "The #{field.name} field." %> +<%- end -%> + * @returns The newly allocated and initialized node. + */ +PRISM_EXPORTED_FUNCTION pm_<%= node.human %>_t * pm_<%= node.human %>_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location<%= params.empty? ? "" : ", #{params.join(", ")}" %>); +<%- end -%> /** * When we're serializing to Java, we want to skip serializing the location diff --git a/prism/templates/include/prism/diagnostic.h.erb b/prism/templates/include/prism/diagnostic.h.erb deleted file mode 100644 index 07bbc8fae7..0000000000 --- a/prism/templates/include/prism/diagnostic.h.erb +++ /dev/null @@ -1,130 +0,0 @@ -/** - * @file diagnostic.h - * - * A list of diagnostics generated during parsing. - */ -#ifndef PRISM_DIAGNOSTIC_H -#define PRISM_DIAGNOSTIC_H - -#include "prism/ast.h" -#include "prism/defines.h" -#include "prism/util/pm_list.h" - -#include <stdbool.h> -#include <stdlib.h> -#include <assert.h> - -/** - * The diagnostic IDs of all of the diagnostics, used to communicate the types - * of errors between the parser and the user. - */ -typedef enum { - // These are the error diagnostics. - <%- errors.each do |error| -%> - PM_ERR_<%= error.name %>, - <%- end -%> - - // These are the warning diagnostics. - <%- warnings.each do |warning| -%> - PM_WARN_<%= warning.name %>, - <%- end -%> -} pm_diagnostic_id_t; - -/** - * This struct represents a diagnostic generated during parsing. - * - * @extends pm_list_node_t - */ -typedef struct { - /** The embedded base node. */ - pm_list_node_t node; - - /** The location of the diagnostic in the source. */ - pm_location_t location; - - /** The ID of the diagnostic. */ - pm_diagnostic_id_t diag_id; - - /** The message associated with the diagnostic. */ - const char *message; - - /** - * Whether or not the memory related to the message of this diagnostic is - * owned by this diagnostic. If it is, it needs to be freed when the - * diagnostic is freed. - */ - bool owned; - - /** - * The level of the diagnostic, see `pm_error_level_t` and - * `pm_warning_level_t` for possible values. - */ - uint8_t level; -} pm_diagnostic_t; - -/** - * The levels of errors generated during parsing. - */ -typedef enum { - /** For errors that should raise a syntax error. */ - PM_ERROR_LEVEL_SYNTAX = 0, - - /** For errors that should raise an argument error. */ - PM_ERROR_LEVEL_ARGUMENT = 1, - - /** For errors that should raise a load error. */ - PM_ERROR_LEVEL_LOAD = 2 -} pm_error_level_t; - -/** - * The levels of warnings generated during parsing. - */ -typedef enum { - /** For warnings which should be emitted if $VERBOSE != nil. */ - PM_WARNING_LEVEL_DEFAULT = 0, - - /** For warnings which should be emitted if $VERBOSE == true. */ - PM_WARNING_LEVEL_VERBOSE = 1 -} pm_warning_level_t; - -/** - * Get the human-readable name of the given diagnostic ID. - * - * @param diag_id The diagnostic ID. - * @return The human-readable name of the diagnostic ID. - */ -const char * pm_diagnostic_id_human(pm_diagnostic_id_t diag_id); - -/** - * Append a diagnostic to the given list of diagnostics that is using shared - * memory for its message. - * - * @param list The list to append to. - * @param start The start of the diagnostic. - * @param end The end of the diagnostic. - * @param diag_id The diagnostic ID. - * @return Whether the diagnostic was successfully appended. - */ -bool pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id); - -/** - * Append a diagnostic to the given list of diagnostics that is using a format - * string for its message. - * - * @param list The list to append to. - * @param start The start of the diagnostic. - * @param end The end of the diagnostic. - * @param diag_id The diagnostic ID. - * @param ... The arguments to the format string for the message. - * @return Whether the diagnostic was successfully appended. - */ -bool pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id, ...); - -/** - * Deallocate the internal state of the given diagnostic list. - * - * @param list The list to deallocate. - */ -void pm_diagnostic_list_free(pm_list_t *list); - -#endif diff --git a/prism/templates/include/prism/internal/diagnostic.h.erb b/prism/templates/include/prism/internal/diagnostic.h.erb new file mode 100644 index 0000000000..ee44ff5382 --- /dev/null +++ b/prism/templates/include/prism/internal/diagnostic.h.erb @@ -0,0 +1,60 @@ +#ifndef PRISM_INTERNAL_DIAGNOSTIC_H +#define PRISM_INTERNAL_DIAGNOSTIC_H + +#include "prism/internal/list.h" + +#include "prism/arena.h" +#include "prism/diagnostic.h" + +/* + * The diagnostic IDs of all of the diagnostics, used to communicate the types + * of errors between the parser and the user. + */ +typedef enum { + /* These are the error diagnostics. */ + <%- errors.each do |error| -%> + PM_ERR_<%= error.name %>, + <%- end -%> + + /* These are the warning diagnostics. */ + <%- warnings.each do |warning| -%> + PM_WARN_<%= warning.name %>, + <%- end -%> +} pm_diagnostic_id_t; + +/* + * This struct represents a diagnostic generated during parsing. + */ +struct pm_diagnostic_t { + /* The embedded base node. */ + pm_list_node_t node; + + /* The location of the diagnostic in the source. */ + pm_location_t location; + + /* The ID of the diagnostic. */ + pm_diagnostic_id_t diag_id; + + /* The message associated with the diagnostic. */ + const char *message; + + /* + * The level of the diagnostic, see `pm_error_level_t` and + * `pm_warning_level_t` for possible values. + */ + uint8_t level; +}; + +/* + * Append a diagnostic to the given list of diagnostics that is using shared + * memory for its message. + */ +void pm_diagnostic_list_append(pm_arena_t *arena, pm_list_t *list, uint32_t start, uint32_t length, pm_diagnostic_id_t diag_id); + +/* + * Append a diagnostic to the given list of diagnostics that is using a format + * string for its message. + */ +void pm_diagnostic_list_append_format(pm_arena_t *arena, pm_list_t *list, uint32_t start, uint32_t length, pm_diagnostic_id_t diag_id, ...); + +#endif diff --git a/prism/templates/lib/prism/compiler.rb.erb b/prism/templates/lib/prism/compiler.rb.erb index 45ed88d8de..13317cac04 100644 --- a/prism/templates/lib/prism/compiler.rb.erb +++ b/prism/templates/lib/prism/compiler.rb.erb @@ -1,3 +1,6 @@ +#-- +# rbs_inline: enabled + module Prism # A compiler is a visitor that returns the value of each node as it visits. # This is as opposed to a visitor which will only walk the tree. This can be @@ -18,24 +21,32 @@ module Prism # class Compiler < Visitor # Visit an individual node. - def visit(node) + #-- + #: (node?) -> untyped + def visit(node) # :nodoc: node&.accept(self) end # Visit a list of nodes. - def visit_all(nodes) + #-- + #: (Array[node?]) -> untyped + def visit_all(nodes) # :nodoc: nodes.map { |node| node&.accept(self) } end # Visit the child nodes of the given node. - def visit_child_nodes(node) - node.compact_child_nodes.map { |node| node.accept(self) } + #-- + #: (node) -> Array[untyped] + def visit_child_nodes(node) # :nodoc: + node.each_child_node.map { |node| node.accept(self) } end <%- nodes.each_with_index do |node, index| -%> <%= "\n" if index != 0 -%> - # Compile a <%= node.name %> node - alias visit_<%= node.human %> visit_child_nodes + #: (<%= node.name %>) -> Array[untyped] + def visit_<%= node.human %>(node) # :nodoc: + node.each_child_node.map { |node| node.accept(self) } + end <%- end -%> end end diff --git a/prism/templates/lib/prism/dispatcher.rb.erb b/prism/templates/lib/prism/dispatcher.rb.erb index 0db0003464..5991b0c904 100644 --- a/prism/templates/lib/prism/dispatcher.rb.erb +++ b/prism/templates/lib/prism/dispatcher.rb.erb @@ -1,3 +1,6 @@ +#-- +# rbs_inline: enabled + module Prism # The dispatcher class fires events for nodes that are found while walking an # AST to all registered listeners. It's useful for performing different types @@ -32,37 +35,52 @@ module Prism # dispatcher.dispatch_once(integer) # class Dispatcher < Visitor - # attr_reader listeners: Hash[Symbol, Array[Listener]] - attr_reader :listeners + # A hash mapping event names to arrays of listeners that should be notified + # when that event is fired. + attr_reader :listeners #: Hash[Symbol, Array[untyped]] # Initialize a new dispatcher. + #-- + #: () -> void def initialize @listeners = {} end # Register a listener for one or more events. - # - # def register: (Listener, *Symbol) -> void + #-- + #: (untyped, *Symbol) -> void def register(listener, *events) + register_events(listener, events) + end + + # Register all public methods of a listener that match the pattern + # `on_<node_name>_(enter|leave)`. + #-- + #: (untyped) -> void + def register_public_methods(listener) + register_events(listener, listener.public_methods(false).grep(/\Aon_.+_(?:enter|leave)\z/)) + end + + # Register a listener for the given events. + #-- + #: (untyped, Array[Symbol]) -> void + private def register_events(listener, events) # :nodoc: events.each { |event| (listeners[event] ||= []) << listener } end # Walks `root` dispatching events to all registered listeners. - # - # def dispatch: (Node) -> void alias dispatch visit # Dispatches a single event for `node` to all registered listeners. - # - # def dispatch_once: (Node) -> void + #-- + #: (node node) -> void def dispatch_once(node) node.accept(DispatchOnce.new(listeners)) end <%- nodes.each do |node| -%> - # Dispatch enter and leave events for <%= node.name %> nodes and continue - # walking the tree. - def visit_<%= node.human %>(node) + #: (<%= node.name %> node) -> void + def visit_<%= node.human %>(node) # :nodoc: listeners[:on_<%= node.human %>_enter]&.each { |listener| listener.on_<%= node.human %>_enter(node) } super listeners[:on_<%= node.human %>_leave]&.each { |listener| listener.on_<%= node.human %>_leave(node) } @@ -70,14 +88,17 @@ module Prism <%- end -%> class DispatchOnce < Visitor # :nodoc: - attr_reader :listeners + attr_reader :listeners #: Hash[Symbol, Array[untyped]] + #: (Hash[Symbol, Array[untyped]] listeners) -> void def initialize(listeners) @listeners = listeners end <%- nodes.each do |node| -%> # Dispatch enter and leave events for <%= node.name %> nodes. + #-- + #: (<%= node.name %> node) -> void def visit_<%= node.human %>(node) listeners[:on_<%= node.human %>_enter]&.each { |listener| listener.on_<%= node.human %>_enter(node) } listeners[:on_<%= node.human %>_leave]&.each { |listener| listener.on_<%= node.human %>_leave(node) } diff --git a/prism/templates/lib/prism/dot_visitor.rb.erb b/prism/templates/lib/prism/dot_visitor.rb.erb index e9c81e4545..88ef1e1f36 100644 --- a/prism/templates/lib/prism/dot_visitor.rb.erb +++ b/prism/templates/lib/prism/dot_visitor.rb.erb @@ -1,18 +1,26 @@ -require "cgi" +#-- +# rbs_inline: enabled + +require "cgi/escape" +require "cgi/util" unless defined?(CGI::EscapeExt) module Prism # This visitor provides the ability to call Node#to_dot, which converts a # subtree into a graphviz dot graph. class DotVisitor < Visitor class Field # :nodoc: - attr_reader :name, :value, :port + attr_reader :name #: String + attr_reader :value #: String? + attr_reader :port #: bool + #: (String name, String? value, bool port) -> void def initialize(name, value, port) @name = name @value = value @port = port end + #: () -> String def to_dot if port "<tr><td align=\"left\" colspan=\"2\" port=\"#{name}\">#{name}</td></tr>" @@ -23,17 +31,21 @@ module Prism end class Table # :nodoc: - attr_reader :name, :fields + attr_reader :name #: String + attr_reader :fields #: Array[Field] + #: (String name) -> void def initialize(name) @name = name @fields = [] end + #: (String name, ?String? value, ?port: bool) -> void def field(name, value = nil, port: false) fields << Field.new(name, value, port) end + #: () -> String def to_dot dot = <<~DOT <table border="0" cellborder="1" cellspacing="0" cellpadding="4"> @@ -49,26 +61,31 @@ module Prism end class Digraph # :nodoc: - attr_reader :nodes, :waypoints, :edges + attr_reader :nodes, :waypoints, :edges #: Array[String] + #: () -> void def initialize @nodes = [] @waypoints = [] @edges = [] end + #: (String value) -> void def node(value) nodes << value end + #: (String value) -> void def waypoint(value) waypoints << value end + #: (String value) -> void def edge(value) edges << value end + #: () -> String def to_dot <<~DOT digraph "Prism" { @@ -92,21 +109,25 @@ module Prism private_constant :Field, :Table, :Digraph # The digraph that is being built. - attr_reader :digraph + attr_reader :digraph #: Digraph # Initialize a new dot visitor. + #-- + #: () -> void def initialize @digraph = Digraph.new end # Convert this visitor into a graphviz dot graph string. + #-- + #: () -> String def to_dot digraph.to_dot end <%- nodes.each do |node| -%> - # Visit a <%= node.name %> node. - def visit_<%= node.human %>(node) + #: (<%= node.name %>) -> void + def visit_<%= node.human %>(node) # :nodoc: table = Table.new("<%= node.name %>") id = node_id(node) <%- if (node_flags = node.flags) -%> @@ -151,7 +172,7 @@ module Prism <%- end -%> <%- end -%> - digraph.nodes << <<~DOT + digraph.node(<<~DOT) #{id} [ label=<#{table.to_dot.gsub(/\n/, "\n ")}> ]; @@ -164,19 +185,25 @@ module Prism private # Generate a unique node ID for a node throughout the digraph. - def node_id(node) + #-- + #: (node) -> String + def node_id(node) # :nodoc: "Node_#{node.object_id}" end - # Inspect a location to display the start and end line and column numbers. - def location_inspect(location) + # Inspect a location to display the start and end line and columns in bytes. + #-- + #: (Location) -> String + def location_inspect(location) # :nodoc: "(#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column})" end <%- flags.each do |flag| -%> # Inspect a node that has <%= flag.human %> flags to display the flags as a # comma-separated list. - def <%= flag.human %>_inspect(node) + #-- + #: (<%= nodes.filter_map { |node| node.name if node.flags == flag }.join(" | ") %> node) -> String + def <%= flag.human %>_inspect(node) # :nodoc: flags = [] #: Array[String] <%- flag.values.each do |value| -%> flags << "<%= value.name.downcase %>" if node.<%= value.name.downcase %>? diff --git a/prism/templates/lib/prism/dsl.rb.erb b/prism/templates/lib/prism/dsl.rb.erb index e16ebb7110..be7dc6d9c1 100644 --- a/prism/templates/lib/prism/dsl.rb.erb +++ b/prism/templates/lib/prism/dsl.rb.erb @@ -1,8 +1,11 @@ +#-- +# rbs_inline: enabled + module Prism # The DSL module provides a set of methods that can be used to create prism # nodes in a more concise manner. For example, instead of writing: # - # source = Prism::Source.for("[1]") + # source = Prism::Source.for("[1]", 1, [0]) # # Prism::ArrayNode.new( # source, @@ -56,17 +59,31 @@ module Prism extend self # Create a new Source object. + #-- + #: (String string) -> Source def source(string) - Source.for(string) + Source.for(string, 1, build_offsets(string)) end # Create a new Location object. + #-- + #: (?source: Source, ?start_offset: Integer, ?length: Integer) -> Location def location(source: default_source, start_offset: 0, length: 0) Location.new(source, start_offset, length) end <%- nodes.each do |node| -%> + <%- + params = [ + ["source", "Source"], + ["node_id", "Integer"], + ["location", "Location"], + ["flags", "Integer"] + ].concat(node.fields.map { |field| [field.name, field.rbs_class] }) + -%> # Create a new <%= node.name %> node. + #-- + #: (<%= params.map { |(name, type)| "?#{name}: #{type}" }.join(", ") %>) -> <%= node.name %> def <%= node.human %>(<%= ["source: default_source", "node_id: 0", "location: default_location", "flags: 0", *node.fields.map { |field| case field when Prism::Template::NodeField @@ -100,6 +117,8 @@ module Prism <%- flags.each do |flag| -%> # Retrieve the value of one of the <%= flag.name %> flags. + #-- + #: (Symbol name) -> Integer def <%= flag.human.chomp("s") %>(name) case name <%- flag.values.each do |value| -%> @@ -114,20 +133,40 @@ module Prism # The default source object that gets attached to nodes and locations if no # source is specified. + #-- + #: () -> Source def default_source - Source.for("") + Source.for("", 1, [0]) end # The default location object that gets attached to nodes if no location is # specified, which uses the given source. + #-- + #: () -> Location def default_location Location.new(default_source, 0, 0) end # The default node that gets attached to nodes if no node is specified for a # required node field. + #-- + #: (Source source, Location location) -> node def default_node(source, location) - MissingNode.new(source, -1, location, 0) + ErrorRecoveryNode.new(source, -1, location, 0, nil) + end + + private + + # Build the newline byte offset array for the given source string. + #-- + #: (String source) -> Array[Integer] + def build_offsets(source) + offsets = [0] + start = 0 + while (index = source.byteindex("\n", start)) + offsets << (start = index + 1) + end + offsets end end end diff --git a/prism/templates/lib/prism/inspect_visitor.rb.erb b/prism/templates/lib/prism/inspect_visitor.rb.erb index 3cfe615d85..820f5ae75f 100644 --- a/prism/templates/lib/prism/inspect_visitor.rb.erb +++ b/prism/templates/lib/prism/inspect_visitor.rb.erb @@ -1,3 +1,6 @@ +#-- +# rbs_inline: enabled + module Prism # This visitor is responsible for composing the strings that get returned by # the various #inspect methods defined on each of the nodes. @@ -7,8 +10,9 @@ module Prism # when we hit an element in that list. In this case, we have a special # command that replaces the subsequent indent with the given value. class Replace # :nodoc: - attr_reader :value + attr_reader :value #: String + #: (String value) -> void def initialize(value) @value = value end @@ -17,19 +21,25 @@ module Prism private_constant :Replace # The current prefix string. - attr_reader :indent + # :stopdoc: + attr_reader :indent #: String + # :startdoc: # The list of commands that we need to execute in order to compose the # final string. - attr_reader :commands + #: stopdoc: + attr_reader :commands #: Array[[String | node | Replace, String]] + # :startdoc: - # Initializes a new instance of the InspectVisitor. - def initialize(indent = +"") + #: (?String indent) -> void + def initialize(indent = +"") # :nodoc: @indent = indent @commands = [] end # Compose an inspect string for the given node. + #-- + #: (node node) -> String def self.compose(node) visitor = new node.accept(visitor) @@ -37,7 +47,9 @@ module Prism end # Compose the final string. - def compose + #-- + #: () -> String + def compose # :nodoc: buffer = +"" replace = nil @@ -66,8 +78,8 @@ module Prism end <%- nodes.each do |node| -%> - # Inspect a <%= node.name %> node. - def visit_<%= node.human %>(node) + #: (<%= node.name %> node) -> void + def visit_<%= node.human %>(node) # :nodoc: commands << [inspect_node(<%= node.name.inspect %>, node), indent] <%- (fields = [node.flags || Prism::Template::Flags.empty, *node.fields]).each_with_index do |field, index| -%> <%- pointer = index == fields.length - 1 ? "└── " : "├── " -%> @@ -114,13 +126,17 @@ module Prism private # Compose a header for the given node. - def inspect_node(name, node) + #-- + #: (String name, node node) -> String + def inspect_node(name, node) # :nodoc: location = node.location "@ #{name} (location: (#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column}))\n" end # Compose a string representing the given inner location field. - def inspect_location(location) + #-- + #: (Location? location) -> String + def inspect_location(location) # :nodoc: if location "(#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column}) = #{location.slice.inspect}" else diff --git a/prism/templates/lib/prism/mutation_compiler.rb.erb b/prism/templates/lib/prism/mutation_compiler.rb.erb index 565ee4e315..2d555048d2 100644 --- a/prism/templates/lib/prism/mutation_compiler.rb.erb +++ b/prism/templates/lib/prism/mutation_compiler.rb.erb @@ -1,3 +1,6 @@ +#-- +# rbs_inline: enabled + module Prism # This visitor walks through the tree and copies each node as it is being # visited. This is useful for consumers that want to mutate the tree, as you @@ -5,8 +8,8 @@ module Prism class MutationCompiler < Compiler <%- nodes.each_with_index do |node, index| -%> <%= "\n" if index != 0 -%> - # Copy a <%= node.name %> node - def visit_<%= node.human %>(node) + #: (<%= node.name %>) -> node? + def visit_<%= node.human %>(node) # :nodoc: <%- fields = node.fields.select { |field| [Prism::Template::NodeField, Prism::Template::OptionalNodeField, Prism::Template::NodeListField].include?(field.class) } -%> <%- if fields.any? -%> node.copy(<%= fields.map { |field| "#{field.name}: #{field.is_a?(Prism::Template::NodeListField) ? "visit_all" : "visit"}(node.#{field.name})" }.join(", ") %>) diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index e4ca185719..fb13051aba 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -1,24 +1,49 @@ +#-- +# rbs_inline: enabled + module Prism + # @rbs! + # interface _Repository + # def enter: (Integer node_id, Symbol field_name) -> Relocation::Entry + # end + # + # interface _Node + # def deconstruct: () -> Array[Prism::node?] + # def inspect: () -> String + # end + # + # type node = Node & _Node + # This represents a node in the tree. It is the parent class of all of the # various node types. class Node # A pointer to the source that this node was created from. - attr_reader :source + # :stopdoc: + attr_reader :source #: Source private :source + # :startdoc: # A unique identifier for this node. This is used in a very specific # use case where you want to keep around a reference to a node without # having to keep around the syntax tree in memory. This unique identifier # will be consistent across multiple parses of the same source code. - attr_reader :node_id + attr_reader :node_id #: Integer + + # The location associated with this node. For lazily loading Location + # objects, we keep it as a packed integer until it is accessed. + # @rbs @location: Location | Integer # Save this node using a saved source so that it can be retrieved later. + #-- + #: (_Repository repository) -> Relocation::Entry def save(repository) repository.enter(node_id, :itself) end # A Location instance that represents the location of this node in the # source. + #-- + #: () -> Location def location location = @location return location if location.is_a?(Location) @@ -26,104 +51,151 @@ module Prism end # Save the location using a saved source so that it can be retrieved later. + #-- + #: (_Repository repository) -> Relocation::Entry def save_location(repository) repository.enter(node_id, :location) end - # Delegates to the start_line of the associated location object. + # -------------------------------------------------------------------------- + # :section: Location Delegators + # These methods provide convenient access to the underlying Location object. + # -------------------------------------------------------------------------- + + # Delegates to [`start_line`](rdoc-ref:Location#start_line) of the associated location object. + #-- + #: () -> Integer def start_line location.start_line end - # Delegates to the end_line of the associated location object. + # Delegates to [`end_line`](rdoc-ref:Location#end_line) of the associated location object. + #-- + #: () -> Integer def end_line location.end_line end - # The start offset of the node in the source. This method is effectively a - # delegate method to the location object. + # Delegates to [`start_offset`](rdoc-ref:Location#start_offset) of the associated location object. + #-- + #: () -> Integer def start_offset location = @location location.is_a?(Location) ? location.start_offset : location >> 32 end - # The end offset of the node in the source. This method is effectively a - # delegate method to the location object. + # Delegates to [`end_offset`](rdoc-ref:Location#end_offset) of the associated location object. + #-- + #: () -> Integer def end_offset location = @location location.is_a?(Location) ? location.end_offset : ((location >> 32) + (location & 0xFFFFFFFF)) end - # Delegates to the start_character_offset of the associated location object. + # Delegates to [`start_character_offset`](rdoc-ref:Location#start_character_offset) + # of the associated location object. + #-- + #: () -> Integer def start_character_offset location.start_character_offset end - # Delegates to the end_character_offset of the associated location object. + # Delegates to [`end_character_offset`](rdoc-ref:Location#end_character_offset) + # of the associated location object. + #-- + #: () -> Integer def end_character_offset location.end_character_offset end - # Delegates to the cached_start_code_units_offset of the associated location - # object. + # Delegates to [`cached_start_code_units_offset`](rdoc-ref:Location#cached_start_code_units_offset) + # of the associated location object. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_start_code_units_offset(cache) location.cached_start_code_units_offset(cache) end - # Delegates to the cached_end_code_units_offset of the associated location - # object. + # Delegates to [`cached_end_code_units_offset`](rdoc-ref:Location#cached_end_code_units_offset) + # of the associated location object. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_end_code_units_offset(cache) location.cached_end_code_units_offset(cache) end - # Delegates to the start_column of the associated location object. + # Delegates to [`start_column`](rdoc-ref:Location#start_column) of the associated location object. + #-- + #: () -> Integer def start_column location.start_column end - # Delegates to the end_column of the associated location object. + # Delegates to [`end_column`](rdoc-ref:Location#end_column) of the associated location object. + #-- + #: () -> Integer def end_column location.end_column end - # Delegates to the start_character_column of the associated location object. + # Delegates to [`start_character_column`](rdoc-ref:Location#start_character_column) + # of the associated location object. + #-- + #: () -> Integer def start_character_column location.start_character_column end - # Delegates to the end_character_column of the associated location object. + # Delegates to [`end_character_column`](rdoc-ref:Location#end_character_column) + # of the associated location object. + #-- + #: () -> Integer def end_character_column location.end_character_column end - # Delegates to the cached_start_code_units_column of the associated location - # object. + # Delegates to [`cached_start_code_units_column`](rdoc-ref:Location#cached_start_code_units_column) + # of the associated location object. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_start_code_units_column(cache) location.cached_start_code_units_column(cache) end - # Delegates to the cached_end_code_units_column of the associated location - # object. + # Delegates to [`cached_end_code_units_column`](rdoc-ref:Location#cached_end_code_units_column) + # of the associated location object. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_end_code_units_column(cache) location.cached_end_code_units_column(cache) end - # Delegates to the leading_comments of the associated location object. + # Delegates to [`leading_comments`](rdoc-ref:Location#leading_comments) of the associated location object. + #-- + #: () -> Array[Comment] def leading_comments location.leading_comments end - # Delegates to the trailing_comments of the associated location object. + # Delegates to [`trailing_comments`](rdoc-ref:Location#trailing_comments) of the associated location object. + #-- + #: () -> Array[Comment] def trailing_comments location.trailing_comments end - # Delegates to the comments of the associated location object. + # Delegates to [`comments`](rdoc-ref:Location#comments) of the associated location object. + #-- + #: () -> Array[Comment] def comments location.comments end + # :section: + # Returns all of the lines of the source code associated with this node. + #-- + #: () -> Array[String] def source_lines location.source_lines end @@ -133,6 +205,8 @@ module Prism alias script_lines source_lines # Slice the location of the node from the source. + #-- + #: () -> String def slice location.slice end @@ -140,28 +214,38 @@ module Prism # Slice the location of the node from the source, starting at the beginning # of the line that the location starts on, ending at the end of the line # that the location ends on. + #-- + #: () -> String def slice_lines location.slice_lines end # An bitset of flags for this node. There are certain flags that are common # for all nodes, and then some nodes have specific flags. - attr_reader :flags + # :stopdoc: + attr_reader :flags #: Integer protected :flags + # :startdoc: # Returns true if the node has the newline flag set. + #-- + #: () -> bool def newline? flags.anybits?(NodeFlags::NEWLINE) end # Returns true if the node has the static literal flag set. + #-- + #: () -> bool def static_literal? flags.anybits?(NodeFlags::STATIC_LITERAL) end # Similar to inspect, but respects the current level of indentation given by # the pretty print object. - def pretty_print(q) + #-- + #: (PP q) -> void + def pretty_print(q) # :nodoc: q.seplist(inspect.chomp.each_line, -> { q.breakable }) do |line| q.text(line.chomp) end @@ -169,6 +253,8 @@ module Prism end # Convert this node into a graphviz dot graph string. + #-- + #: () -> String def to_dot # @type self: node DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot @@ -180,28 +266,18 @@ module Prism # # Important to note is that the column given to this method should be in # bytes, as opposed to characters or code units. + #-- + #: (Integer line, Integer column) -> Array[node] def tunnel(line, column) - queue = [self] #: Array[Prism::node] - result = [] #: Array[Prism::node] + queue = [self] #: Array[node] + result = [] #: Array[node] + offset = source.byte_offset(line, column) while (node = queue.shift) result << node - node.compact_child_nodes.each do |child_node| - child_location = child_node.location - - start_line = child_location.start_line - end_line = child_location.end_line - - if start_line == end_line - if line == start_line && column >= child_location.start_column && column < child_location.end_column - queue << child_node - break - end - elsif (line == start_line && column >= child_location.start_column) || (line == end_line && column < child_location.end_column) - queue << child_node - break - elsif line > start_line && line < end_line + node.each_child_node do |child_node| + if child_node.start_offset <= offset && offset < child_node.end_offset queue << child_node break end @@ -212,13 +288,14 @@ module Prism end # Returns the first node that matches the given block when visited in a - # depth-first search. This is useful for finding a node that matches a + # breadth-first search. This is useful for finding a node that matches a # particular condition. # # node.breadth_first_search { |node| node.node_id == node_id } - # - def breadth_first_search(&block) - queue = [self] #: Array[Prism::node] + #-- + #: () { (node) -> bool } -> node? + def breadth_first_search(&blk) + queue = [self] #: Array[node] while (node = queue.shift) return node if yield node @@ -227,10 +304,33 @@ module Prism nil end + alias find breadth_first_search + + # Returns all of the nodes that match the given block when visited in a + # breadth-first search. This is useful for finding all nodes that match a + # particular condition. + # + # node.breadth_first_search_all { |node| node.is_a?(Prism::CallNode) } + #-- + #: () { (node) -> bool } -> Array[node] + def breadth_first_search_all(&blk) + queue = [self] #: Array[Prism::node] + results = [] #: Array[Prism::node] + + while (node = queue.shift) + results << node if yield node + queue.concat(node.compact_child_nodes) + end + + results + end + alias find_all breadth_first_search_all # Returns a list of the fields that exist for this node class. Fields # describe the structure of the node. This kind of reflection is useful for # things like recursively visiting each node _and_ field in the tree. + #-- + #: () -> Array[Reflection::Field] def self.fields # This method should only be called on subclasses of Node, not Node # itself. @@ -240,38 +340,57 @@ module Prism end # -------------------------------------------------------------------------- - # :section: Node interface - # These methods are effectively abstract methods that must be implemented by - # the various subclasses of Node. They are here to make it easier to work - # with typecheckers. + # :section: Node Interface + # These methods are effectively abstract methods that are implemented by + # the various subclasses of Node. # -------------------------------------------------------------------------- # Accepts a visitor and calls back into the specialized visit function. + #-- + #: (_Visitor visitor) -> untyped def accept(visitor) raise NoMethodError, "undefined method `accept' for #{inspect}" end # Returns an array of child nodes, including `nil`s in the place of optional # nodes that were not present. + #-- + #: () -> Array[node?] def child_nodes raise NoMethodError, "undefined method `child_nodes' for #{inspect}" end alias deconstruct child_nodes + # With a block given, yields each child node. Without a block, returns + # an enumerator that contains each child node. Excludes any `nil`s in + # the place of optional nodes that were not present. + #-- + #: () -> Enumerator[node, void] + #: () { (node) -> void } -> void + def each_child_node(&blk) + raise NoMethodError, "undefined method `each_child_node' for #{inspect}" + end + # Returns an array of child nodes, excluding any `nil`s in the place of # optional nodes that were not present. + #-- + #: () -> Array[node] def compact_child_nodes raise NoMethodError, "undefined method `compact_child_nodes' for #{inspect}" end # Returns an array of child nodes and locations that could potentially have # comments attached to them. + #-- + #: () -> Array[node | Location] def comment_targets raise NoMethodError, "undefined method `comment_targets' for #{inspect}" end # Returns a string representation of the node. + #-- + #: () -> String def inspect raise NoMethodError, "undefined method `inspect' for #{inspect}" end @@ -288,6 +407,8 @@ module Prism # it uses a single integer comparison, but also because if you're on CRuby # you can take advantage of the fact that case statements with all symbol # keys will use a jump table. + #-- + #: () -> Symbol def type raise NoMethodError, "undefined method `type' for #{inspect}" end @@ -296,6 +417,8 @@ module Prism # splitting on the type of the node without having to do a long === chain. # Note that like #type, it will still be slower than using == for a single # class, but should be faster in a case statement or an array comparison. + #-- + #: () -> Symbol def self.type raise NoMethodError, "undefined method `type' for #{inspect}" end @@ -306,7 +429,13 @@ module Prism #<%= line %> <%- end -%> class <%= node.name -%> < Node + <%- node.fields.each do |field| -%> + # @rbs @<%= field.name %>: <%= field.rbs_class %> + <%- end -%> + # Initialize a new <%= node.name %> node. + #-- + #: (Source source, Integer node_id, Location location, Integer flags, <%= node.fields.map { |field| "#{field.rbs_class} #{field.name}" }.join(", ") %>) -> void def initialize(<%= ["source", "node_id", "location", "flags", *node.fields.map(&:name)].join(", ") %>) @source = source @node_id = node_id @@ -320,12 +449,27 @@ module Prism <%- end -%> end - # def accept: (Visitor visitor) -> void + # --------- + # :section: Repository + # Methods related to Relocation. + # --------- + + # ---------------------------------------------------------------------------------- + # :section: Node Interface + # These methods are present on all subclasses of Node. + # Read the [node interface docs](Node.html#node-interface) for more information. + # ---------------------------------------------------------------------------------- + + # See Node.accept. + #-- + #: (_Visitor visitor) -> untyped def accept(visitor) visitor.visit_<%= node.human %>(self) end - # def child_nodes: () -> Array[nil | Node] + # See Node.child_nodes. + #-- + #: () -> Array[node?] def child_nodes [<%= node.fields.map { |field| case field @@ -335,7 +479,28 @@ module Prism }.compact.join(", ") %>] end - # def compact_child_nodes: () -> Array[Node] + # See Node.each_child_node. + #-- + #: () -> Enumerator[node, void] + #: () { (node) -> void } -> void + def each_child_node(&blk) + return to_enum(:each_child_node) unless block_given? + + <%- node.fields.each do |field| -%> + <%- case field -%> + <%- when Prism::Template::NodeField -%> + yield <%= field.name %> + <%- when Prism::Template::OptionalNodeField -%> + if (<%= field.name %> = self.<%= field.name %>); yield <%= field.name %>; end + <%- when Prism::Template::NodeListField -%> + <%= field.name %>.each { |node| yield node } + <%- end -%> + <%- end -%> + end + + # See Node.compact_child_nodes. + #-- + #: () -> Array[node] def compact_child_nodes <%- if node.fields.any? { |field| field.is_a?(Prism::Template::OptionalNodeField) } -%> compact = [] #: Array[Prism::node] @@ -344,7 +509,7 @@ module Prism <%- when Prism::Template::NodeField -%> compact << <%= field.name %> <%- when Prism::Template::OptionalNodeField -%> - compact << <%= field.name %> if <%= field.name %> + if (<%= field.name %> = self.<%= field.name %>); compact << <%= field.name %>; end <%- when Prism::Template::NodeListField -%> compact.concat(<%= field.name %>) <%- end -%> @@ -360,7 +525,9 @@ module Prism <%- end -%> end - # def comment_targets: () -> Array[Node | Location] + # See Node.comment_targets. + #-- + #: () -> Array[node | Location] def comment_targets [<%= node.fields.map { |field| case field @@ -370,50 +537,101 @@ module Prism }.compact.join(", ") %>] #: Array[Prism::node | Location] end - # def copy: (<%= (["?node_id: Integer", "?location: Location", "?flags: Integer"] + node.fields.map { |field| "?#{field.name}: #{field.rbs_class}" }).join(", ") %>) -> <%= node.name %> + # :call-seq: + # copy(**fields) -> <%= node.name %> + # + # Creates a copy of self with the given fields, using self as the template. + #-- + #: (?node_id: Integer, ?location: Location, ?flags: Integer, <%= node.fields.map { |field| "?#{field.name}: #{field.rbs_class}" }.join(", ") %>) -> <%= node.name %> def copy(<%= (["node_id", "location", "flags"] + node.fields.map(&:name)).map { |field| "#{field}: self.#{field}" }.join(", ") %>) <%= node.name %>.new(<%= ["source", "node_id", "location", "flags", *node.fields.map(&:name)].join(", ") %>) end - # def deconstruct: () -> Array[nil | Node] alias deconstruct child_nodes - # def deconstruct_keys: (Array[Symbol] keys) -> { <%= (["node_id: Integer", "location: Location"] + node.fields.map { |field| "#{field.name}: #{field.rbs_class}" }).join(", ") %> } - def deconstruct_keys(keys) + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] + def deconstruct_keys(keys) # :nodoc: { <%= (["node_id: node_id", "location: location"] + node.fields.map { |field| "#{field.name}: #{field.name}" }).join(", ") %> } end + + # See `Node#type`. + #-- + #: () -> :<%= node.human %> + def type + :<%= node.human %> + end + + # See `Node.type`. + #-- + #: () -> :<%= node.human %> + def self.type + :<%= node.human %> + end + + #: () -> String + def inspect # :nodoc: + InspectVisitor.compose(self) + end + + # :section: + <%- if (node_flags = node.flags) -%> <%- node_flags.values.each do |value| -%> - - # def <%= value.name.downcase %>?: () -> bool + # :category: Flags + # <%= value.comment %> + #-- + #: () -> bool def <%= value.name.downcase %>? flags.anybits?(<%= node_flags.name %>::<%= value.name %>) end + <%- end -%> <%- end -%> <%- node.fields.each do |field| -%> - + <%- case field -%> + <%- when Prism::Template::LocationField -%> + # :category: Locations + # :call-seq: + # <%= field.name %> -> <%= field.call_seq_type %> + # <%- if field.comment.nil? -%> - # attr_reader <%= field.name %>: <%= field.rbs_class %> + # Returns the Location represented by `<%= field.name %>`. <%- else -%> <%- field.each_comment_line do |line| -%> #<%= line %> <%- end -%> <%- end -%> - <%- case field -%> - <%- when Prism::Template::LocationField -%> + #-- + #: () -> Location def <%= field.name %> location = @<%= field.name %> return location if location.is_a?(Location) @<%= field.name %> = Location.new(source, location >> 32, location & 0xFFFFFFFF) end + # :category: Repository # Save the <%= field.name %> location using the given saved source so that # it can be retrieved later. + #-- + #: (_Repository repository) -> Relocation::Entry def save_<%= field.name %>(repository) repository.enter(node_id, :<%= field.name %>) end + <%- when Prism::Template::OptionalLocationField -%> + # :category: Locations + # :call-seq: + # <%= field.name %> -> <%= field.call_seq_type %> + # + <%- if field.comment.nil? -%> + # Returns the Location represented by `<%= field.name %>`. + <%- else -%> + <%- field.each_comment_line do |line| -%> + #<%= line %> + <%- end -%> + <%- end -%> + #-- + #: () -> Location? def <%= field.name %> location = @<%= field.name %> case location @@ -426,54 +644,69 @@ module Prism end end + # :category: Repository # Save the <%= field.name %> location using the given saved source so that # it can be retrieved later. + #-- + #: (_Repository repository) -> Relocation::Entry? def save_<%= field.name %>(repository) repository.enter(node_id, :<%= field.name %>) unless @<%= field.name %>.nil? end <%- else -%> - attr_reader :<%= field.name %> + # :call-seq: + # <%= field.name %> -> <%= field.call_seq_type %> + # + <%- if field.comment.nil? -%> + # Returns the `<%= field.name %>` attribute. + <%- else -%> + <%- field.each_comment_line do |line| -%> + #<%= line %> <%- end -%> <%- end -%> + #-- + #: () -> <%= field.rbs_class %> + def <%= field.name %> + @<%= field.name %> + end + + <%- end -%> + <%- end -%> + # :section: Slicing + <%- node.fields.each do |field| -%> <%- case field -%> <%- when Prism::Template::LocationField -%> <%- raise unless field.name.end_with?("_loc") -%> <%- next if node.fields.any? { |other| other.name == field.name.delete_suffix("_loc") } -%> - - # def <%= field.name.delete_suffix("_loc") %>: () -> String + # :call-seq: + # <%= field.name.delete_suffix("_loc") %> -> String + # + # Slice the location of <%= field.name %> from the source. + #-- + #: () -> String def <%= field.name.delete_suffix("_loc") %> <%= field.name %>.slice end + <%- when Prism::Template::OptionalLocationField -%> <%- raise unless field.name.end_with?("_loc") -%> <%- next if node.fields.any? { |other| other.name == field.name.delete_suffix("_loc") } -%> - - # def <%= field.name.delete_suffix("_loc") %>: () -> String? + # :call-seq: + # <%= field.name.delete_suffix("_loc") %> -> String | nil + # + # Slice the location of <%= field.name %> from the source. + #-- + #: () -> String? def <%= field.name.delete_suffix("_loc") %> <%= field.name %>&.slice end + <%- end -%> <%- end -%> + # :section: - # def inspect -> String - def inspect - InspectVisitor.compose(self) - end - - # Return a symbol representation of this node type. See `Node#type`. - def type - :<%= node.human %> - end - - # Return a symbol representation of this node type. See `Node::type`. - def self.type - :<%= node.human %> - end - - # Implements case-equality for the node. This is effectively == but without - # comparing the value of locations. Locations are checked only for presence. - def ===(other) + #: (untyped other) -> boolish + def ===(other) # :nodoc: other.is_a?(<%= node.name %>)<%= " &&" if (fields = [*node.flags, *node.fields]).any? %> <%- fields.each_with_index do |field, index| -%> <%- if field.is_a?(Prism::Template::LocationField) || field.is_a?(Prism::Template::OptionalLocationField) -%> diff --git a/prism/templates/lib/prism/reflection.rb.erb b/prism/templates/lib/prism/reflection.rb.erb index 6c8b2f4d25..0012f120b2 100644 --- a/prism/templates/lib/prism/reflection.rb.erb +++ b/prism/templates/lib/prism/reflection.rb.erb @@ -1,3 +1,6 @@ +#-- +# rbs_inline: enabled + module Prism # The Reflection module provides the ability to reflect on the structure of # the syntax tree itself, as opposed to looking at a single syntax tree. This @@ -7,9 +10,11 @@ module Prism # for all other field types. class Field # The name of the field. - attr_reader :name + attr_reader :name #: Symbol # Initializes the field with the given name. + #-- + #: (Symbol name) -> void def initialize(name) @name = name end @@ -83,9 +88,11 @@ module Prism # the bitset should be accessed through their query methods. class FlagsField < Field # The names of the flags in the bitset. - attr_reader :flags + attr_reader :flags #: Array[Symbol] # Initializes the flags field with the given name and flags. + #-- + #: (Symbol name, Array[Symbol] flags) -> void def initialize(name, flags) super(name) @flags = flags @@ -93,6 +100,8 @@ module Prism end # Returns the fields for the given node. + #-- + #: (singleton(Node) node) -> Array[Field] def self.fields_for(node) case node.type <%- nodes.each do |node| -%> diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 52821e0f7d..a676f957af 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -1,16 +1,19 @@ +#-- +# rbs_inline: enabled + require "stringio" require_relative "polyfill/unpack1" module Prism # A module responsible for deserializing parse results. - module Serialize + module Serialize # :nodoc: # The major version of prism that we are expecting to find in the serialized # strings. MAJOR_VERSION = 1 # The minor version of prism that we are expecting to find in the serialized # strings. - MINOR_VERSION = 3 + MINOR_VERSION = 9 # The patch version of prism that we are expecting to find in the serialized # strings. @@ -20,9 +23,11 @@ module Prism # # The formatting of the source of this method is purposeful to illustrate # the structure of the serialized data. + #-- + #: (String input, String serialized, bool freeze) -> ParseResult def self.load_parse(input, serialized, freeze) input = input.dup - source = Source.for(input) + source = Source.for(input, 1, []) loader = Loader.new(source, serialized) loader.load_header @@ -38,16 +43,17 @@ module Prism data_loc = loader.load_optional_location_object(freeze) errors = loader.load_errors(encoding, freeze) warnings = loader.load_warnings(encoding, freeze) + continuable = loader.load_bool cpool_base = loader.load_uint32 cpool_size = loader.load_varuint - constant_pool = ConstantPool.new(input, serialized, cpool_base, cpool_size) + constant_pool = ConstantPool.new(serialized, cpool_base, cpool_size) - node = loader.load_node(constant_pool, encoding, freeze) + node = loader.load_node(constant_pool, encoding, freeze) #: ProgramNode loader.load_constant_pool(constant_pool) raise unless loader.eof? - result = ParseResult.new(node, comments, magic_comments, data_loc, errors, warnings, source) + result = ParseResult.new(node, comments, magic_comments, data_loc, errors, warnings, continuable, source) result.freeze if freeze input.force_encoding(encoding) @@ -73,8 +79,10 @@ module Prism # # The formatting of the source of this method is purposeful to illustrate # the structure of the serialized data. + #-- + #: (String input, String serialized, bool freeze) -> LexResult def self.load_lex(input, serialized, freeze) - source = Source.for(input) + source = Source.for(input, 1, []) loader = Loader.new(source, serialized) tokens = loader.load_tokens @@ -90,9 +98,10 @@ module Prism data_loc = loader.load_optional_location_object(freeze) errors = loader.load_errors(encoding, freeze) warnings = loader.load_warnings(encoding, freeze) + continuable = loader.load_bool raise unless loader.eof? - result = LexResult.new(tokens, comments, magic_comments, data_loc, errors, warnings, source) + result = LexResult.new(tokens, comments, magic_comments, data_loc, errors, warnings, continuable, source) tokens.each do |token| token[0].value.force_encoding(encoding) @@ -117,8 +126,10 @@ module Prism # # The formatting of the source of this method is purposeful to illustrate # the structure of the serialized data. + #-- + #: (String input, String serialized, bool freeze) -> Array[Comment] def self.load_parse_comments(input, serialized, freeze) - source = Source.for(input) + source = Source.for(input, 1, []) loader = Loader.new(source, serialized) loader.load_header @@ -139,8 +150,10 @@ module Prism # # The formatting of the source of this method is purposeful to illustrate # the structure of the serialized data. + #-- + #: (String input, String serialized, bool freeze) -> ParseLexResult def self.load_parse_lex(input, serialized, freeze) - source = Source.for(input) + source = Source.for(input, 1, []) loader = Loader.new(source, serialized) tokens = loader.load_tokens @@ -157,17 +170,18 @@ module Prism data_loc = loader.load_optional_location_object(freeze) errors = loader.load_errors(encoding, freeze) warnings = loader.load_warnings(encoding, freeze) + continuable = loader.load_bool cpool_base = loader.load_uint32 cpool_size = loader.load_varuint - constant_pool = ConstantPool.new(input, serialized, cpool_base, cpool_size) + constant_pool = ConstantPool.new(serialized, cpool_base, cpool_size) - node = loader.load_node(constant_pool, encoding, freeze) + node = loader.load_node(constant_pool, encoding, freeze) #: ProgramNode loader.load_constant_pool(constant_pool) raise unless loader.eof? - value = [node, tokens] - result = ParseLexResult.new(value, comments, magic_comments, data_loc, errors, warnings, source) + value = [node, tokens] #: [ProgramNode, Array[[Token, Integer]]] + result = ParseLexResult.new(value, comments, magic_comments, data_loc, errors, warnings, continuable, source) tokens.each do |token| token[0].value.force_encoding(encoding) @@ -189,34 +203,36 @@ module Prism end class ConstantPool # :nodoc: - attr_reader :size + attr_reader :size #: Integer + + # @rbs @serialized: String + # @rbs @base: Integer + # @rbs @pool: Array[Symbol?] - def initialize(input, serialized, base, size) - @input = input + #: (String serialized, Integer base, Integer size) -> void + def initialize(serialized, base, size) @serialized = serialized @base = base @size = size @pool = Array.new(size, nil) end + #: (Integer index, Encoding encoding) -> Symbol def get(index, encoding) @pool[index] ||= begin offset = @base + index * 8 - start = @serialized.unpack1("L", offset: offset) - length = @serialized.unpack1("L", offset: offset + 4) + start = @serialized.unpack1("L", offset: offset) #: Integer + length = @serialized.unpack1("L", offset: offset + 4) #: Integer - if start.nobits?(1 << 31) - @input.byteslice(start, length).force_encoding(encoding).to_sym - else - @serialized.byteslice(start & ((1 << 31) - 1), length).force_encoding(encoding).to_sym - end + (@serialized.byteslice(start, length) or raise).force_encoding(encoding).to_sym end end end if RUBY_ENGINE == "truffleruby" # StringIO is synchronized and that adds a high overhead on TruffleRuby. + # @rbs skip class FastStringIO # :nodoc: attr_accessor :pos @@ -246,8 +262,11 @@ module Prism end class Loader # :nodoc: - attr_reader :input, :io, :source + attr_reader :input #: String + attr_reader :io #: StringIO + attr_reader :source #: Source + #: (Source source, String serialized) -> void def initialize(source, serialized) @input = source.source.dup raise unless serialized.encoding == Encoding::BINARY @@ -256,40 +275,46 @@ module Prism define_load_node_lambdas if RUBY_ENGINE != "ruby" end + #: () -> bool def eof? io.getbyte io.eof? end + #: (ConstantPool constant_pool) -> void def load_constant_pool(constant_pool) trailer = 0 constant_pool.size.times do |index| - start, length = io.read(8).unpack("L2") - trailer += length if start.anybits?(1 << 31) + length = (io.read(8) or raise).unpack1("L", offset: 4) #: Integer + trailer += length end io.read(trailer) end + #: () -> void def load_header raise "Invalid serialization" if io.read(5) != "PRISM" - raise "Invalid serialization" if io.read(3).unpack("C3") != [MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION] + raise "Invalid serialization" if (io.read(3) or raise).unpack("C3") != [MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION] raise "Invalid serialization (location fields must be included but are not)" if io.getbyte != 0 end + #: () -> Encoding def load_encoding - encoding = Encoding.find(io.read(load_varuint)) + encoding = Encoding.find((io.read(load_varuint) or raise)) or raise @input = input.force_encoding(encoding).freeze encoding end + #: (bool freeze) -> Array[Integer] def load_line_offsets(freeze) offsets = Array.new(load_varuint) { load_varuint } offsets.freeze if freeze offsets end + #: (bool freeze) -> Array[Comment] def load_comments(freeze) comments = Array.new(load_varuint) do @@ -297,6 +322,7 @@ module Prism case load_varuint when 0 then InlineComment.new(load_location_object(freeze)) when 1 then EmbDocComment.new(load_location_object(freeze)) + else raise end comment.freeze if freeze @@ -307,6 +333,7 @@ module Prism comments end + #: (bool freeze) -> Array[MagicComment] def load_magic_comments(freeze) magic_comments = Array.new(load_varuint) do @@ -331,10 +358,11 @@ module Prism <%- warnings.each do |warning| -%> <%= warning.name.downcase.to_sym.inspect %>, <%- end -%> - ].freeze + ].freeze #: Array[Symbol] private_constant :DIAGNOSTIC_TYPES + #: () -> Symbol def load_error_level level = io.getbyte @@ -350,13 +378,14 @@ module Prism end end + #: (Encoding encoding, bool freeze) -> Array[ParseError] def load_errors(encoding, freeze) errors = Array.new(load_varuint) do error = ParseError.new( DIAGNOSTIC_TYPES.fetch(load_varuint), - load_embedded_string(encoding), + load_string(encoding), load_location_object(freeze), load_error_level ) @@ -369,6 +398,7 @@ module Prism errors end + #: () -> Symbol def load_warning_level level = io.getbyte @@ -382,13 +412,14 @@ module Prism end end + #: (Encoding encoding, bool freeze) -> Array[ParseWarning] def load_warnings(encoding, freeze) warnings = Array.new(load_varuint) do warning = ParseWarning.new( DIAGNOSTIC_TYPES.fetch(load_varuint), - load_embedded_string(encoding), + load_string(encoding), load_location_object(freeze), load_warning_level ) @@ -401,15 +432,15 @@ module Prism warnings end + #: () -> Array[[Token, Integer]] def load_tokens - tokens = [] + tokens = [] #: Array[[Token, Integer]] while (type = TOKEN_TYPES.fetch(load_varuint)) - start = load_varuint - length = load_varuint + location = load_location_object(false) + lex_state = load_varuint - location = Location.new(@source, start, length) token = Token.new(@source, type, location.slice, location) tokens << [token, lex_state] @@ -420,25 +451,29 @@ module Prism # variable-length integer using https://en.wikipedia.org/wiki/LEB128 # This is also what protobuf uses: https://protobuf.dev/programming-guides/encoding/#varints + #-- + #: () -> Integer def load_varuint - n = io.getbyte + n = (io.getbyte or raise) if n < 128 n else n -= 128 shift = 0 - while (b = io.getbyte) >= 128 + while (b = (io.getbyte or raise)) >= 128 n += (b - 128) << (shift += 7) end n + (b << (shift + 7)) end end + #: () -> Integer def load_varsint n = load_varuint (n >> 1) ^ (-(n & 1)) end + #: () -> Integer def load_integer negative = io.getbyte != 0 length = load_varuint @@ -450,14 +485,22 @@ module Prism value end + #: () -> Float def load_double - io.read(8).unpack1("D") + (io.read(8) or raise).unpack1("D") #: Float end + #: () -> bool + def load_bool + (io.getbyte or raise) != 0 + end + + #: () -> Integer def load_uint32 - io.read(4).unpack1("L") + (io.read(4) or raise).unpack1("L") #: Integer end + #: (ConstantPool constant_pool, Encoding encoding, bool freeze) -> node? def load_optional_node(constant_pool, encoding, freeze) if io.getbyte != 0 io.pos -= 1 @@ -465,90 +508,121 @@ module Prism end end - def load_embedded_string(encoding) - io.read(load_varuint).force_encoding(encoding).freeze - end - + #: (Encoding encoding) -> String def load_string(encoding) - case (type = io.getbyte) - when 1 - input.byteslice(load_varuint, load_varuint).force_encoding(encoding).freeze - when 2 - load_embedded_string(encoding) - else - raise "Unknown serialized string type: #{type}" - end + (io.read(load_varuint) or raise).force_encoding(encoding).freeze end + #: (bool freeze) -> Location def load_location_object(freeze) location = Location.new(source, load_varuint, load_varuint) location.freeze if freeze location end + # Load a location object from the serialized data. Note that we are lying + # about the signature a bit here, because we sometimes load it as a packed + # integer instead of an object. + #-- + #: (bool freeze) -> Location def load_location(freeze) return load_location_object(freeze) if freeze - (load_varuint << 32) | load_varuint + (load_varuint << 32) | load_varuint #: Location end + # Load an optional location object from the serialized data if it is + # present. Note that we are lying about the signature a bit here, because + # we sometimes load it as a packed integer instead of an object. + #-- + #: (bool freeze) -> Location? def load_optional_location(freeze) load_location(freeze) if io.getbyte != 0 end + #: (bool freeze) -> Location? def load_optional_location_object(freeze) load_location_object(freeze) if io.getbyte != 0 end + #: (ConstantPool constant_pool, Encoding encoding) -> Symbol def load_constant(constant_pool, encoding) index = load_varuint constant_pool.get(index - 1, encoding) end + #: (ConstantPool constant_pool, Encoding encoding) -> Symbol? def load_optional_constant(constant_pool, encoding) index = load_varuint constant_pool.get(index - 1, encoding) if index != 0 end if RUBY_ENGINE == "ruby" + #: (ConstantPool constant_pool, Encoding encoding, bool freeze) -> node def load_node(constant_pool, encoding, freeze) type = io.getbyte node_id = load_varuint - location = load_location(freeze) - value = case type - <%- nodes.each_with_index do |node, index| -%> - when <%= index + 1 %> then - <%- if node.needs_serialized_length? -%> - load_uint32 - <%- end -%> - <%= node.name %>.new(<%= ["source", "node_id", "location", "load_varuint", *node.fields.map { |field| - case field - when Prism::Template::NodeField then "load_node(constant_pool, encoding, freeze)" - when Prism::Template::OptionalNodeField then "load_optional_node(constant_pool, encoding, freeze)" - when Prism::Template::StringField then "load_string(encoding)" - when Prism::Template::NodeListField then "Array.new(load_varuint) { load_node(constant_pool, encoding, freeze) }.tap { |nodes| nodes.freeze if freeze }" - when Prism::Template::ConstantField then "load_constant(constant_pool, encoding)" - when Prism::Template::OptionalConstantField then "load_optional_constant(constant_pool, encoding)" - when Prism::Template::ConstantListField then "Array.new(load_varuint) { load_constant(constant_pool, encoding) }.tap { |constants| constants.freeze if freeze }" - when Prism::Template::LocationField then "load_location(freeze)" - when Prism::Template::OptionalLocationField then "load_optional_location(freeze)" - when Prism::Template::UInt8Field then "io.getbyte" - when Prism::Template::UInt32Field then "load_varuint" - when Prism::Template::IntegerField then "load_integer" - when Prism::Template::DoubleField then "load_double" - else raise - end - }].join(", ") -%>) + location = load_location(freeze) #: Location + value = + case type + <%- nodes.each_with_index do |node, index| -%> + when <%= index + 1 %> + <%- if node.needs_serialized_length? -%> + load_uint32 + <%- end -%> + <%= node.name %>.new( + source, + node_id, + location, + load_varuint, + <%- node.fields.each do |field| -%> + <%- case field -%> + <%- when Prism::Template::NodeField -%> + load_node(constant_pool, encoding, freeze), #: <%= field.rbs_class %> + <%- when Prism::Template::OptionalNodeField -%> + load_optional_node(constant_pool, encoding, freeze), #: <%= field.rbs_class %> + <%- when Prism::Template::StringField -%> + load_string(encoding), + <%- when Prism::Template::NodeListField -%> + Array.new(load_varuint) do + load_node(constant_pool, encoding, freeze) #: <%= field.element_rbs_class %> + end.tap { |nodes| nodes.freeze if freeze }, + <%- when Prism::Template::ConstantField -%> + load_constant(constant_pool, encoding), + <%- when Prism::Template::OptionalConstantField -%> + load_optional_constant(constant_pool, encoding), + <%- when Prism::Template::ConstantListField -%> + Array.new(load_varuint) { load_constant(constant_pool, encoding) }.tap { |constants| constants.freeze if freeze }, + <%- when Prism::Template::LocationField -%> + load_location(freeze), + <%- when Prism::Template::OptionalLocationField -%> + load_optional_location(freeze), + <%- when Prism::Template::UInt8Field -%> + (io.getbyte or raise), + <%- when Prism::Template::UInt32Field -%> + load_varuint, + <%- when Prism::Template::IntegerField -%> + load_integer, + <%- when Prism::Template::DoubleField -%> + load_double, + <%- else raise -%> + <%- end -%> + <%- end -%> + ) <%- end -%> - end + else + raise "Unknown node type: #{type}" + end value.freeze if freeze value end else + # @rbs skip def load_node(constant_pool, encoding, freeze) - @load_node_lambdas[io.getbyte].call(constant_pool, encoding, freeze) + @load_node_lambdas[(io.getbyte or raise)].call(constant_pool, encoding, freeze) end + # @rbs skip def define_load_node_lambdas @load_node_lambdas = [ nil, @@ -559,24 +633,46 @@ module Prism <%- if node.needs_serialized_length? -%> load_uint32 <%- end -%> - value = <%= node.name %>.new(<%= ["source", "node_id", "location", "load_varuint", *node.fields.map { |field| - case field - when Prism::Template::NodeField then "load_node(constant_pool, encoding, freeze)" - when Prism::Template::OptionalNodeField then "load_optional_node(constant_pool, encoding, freeze)" - when Prism::Template::StringField then "load_string(encoding)" - when Prism::Template::NodeListField then "Array.new(load_varuint) { load_node(constant_pool, encoding, freeze) }" - when Prism::Template::ConstantField then "load_constant(constant_pool, encoding)" - when Prism::Template::OptionalConstantField then "load_optional_constant(constant_pool, encoding)" - when Prism::Template::ConstantListField then "Array.new(load_varuint) { load_constant(constant_pool, encoding) }" - when Prism::Template::LocationField then "load_location(freeze)" - when Prism::Template::OptionalLocationField then "load_optional_location(freeze)" - when Prism::Template::UInt8Field then "io.getbyte" - when Prism::Template::UInt32Field then "load_varuint" - when Prism::Template::IntegerField then "load_integer" - when Prism::Template::DoubleField then "load_double" - else raise - end - }].join(", ") -%>) + value = + <%= node.name %>.new( + source, + node_id, + location, + load_varuint, + <%- node.fields.map do |field| -%> + <%- case field -%> + <%- when Prism::Template::NodeField -%> + load_node(constant_pool, encoding, freeze), #: <%= field.rbs_class %> + <%- when Prism::Template::OptionalNodeField -%> + load_optional_node(constant_pool, encoding, freeze), #: <%= field.rbs_class %> + <%- when Prism::Template::StringField -%> + load_string(encoding), + <%- when Prism::Template::NodeListField -%> + Array.new(load_varuint) do + load_node(constant_pool, encoding, freeze) #: <%= field.element_rbs_class %> + end, + <%- when Prism::Template::ConstantField -%> + load_constant(constant_pool, encoding), + <%- when Prism::Template::OptionalConstantField -%> + load_optional_constant(constant_pool, encoding), + <%- when Prism::Template::ConstantListField -%> + Array.new(load_varuint) { load_constant(constant_pool, encoding) }, + <%- when Prism::Template::LocationField -%> + load_location(freeze), + <%- when Prism::Template::OptionalLocationField -%> + load_optional_location(freeze), + <%- when Prism::Template::UInt8Field -%> + (io.getbyte or raise), + <%- when Prism::Template::UInt32Field -%> + load_varuint, + <%- when Prism::Template::IntegerField -%> + load_integer, + <%- when Prism::Template::DoubleField -%> + load_double, + <%- else raise -%> + <%- end -%> + <%- end -%> + ) value.freeze if freeze value }, @@ -584,6 +680,10 @@ module Prism ] end end + + # @rbs! + # @load_node_lambdas: Array[Proc] + # def define_load_node_lambdas: () -> void end # The token types that can be indexed by their enum values. @@ -592,7 +692,7 @@ module Prism <%- tokens.each do |token| -%> <%= token.name.to_sym.inspect %>, <%- end -%> - ] + ].freeze #: Array[Symbol?] private_constant :MAJOR_VERSION, :MINOR_VERSION, :PATCH_VERSION private_constant :ConstantPool, :FastStringIO, :Loader, :TOKEN_TYPES diff --git a/prism/templates/lib/prism/visitor.rb.erb b/prism/templates/lib/prism/visitor.rb.erb index 4b30a1815b..f23e87d99e 100644 --- a/prism/templates/lib/prism/visitor.rb.erb +++ b/prism/templates/lib/prism/visitor.rb.erb @@ -1,4 +1,14 @@ +#-- +# rbs_inline: enabled + module Prism + # @rbs! + # interface _Visitor + # <% nodes.each do |node| %> + # def visit_<%= node.human %>: (<%= node.name %>) -> void + # <% end %> + # end + # A class that knows how to walk down the tree. None of the individual visit # methods are implemented on this visitor, so it forces the consumer to # implement each one that they need. For a default implementation that @@ -6,21 +16,27 @@ module Prism class BasicVisitor # Calls `accept` on the given node if it is not `nil`, which in turn should # call back into this visitor by calling the appropriate `visit_*` method. + #-- + #: (node? node) -> void def visit(node) # @type self: _Visitor node&.accept(self) end # Visits each node in `nodes` by calling `accept` on each one. + #-- + #: (Array[node?] nodes) -> void def visit_all(nodes) # @type self: _Visitor nodes.each { |node| node&.accept(self) } end # Visits the child nodes of `node` by calling `accept` on each one. + #-- + #: (node node) -> void def visit_child_nodes(node) # @type self: _Visitor - node.compact_child_nodes.each { |node| node.accept(self) } + node.each_child_node { |node| node.accept(self) } end end @@ -34,7 +50,7 @@ module Prism # # class FooCalls < Prism::Visitor # def visit_call_node(node) - # if node.name == "foo" + # if node.name == :foo # # Do something with the node # end # @@ -47,7 +63,11 @@ module Prism <%- nodes.each_with_index do |node, index| -%> <%= "\n" if index != 0 -%> # Visit a <%= node.name %> node - alias visit_<%= node.human %> visit_child_nodes + #-- + #: (<%= node.name %> node) -> void + def visit_<%= node.human %>(node) + node.each_child_node { |node| node.accept(self) } + end <%- end -%> end end diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index ce98dc5acd..0dea732869 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -1,4 +1,16 @@ -#include "prism/diagnostic.h" +#include "prism/internal/diagnostic.h" + +#include "prism/compiler/inline.h" + +#include "prism/internal/allocator.h" +#include "prism/internal/arena.h" +#include "prism/internal/list.h" + +#include <assert.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> #define PM_DIAGNOSTIC_ID_MAX <%= errors.length + warnings.length %> @@ -75,16 +87,16 @@ typedef struct { * * `PM_WARNING_LEVEL_VERBOSE` - Warnings that appear with `-w`, as in `ruby -w -c -e 'code'`. */ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { - // Special error that can be replaced + /* Special error that can be replaced */ [PM_ERR_CANNOT_PARSE_EXPRESSION] = { "cannot parse the expression", PM_ERROR_LEVEL_SYNTAX }, - // Errors that should raise argument errors + /* Errors that should raise argument errors */ [PM_ERR_INVALID_ENCODING_MAGIC_COMMENT] = { "unknown or invalid encoding in the magic comment", PM_ERROR_LEVEL_ARGUMENT }, - // Errors that should raise load errors + /* Errors that should raise load errors */ [PM_ERR_SCRIPT_NOT_FOUND] = { "no Ruby script found in input", PM_ERROR_LEVEL_LOAD }, - // Errors that should raise syntax errors + /* Errors that should raise syntax errors */ [PM_ERR_ALIAS_ARGUMENT] = { "invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ALIAS_ARGUMENT_NUMBERED_REFERENCE] = { "invalid argument being passed to `alias`; can't make alias for the number variables", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_AMPAMPEQ_MULTI_ASSIGN] = { "unexpected `&&=` in a multiple assignment", PM_ERROR_LEVEL_SYNTAX }, @@ -102,6 +114,8 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_ARGUMENT_FORWARDING_UNBOUND] = { "unexpected `...` in an non-parenthesized call", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_AMPERSAND] = { "unexpected `&`; no anonymous block parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES] = { "unexpected ... when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES_LAMBDA] = { "unexpected ... in lambda argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES_BLOCK] = { "unexpected ... in block argument", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_STAR] = { "unexpected `*`; no anonymous rest parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR] = { "unexpected `**`; no anonymous keyword rest parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT] = { "unexpected `*` splat argument after a `**` keyword splat argument", PM_ERROR_LEVEL_SYNTAX }, @@ -144,7 +158,9 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_CONDITIONAL_WHILE_PREDICATE] = { "expected a predicate expression for the `while` statement", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = { "expected a constant after the `::` operator", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_ENDLESS] = { "could not parse the endless method body", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_ENDLESS_PARAMETERS] = { "could not parse the endless method parameters", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_ENDLESS_SETTER] = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_ENDLESS_DO_BLOCK] = { "unexpected `do` for block in an endless method definition", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_NAME] = { "unexpected %s; expected a method name", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_PARAMS_TERM] = { "expected a delimiter to close the parameters", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_PARAMS_TERM_PAREN] = { "unexpected %s; expected a `)` to close the parameters", PM_ERROR_LEVEL_SYNTAX }, @@ -184,6 +200,8 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_EXPECT_FOR_DELIMITER] = { "unexpected %s; expected a 'do', newline, or ';' after the 'for' loop collection", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_IDENT_REQ_PARAMETER] = { "expected an identifier for the required parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_IN_DELIMITER] = { "expected a delimiter after the patterns of an `in` clause", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_LPAREN_AFTER_NOT_LPAREN] = { "expected a `(` immediately after `not`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_LPAREN_AFTER_NOT_OTHER] = { "expected a `(` after `not`", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER] = { "expected a `(` to start a required parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_MESSAGE] = { "unexpected %s; expecting a message to send to the receiver", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_RBRACKET] = { "expected a matching `]`", PM_ERROR_LEVEL_SYNTAX }, @@ -298,6 +316,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_PARAMETER_UNEXPECTED_NO_KW] = { "unexpected **nil; no keywords marker disallowed after keywords", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_PATTERN_ARRAY_MULTIPLE_RESTS] = { "unexpected multiple '*' rest patterns in an array pattern", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_PATTERN_CAPTURE_DUPLICATE] = { "duplicated variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_CAPTURE_IN_ALTERNATIVE] = { "variable capture in alternative pattern", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET] = { "expected a pattern expression after the `[` operator", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA] = { "expected a pattern expression after `,`", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET] = { "expected a pattern expression after `=>`", PM_ERROR_LEVEL_SYNTAX }, @@ -323,13 +342,15 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_PATTERN_TERM_PAREN] = { "expected a `)` to close the pattern expression", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN] = { "unexpected `||=` in a multiple assignment", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_REGEXP_ENCODING_OPTION_MISMATCH] = { "regexp encoding option '%c' differs from source encoding '%s'", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_ESCAPED_NON_ASCII_IN_UTF8] = { "escaped non ASCII character in UTF-8 regexp: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_REGEXP_INCOMPAT_CHAR_ENCODING] = { "incompatible character encoding: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_REGEXP_NON_ESCAPED_MBC] = { "/.../n has a non escaped non ASCII character in non ASCII-8BIT script: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_INVALID_CHAR_PROPERTY] = { "invalid character property name {%.*s}: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_REGEXP_INVALID_UNICODE_RANGE] = { "invalid Unicode range: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_NON_ESCAPED_MBC] = { "/.../n has a non escaped non ASCII character in non ASCII-8BIT script: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_REGEXP_PARSE_ERROR] = { "%s", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_REGEXP_UNKNOWN_OPTIONS] = { "unknown regexp %s - %.*s", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_REGEXP_TERM] = { "unterminated regexp meets end of file; expected a closing delimiter", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_REGEXP_UTF8_CHAR_NON_UTF8_REGEXP] = { "UTF-8 character in non UTF-8 regexp: /%s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_UTF8_CHAR_NON_UTF8_REGEXP] = { "UTF-8 character in non UTF-8 regexp: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_RESCUE_EXPRESSION] = { "expected a rescued expression", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_RESCUE_MODIFIER_VALUE] = { "expected a value after the `rescue` modifier", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_RESCUE_TERM] = { "expected a closing delimiter for the `rescue` clause", PM_ERROR_LEVEL_SYNTAX }, @@ -344,7 +365,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_STRING_INTERPOLATED_TERM] = { "unterminated string; expected a closing delimiter for the interpolated string", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_STRING_LITERAL_EOF] = { "unterminated string meets end of file", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_STRING_LITERAL_TERM] = { "unexpected %s, expected a string literal terminator", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_SYMBOL_INVALID] = { "invalid symbol", PM_ERROR_LEVEL_SYNTAX }, // TODO expected symbol? prism.c ~9719 + [PM_ERR_SYMBOL_INVALID] = { "invalid symbol", PM_ERROR_LEVEL_SYNTAX }, /* TODO expected symbol? prism.c ~9719 */ [PM_ERR_SYMBOL_TERM_DYNAMIC] = { "unterminated quoted string; expected a closing delimiter for the dynamic symbol", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_SYMBOL_TERM_INTERPOLATED] = { "unterminated symbol; expected a closing delimiter for the interpolated symbol", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_TERNARY_COLON] = { "expected a `:` after the true expression of a ternary operator", PM_ERROR_LEVEL_SYNTAX }, @@ -358,6 +379,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_UNEXPECTED_INDEX_KEYWORDS] = { "unexpected keyword arg given in index assignment; keywords are not allowed in index assignment expressions", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNEXPECTED_LABEL] = { "unexpected label", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNEXPECTED_MULTI_WRITE] = { "unexpected multiple assignment; multiple assignment is not allowed in this context", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNEXPECTED_PARAMETER_DEFAULT_VALUE] = { "unexpected %s; expected a default value for a parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNEXPECTED_RANGE_OPERATOR] = { "unexpected range operator; .. and ... are non-associative and cannot be chained", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNEXPECTED_SAFE_NAVIGATION] = { "&. inside multiple assignment destination", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT] = { "unexpected %s, assuming it is closing the parent %s", PM_ERROR_LEVEL_SYNTAX }, @@ -370,7 +392,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_WRITE_TARGET_UNEXPECTED] = { "unexpected write target", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_XSTRING_TERM] = { "expected a closing delimiter for the `%x` or backtick string", PM_ERROR_LEVEL_SYNTAX }, - // Warnings + /* Warnings */ [PM_WARN_AMBIGUOUS_BINARY_OPERATOR] = { "'%s' after local variable or literal is interpreted as binary operator even though it seems like %s", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS] = { "ambiguous first argument; put parentheses or a space even after `-` operator", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS] = { "ambiguous first argument; put parentheses or a space even after `+` operator", PM_WARNING_LEVEL_VERBOSE }, @@ -406,8 +428,8 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { /** * Get the human-readable name of the given diagnostic ID. */ -const char * -pm_diagnostic_id_human(pm_diagnostic_id_t diag_id) { +static const char * +pm_diagnostic_id_name(pm_diagnostic_id_t diag_id) { switch (diag_id) { <%- errors.each do |error| -%> case PM_ERR_<%= error.name %>: return "<%= error.name.downcase %>"; @@ -421,8 +443,8 @@ pm_diagnostic_id_human(pm_diagnostic_id_t diag_id) { return ""; } -static inline const char * -pm_diagnostic_message(pm_diagnostic_id_t diag_id) { +static PRISM_INLINE const char * +pm_diagnostic_id_message(pm_diagnostic_id_t diag_id) { assert(diag_id < PM_DIAGNOSTIC_ID_MAX); const char *message = diagnostic_messages[diag_id].message; @@ -431,91 +453,102 @@ pm_diagnostic_message(pm_diagnostic_id_t diag_id) { return message; } -static inline uint8_t -pm_diagnostic_level(pm_diagnostic_id_t diag_id) { +static PRISM_INLINE uint8_t +pm_diagnostic_id_level(pm_diagnostic_id_t diag_id) { assert(diag_id < PM_DIAGNOSTIC_ID_MAX); return (uint8_t) diagnostic_messages[diag_id].level; } /** + * Get the type of the given diagnostic. + */ +const char * +pm_diagnostic_type(const pm_diagnostic_t *diagnostic) { + return pm_diagnostic_id_name(diagnostic->diag_id); +} + +/** + * Get the location of the given diagnostic. + */ +pm_location_t +pm_diagnostic_location(const pm_diagnostic_t *diagnostic) { + return diagnostic->location; +} + +/** + * Get the message of the given diagnostic. + */ +const char * +pm_diagnostic_message(const pm_diagnostic_t *diagnostic) { + return diagnostic->message; +} + +/** + * Get the error level associated with the given diagnostic. + */ +pm_error_level_t +pm_diagnostic_error_level(const pm_diagnostic_t *diagnostic) { + return (pm_error_level_t) pm_diagnostic_id_level(diagnostic->diag_id); +} + +/** + * Get the warning level associated with the given diagnostic. + */ +pm_warning_level_t +pm_diagnostic_warning_level(const pm_diagnostic_t *diagnostic) { + return (pm_warning_level_t) pm_diagnostic_id_level(diagnostic->diag_id); +} + +/** * Append an error to the given list of diagnostic. */ -bool -pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id) { - pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(1, sizeof(pm_diagnostic_t)); - if (diagnostic == NULL) return false; +void +pm_diagnostic_list_append(pm_arena_t *arena, pm_list_t *list, uint32_t start, uint32_t length, pm_diagnostic_id_t diag_id) { + pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) pm_arena_zalloc(arena, sizeof(pm_diagnostic_t), PRISM_ALIGNOF(pm_diagnostic_t)); *diagnostic = (pm_diagnostic_t) { - .location = { start, end }, + .location = { .start = start, .length = length }, .diag_id = diag_id, - .message = pm_diagnostic_message(diag_id), - .owned = false, - .level = pm_diagnostic_level(diag_id) + .message = pm_diagnostic_id_message(diag_id), + .level = pm_diagnostic_id_level(diag_id) }; pm_list_append(list, (pm_list_node_t *) diagnostic); - return true; } /** * Append a diagnostic to the given list of diagnostics that is using a format * string for its message. */ -bool -pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id, ...) { +void +pm_diagnostic_list_append_format(pm_arena_t *arena, pm_list_t *list, uint32_t start, uint32_t length, pm_diagnostic_id_t diag_id, ...) { va_list arguments; va_start(arguments, diag_id); - const char *format = pm_diagnostic_message(diag_id); + const char *format = pm_diagnostic_id_message(diag_id); int result = vsnprintf(NULL, 0, format, arguments); va_end(arguments); if (result < 0) { - return false; + return; } - pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(1, sizeof(pm_diagnostic_t)); - if (diagnostic == NULL) { - return false; - } + pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) pm_arena_zalloc(arena, sizeof(pm_diagnostic_t), PRISM_ALIGNOF(pm_diagnostic_t)); - size_t length = (size_t) (result + 1); - char *message = (char *) xmalloc(length); - if (message == NULL) { - xfree(diagnostic); - return false; - } + size_t message_length = (size_t) (result + 1); + char *message = (char *) pm_arena_alloc(arena, message_length, 1); va_start(arguments, diag_id); - vsnprintf(message, length, format, arguments); + vsnprintf(message, message_length, format, arguments); va_end(arguments); *diagnostic = (pm_diagnostic_t) { - .location = { start, end }, + .location = { .start = start, .length = length }, .diag_id = diag_id, .message = message, - .owned = true, - .level = pm_diagnostic_level(diag_id) + .level = pm_diagnostic_id_level(diag_id) }; pm_list_append(list, (pm_list_node_t *) diagnostic); - return true; -} - -/** - * Deallocate the internal state of the given diagnostic list. - */ -void -pm_diagnostic_list_free(pm_list_t *list) { - pm_diagnostic_t *node = (pm_diagnostic_t *) list->head; - - while (node != NULL) { - pm_diagnostic_t *next = (pm_diagnostic_t *) node->node.next; - - if (node->owned) xfree((void *) node->message); - xfree(node); - - node = next; - } } diff --git a/prism/templates/src/json.c.erb b/prism/templates/src/json.c.erb new file mode 100644 index 0000000000..5c4ab8d92a --- /dev/null +++ b/prism/templates/src/json.c.erb @@ -0,0 +1,130 @@ +#include "prism/json.h" + +// Ensure this translation unit is never empty, even when JSON is excluded. +typedef int pm_json_unused_t; + +#ifndef PRISM_EXCLUDE_JSON + +#include "prism/internal/buffer.h" +#include "prism/internal/constant_pool.h" +#include "prism/internal/integer.h" +#include "prism/internal/parser.h" + +#include <inttypes.h> + +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_location_t *location) { + pm_buffer_append_format(buffer, "{\"start\":%" PRIu32 ",\"length\":%" PRIu32 "}", location->start, location->length); +} + +/** + * Dump JSON to the given buffer. + */ +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, &cast->base.location); + <%- [*node.flags, *node.fields].each_with_index do |field, index| -%> + + // Dump the <%= field.name %> field + pm_buffer_append_byte(buffer, ','); + <%- if field.is_a?(Prism::Template::Flags) -%> + pm_buffer_append_string(buffer, "\"flags\":", 8); + <%- else -%> + pm_buffer_append_string(buffer, "\"<%= field.name %>\":", <%= field.name.bytesize + 3 %>); + <%- end -%> + <%- 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, &cast-><%= field.name %>); + <%- when Prism::Template::OptionalLocationField -%> + if (cast-><%= field.name %>.length != 0) { + pm_dump_json_location(buffer, &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::Flags -%> + size_t flags = 0; + pm_buffer_append_byte(buffer, '['); + <%- node.flags.values.each_with_index do |value, index| -%> + if (PM_NODE_FLAG_P(cast, PM_<%= node.flags.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 diff --git a/prism/templates/src/node.c.erb b/prism/templates/src/node.c.erb index 2357e55200..f51aff6e53 100644 --- a/prism/templates/src/node.c.erb +++ b/prism/templates/src/node.c.erb @@ -1,153 +1,85 @@ #line <%= __LINE__ + 1 %> "prism/templates/src/<%= File.basename(__FILE__) %>" -#include "prism/node.h" +#include "prism/internal/node.h" + +#include "prism/internal/arena.h" + +#include <stdlib.h> /** * 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. + * capacity in the list, this function does nothing. Otherwise it allocates a + * new array from the arena (abandon-and-copy strategy) and copies the existing + * data into it. */ -static bool -pm_node_list_grow(pm_node_list_t *list, size_t size) { +static void +pm_node_list_grow(pm_arena_t *arena, 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; + // Guard against overflow on the addition. + if (requested_size < list->size) abort(); - // If the requested size is within the existing capacity, return true. - if (requested_size < list->capacity) return true; + // If the requested size is within the existing capacity, return. + if (requested_size <= list->capacity) return; - // Otherwise, reallocate the list to be twice as large as it was before. + // Otherwise, compute the next capacity by doubling. 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. + // Guard against overflow on the doubling. 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; + if (next_capacity == 0) abort(); + next_capacity *= 2; } - pm_node_t **nodes = (pm_node_t **) xrealloc(list->nodes, sizeof(pm_node_t *) * next_capacity); - if (nodes == NULL) return false; + // Allocate a new array from the arena (old array is abandoned). + pm_node_t **nodes = (pm_node_t **) pm_arena_alloc(arena, sizeof(pm_node_t *) * next_capacity, PRISM_ALIGNOF(pm_node_t *)); + + // Copy old data into the new array. + if (list->size > 0) { + memcpy(nodes, list->nodes, list->size * sizeof(pm_node_t *)); + } list->nodes = nodes; list->capacity = next_capacity; - return true; } /** - * Append a new node onto the end of the node list. + * Slow path for pm_node_list_append: grow the list and append the node. + * Do not call directly - use pm_node_list_append instead. */ 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; - } +pm_node_list_append_slow(pm_arena_t *arena, pm_node_list_t *list, pm_node_t *node) { + pm_node_list_grow(arena, 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++; - } +pm_node_list_prepend(pm_arena_t *arena, pm_node_list_t *list, pm_node_t *node) { + pm_node_list_grow(arena, 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)) { +pm_node_list_concat(pm_arena_t *arena, pm_node_list_t *list, pm_node_list_t *other) { + if (other->size > 0) { + pm_node_list_grow(arena, 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 %> "prism/templates/src/<%= 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::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::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 %> "prism/templates/src/<%= File.basename(__FILE__) %>" - default: - assert(false && "unreachable"); - break; - } - xfree(node); -} - -/** * 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) +const char * +pm_node_type(pm_node_type_t node_type) { switch (node_type) { <%- nodes.each do |node| -%> @@ -166,7 +98,7 @@ pm_node_type_to_str(pm_node_type_t node_type) * pointer and is passed to the visitor callback for consumers to use as they * see fit. */ -PRISM_EXPORTED_FUNCTION void +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); } @@ -176,7 +108,7 @@ pm_visit_node(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void * default behavior for walking the tree that is called from pm_visit_node if * the callback returns true. */ -PRISM_EXPORTED_FUNCTION void +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| -%> @@ -212,122 +144,23 @@ pm_visit_child_nodes(const pm_node_t *node, bool (*visitor)(const pm_node_t *nod break; } } +<%- nodes.each do |node| -%> -// 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); -} - +<%- params = node.fields.map(&:c_param) -%> /** - * Dump JSON to the given buffer. + * Allocate and initialize a new <%= node.name %> node. */ -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.flags, *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::Flags -%> - size_t flags = 0; - pm_buffer_append_byte(buffer, '['); - <%- node.flags.values.each_with_index do |value, index| -%> - if (PM_NODE_FLAG_P(cast, PM_<%= node.flags.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_<%= node.human %>_t * +pm_<%= node.human %>_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location<%= params.empty? ? "" : ", #{params.join(", ")}" %>) { + pm_<%= node.human %>_t *node = (pm_<%= node.human %>_t *) pm_arena_alloc(arena, sizeof(pm_<%= node.human %>_t), PRISM_ALIGNOF(pm_<%= node.human %>_t)); + + *node = (pm_<%= node.human %>_t) { + .base = { .type = <%= node.type %>, .flags = flags, .node_id = node_id, .location = location }<%= node.fields.empty? ? "" : "," %> +<%- node.fields.each_with_index do |field, index| -%> + .<%= field.name %> = <%= field.name %><%= index < node.fields.size - 1 ? "," : "" %> +<%- end -%> + }; - pm_buffer_append_byte(buffer, '}'); - break; - } - <%- end -%> - case PM_SCOPE_NODE: - break; - } + return node; } - -#endif +<%- end -%> diff --git a/prism/templates/src/prettyprint.c.erb b/prism/templates/src/prettyprint.c.erb index 639c2fecf3..f12531d934 100644 --- a/prism/templates/src/prettyprint.c.erb +++ b/prism/templates/src/prettyprint.c.erb @@ -1,23 +1,34 @@ <%# 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. +/* 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) {} +/* Ensure this translation unit is never empty, even when prettyprint is + * excluded. */ +typedef int pm_prettyprint_unused_t; #else -static inline void +#include "prism/compiler/inline.h" +#include "prism/internal/buffer.h" +#include "prism/internal/constant_pool.h" +#include "prism/internal/integer.h" +#include "prism/internal/parser.h" +#include "prism/line_offset_list.h" + +#include <inttypes.h> + +static PRISM_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_line_column_t start = pm_line_offset_list_line_column(&parser->line_offsets, location->start, parser->start_line); + pm_line_column_t end = pm_line_offset_list_line_column(&parser->line_offsets, location->start + location->length, parser->start_line); pm_buffer_append_format(output_buffer, "(%" PRIi32 ",%" PRIu32 ")-(%" PRIi32 ",%" PRIu32 ")", start.line, start.column, end.line, end.column); } -static inline void +static PRISM_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); @@ -106,17 +117,17 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm 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_source(output_buffer, parser->start + location->start, (size_t) location->length, 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) { + if (location->length == 0) { 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_source(output_buffer, parser->start + location->start, (size_t) location->length, PM_BUFFER_ESCAPING_RUBY); pm_buffer_append_string(output_buffer, "\"\n", 2); } <%- when Prism::Template::UInt8Field -%> @@ -156,11 +167,11 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm /** * Pretty-prints the AST represented by the given node to the given buffer. */ -PRISM_EXPORTED_FUNCTION void +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); + pm_buffer_cleanup(&prefix_buffer); } #endif diff --git a/prism/templates/src/serialize.c.erb b/prism/templates/src/serialize.c.erb index 9f8f0cbd07..3d9811e5db 100644 --- a/prism/templates/src/serialize.c.erb +++ b/prism/templates/src/serialize.c.erb @@ -1,57 +1,58 @@ -#include "prism.h" +#include "prism/excludes.h" + +/* We optionally support serializing to a binary string. For systems that do not + * want or need this functionality, it can be turned off with the + * PRISM_EXCLUDE_SERIALIZATION define. */ +#ifdef PRISM_EXCLUDE_SERIALIZATION + +/* Ensure this translation unit is never empty, even when serialization is + * excluded. */ +typedef int pm_serialize_unused_t; + +#else + +#include "prism/compiler/inline.h" -// We optionally support serializing to a binary string. For systems that don't -// want or need this functionality, it can be turned off with the -// PRISM_EXCLUDE_SERIALIZATION define. -#ifndef PRISM_EXCLUDE_SERIALIZATION +#include "prism/internal/buffer.h" +#include "prism/internal/comments.h" +#include "prism/internal/diagnostic.h" +#include "prism/internal/encoding.h" +#include "prism/internal/list.h" +#include "prism/internal/magic_comments.h" +#include "prism/internal/options.h" +#include "prism/internal/parser.h" +#include "prism.h" +#include "prism/ast.h" +#include "prism/line_offset_list.h" + +#include <assert.h> #include <stdio.h> +#include <string.h> -static inline uint32_t +static PRISM_INLINE uint32_t pm_ptrdifft_to_u32(ptrdiff_t value) { assert(value >= 0 && ((unsigned long) value) < UINT32_MAX); return (uint32_t) value; } -static inline uint32_t +static PRISM_INLINE uint32_t pm_sizet_to_u32(size_t value) { assert(value < UINT32_MAX); return (uint32_t) value; } static void -pm_serialize_location(const pm_parser_t *parser, const pm_location_t *location, pm_buffer_t *buffer) { - assert(location->start); - assert(location->end); - assert(location->start <= location->end); - - pm_buffer_append_varuint(buffer, pm_ptrdifft_to_u32(location->start - parser->start)); - pm_buffer_append_varuint(buffer, pm_ptrdifft_to_u32(location->end - location->start)); +pm_serialize_location(const pm_location_t *location, pm_buffer_t *buffer) { + pm_buffer_append_varuint(buffer, location->start); + pm_buffer_append_varuint(buffer, location->length); } static void -pm_serialize_string(const pm_parser_t *parser, const pm_string_t *string, pm_buffer_t *buffer) { - switch (string->type) { - case PM_STRING_SHARED: { - pm_buffer_append_byte(buffer, 1); - pm_buffer_append_varuint(buffer, pm_ptrdifft_to_u32(pm_string_source(string) - parser->start)); - pm_buffer_append_varuint(buffer, pm_sizet_to_u32(pm_string_length(string))); - break; - } - case PM_STRING_OWNED: - case PM_STRING_CONSTANT: { - uint32_t length = pm_sizet_to_u32(pm_string_length(string)); - pm_buffer_append_byte(buffer, 2); - pm_buffer_append_varuint(buffer, length); - pm_buffer_append_bytes(buffer, pm_string_source(string), length); - break; - } -#ifdef PRISM_HAS_MMAP - case PM_STRING_MAPPED: - assert(false && "Cannot serialize mapped strings."); - break; -#endif - } +pm_serialize_string(const pm_string_t *string, pm_buffer_t *buffer) { + uint32_t length = pm_sizet_to_u32(pm_string_length(string)); + pm_buffer_append_varuint(buffer, length); + pm_buffer_append_bytes(buffer, pm_string_source(string), length); } static void @@ -72,12 +73,10 @@ static void pm_serialize_node(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) { pm_buffer_append_byte(buffer, (uint8_t) PM_NODE_TYPE(node)); - size_t offset = buffer->length; - - <%- unless Prism::Template::SERIALIZE_ONLY_SEMANTICS_FIELDS -%> + <%- if Prism::Template::INCLUDE_NODE_ID -%> pm_buffer_append_varuint(buffer, node->node_id); <%- end -%> - pm_serialize_location(parser, &node->location, buffer); + pm_serialize_location(&node->location, buffer); switch (PM_NODE_TYPE(node)) { // We do not need to serialize a ScopeNode ever as @@ -106,7 +105,7 @@ pm_serialize_node(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) { pm_serialize_node(parser, (pm_node_t *)((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); } <%- when Prism::Template::StringField -%> - pm_serialize_string(parser, &((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); + pm_serialize_string(&((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); <%- when Prism::Template::NodeListField -%> uint32_t <%= field.name %>_size = pm_sizet_to_u32(((pm_<%= node.human %>_t *)node)-><%= field.name %>.size); pm_buffer_append_varuint(buffer, <%= field.name %>_size); @@ -123,15 +122,15 @@ pm_serialize_node(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) { } <%- when Prism::Template::LocationField -%> <%- if field.should_be_serialized? -%> - pm_serialize_location(parser, &((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); + pm_serialize_location(&((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); <%- end -%> <%- when Prism::Template::OptionalLocationField -%> <%- if field.should_be_serialized? -%> - if (((pm_<%= node.human %>_t *)node)-><%= field.name %>.start == NULL) { + if (((pm_<%= node.human %>_t *)node)-><%= field.name %>.length == 0) { pm_buffer_append_byte(buffer, 0); } else { pm_buffer_append_byte(buffer, 1); - pm_serialize_location(parser, &((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); + pm_serialize_location(&((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); } <%- end -%> <%- when Prism::Template::UInt8Field -%> @@ -148,7 +147,7 @@ pm_serialize_node(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) { <%- end -%> <%- if node.needs_serialized_length? -%> // serialize length - uint32_t length = pm_sizet_to_u32(buffer->length - offset - sizeof(uint32_t)); + uint32_t length = pm_sizet_to_u32(buffer->length - length_offset); memcpy(buffer->value + length_offset, &length, sizeof(uint32_t)); <%- end -%> break; @@ -158,7 +157,7 @@ pm_serialize_node(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) { } static void -pm_serialize_newline_list(pm_newline_list_t *list, pm_buffer_t *buffer) { +pm_serialize_line_offset_list(pm_line_offset_list_t *list, pm_buffer_t *buffer) { uint32_t size = pm_sizet_to_u32(list->size); pm_buffer_append_varuint(buffer, size); @@ -169,60 +168,60 @@ pm_serialize_newline_list(pm_newline_list_t *list, pm_buffer_t *buffer) { } static void -pm_serialize_comment(pm_parser_t *parser, pm_comment_t *comment, pm_buffer_t *buffer) { +pm_serialize_comment(pm_comment_t *comment, pm_buffer_t *buffer) { // serialize type pm_buffer_append_byte(buffer, (uint8_t) comment->type); // serialize location - pm_serialize_location(parser, &comment->location, buffer); + pm_serialize_location(&comment->location, buffer); } /** * Serialize the given list of comments to the given buffer. */ void -pm_serialize_comment_list(pm_parser_t *parser, pm_list_t *list, pm_buffer_t *buffer) { +pm_serialize_comment_list(pm_list_t *list, pm_buffer_t *buffer) { pm_buffer_append_varuint(buffer, pm_sizet_to_u32(pm_list_size(list))); pm_comment_t *comment; for (comment = (pm_comment_t *) list->head; comment != NULL; comment = (pm_comment_t *) comment->node.next) { - pm_serialize_comment(parser, comment, buffer); + pm_serialize_comment(comment, buffer); } } static void -pm_serialize_magic_comment(pm_parser_t *parser, pm_magic_comment_t *magic_comment, pm_buffer_t *buffer) { +pm_serialize_magic_comment(pm_magic_comment_t *magic_comment, pm_buffer_t *buffer) { // serialize key location - pm_buffer_append_varuint(buffer, pm_ptrdifft_to_u32(magic_comment->key_start - parser->start)); - pm_buffer_append_varuint(buffer, pm_sizet_to_u32(magic_comment->key_length)); + pm_buffer_append_varuint(buffer, magic_comment->key.start); + pm_buffer_append_varuint(buffer, magic_comment->key.length); // serialize value location - pm_buffer_append_varuint(buffer, pm_ptrdifft_to_u32(magic_comment->value_start - parser->start)); - pm_buffer_append_varuint(buffer, pm_sizet_to_u32(magic_comment->value_length)); + pm_buffer_append_varuint(buffer, magic_comment->value.start); + pm_buffer_append_varuint(buffer, magic_comment->value.length); } static void -pm_serialize_magic_comment_list(pm_parser_t *parser, pm_list_t *list, pm_buffer_t *buffer) { +pm_serialize_magic_comment_list(pm_list_t *list, pm_buffer_t *buffer) { pm_buffer_append_varuint(buffer, pm_sizet_to_u32(pm_list_size(list))); pm_magic_comment_t *magic_comment; for (magic_comment = (pm_magic_comment_t *) list->head; magic_comment != NULL; magic_comment = (pm_magic_comment_t *) magic_comment->node.next) { - pm_serialize_magic_comment(parser, magic_comment, buffer); + pm_serialize_magic_comment(magic_comment, buffer); } } static void pm_serialize_data_loc(const pm_parser_t *parser, pm_buffer_t *buffer) { - if (parser->data_loc.end == NULL) { + if (parser->data_loc.length == 0) { pm_buffer_append_byte(buffer, 0); } else { pm_buffer_append_byte(buffer, 1); - pm_serialize_location(parser, &parser->data_loc, buffer); + pm_serialize_location(&parser->data_loc, buffer); } } static void -pm_serialize_diagnostic(pm_parser_t *parser, pm_diagnostic_t *diagnostic, pm_buffer_t *buffer) { +pm_serialize_diagnostic(pm_diagnostic_t *diagnostic, pm_buffer_t *buffer) { // serialize the type pm_buffer_append_varuint(buffer, (uint32_t) diagnostic->diag_id); @@ -232,18 +231,18 @@ pm_serialize_diagnostic(pm_parser_t *parser, pm_diagnostic_t *diagnostic, pm_buf pm_buffer_append_string(buffer, diagnostic->message, message_length); // serialize location - pm_serialize_location(parser, &diagnostic->location, buffer); + pm_serialize_location(&diagnostic->location, buffer); pm_buffer_append_byte(buffer, diagnostic->level); } static void -pm_serialize_diagnostic_list(pm_parser_t *parser, pm_list_t *list, pm_buffer_t *buffer) { +pm_serialize_diagnostic_list(pm_list_t *list, pm_buffer_t *buffer) { pm_buffer_append_varuint(buffer, pm_sizet_to_u32(pm_list_size(list))); pm_diagnostic_t *diagnostic; for (diagnostic = (pm_diagnostic_t *) list->head; diagnostic != NULL; diagnostic = (pm_diagnostic_t *) diagnostic->node.next) { - pm_serialize_diagnostic(parser, diagnostic, buffer); + pm_serialize_diagnostic(diagnostic, buffer); } } @@ -261,14 +260,15 @@ static void pm_serialize_metadata(pm_parser_t *parser, pm_buffer_t *buffer) { pm_serialize_encoding(parser->encoding, buffer); pm_buffer_append_varsint(buffer, parser->start_line); - pm_serialize_newline_list(&parser->newline_list, buffer); + pm_serialize_line_offset_list(&parser->line_offsets, buffer); <%- unless Prism::Template::SERIALIZE_ONLY_SEMANTICS_FIELDS -%> - pm_serialize_comment_list(parser, &parser->comment_list, buffer); + pm_serialize_comment_list(&parser->comment_list, buffer); <%- end -%> - pm_serialize_magic_comment_list(parser, &parser->magic_comment_list, buffer); + pm_serialize_magic_comment_list(&parser->magic_comment_list, buffer); pm_serialize_data_loc(parser, buffer); - pm_serialize_diagnostic_list(parser, &parser->error_list, buffer); - pm_serialize_diagnostic_list(parser, &parser->warning_list, buffer); + pm_serialize_diagnostic_list(&parser->error_list, buffer); + pm_serialize_diagnostic_list(&parser->warning_list, buffer); + pm_buffer_append_byte(buffer, (uint8_t) parser->continuable); } #line <%= __LINE__ + 1 %> "prism/templates/src/<%= File.basename(__FILE__) %>" @@ -308,28 +308,12 @@ pm_serialize_content(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) pm_constant_t *constant = &parser->constant_pool.constants[bucket->id - 1]; size_t buffer_offset = offset + ((((size_t)bucket->id) - 1) * 8); - if (bucket->type == PM_CONSTANT_POOL_BUCKET_OWNED || bucket->type == PM_CONSTANT_POOL_BUCKET_CONSTANT) { - // Since this is an owned or constant constant, we are going to - // write its contents into the buffer after the constant pool. - // So effectively in place of the source offset, we have a - // buffer offset. We will add a leading 1 to indicate that this - // is a buffer offset. - uint32_t content_offset = pm_sizet_to_u32(buffer->length); - uint32_t owned_mask = (uint32_t) (1 << 31); + // Write the constant contents into the buffer after the constant + // pool. In place of the source offset, we store a buffer offset. + uint32_t content_offset = pm_sizet_to_u32(buffer->length); + memcpy(buffer->value + buffer_offset, &content_offset, 4); + pm_buffer_append_bytes(buffer, constant->start, constant->length); - assert(content_offset < owned_mask); - content_offset |= owned_mask; - - memcpy(buffer->value + buffer_offset, &content_offset, 4); - pm_buffer_append_bytes(buffer, constant->start, constant->length); - } else { - // Since this is a shared constant, we are going to write its - // source offset directly into the buffer. - uint32_t source_offset = pm_ptrdifft_to_u32(constant->start - parser->start); - memcpy(buffer->value + buffer_offset, &source_offset, 4); - } - - // Now we can write the length of the constant into the buffer. uint32_t constant_length = pm_sizet_to_u32(constant->length); memcpy(buffer->value + buffer_offset + 4, &constant_length, 4); } @@ -337,7 +321,7 @@ pm_serialize_content(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) } static void -serialize_token(void *data, pm_parser_t *parser, pm_token_t *token) { +serialize_token(pm_parser_t *parser, pm_token_t *token, void *data) { pm_buffer_t *buffer = (pm_buffer_t *) data; pm_buffer_append_varuint(buffer, token->type); @@ -349,58 +333,72 @@ serialize_token(void *data, pm_parser_t *parser, pm_token_t *token) { /** * Lex the given source and serialize to the given buffer. */ -PRISM_EXPORTED_FUNCTION void +void pm_serialize_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, const char *data) { pm_options_t options = { 0 }; pm_options_read(&options, data); + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, source, size, &options); + pm_parser_init(&arena, &parser, source, size, &options); - pm_lex_callback_t lex_callback = (pm_lex_callback_t) { - .data = (void *) buffer, - .callback = serialize_token, - }; - - parser.lex_callback = &lex_callback; - pm_node_t *node = pm_parse(&parser); + pm_parser_lex_callback_set(&parser, serialize_token, buffer); + pm_parse(&parser); // Append 0 to mark end of tokens. pm_buffer_append_byte(buffer, 0); pm_serialize_metadata(&parser, buffer); - pm_node_destroy(&parser, node); - pm_parser_free(&parser); - pm_options_free(&options); + pm_parser_cleanup(&parser); + pm_arena_cleanup(&arena); + pm_options_cleanup(&options); } /** * Parse and serialize both the AST and the tokens represented by the given * source to the given buffer. */ -PRISM_EXPORTED_FUNCTION void +void pm_serialize_parse_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, const char *data) { pm_options_t options = { 0 }; pm_options_read(&options, data); + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, source, size, &options); - - pm_lex_callback_t lex_callback = (pm_lex_callback_t) { - .data = (void *) buffer, - .callback = serialize_token, - }; + pm_parser_init(&arena, &parser, source, size, &options); - parser.lex_callback = &lex_callback; + pm_parser_lex_callback_set(&parser, serialize_token, buffer); pm_node_t *node = pm_parse(&parser); pm_buffer_append_byte(buffer, 0); pm_serialize(&parser, node, buffer); - pm_node_destroy(&parser, node); - pm_parser_free(&parser); - pm_options_free(&options); + pm_parser_cleanup(&parser); + pm_arena_cleanup(&arena); + pm_options_cleanup(&options); +} + +/** + * Parse the source and return true if it parses without errors or warnings. + */ +bool +pm_serialize_parse_success_p(const uint8_t *source, size_t size, const char *data) { + pm_options_t options = { 0 }; + pm_options_read(&options, data); + + pm_arena_t arena = { 0 }; + pm_parser_t parser; + pm_parser_init(&arena, &parser, source, size, &options); + + pm_parse(&parser); + + bool result = parser.error_list.size == 0; + pm_parser_cleanup(&parser); + pm_arena_cleanup(&arena); + pm_options_cleanup(&options); + + return result; } #endif diff --git a/prism/templates/src/token_type.c.erb b/prism/templates/src/tokens.c.erb index f196393ee1..1e82954738 100644 --- a/prism/templates/src/token_type.c.erb +++ b/prism/templates/src/tokens.c.erb @@ -1,12 +1,12 @@ -#include <string.h> - #include "prism/ast.h" +#include <assert.h> + /** * Returns a string representation of the given token type. */ -PRISM_EXPORTED_FUNCTION const char * -pm_token_type_name(pm_token_type_t token_type) { +const char * +pm_token_type(pm_token_type_t token_type) { switch (token_type) { <%- tokens.each do |token| -%> case PM_TOKEN_<%= token.name %>: @@ -27,14 +27,10 @@ pm_token_type_name(pm_token_type_t token_type) { * Returns the human name of the given token type. */ const char * -pm_token_type_human(pm_token_type_t token_type) { +pm_token_str(pm_token_type_t token_type) { switch (token_type) { case PM_TOKEN_EOF: return "end-of-input"; - case PM_TOKEN_MISSING: - return "missing token"; - case PM_TOKEN_NOT_PROVIDED: - return "not provided token"; case PM_TOKEN_AMPERSAND: return "'&'"; case PM_TOKEN_AMPERSAND_AMPERSAND: @@ -171,6 +167,8 @@ pm_token_type_human(pm_token_type_t token_type) { return "'defined?'"; case PM_TOKEN_KEYWORD_DO: return "'do'"; + case PM_TOKEN_KEYWORD_DO_BLOCK: + return "'do'"; case PM_TOKEN_KEYWORD_DO_LOOP: return "'do'"; case PM_TOKEN_KEYWORD_ELSE: @@ -362,8 +360,8 @@ pm_token_type_human(pm_token_type_t token_type) { return ""; } - // Provide a default, because some compilers can't determine that the above - // switch is exhaustive. + /* Provide a default, because some compilers cannot determine that the above + * switch is exhaustive. */ assert(false && "unreachable"); return ""; } diff --git a/prism/templates/template.rb b/prism/templates/template.rb index 7068c098d3..0fdeda561f 100755 --- a/prism/templates/template.rb +++ b/prism/templates/template.rb @@ -6,13 +6,13 @@ require "fileutils" require "yaml" module Prism - module Template + module Template # :nodoc: all SERIALIZE_ONLY_SEMANTICS_FIELDS = ENV.fetch("PRISM_SERIALIZE_ONLY_SEMANTICS_FIELDS", false) - REMOVE_ON_ERROR_TYPES = SERIALIZE_ONLY_SEMANTICS_FIELDS CHECK_FIELD_KIND = ENV.fetch("CHECK_FIELD_KIND", false) - JAVA_BACKEND = ENV["PRISM_JAVA_BACKEND"] || "truffleruby" - JAVA_STRING_TYPE = JAVA_BACKEND == "jruby" ? "org.jruby.RubySymbol" : "String" + JAVA_BACKEND = ENV["PRISM_JAVA_BACKEND"] || "default" + JAVA_IDENTIFIER_TYPE = JAVA_BACKEND == "truffleruby" ? "String" : "byte[]" + INCLUDE_NODE_ID = !SERIALIZE_ONLY_SEMANTICS_FIELDS || JAVA_BACKEND == "jruby" COMMON_FLAGS_COUNT = 2 @@ -48,6 +48,14 @@ module Prism end end + # This module contains methods for escaping characters in Doxygen comments. + module Doxygen + # Similar to /verbatim ... /endverbatim but doesn't wrap the result in a code block. + def self.verbatim(value) + value.gsub(/[*%!`#<>_+@-]/, '\\\\\0') + end + end + # A comment attached to a field or node. class ConfigComment attr_reader :value @@ -96,6 +104,11 @@ module Prism # Some node fields can be specialized if they point to a specific kind of # node and not just a generic node. class NodeKindField < Field + # The C type to use for this field as a function parameter. + def c_param + "struct #{c_type} *#{name}" + end + def initialize(kind:, **options) @kind = kind super(**options) @@ -141,27 +154,27 @@ module Prism if specific_kind specific_kind elsif union_kind - union_kind.join(" | ") + "(#{union_kind.join(" | ")})" else "Prism::node" end end - def rbi_class + def call_seq_type if specific_kind - "Prism::#{specific_kind}" + specific_kind elsif union_kind - "T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")})" + union_kind.join(" | ") else - "Prism::Node" + "Node" end end def check_field_kind if union_kind - "[#{union_kind.join(', ')}].include?(#{name}.class)" + "[#{union_kind.join(', ')}, ErrorRecoveryNode].include?(#{name}.class)" else - "#{name}.is_a?(#{ruby_type})" + "#{name}.is_a?(#{ruby_type}) || #{name}.is_a?(ErrorRecoveryNode)" end end end @@ -173,27 +186,27 @@ module Prism if specific_kind "#{specific_kind}?" elsif union_kind - [*union_kind, "nil"].join(" | ") + "(#{union_kind.join(" | ")})?" else "Prism::node?" end end - def rbi_class + def call_seq_type if specific_kind - "T.nilable(Prism::#{specific_kind})" + "#{specific_kind} | nil" elsif union_kind - "T.nilable(T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")}))" + [*union_kind, "nil"].join(" | ") else - "T.nilable(Prism::Node)" + "Node | nil" end end def check_field_kind if union_kind - "[#{union_kind.join(', ')}, NilClass].include?(#{name}.class)" + "[#{union_kind.join(', ')}, ErrorRecoveryNode, NilClass].include?(#{name}.class)" else - "#{name}.nil? || #{name}.is_a?(#{ruby_type})" + "#{name}.nil? || #{name}.is_a?(#{ruby_type}) || #{name}.is_a?(ErrorRecoveryNode)" end end end @@ -201,23 +214,31 @@ module Prism # This represents a field on a node that is a list of nodes. We pass them as # references and store them directly on the struct. class NodeListField < NodeKindField - def rbs_class + def c_param + "pm_node_list_t #{name}" + end + + def element_rbs_class if specific_kind - "Array[#{specific_kind}]" + "#{specific_kind}" elsif union_kind - "Array[#{union_kind.join(" | ")}]" + "#{union_kind.join(" | ")}" else - "Array[Prism::node]" + "Prism::node" end end - def rbi_class + def rbs_class + "Array[#{element_rbs_class}]" + end + + def call_seq_type if specific_kind - "T::Array[Prism::#{specific_kind}]" + "Array[#{specific_kind}]" elsif union_kind - "T::Array[T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")})]" + "Array[#{union_kind.join(" | ")}]" else - "T::Array[Prism::Node]" + "Array[Node]" end end @@ -227,9 +248,9 @@ module Prism def check_field_kind if union_kind - "#{name}.all? { |n| [#{union_kind.join(', ')}].include?(n.class) }" + "#{name}.all? { |n| [#{union_kind.join(', ')}, ErrorRecoveryNode].include?(n.class) }" else - "#{name}.all? { |n| n.is_a?(#{ruby_type}) }" + "#{name}.all? { |n| n.is_a?(#{ruby_type}) || n.is_a?(ErrorRecoveryNode) }" end end end @@ -237,58 +258,74 @@ module Prism # This represents a field on a node that is the ID of a string interned # through the parser's constant pool. class ConstantField < Field + def c_param + "pm_constant_id_t #{name}" + end + def rbs_class "Symbol" end - def rbi_class + def call_seq_type "Symbol" end def java_type - JAVA_STRING_TYPE + JAVA_IDENTIFIER_TYPE end end # This represents a field on a node that is the ID of a string interned # through the parser's constant pool and can be optionally null. class OptionalConstantField < Field + def c_param + "pm_constant_id_t #{name}" + end + def rbs_class "Symbol?" end - def rbi_class - "T.nilable(Symbol)" + def call_seq_type + "Symbol | nil" end def java_type - JAVA_STRING_TYPE + JAVA_IDENTIFIER_TYPE end end # This represents a field on a node that is a list of IDs that are associated # with strings interned through the parser's constant pool. class ConstantListField < Field + def c_param + "pm_constant_id_list_t #{name}" + end + def rbs_class "Array[Symbol]" end - def rbi_class - "T::Array[Symbol]" + def call_seq_type + "Array[Symbol]" end def java_type - "#{JAVA_STRING_TYPE}[]" + "#{JAVA_IDENTIFIER_TYPE}[]" end end # This represents a field on a node that is a string. class StringField < Field + def c_param + "pm_string_t #{name}" + end + def rbs_class "String" end - def rbi_class + def call_seq_type "String" end @@ -299,6 +336,10 @@ module Prism # This represents a field on a node that is a location. class LocationField < Field + def c_param + "pm_location_t #{name}" + end + def semantic_field? false end @@ -307,8 +348,8 @@ module Prism "Location" end - def rbi_class - "Prism::Location" + def call_seq_type + "Location" end def java_type @@ -318,6 +359,10 @@ module Prism # This represents a field on a node that is a location that is optional. class OptionalLocationField < Field + def c_param + "pm_location_t #{name}" + end + def semantic_field? false end @@ -326,8 +371,8 @@ module Prism "Location?" end - def rbi_class - "T.nilable(Prism::Location)" + def call_seq_type + "Location | nil" end def java_type @@ -337,11 +382,15 @@ module Prism # This represents an integer field. class UInt8Field < Field + def c_param + "uint8_t #{name}" + end + def rbs_class "Integer" end - def rbi_class + def call_seq_type "Integer" end @@ -352,11 +401,15 @@ module Prism # This represents an integer field. class UInt32Field < Field + def c_param + "uint32_t #{name}" + end + def rbs_class "Integer" end - def rbi_class + def call_seq_type "Integer" end @@ -368,11 +421,15 @@ module Prism # This represents an arbitrarily-sized integer. When it gets to Ruby it will # be an Integer. class IntegerField < Field + def c_param + "pm_integer_t #{name}" + end + def rbs_class "Integer" end - def rbi_class + def call_seq_type "Integer" end @@ -384,11 +441,15 @@ module Prism # This represents a double-precision floating point number. When it gets to # Ruby it will be a Float. class DoubleField < Field + def c_param + "double #{name}" + end + def rbs_class "Float" end - def rbi_class + def call_seq_type "Float" end @@ -431,9 +492,6 @@ module Prism when "pattern expression" # the list of all possible types is too long with 37+ different classes "Node" - when Hash - kind = kind.fetch("on error") - REMOVE_ON_ERROR_TYPES ? nil : kind else kind end @@ -546,33 +604,17 @@ module Prism extension = File.extname(filepath.gsub(".erb", "")) heading = - case extension - when ".rb" + if extension == ".rb" <<~HEADING # frozen_string_literal: true + # :markup: markdown =begin + -- This file is generated by the templates/template.rb script and should not be modified manually. See #{filepath} if you are looking to modify the template - =end - - HEADING - when ".rbs" - <<~HEADING - # This file is generated by the templates/template.rb script and should not be - # modified manually. See #{filepath} - # if you are looking to modify the template - - HEADING - when ".rbi" - <<~HEADING - # typed: strict - - =begin - This file is generated by the templates/template.rb script and should not be - modified manually. See #{filepath} - if you are looking to modify the template + ++ =end HEADING @@ -581,7 +623,7 @@ module Prism /*----------------------------------------------------------------------------*/ /* This file is generated by the templates/template.rb script and should not */ /* be modified manually. See */ - /* #{filepath + " " * (74 - filepath.size) } */ + /* #{filepath.ljust(74)} */ /* if you are looking to modify the */ /* template */ /*----------------------------------------------------------------------------*/ @@ -601,8 +643,14 @@ module Prism end end - FileUtils.mkdir_p(File.dirname(write_to)) - File.write(write_to, contents) + begin + FileUtils.mkdir_p(File.dirname(write_to)) + File.write(write_to, contents) + rescue SystemCallError # EACCES, EPERM, EROFS, etc. + # Fall back to the current directory + FileUtils.mkdir_p(File.dirname(name)) + File.write(name, contents) + end end private @@ -638,13 +686,13 @@ module Prism TEMPLATES = [ "ext/prism/api_node.c", "include/prism/ast.h", - "include/prism/diagnostic.h", + "include/prism/internal/diagnostic.h", "javascript/src/deserialize.js", "javascript/src/nodes.js", "javascript/src/visitor.js", - "java/org/prism/Loader.java", - "java/org/prism/Nodes.java", - "java/org/prism/AbstractNodeVisitor.java", + "java/api/src/main/java-templates/org/ruby_lang/prism/Loader.java", + "java/api/src/main/java-templates/org/ruby_lang/prism/Nodes.java", + "java/api/src/main/java-templates/org/ruby_lang/prism/AbstractNodeVisitor.java", "lib/prism/compiler.rb", "lib/prism/dispatcher.rb", "lib/prism/dot_visitor.rb", @@ -656,19 +704,11 @@ module Prism "lib/prism/serialize.rb", "lib/prism/visitor.rb", "src/diagnostic.c", + "src/json.c", "src/node.c", "src/prettyprint.c", "src/serialize.c", - "src/token_type.c", - "rbi/prism/dsl.rbi", - "rbi/prism/node.rbi", - "rbi/prism/visitor.rbi", - "sig/prism.rbs", - "sig/prism/dsl.rbs", - "sig/prism/mutation_compiler.rbs", - "sig/prism/node.rbs", - "sig/prism/visitor.rbs", - "sig/prism/_private/dot_visitor.rbs" + "src/tokens.c" ] end end |
