diff --git a/bin/parser b/bin/parser new file mode 100755 index 00000000000..cef8a670971 --- /dev/null +++ b/bin/parser @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "prism" +require "parser/current" + +source = (ARGV[0] == "-e") ? ARGV[1] : File.read(ARGV[0] || "test.rb") +prism = Prism::Translation::Parser.parse(source) +parser = Parser::CurrentRuby.parse(source) + +puts "Prism:" +pp prism + +puts "Parser:" +pp parser diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index 7cc18ac5de3..589b33b6fee 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -9,6 +9,20 @@ module Translation # the parser gem, and overrides the parse* methods to parse with prism and # then translate. class Parser < ::Parser::Base + # The parser gem has a list of diagnostics with a hard-coded set of error + # messages. We create our own diagnostic class in order to set our own + # error messages. + class Diagnostic < ::Parser::Diagnostic + # The message generated by prism. + attr_reader :message + + # Initialize a new diagnostic with the given message and location. + def initialize(message, location) + @message = message + super(:error, :prism_error, {}, location, []) + end + end + Racc_debug_parser = false # :nodoc: def version # :nodoc: @@ -28,10 +42,9 @@ def parse(source_buffer) @source_buffer = source_buffer source = source_buffer.source - build_ast( - Prism.parse(source, filepath: source_buffer.name).value, - build_offset_cache(source) - ) + result = unwrap(Prism.parse(source, filepath: source_buffer.name)) + + build_ast(result.value, build_offset_cache(source)) ensure @source_buffer = nil end @@ -42,7 +55,7 @@ def parse_with_comments(source_buffer) source = source_buffer.source offset_cache = build_offset_cache(source) - result = Prism.parse(source, filepath: source_buffer.name) + result = unwrap(Prism.parse(source, filepath: source_buffer.name)) [ build_ast(result.value, offset_cache), @@ -59,7 +72,8 @@ def tokenize(source_buffer, _recover = false) source = source_buffer.source offset_cache = build_offset_cache(source) - result = Prism.parse_lex(source, filepath: source_buffer.name) + result = unwrap(Prism.parse_lex(source, filepath: source_buffer.name)) + program, tokens = result.value [ @@ -79,6 +93,18 @@ def try_declare_numparam(node) private + # If there was a error generated during the parse, then raise an + # appropriate syntax error. Otherwise return the result. + def unwrap(result) + return result if result.success? + + error = result.errors.first + offset_cache = build_offset_cache(source_buffer.source) + + diagnostic = Diagnostic.new(error.message, build_range(error.location, offset_cache)) + raise ::Parser::SyntaxError, diagnostic + end + # Prism deals with offsets in bytes, while the parser gem deals with # offsets in characters. We need to handle this conversion in order to # build the parser gem AST. @@ -109,15 +135,7 @@ def build_ast(program, offset_cache) # Build the parser gem comments from the prism comments. def build_comments(comments, offset_cache) comments.map do |comment| - location = comment.location - - ::Parser::Source::Comment.new( - ::Parser::Source::Range.new( - source_buffer, - offset_cache[location.start_offset], - offset_cache[location.end_offset] - ) - ) + ::Parser::Source::Comment.new(build_range(comment.location, offset_cache)) end end @@ -126,6 +144,15 @@ def build_tokens(tokens, offset_cache) Lexer.new(source_buffer, tokens.map(&:first), offset_cache).to_a end + # Build a range from a prism location. + def build_range(location, offset_cache) + ::Parser::Source::Range.new( + source_buffer, + offset_cache[location.start_offset], + offset_cache[location.end_offset] + ) + end + require_relative "parser/compiler" require_relative "parser/lexer" diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index d03de9efc55..ccd02c2181b 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -83,11 +83,16 @@ def visit_array_pattern_node(node) elements = [*node.requireds] elements << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode) elements.concat(node.posts) + visited = visit_all(elements) + + if node.rest.is_a?(ImplicitRestNode) + visited[-1] = builder.match_with_trailing_comma(visited[-1], token(node.rest.location)) + end if node.constant - builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.array_pattern(nil, visit_all(elements), nil), token(node.closing_loc)) + builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.array_pattern(nil, visited, nil), token(node.closing_loc)) else - builder.array_pattern(token(node.opening_loc), visit_all(elements), token(node.closing_loc)) + builder.array_pattern(token(node.opening_loc), visited, token(node.closing_loc)) end end