class Prism::Translation::Parser
then translate.
the parser gem, and overrides the parse* methods to parse with prism and
whitequark/parser gem’s syntax tree. It inherits from the base parser for
This class is the entry-point for converting a prism syntax tree into the
def build_ast(program, offset_cache)
def build_ast(program, offset_cache) program.accept(Compiler.new(self, offset_cache)) end
def build_comments(comments, offset_cache)
def build_comments(comments, offset_cache) comments.map do |comment| ::Parser::Source::Comment.new(build_range(comment.location, offset_cache)) end end
def build_offset_cache(source)
just use the offset directly. Otherwise, we build an array where the
If the bytesize of the source is the same as the length, then we can
build the parser gem AST.
offsets in characters. We need to handle this conversion in order to
Prism deals with offsets in bytes, while the parser gem deals with
def build_offset_cache(source) if source.bytesize == source.length -> (offset) { offset } else offset_cache = [] offset = 0 source.each_char do |char| char.bytesize.times { offset_cache << offset } offset += 1 end offset_cache << offset end end
def build_range(location, offset_cache)
def build_range(location, offset_cache) ::Parser::Source::Range.new( source_buffer, offset_cache[location.start_offset], offset_cache[location.end_offset] ) end
def build_tokens(tokens, offset_cache)
def build_tokens(tokens, offset_cache) Lexer.new(source_buffer, tokens, offset_cache).to_a end
def convert_for_prism(version)
def convert_for_prism(version) case version when 33 "3.3.1" when 34 "3.4.0" else "latest" end end
def default_encoding
def default_encoding Encoding::UTF_8 end
def error_diagnostic(error, offset_cache)
def error_diagnostic(error, offset_cache) location = error.location diagnostic_location = build_range(location, offset_cache) case error.type when :argument_block_multi Diagnostic.new(:error, :block_and_blockarg, {}, diagnostic_location, []) when :argument_formal_constant Diagnostic.new(:error, :argument_const, {}, diagnostic_location, []) when :argument_formal_class Diagnostic.new(:error, :argument_cvar, {}, diagnostic_location, []) when :argument_formal_global Diagnostic.new(:error, :argument_gvar, {}, diagnostic_location, []) when :argument_formal_ivar Diagnostic.new(:error, :argument_ivar, {}, diagnostic_location, []) when :argument_no_forwarding_amp Diagnostic.new(:error, :no_anonymous_blockarg, {}, diagnostic_location, []) when :argument_no_forwarding_star Diagnostic.new(:error, :no_anonymous_restarg, {}, diagnostic_location, []) when :argument_no_forwarding_star_star Diagnostic.new(:error, :no_anonymous_kwrestarg, {}, diagnostic_location, []) when :begin_lonely_else location = location.copy(length: 4) diagnostic_location = build_range(location, offset_cache) Diagnostic.new(:error, :useless_else, {}, diagnostic_location, []) when :class_name, :module_name Diagnostic.new(:error, :module_name_const, {}, diagnostic_location, []) when :class_in_method Diagnostic.new(:error, :class_in_def, {}, diagnostic_location, []) when :def_endless_setter Diagnostic.new(:error, :endless_setter, {}, diagnostic_location, []) when :embdoc_term Diagnostic.new(:error, :embedded_document, {}, diagnostic_location, []) when :incomplete_variable_class, :incomplete_variable_class_3_3 location = location.copy(length: location.length + 1) diagnostic_location = build_range(location, offset_cache) Diagnostic.new(:error, :cvar_name, { name: location.slice }, diagnostic_location, []) when :incomplete_variable_instance, :incomplete_variable_instance_3_3 location = location.copy(length: location.length + 1) diagnostic_location = build_range(location, offset_cache) Diagnostic.new(:error, :ivar_name, { name: location.slice }, diagnostic_location, []) when :invalid_variable_global, :invalid_variable_global_3_3 Diagnostic.new(:error, :gvar_name, { name: location.slice }, diagnostic_location, []) when :module_in_method Diagnostic.new(:error, :module_in_def, {}, diagnostic_location, []) when :numbered_parameter_ordinary Diagnostic.new(:error, :ordinary_param_defined, {}, diagnostic_location, []) when :numbered_parameter_outer_scope Diagnostic.new(:error, :numparam_used_in_outer_scope, {}, diagnostic_location, []) when :parameter_circular Diagnostic.new(:error, :circular_argument_reference, { var_name: location.slice }, diagnostic_location, []) when :parameter_name_repeat Diagnostic.new(:error, :duplicate_argument, {}, diagnostic_location, []) when :parameter_numbered_reserved Diagnostic.new(:error, :reserved_for_numparam, { name: location.slice }, diagnostic_location, []) when :regexp_unknown_options Diagnostic.new(:error, :regexp_options, { options: location.slice[1..] }, diagnostic_location, []) when :singleton_for_literals Diagnostic.new(:error, :singleton_literal, {}, diagnostic_location, []) when :string_literal_eof Diagnostic.new(:error, :string_eof, {}, diagnostic_location, []) when :unexpected_token_ignore Diagnostic.new(:error, :unexpected_token, { token: location.slice }, diagnostic_location, []) when :write_target_in_method Diagnostic.new(:error, :dynamic_const, {}, diagnostic_location, []) else PrismDiagnostic.new(error.message, :error, error.type, diagnostic_location) end end
def parse(source_buffer)
def parse(source_buffer) @source_buffer = source_buffer source = source_buffer.source offset_cache = build_offset_cache(source) result = unwrap(Prism.parse(source, filepath: source_buffer.name, version: convert_for_prism(version), scopes: [[]], encoding: false), offset_cache) build_ast(result.value, offset_cache) ensure @source_buffer = nil end
def parse_with_comments(source_buffer)
def parse_with_comments(source_buffer) @source_buffer = source_buffer source = source_buffer.source offset_cache = build_offset_cache(source) result = unwrap(Prism.parse(source, filepath: source_buffer.name, version: convert_for_prism(version), scopes: [[]], encoding: false), offset_cache) [ build_ast(result.value, offset_cache), build_comments(result.comments, offset_cache) ] ensure @source_buffer = nil end
def tokenize(source_buffer, recover = false)
Parses a source buffer and returns the AST, the source code comments,
def tokenize(source_buffer, recover = false) @source_buffer = source_buffer source = source_buffer.source offset_cache = build_offset_cache(source) result = begin unwrap(Prism.parse_lex(source, filepath: source_buffer.name, version: convert_for_prism(version), scopes: [[]], encoding: false), offset_cache) rescue ::Parser::SyntaxError raise if !recover end program, tokens = result.value ast = build_ast(program, offset_cache) if result.success? [ ast, build_comments(result.comments, offset_cache), build_tokens(tokens, offset_cache) ] ensure @source_buffer = nil end
def try_declare_numparam(node)
Since prism resolves num params for us, we don't need to support this
def try_declare_numparam(node) node.children[0].match?(/\A_[1-9]\z/) end
def unwrap(result, offset_cache)
If there was a error generated during the parse, then raise an
def unwrap(result, offset_cache) result.errors.each do |error| next unless valid_error?(error) diagnostics.process(error_diagnostic(error, offset_cache)) end result.warnings.each do |warning| next unless valid_warning?(warning) diagnostic = warning_diagnostic(warning, offset_cache) diagnostics.process(diagnostic) if diagnostic end result end
def valid_error?(error)
This is a hook to allow consumers to disable some errors if they don't
def valid_error?(error) true end
def valid_warning?(warning)
This is a hook to allow consumers to disable some warnings if they don't
def valid_warning?(warning) true end
def version # :nodoc:
def version # :nodoc: 34 end
def warning_diagnostic(warning, offset_cache)
def warning_diagnostic(warning, offset_cache) diagnostic_location = build_range(warning.location, offset_cache) case warning.type when :ambiguous_first_argument_plus Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "+" }, diagnostic_location, []) when :ambiguous_first_argument_minus Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "-" }, diagnostic_location, []) when :ambiguous_prefix_ampersand Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "&" }, diagnostic_location, []) when :ambiguous_prefix_star Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "*" }, diagnostic_location, []) when :ambiguous_prefix_star_star Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "**" }, diagnostic_location, []) when :ambiguous_slash Diagnostic.new(:warning, :ambiguous_regexp, {}, diagnostic_location, []) when :dot_dot_dot_eol Diagnostic.new(:warning, :triple_dot_at_eol, {}, diagnostic_location, []) when :duplicated_hash_key # skip, parser does this on its own else PrismDiagnostic.new(warning.message, :warning, warning.type, diagnostic_location) end end
def yyerror # :nodoc:
def yyerror # :nodoc: end