[Feature #19741] Sync all files in yarp
This commit is the initial sync of all files from ruby/yarp into ruby/ruby. Notably, it does the following: * Sync all ruby/yarp/lib/ files to ruby/ruby/lib/yarp * Sync all ruby/yarp/src/ files to ruby/ruby/yarp/ * Sync all ruby/yarp/test/ files to ruby/ruby/test/yarp
This commit is contained in:
parent
08478fefca
commit
cc7f765f2c
Notes:
git
2023-06-21 18:26:01 +00:00
248
lib/yarp.rb
Normal file
248
lib/yarp.rb
Normal file
@ -0,0 +1,248 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module YARP
|
||||
# This represents a location in the source corresponding to a node or token.
|
||||
class Location
|
||||
attr_reader :start_offset, :length
|
||||
|
||||
def initialize(start_offset, length)
|
||||
@start_offset = start_offset
|
||||
@length = length
|
||||
end
|
||||
|
||||
def end_offset
|
||||
@start_offset + @length
|
||||
end
|
||||
|
||||
def deconstruct_keys(keys)
|
||||
{ start_offset: start_offset, end_offset: end_offset }
|
||||
end
|
||||
|
||||
def pretty_print(q)
|
||||
q.text("(#{start_offset}...#{end_offset})")
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other in Location[start_offset: ^(start_offset), end_offset: ^(end_offset)]
|
||||
end
|
||||
|
||||
def self.null
|
||||
new(0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
# This represents a comment that was encountered during parsing.
|
||||
class Comment
|
||||
attr_reader :type, :location
|
||||
|
||||
def initialize(type, location)
|
||||
@type = type
|
||||
@location = location
|
||||
end
|
||||
|
||||
def deconstruct_keys(keys)
|
||||
{ type: type, location: location }
|
||||
end
|
||||
end
|
||||
|
||||
# This represents an error that was encountered during parsing.
|
||||
class ParseError
|
||||
attr_reader :message, :location
|
||||
|
||||
def initialize(message, location)
|
||||
@message = message
|
||||
@location = location
|
||||
end
|
||||
|
||||
def deconstruct_keys(keys)
|
||||
{ message: message, location: location }
|
||||
end
|
||||
end
|
||||
|
||||
# This represents a warning that was encountered during parsing.
|
||||
class ParseWarning
|
||||
attr_reader :message, :location
|
||||
|
||||
def initialize(message, location)
|
||||
@message = message
|
||||
@location = location
|
||||
end
|
||||
|
||||
def deconstruct_keys(keys)
|
||||
{ message: message, location: location }
|
||||
end
|
||||
end
|
||||
|
||||
# This represents the result of a call to ::parse or ::parse_file. It contains
|
||||
# the AST, any comments that were encounters, and any errors that were
|
||||
# encountered.
|
||||
class ParseResult
|
||||
attr_reader :value, :comments, :errors, :warnings
|
||||
|
||||
def initialize(value, comments, errors, warnings)
|
||||
@value = value
|
||||
@comments = comments
|
||||
@errors = errors
|
||||
@warnings = warnings
|
||||
end
|
||||
|
||||
def deconstruct_keys(keys)
|
||||
{ value: value, comments: comments, errors: errors, warnings: warnings }
|
||||
end
|
||||
|
||||
def success?
|
||||
errors.empty?
|
||||
end
|
||||
|
||||
def failure?
|
||||
!success?
|
||||
end
|
||||
end
|
||||
|
||||
# This represents a token from the Ruby source.
|
||||
class Token
|
||||
attr_reader :type, :value, :start_offset, :length
|
||||
|
||||
def initialize(type, value, start_offset, length)
|
||||
@type = type
|
||||
@value = value
|
||||
@start_offset = start_offset
|
||||
@length = length
|
||||
end
|
||||
|
||||
def end_offset
|
||||
@start_offset + @length
|
||||
end
|
||||
|
||||
def location
|
||||
Location.new(@start_offset, @length)
|
||||
end
|
||||
|
||||
def deconstruct_keys(keys)
|
||||
{ type: type, value: value, location: location }
|
||||
end
|
||||
|
||||
def pretty_print(q)
|
||||
q.group do
|
||||
q.text(type.to_s)
|
||||
self.location.pretty_print(q)
|
||||
q.text("(")
|
||||
q.nest(2) do
|
||||
q.breakable("")
|
||||
q.pp(value)
|
||||
end
|
||||
q.breakable("")
|
||||
q.text(")")
|
||||
end
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other in Token[type: ^(type), value: ^(value)]
|
||||
end
|
||||
end
|
||||
|
||||
# This represents a node in the tree.
|
||||
class Node
|
||||
attr_reader :start_offset, :length
|
||||
|
||||
def end_offset
|
||||
@start_offset + @length
|
||||
end
|
||||
|
||||
def location
|
||||
Location.new(@start_offset, @length)
|
||||
end
|
||||
|
||||
def pretty_print(q)
|
||||
q.group do
|
||||
q.text(self.class.name.split("::").last)
|
||||
self.location.pretty_print(q)
|
||||
q.text("(")
|
||||
q.nest(2) do
|
||||
deconstructed = deconstruct_keys([])
|
||||
deconstructed.delete(:location)
|
||||
|
||||
q.breakable("")
|
||||
q.seplist(deconstructed, lambda { q.comma_breakable }, :each_value) { |value| q.pp(value) }
|
||||
end
|
||||
q.breakable("")
|
||||
q.text(")")
|
||||
end
|
||||
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
|
||||
# continues walking the tree, see the Visitor class.
|
||||
class BasicVisitor
|
||||
def visit(node)
|
||||
node&.accept(self)
|
||||
end
|
||||
|
||||
def visit_all(nodes)
|
||||
nodes.map { |node| visit(node) }
|
||||
end
|
||||
|
||||
def visit_child_nodes(node)
|
||||
visit_all(node.child_nodes)
|
||||
end
|
||||
end
|
||||
|
||||
# This lexes with the Ripper lex. It drops any space events but otherwise
|
||||
# returns the same tokens.
|
||||
# [raises SyntaxError] if the syntax in source is invalid
|
||||
def self.lex_ripper(source)
|
||||
previous = []
|
||||
results = []
|
||||
|
||||
Ripper.lex(source, raise_errors: true).each do |token|
|
||||
case token[1]
|
||||
when :on_sp
|
||||
# skip
|
||||
when :on_tstring_content
|
||||
if previous[1] == :on_tstring_content &&
|
||||
(token[2].start_with?("\#$") || token[2].start_with?("\#@"))
|
||||
previous[2] << token[2]
|
||||
else
|
||||
results << token
|
||||
previous = token
|
||||
end
|
||||
when :on_words_sep
|
||||
if previous[1] == :on_words_sep
|
||||
previous[2] << token[2]
|
||||
else
|
||||
results << token
|
||||
previous = token
|
||||
end
|
||||
else
|
||||
results << token
|
||||
previous = token
|
||||
end
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
# Load the serialized AST using the source as a reference into a tree.
|
||||
def self.load(source, serialized)
|
||||
Serialize.load(source, serialized)
|
||||
end
|
||||
|
||||
def self.parse(source, filepath=nil)
|
||||
_parse(source, filepath)
|
||||
end
|
||||
end
|
||||
|
||||
require_relative "yarp/lex_compat"
|
||||
require_relative "yarp/node"
|
||||
require_relative "yarp/ripper_compat"
|
||||
require_relative "yarp/serialize"
|
||||
require_relative "yarp/pack"
|
||||
require "yarp.so"
|
||||
|
||||
module YARP
|
||||
class << self
|
||||
private :_parse
|
||||
end
|
||||
end
|
166
lib/yarp/language_server.rb
Normal file
166
lib/yarp/language_server.rb
Normal file
@ -0,0 +1,166 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cgi"
|
||||
require "json"
|
||||
require "uri"
|
||||
|
||||
module YARP
|
||||
# YARP additionally ships with a language server conforming to the
|
||||
# language server protocol. It can be invoked by running the yarp-lsp
|
||||
# bin script (bin/yarp-lsp)
|
||||
class LanguageServer
|
||||
GITHUB_TEMPLATE = <<~TEMPLATE
|
||||
Reporting issue with error `%{error}`.
|
||||
|
||||
## Expected behavior
|
||||
<!-- TODO: Briefly explain what the expected behavior should be on this example. -->
|
||||
|
||||
## Actual behavior
|
||||
<!-- TODO: Describe here what actually happened. -->
|
||||
|
||||
## Steps to reproduce the problem
|
||||
<!-- TODO: Describe how we can reproduce the problem. -->
|
||||
|
||||
## Additional information
|
||||
<!-- TODO: Include any additional information, such as screenshots. -->
|
||||
|
||||
TEMPLATE
|
||||
|
||||
attr_reader :input, :output
|
||||
|
||||
def initialize(
|
||||
input: $stdin,
|
||||
output: $stdout
|
||||
)
|
||||
@input = input.binmode
|
||||
@output = output.binmode
|
||||
end
|
||||
|
||||
# rubocop:disable Layout/LineLength
|
||||
def run
|
||||
store =
|
||||
Hash.new do |hash, uri|
|
||||
filepath = CGI.unescape(URI.parse(uri).path)
|
||||
File.exist?(filepath) ? (hash[uri] = File.read(filepath)) : nil
|
||||
end
|
||||
|
||||
while (headers = input.gets("\r\n\r\n"))
|
||||
source = input.read(headers[/Content-Length: (\d+)/i, 1].to_i)
|
||||
request = JSON.parse(source, symbolize_names: true)
|
||||
|
||||
# stree-ignore
|
||||
case request
|
||||
in { method: "initialize", id: }
|
||||
store.clear
|
||||
write(id: id, result: { capabilities: capabilities })
|
||||
in { method: "initialized" }
|
||||
# ignored
|
||||
in { method: "shutdown" } # tolerate missing ID to be a good citizen
|
||||
store.clear
|
||||
write(id: request[:id], result: {})
|
||||
in { method: "exit"}
|
||||
return
|
||||
in { method: "textDocument/didChange", params: { textDocument: { uri: }, contentChanges: [{ text: }, *] } }
|
||||
store[uri] = text
|
||||
in { method: "textDocument/didOpen", params: { textDocument: { uri:, text: } } }
|
||||
store[uri] = text
|
||||
in { method: "textDocument/didClose", params: { textDocument: { uri: } } }
|
||||
store.delete(uri)
|
||||
in { method: "textDocument/diagnostic", id:, params: { textDocument: { uri: } } }
|
||||
contents = store[uri]
|
||||
write(id: id, result: contents ? diagnostics(contents) : nil)
|
||||
in { method: "textDocument/codeAction", id:, params: { textDocument: { uri: }, context: { diagnostics: }}}
|
||||
contents = store[uri]
|
||||
write(id: id, result: contents ? code_actions(contents, diagnostics) : nil)
|
||||
in { method: %r{\$/.+} }
|
||||
# ignored
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Layout/LineLength
|
||||
|
||||
private
|
||||
|
||||
def capabilities
|
||||
{
|
||||
codeActionProvider: {
|
||||
codeActionKinds: [
|
||||
'quickfix',
|
||||
],
|
||||
},
|
||||
diagnosticProvider: {
|
||||
interFileDependencies: false,
|
||||
workspaceDiagnostics: false,
|
||||
},
|
||||
textDocumentSync: {
|
||||
change: 1,
|
||||
openClose: true
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
def code_actions(source, diagnostics)
|
||||
diagnostics.map do |diagnostic|
|
||||
message = diagnostic[:message]
|
||||
issue_content = URI.encode_www_form_component(GITHUB_TEMPLATE % {error: message})
|
||||
issue_link = "https://github.com/ruby/yarp/issues/new?&labels=Bug&body=#{issue_content}"
|
||||
|
||||
{
|
||||
title: "Report incorrect error: `#{diagnostic[:message]}`",
|
||||
kind: "quickfix",
|
||||
diagnostics: [diagnostic],
|
||||
command: {
|
||||
title: "Report incorrect error",
|
||||
command: "vscode.open",
|
||||
arguments: [issue_link]
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def diagnostics(source)
|
||||
offsets = Hash.new do |hash, key|
|
||||
slice = source.byteslice(...key)
|
||||
lineno = slice.count("\n")
|
||||
|
||||
char = slice.length
|
||||
newline = source.rindex("\n", [char - 1, 0].max) || -1
|
||||
hash[key] = { line: lineno, character: char - newline - 1 }
|
||||
end
|
||||
|
||||
parse_output = YARP.parse(source)
|
||||
|
||||
{
|
||||
kind: "full",
|
||||
items: [
|
||||
*parse_output.errors.map do |error|
|
||||
{
|
||||
range: {
|
||||
start: offsets[error.location.start_offset],
|
||||
end: offsets[error.location.end_offset],
|
||||
},
|
||||
message: error.message,
|
||||
severity: 1,
|
||||
}
|
||||
end,
|
||||
*parse_output.warnings.map do |warning|
|
||||
{
|
||||
range: {
|
||||
start: offsets[warning.location.start_offset],
|
||||
end: offsets[warning.location.end_offset],
|
||||
},
|
||||
message: warning.message,
|
||||
severity: 2,
|
||||
}
|
||||
end,
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def write(value)
|
||||
response = value.merge(jsonrpc: "2.0").to_json
|
||||
output.print("Content-Length: #{response.bytesize}\r\n\r\n#{response}")
|
||||
output.flush
|
||||
end
|
||||
end
|
||||
end
|
749
lib/yarp/lex_compat.rb
Normal file
749
lib/yarp/lex_compat.rb
Normal file
@ -0,0 +1,749 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "delegate"
|
||||
|
||||
module YARP
|
||||
# This class is responsible for lexing the source using YARP and then
|
||||
# converting those tokens to be compatible with Ripper. In the vast majority
|
||||
# of cases, this is a one-to-one mapping of the token type. Everything else
|
||||
# generally lines up. However, there are a few cases that require special
|
||||
# handling.
|
||||
class LexCompat
|
||||
# This is a mapping of YARP token types to Ripper token types. This is a
|
||||
# many-to-one mapping because we split up our token types, whereas Ripper
|
||||
# tends to group them.
|
||||
RIPPER = {
|
||||
AMPERSAND: :on_op,
|
||||
AMPERSAND_AMPERSAND: :on_op,
|
||||
AMPERSAND_AMPERSAND_EQUAL: :on_op,
|
||||
AMPERSAND_DOT: :on_op,
|
||||
AMPERSAND_EQUAL: :on_op,
|
||||
BACK_REFERENCE: :on_backref,
|
||||
BACKTICK: :on_backtick,
|
||||
BANG: :on_op,
|
||||
BANG_EQUAL: :on_op,
|
||||
BANG_TILDE: :on_op,
|
||||
BRACE_LEFT: :on_lbrace,
|
||||
BRACE_RIGHT: :on_rbrace,
|
||||
BRACKET_LEFT: :on_lbracket,
|
||||
BRACKET_LEFT_ARRAY: :on_lbracket,
|
||||
BRACKET_LEFT_RIGHT: :on_op,
|
||||
BRACKET_LEFT_RIGHT_EQUAL: :on_op,
|
||||
BRACKET_RIGHT: :on_rbracket,
|
||||
CARET: :on_op,
|
||||
CARET_EQUAL: :on_op,
|
||||
CHARACTER_LITERAL: :on_CHAR,
|
||||
CLASS_VARIABLE: :on_cvar,
|
||||
COLON: :on_op,
|
||||
COLON_COLON: :on_op,
|
||||
COMMA: :on_comma,
|
||||
COMMENT: :on_comment,
|
||||
CONSTANT: :on_const,
|
||||
DOT: :on_period,
|
||||
DOT_DOT: :on_op,
|
||||
DOT_DOT_DOT: :on_op,
|
||||
EMBDOC_BEGIN: :on_embdoc_beg,
|
||||
EMBDOC_END: :on_embdoc_end,
|
||||
EMBDOC_LINE: :on_embdoc,
|
||||
EMBEXPR_BEGIN: :on_embexpr_beg,
|
||||
EMBEXPR_END: :on_embexpr_end,
|
||||
EMBVAR: :on_embvar,
|
||||
EOF: :on_eof,
|
||||
EQUAL: :on_op,
|
||||
EQUAL_EQUAL: :on_op,
|
||||
EQUAL_EQUAL_EQUAL: :on_op,
|
||||
EQUAL_GREATER: :on_op,
|
||||
EQUAL_TILDE: :on_op,
|
||||
FLOAT: :on_float,
|
||||
GREATER: :on_op,
|
||||
GREATER_EQUAL: :on_op,
|
||||
GREATER_GREATER: :on_op,
|
||||
GREATER_GREATER_EQUAL: :on_op,
|
||||
GLOBAL_VARIABLE: :on_gvar,
|
||||
HEREDOC_END: :on_heredoc_end,
|
||||
HEREDOC_START: :on_heredoc_beg,
|
||||
IDENTIFIER: :on_ident,
|
||||
IGNORED_NEWLINE: :on_ignored_nl,
|
||||
IMAGINARY_NUMBER: :on_imaginary,
|
||||
INTEGER: :on_int,
|
||||
INSTANCE_VARIABLE: :on_ivar,
|
||||
INVALID: :INVALID,
|
||||
KEYWORD___ENCODING__: :on_kw,
|
||||
KEYWORD___LINE__: :on_kw,
|
||||
KEYWORD___FILE__: :on_kw,
|
||||
KEYWORD_ALIAS: :on_kw,
|
||||
KEYWORD_AND: :on_kw,
|
||||
KEYWORD_BEGIN: :on_kw,
|
||||
KEYWORD_BEGIN_UPCASE: :on_kw,
|
||||
KEYWORD_BREAK: :on_kw,
|
||||
KEYWORD_CASE: :on_kw,
|
||||
KEYWORD_CLASS: :on_kw,
|
||||
KEYWORD_DEF: :on_kw,
|
||||
KEYWORD_DEFINED: :on_kw,
|
||||
KEYWORD_DO: :on_kw,
|
||||
KEYWORD_DO_LOOP: :on_kw,
|
||||
KEYWORD_ELSE: :on_kw,
|
||||
KEYWORD_ELSIF: :on_kw,
|
||||
KEYWORD_END: :on_kw,
|
||||
KEYWORD_END_UPCASE: :on_kw,
|
||||
KEYWORD_ENSURE: :on_kw,
|
||||
KEYWORD_FALSE: :on_kw,
|
||||
KEYWORD_FOR: :on_kw,
|
||||
KEYWORD_IF: :on_kw,
|
||||
KEYWORD_IF_MODIFIER: :on_kw,
|
||||
KEYWORD_IN: :on_kw,
|
||||
KEYWORD_MODULE: :on_kw,
|
||||
KEYWORD_NEXT: :on_kw,
|
||||
KEYWORD_NIL: :on_kw,
|
||||
KEYWORD_NOT: :on_kw,
|
||||
KEYWORD_OR: :on_kw,
|
||||
KEYWORD_REDO: :on_kw,
|
||||
KEYWORD_RESCUE: :on_kw,
|
||||
KEYWORD_RESCUE_MODIFIER: :on_kw,
|
||||
KEYWORD_RETRY: :on_kw,
|
||||
KEYWORD_RETURN: :on_kw,
|
||||
KEYWORD_SELF: :on_kw,
|
||||
KEYWORD_SUPER: :on_kw,
|
||||
KEYWORD_THEN: :on_kw,
|
||||
KEYWORD_TRUE: :on_kw,
|
||||
KEYWORD_UNDEF: :on_kw,
|
||||
KEYWORD_UNLESS: :on_kw,
|
||||
KEYWORD_UNLESS_MODIFIER: :on_kw,
|
||||
KEYWORD_UNTIL: :on_kw,
|
||||
KEYWORD_UNTIL_MODIFIER: :on_kw,
|
||||
KEYWORD_WHEN: :on_kw,
|
||||
KEYWORD_WHILE: :on_kw,
|
||||
KEYWORD_WHILE_MODIFIER: :on_kw,
|
||||
KEYWORD_YIELD: :on_kw,
|
||||
LABEL: :on_label,
|
||||
LABEL_END: :on_label_end,
|
||||
LAMBDA_BEGIN: :on_tlambeg,
|
||||
LESS: :on_op,
|
||||
LESS_EQUAL: :on_op,
|
||||
LESS_EQUAL_GREATER: :on_op,
|
||||
LESS_LESS: :on_op,
|
||||
LESS_LESS_EQUAL: :on_op,
|
||||
MINUS: :on_op,
|
||||
MINUS_EQUAL: :on_op,
|
||||
MINUS_GREATER: :on_tlambda,
|
||||
NEWLINE: :on_nl,
|
||||
NUMBERED_REFERENCE: :on_backref,
|
||||
PARENTHESIS_LEFT: :on_lparen,
|
||||
PARENTHESIS_LEFT_PARENTHESES: :on_lparen,
|
||||
PARENTHESIS_RIGHT: :on_rparen,
|
||||
PERCENT: :on_op,
|
||||
PERCENT_EQUAL: :on_op,
|
||||
PERCENT_LOWER_I: :on_qsymbols_beg,
|
||||
PERCENT_LOWER_W: :on_qwords_beg,
|
||||
PERCENT_LOWER_X: :on_backtick,
|
||||
PERCENT_UPPER_I: :on_symbols_beg,
|
||||
PERCENT_UPPER_W: :on_words_beg,
|
||||
PIPE: :on_op,
|
||||
PIPE_EQUAL: :on_op,
|
||||
PIPE_PIPE: :on_op,
|
||||
PIPE_PIPE_EQUAL: :on_op,
|
||||
PLUS: :on_op,
|
||||
PLUS_EQUAL: :on_op,
|
||||
QUESTION_MARK: :on_op,
|
||||
RATIONAL_NUMBER: :on_rational,
|
||||
REGEXP_BEGIN: :on_regexp_beg,
|
||||
REGEXP_END: :on_regexp_end,
|
||||
SEMICOLON: :on_semicolon,
|
||||
SLASH: :on_op,
|
||||
SLASH_EQUAL: :on_op,
|
||||
STAR: :on_op,
|
||||
STAR_EQUAL: :on_op,
|
||||
STAR_STAR: :on_op,
|
||||
STAR_STAR_EQUAL: :on_op,
|
||||
STRING_BEGIN: :on_tstring_beg,
|
||||
STRING_CONTENT: :on_tstring_content,
|
||||
STRING_END: :on_tstring_end,
|
||||
SYMBOL_BEGIN: :on_symbeg,
|
||||
TILDE: :on_op,
|
||||
UCOLON_COLON: :on_op,
|
||||
UDOT_DOT: :on_op,
|
||||
UDOT_DOT_DOT: :on_op,
|
||||
UMINUS: :on_op,
|
||||
UMINUS_NUM: :on_op,
|
||||
UPLUS: :on_op,
|
||||
USTAR: :on_op,
|
||||
USTAR_STAR: :on_op,
|
||||
WORDS_SEP: :on_words_sep,
|
||||
__END__: :on___end__
|
||||
}.freeze
|
||||
|
||||
# When we produce tokens, we produce the same arrays that Ripper does.
|
||||
# However, we add a couple of convenience methods onto them to make them a
|
||||
# little easier to work with. We delegate all other methods to the array.
|
||||
class Token < SimpleDelegator
|
||||
def location
|
||||
self[0]
|
||||
end
|
||||
|
||||
def event
|
||||
self[1]
|
||||
end
|
||||
|
||||
def value
|
||||
self[2]
|
||||
end
|
||||
|
||||
def state
|
||||
self[3]
|
||||
end
|
||||
end
|
||||
|
||||
# Ripper doesn't include the rest of the token in the event, so we need to
|
||||
# trim it down to just the content on the first line when comparing.
|
||||
class EndContentToken < Token
|
||||
def ==(other)
|
||||
[self[0], self[1], self[2][0..self[2].index("\n")], self[3]] == other
|
||||
end
|
||||
end
|
||||
|
||||
# It is extremely non obvious which state the parser is in when comments get
|
||||
# dispatched. Because of this we don't both comparing state when comparing
|
||||
# against other comment tokens.
|
||||
class CommentToken < Token
|
||||
def ==(other)
|
||||
self[0...-1] == other[0...-1]
|
||||
end
|
||||
end
|
||||
|
||||
# Heredoc end tokens are emitted in an odd order, so we don't compare the
|
||||
# state on them.
|
||||
class HeredocEndToken < Token
|
||||
def ==(other)
|
||||
self[0...-1] == other[0...-1]
|
||||
end
|
||||
end
|
||||
|
||||
# Ident tokens for the most part are exactly the same, except sometimes we
|
||||
# know an ident is a local when ripper doesn't (when they are introduced
|
||||
# through named captures in regular expressions). In that case we don't
|
||||
# compare the state.
|
||||
class IdentToken < Token
|
||||
def ==(other)
|
||||
(self[0...-1] == other[0...-1]) && (
|
||||
(other[3] == Ripper::EXPR_LABEL | Ripper::EXPR_END) ||
|
||||
(other[3] & Ripper::EXPR_ARG_ANY != 0)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Ignored newlines can occasionally have a LABEL state attached to them, so
|
||||
# we compare the state differently here.
|
||||
class IgnoredNewlineToken < Token
|
||||
def ==(other)
|
||||
return false unless self[0...-1] == other[0...-1]
|
||||
|
||||
if self[4] == Ripper::EXPR_ARG | Ripper::EXPR_LABELED
|
||||
other[4] & Ripper::EXPR_ARG | Ripper::EXPR_LABELED > 0
|
||||
else
|
||||
self[4] == other[4]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# A heredoc in this case is a list of tokens that belong to the body of the
|
||||
# heredoc that should be appended onto the list of tokens when the heredoc
|
||||
# closes.
|
||||
module Heredoc
|
||||
# Heredocs that are no dash or tilde heredocs are just a list of tokens.
|
||||
# We need to keep them around so that we can insert them in the correct
|
||||
# order back into the token stream and set the state of the last token to
|
||||
# the state that the heredoc was opened in.
|
||||
class PlainHeredoc
|
||||
attr_reader :tokens
|
||||
|
||||
def initialize
|
||||
@tokens = []
|
||||
end
|
||||
|
||||
def <<(token)
|
||||
tokens << token
|
||||
end
|
||||
|
||||
def to_a
|
||||
tokens
|
||||
end
|
||||
end
|
||||
|
||||
# Dash heredocs are a little more complicated. They are a list of tokens
|
||||
# that need to be split on "\\\n" to mimic Ripper's behavior. We also need
|
||||
# to keep track of the state that the heredoc was opened in.
|
||||
class DashHeredoc
|
||||
attr_reader :split, :tokens
|
||||
|
||||
def initialize(split)
|
||||
@split = split
|
||||
@tokens = []
|
||||
end
|
||||
|
||||
def <<(token)
|
||||
tokens << token
|
||||
end
|
||||
|
||||
def to_a
|
||||
embexpr_balance = 0
|
||||
|
||||
tokens.each_with_object([]) do |token, results|
|
||||
case token.event
|
||||
when :on_embexpr_beg
|
||||
embexpr_balance += 1
|
||||
results << token
|
||||
when :on_embexpr_end
|
||||
embexpr_balance -= 1
|
||||
results << token
|
||||
when :on_tstring_content
|
||||
if embexpr_balance == 0
|
||||
lineno = token[0][0]
|
||||
column = token[0][1]
|
||||
|
||||
if split
|
||||
# Split on "\\\n" to mimic Ripper's behavior. Use a lookbehind
|
||||
# to keep the delimiter in the result.
|
||||
token.value.split(/(?<=[^\\]\\\n)|(?<=[^\\]\\\r\n)/).each_with_index do |value, index|
|
||||
column = 0 if index > 0
|
||||
results << Token.new([[lineno, column], :on_tstring_content, value, token.state])
|
||||
lineno += value.count("\n")
|
||||
end
|
||||
else
|
||||
results << token
|
||||
end
|
||||
else
|
||||
results << token
|
||||
end
|
||||
else
|
||||
results << token
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Heredocs that are dedenting heredocs are a little more complicated.
|
||||
# Ripper outputs on_ignored_sp tokens for the whitespace that is being
|
||||
# removed from the output. YARP only modifies the node itself and keeps
|
||||
# the token the same. This simplifies YARP, but makes comparing against
|
||||
# Ripper much harder because there is a length mismatch.
|
||||
#
|
||||
# Fortunately, we already have to pull out the heredoc tokens in order to
|
||||
# insert them into the stream in the correct order. As such, we can do
|
||||
# some extra manipulation on the tokens to make them match Ripper's
|
||||
# output by mirroring the dedent logic that Ripper uses.
|
||||
class DedentingHeredoc
|
||||
TAB_WIDTH = 8
|
||||
|
||||
attr_reader :tokens, :dedent_next, :dedent, :embexpr_balance
|
||||
|
||||
def initialize
|
||||
@tokens = []
|
||||
@dedent_next = true
|
||||
@dedent = nil
|
||||
@embexpr_balance = 0
|
||||
end
|
||||
|
||||
# As tokens are coming in, we track the minimum amount of common leading
|
||||
# whitespace on plain string content tokens. This allows us to later
|
||||
# remove that amount of whitespace from the beginning of each line.
|
||||
def <<(token)
|
||||
case token.event
|
||||
when :on_embexpr_beg, :on_heredoc_beg
|
||||
@embexpr_balance += 1
|
||||
when :on_embexpr_end, :on_heredoc_end
|
||||
@embexpr_balance -= 1
|
||||
when :on_tstring_content
|
||||
if embexpr_balance == 0
|
||||
token.value.split(/(?<=\n)/).each_with_index do |line, index|
|
||||
next if line.strip.empty? && line.end_with?("\n")
|
||||
next if !(dedent_next || index > 0)
|
||||
|
||||
leading = line[/\A(\s*)\n?/, 1]
|
||||
next_dedent = 0
|
||||
|
||||
leading.each_char do |char|
|
||||
if char == "\t"
|
||||
next_dedent = next_dedent - (next_dedent % TAB_WIDTH) + TAB_WIDTH
|
||||
else
|
||||
next_dedent += 1
|
||||
end
|
||||
end
|
||||
|
||||
@dedent = [dedent, next_dedent].compact.min
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@dedent_next = token.event == :on_tstring_content && embexpr_balance == 0
|
||||
tokens << token
|
||||
end
|
||||
|
||||
def to_a
|
||||
# If every line in the heredoc is blank, we still need to split up the
|
||||
# string content token into multiple tokens.
|
||||
if dedent.nil?
|
||||
results = []
|
||||
embexpr_balance = 0
|
||||
|
||||
tokens.each do |token|
|
||||
case token.event
|
||||
when :on_embexpr_beg, :on_heredoc_beg
|
||||
embexpr_balance += 1
|
||||
results << token
|
||||
when :on_embexpr_end, :on_heredoc_end
|
||||
embexpr_balance -= 1
|
||||
results << token
|
||||
when :on_tstring_content
|
||||
if embexpr_balance == 0
|
||||
lineno = token[0][0]
|
||||
column = token[0][1]
|
||||
|
||||
token.value.split(/(?<=\n)/).each_with_index do |value, index|
|
||||
column = 0 if index > 0
|
||||
results << Token.new([[lineno, column], :on_tstring_content, value, token.state])
|
||||
lineno += 1
|
||||
end
|
||||
else
|
||||
results << token
|
||||
end
|
||||
else
|
||||
results << token
|
||||
end
|
||||
end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
# Otherwise, we're going to run through each token in the list and
|
||||
# insert on_ignored_sp tokens for the amount of dedent that we need to
|
||||
# perform. We also need to remove the dedent from the beginning of
|
||||
# each line of plain string content tokens.
|
||||
results = []
|
||||
dedent_next = true
|
||||
embexpr_balance = 0
|
||||
|
||||
tokens.each do |token|
|
||||
# Notice that the structure of this conditional largely matches the
|
||||
# whitespace calculation we performed above. This is because
|
||||
# checking if the subsequent token needs to be dedented is common to
|
||||
# both the dedent calculation and the ignored_sp insertion.
|
||||
case token.event
|
||||
when :on_embexpr_beg
|
||||
embexpr_balance += 1
|
||||
results << token
|
||||
when :on_embexpr_end
|
||||
embexpr_balance -= 1
|
||||
results << token
|
||||
when :on_tstring_content
|
||||
if embexpr_balance == 0
|
||||
# Here we're going to split the string on newlines, but maintain
|
||||
# the newlines in the resulting array. We'll do that with a look
|
||||
# behind assertion.
|
||||
splits = token.value.split(/(?<=\n)/)
|
||||
index = 0
|
||||
|
||||
while index < splits.length
|
||||
line = splits[index]
|
||||
lineno = token[0][0] + index
|
||||
column = token[0][1]
|
||||
|
||||
# Blank lines do not count toward common leading whitespace
|
||||
# calculation and do not need to be dedented.
|
||||
if dedent_next || index > 0
|
||||
column = 0
|
||||
end
|
||||
|
||||
# If the dedent is 0 and we're not supposed to dedent the next
|
||||
# line or this line doesn't start with whitespace, then we
|
||||
# should concatenate the rest of the string to match ripper.
|
||||
if dedent == 0 && (!dedent_next || !line.start_with?(/\s/))
|
||||
line = splits[index..].join
|
||||
index = splits.length
|
||||
end
|
||||
|
||||
# If we are supposed to dedent this line or if this is not the
|
||||
# first line of the string and this line isn't entirely blank,
|
||||
# then we need to insert an on_ignored_sp token and remove the
|
||||
# dedent from the beginning of the line.
|
||||
if (dedent > 0) && (dedent_next || index > 0)
|
||||
deleting = 0
|
||||
deleted_chars = []
|
||||
|
||||
# Gather up all of the characters that we're going to
|
||||
# delete, stopping when you hit a character that would put
|
||||
# you over the dedent amount.
|
||||
line.each_char.with_index do |char, i|
|
||||
case char
|
||||
when "\r"
|
||||
if line.chars[i + 1] == "\n"
|
||||
break
|
||||
end
|
||||
when "\n"
|
||||
break
|
||||
when "\t"
|
||||
deleting = deleting - (deleting % TAB_WIDTH) + TAB_WIDTH
|
||||
else
|
||||
deleting += 1
|
||||
end
|
||||
|
||||
break if deleting > dedent
|
||||
deleted_chars << char
|
||||
end
|
||||
|
||||
# If we have something to delete, then delete it from the
|
||||
# string and insert an on_ignored_sp token.
|
||||
if deleted_chars.any?
|
||||
ignored = deleted_chars.join
|
||||
line.delete_prefix!(ignored)
|
||||
|
||||
results << Token.new([[lineno, 0], :on_ignored_sp, ignored, token[3]])
|
||||
column = ignored.length
|
||||
end
|
||||
end
|
||||
|
||||
results << Token.new([[lineno, column], token[1], line, token[3]]) unless line.empty?
|
||||
index += 1
|
||||
end
|
||||
else
|
||||
results << token
|
||||
end
|
||||
else
|
||||
results << token
|
||||
end
|
||||
|
||||
dedent_next =
|
||||
((token.event == :on_tstring_content) || (token.event == :on_heredoc_end)) &&
|
||||
embexpr_balance == 0
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
end
|
||||
|
||||
# Here we will split between the two types of heredocs and return the
|
||||
# object that will store their tokens.
|
||||
def self.build(opening)
|
||||
case opening.value[2]
|
||||
when "~"
|
||||
DedentingHeredoc.new
|
||||
when "-"
|
||||
DashHeredoc.new(opening.value[3] != "'")
|
||||
else
|
||||
PlainHeredoc.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :source, :offsets, :filepath
|
||||
|
||||
def initialize(source, filepath = "")
|
||||
@source = source
|
||||
@filepath = filepath || ""
|
||||
@offsets = find_offsets(source)
|
||||
end
|
||||
|
||||
def result
|
||||
tokens = []
|
||||
|
||||
state = :default
|
||||
heredoc_stack = [[]]
|
||||
|
||||
result = YARP.lex(source, @filepath)
|
||||
result_value = result.value
|
||||
previous_state = nil
|
||||
|
||||
# If there's a UTF-8 byte-order mark as the start of the file, then ripper
|
||||
# sets every token's on the first line back by 6 bytes. It also keeps the
|
||||
# byte order mark in the first token's value. This is weird, and I don't
|
||||
# want to mirror that in our parser. So instead, we'll match up the values
|
||||
# here, and then match up the locations as we process the tokens.
|
||||
bom = source.bytes[0..2] == [0xEF, 0xBB, 0xBF]
|
||||
result_value[0][0].value.prepend("\xEF\xBB\xBF") if bom
|
||||
|
||||
result_value.each_with_index do |(token, lex_state), index|
|
||||
(lineno, column) = find_location(token.location.start_offset)
|
||||
column -= index == 0 ? 6 : 3 if bom && lineno == 1
|
||||
|
||||
event = RIPPER.fetch(token.type)
|
||||
value = token.value
|
||||
lex_state = Ripper::Lexer::State.new(lex_state)
|
||||
|
||||
token =
|
||||
case event
|
||||
when :on___end__
|
||||
EndContentToken.new([[lineno, column], event, value, lex_state])
|
||||
when :on_comment
|
||||
CommentToken.new([[lineno, column], event, value, lex_state])
|
||||
when :on_heredoc_end
|
||||
# Heredoc end tokens can be emitted in an odd order, so we don't
|
||||
# want to bother comparing the state on them.
|
||||
HeredocEndToken.new([[lineno, column], event, value, lex_state])
|
||||
when :on_embexpr_end, :on_ident
|
||||
if lex_state == Ripper::EXPR_END | Ripper::EXPR_LABEL
|
||||
# In the event that we're comparing identifiers, we're going to
|
||||
# allow a little divergence. Ripper doesn't account for local
|
||||
# variables introduced through named captures in regexes, and we
|
||||
# do, which accounts for this difference.
|
||||
IdentToken.new([[lineno, column], event, value, lex_state])
|
||||
else
|
||||
Token.new([[lineno, column], event, value, lex_state])
|
||||
end
|
||||
when :on_ignored_nl
|
||||
# Ignored newlines can occasionally have a LABEL state attached to
|
||||
# them which doesn't actually impact anything. We don't mirror that
|
||||
# state so we ignored it.
|
||||
IgnoredNewlineToken.new([[lineno, column], event, value, lex_state])
|
||||
when :on_regexp_end
|
||||
# On regex end, Ripper scans and then sets end state, so the ripper
|
||||
# lexed output is begin, when it should be end. YARP sets lex state
|
||||
# correctly to end state, but we want to be able to compare against
|
||||
# Ripper's lexed state. So here, if it's a regexp end token, we
|
||||
# output the state as the previous state, solely for the sake of
|
||||
# comparison.
|
||||
previous_token = result_value[index - 1][0]
|
||||
lex_state =
|
||||
if RIPPER.fetch(previous_token.type) == :on_embexpr_end
|
||||
# If the previous token is embexpr_end, then we have to do even
|
||||
# more processing. The end of an embedded expression sets the
|
||||
# state to the state that it had at the beginning of the
|
||||
# embedded expression. So we have to go and find that state and
|
||||
# set it here.
|
||||
counter = 1
|
||||
current_index = index - 1
|
||||
|
||||
until counter == 0
|
||||
current_index -= 1
|
||||
current_event = RIPPER.fetch(result_value[current_index][0].type)
|
||||
counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0
|
||||
end
|
||||
|
||||
Ripper::Lexer::State.new(result_value[current_index][1])
|
||||
else
|
||||
previous_state
|
||||
end
|
||||
|
||||
Token.new([[lineno, column], event, value, lex_state])
|
||||
else
|
||||
Token.new([[lineno, column], event, value, lex_state])
|
||||
end
|
||||
|
||||
previous_state = lex_state
|
||||
|
||||
# The order in which tokens appear in our lexer is different from the
|
||||
# order that they appear in Ripper. When we hit the declaration of a
|
||||
# heredoc in YARP, we skip forward and lex the rest of the content of
|
||||
# the heredoc before going back and lexing at the end of the heredoc
|
||||
# identifier.
|
||||
#
|
||||
# To match up to ripper, we keep a small state variable around here to
|
||||
# track whether we're in the middle of a heredoc or not. In this way we
|
||||
# can shuffle around the token to match Ripper's output.
|
||||
case state
|
||||
when :default
|
||||
tokens << token
|
||||
|
||||
if event == :on_heredoc_beg
|
||||
state = :heredoc_opened
|
||||
heredoc_stack.last << Heredoc.build(token)
|
||||
end
|
||||
when :heredoc_opened
|
||||
heredoc_stack.last.last << token
|
||||
|
||||
case event
|
||||
when :on_heredoc_beg
|
||||
heredoc_stack << [Heredoc.build(token)]
|
||||
when :on_heredoc_end
|
||||
state = :heredoc_closed
|
||||
end
|
||||
when :heredoc_closed
|
||||
if %i[on_nl on_ignored_nl on_comment].include?(event) || (event == :on_tstring_content && value.end_with?("\n"))
|
||||
if heredoc_stack.size > 1
|
||||
flushing = heredoc_stack.pop
|
||||
heredoc_stack.last.last << token
|
||||
|
||||
flushing.each do |heredoc|
|
||||
heredoc.to_a.each do |flushed_token|
|
||||
heredoc_stack.last.last << flushed_token
|
||||
end
|
||||
end
|
||||
|
||||
state = :heredoc_opened
|
||||
next
|
||||
end
|
||||
elsif event == :on_heredoc_beg
|
||||
tokens << token
|
||||
state = :heredoc_opened
|
||||
heredoc_stack.last << Heredoc.build(token)
|
||||
next
|
||||
elsif heredoc_stack.size > 1
|
||||
heredoc_stack[-2].last << token
|
||||
next
|
||||
end
|
||||
|
||||
heredoc_stack.last.each do |heredoc|
|
||||
tokens.concat(heredoc.to_a)
|
||||
end
|
||||
|
||||
heredoc_stack.last.clear
|
||||
state = :default
|
||||
|
||||
tokens << token
|
||||
end
|
||||
end
|
||||
|
||||
tokens.reject! { |t| t.event == :on_eof }
|
||||
|
||||
# We sort by location to compare against Ripper's output
|
||||
tokens.sort_by!(&:location)
|
||||
|
||||
if result_value.size - 1 > tokens.size
|
||||
raise StandardError, "Lost tokens when performing lex_compat"
|
||||
end
|
||||
|
||||
ParseResult.new(tokens, result.comments, result.errors, result.warnings)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# YARP keeps locations around in the form of ranges of byte offsets from the
|
||||
# start of the file. Ripper keeps locations around in the form of line and
|
||||
# column numbers. To match the output, we keep a cache of the offsets at the
|
||||
# beginning of each line.
|
||||
def find_offsets(source)
|
||||
last_offset = 0
|
||||
offsets = [0]
|
||||
|
||||
source.each_line do |line|
|
||||
last_offset += line.bytesize
|
||||
offsets << last_offset
|
||||
end
|
||||
|
||||
offsets
|
||||
end
|
||||
|
||||
# Given a byte offset, find the line number and column number that it maps
|
||||
# to. We use a binary search over the cached offsets to find the line number
|
||||
# that the offset is on, and then subtract the offset of the previous line
|
||||
# to find the column number.
|
||||
def find_location(value)
|
||||
line_number = offsets.bsearch_index { |offset| offset > value }
|
||||
line_offset = offsets[line_number - 1] if line_number
|
||||
|
||||
[
|
||||
line_number || offsets.length - 1,
|
||||
value - (line_offset || offsets.last)
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
# The constant that wraps the behavior of the lexer to match Ripper's output
|
||||
# is an implementation detail, so we don't want it to be public.
|
||||
private_constant :LexCompat
|
||||
|
||||
# Returns an array of tokens that closely resembles that of the Ripper lexer.
|
||||
# The only difference is that since we don't keep track of lexer state in the
|
||||
# same way, it's going to always return the NONE state.
|
||||
def self.lex_compat(source, filepath = "")
|
||||
LexCompat.new(source, filepath).result
|
||||
end
|
||||
end
|
6434
lib/yarp/node.rb
Normal file
6434
lib/yarp/node.rb
Normal file
File diff suppressed because it is too large
Load Diff
185
lib/yarp/pack.rb
Normal file
185
lib/yarp/pack.rb
Normal file
@ -0,0 +1,185 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module YARP
|
||||
module Pack
|
||||
%i[
|
||||
SPACE
|
||||
COMMENT
|
||||
INTEGER
|
||||
UTF8
|
||||
BER
|
||||
FLOAT
|
||||
STRING_SPACE_PADDED
|
||||
STRING_NULL_PADDED
|
||||
STRING_NULL_TERMINATED
|
||||
STRING_MSB
|
||||
STRING_LSB
|
||||
STRING_HEX_HIGH
|
||||
STRING_HEX_LOW
|
||||
STRING_UU
|
||||
STRING_MIME
|
||||
STRING_BASE64
|
||||
STRING_FIXED
|
||||
STRING_POINTER
|
||||
MOVE
|
||||
BACK
|
||||
NULL
|
||||
|
||||
UNSIGNED
|
||||
SIGNED
|
||||
SIGNED_NA
|
||||
|
||||
AGNOSTIC_ENDIAN
|
||||
LITTLE_ENDIAN
|
||||
BIG_ENDIAN
|
||||
NATIVE_ENDIAN
|
||||
ENDIAN_NA
|
||||
|
||||
SIZE_SHORT
|
||||
SIZE_INT
|
||||
SIZE_LONG
|
||||
SIZE_LONG_LONG
|
||||
SIZE_8
|
||||
SIZE_16
|
||||
SIZE_32
|
||||
SIZE_64
|
||||
SIZE_P
|
||||
SIZE_NA
|
||||
|
||||
LENGTH_FIXED
|
||||
LENGTH_MAX
|
||||
LENGTH_RELATIVE
|
||||
LENGTH_NA
|
||||
].each do |const|
|
||||
const_set(const, const)
|
||||
end
|
||||
|
||||
class Directive
|
||||
attr_reader :version, :variant, :source, :type, :signed, :endian, :size, :length_type, :length
|
||||
|
||||
def initialize(version, variant, source, type, signed, endian, size, length_type, length)
|
||||
@version = version
|
||||
@variant = variant
|
||||
@source = source
|
||||
@type = type
|
||||
@signed = signed
|
||||
@endian = endian
|
||||
@size = size
|
||||
@length_type = length_type
|
||||
@length = length
|
||||
end
|
||||
|
||||
ENDIAN_DESCRIPTIONS = {
|
||||
AGNOSTIC_ENDIAN: 'agnostic',
|
||||
LITTLE_ENDIAN: 'little-endian (VAX)',
|
||||
BIG_ENDIAN: 'big-endian (network)',
|
||||
NATIVE_ENDIAN: 'native-endian',
|
||||
ENDIAN_NA: 'n/a'
|
||||
}
|
||||
|
||||
SIGNED_DESCRIPTIONS = {
|
||||
UNSIGNED: 'unsigned',
|
||||
SIGNED: 'signed',
|
||||
SIGNED_NA: 'n/a'
|
||||
}
|
||||
|
||||
SIZE_DESCRIPTIONS = {
|
||||
SIZE_SHORT: 'short',
|
||||
SIZE_INT: 'int-width',
|
||||
SIZE_LONG: 'long',
|
||||
SIZE_LONG_LONG: 'long long',
|
||||
SIZE_8: '8-bit',
|
||||
SIZE_16: '16-bit',
|
||||
SIZE_32: '32-bit',
|
||||
SIZE_64: '64-bit',
|
||||
SIZE_P: 'pointer-width'
|
||||
}
|
||||
|
||||
def describe
|
||||
case type
|
||||
when SPACE
|
||||
'whitespace'
|
||||
when COMMENT
|
||||
'comment'
|
||||
when INTEGER
|
||||
if size == SIZE_8
|
||||
base = "#{SIGNED_DESCRIPTIONS[signed]} #{SIZE_DESCRIPTIONS[size]} integer"
|
||||
else
|
||||
base = "#{SIGNED_DESCRIPTIONS[signed]} #{SIZE_DESCRIPTIONS[size]} #{ENDIAN_DESCRIPTIONS[endian]} integer"
|
||||
end
|
||||
case length_type
|
||||
when LENGTH_FIXED
|
||||
if length > 1
|
||||
base + ", x#{length}"
|
||||
else
|
||||
base
|
||||
end
|
||||
when LENGTH_MAX
|
||||
base + ', as many as possible'
|
||||
end
|
||||
when UTF8
|
||||
'UTF-8 character'
|
||||
when BER
|
||||
'BER-compressed integer'
|
||||
when FLOAT
|
||||
"#{SIZE_DESCRIPTIONS[size]} #{ENDIAN_DESCRIPTIONS[endian]} float"
|
||||
when STRING_SPACE_PADDED
|
||||
'arbitrary binary string (space padded)'
|
||||
when STRING_NULL_PADDED
|
||||
'arbitrary binary string (null padded, count is width)'
|
||||
when STRING_NULL_TERMINATED
|
||||
'arbitrary binary string (null padded, count is width), except that null is added with *'
|
||||
when STRING_MSB
|
||||
'bit string (MSB first)'
|
||||
when STRING_LSB
|
||||
'bit string (LSB first)'
|
||||
when STRING_HEX_HIGH
|
||||
'hex string (high nibble first)'
|
||||
when STRING_HEX_LOW
|
||||
'hex string (low nibble first)'
|
||||
when STRING_UU
|
||||
'UU-encoded string'
|
||||
when STRING_MIME
|
||||
'quoted printable, MIME encoding'
|
||||
when STRING_BASE64
|
||||
'base64 encoded string'
|
||||
when STRING_FIXED
|
||||
'pointer to a structure (fixed-length string)'
|
||||
when STRING_POINTER
|
||||
'pointer to a null-terminated string'
|
||||
when MOVE
|
||||
'move to absolute position'
|
||||
when BACK
|
||||
'back up a byte'
|
||||
when NULL
|
||||
'null byte'
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Format
|
||||
attr_reader :directives, :encoding
|
||||
|
||||
def initialize(directives, encoding)
|
||||
@directives = directives
|
||||
@encoding = encoding
|
||||
end
|
||||
|
||||
def describe
|
||||
source_width = directives.map { |d| d.source.inspect.length }.max
|
||||
directive_lines = directives.map do |directive|
|
||||
if directive.type == SPACE
|
||||
source = directive.source.inspect
|
||||
else
|
||||
source = directive.source
|
||||
end
|
||||
" #{source.ljust(source_width)} #{directive.describe}"
|
||||
end
|
||||
|
||||
(['Directives:'] + directive_lines + ['Encoding:', " #{encoding}"]).join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
174
lib/yarp/ripper_compat.rb
Normal file
174
lib/yarp/ripper_compat.rb
Normal file
@ -0,0 +1,174 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "ripper"
|
||||
|
||||
module YARP
|
||||
# This class is meant to provide a compatibility layer between YARP and
|
||||
# Ripper. It functions by parsing the entire tree first and then walking it
|
||||
# and executing each of the Ripper callbacks as it goes.
|
||||
#
|
||||
# This class is going to necessarily be slower than the native Ripper API. It
|
||||
# is meant as a stopgap until developers migrate to using YARP. It is also
|
||||
# meant as a test harness for the YARP parser.
|
||||
class RipperCompat
|
||||
# This class mirrors the ::Ripper::SexpBuilder subclass of ::Ripper that
|
||||
# returns the arrays of [type, *children].
|
||||
class SexpBuilder < RipperCompat
|
||||
private
|
||||
|
||||
Ripper::PARSER_EVENTS.each do |event|
|
||||
define_method(:"on_#{event}") do |*args|
|
||||
[event, *args]
|
||||
end
|
||||
end
|
||||
|
||||
Ripper::SCANNER_EVENTS.each do |event|
|
||||
define_method(:"on_#{event}") do |value|
|
||||
[:"@#{event}", value, [lineno, column]]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This class mirrors the ::Ripper::SexpBuilderPP subclass of ::Ripper that
|
||||
# returns the same values as ::Ripper::SexpBuilder except with a couple of
|
||||
# niceties that flatten linked lists into arrays.
|
||||
class SexpBuilderPP < SexpBuilder
|
||||
private
|
||||
|
||||
def _dispatch_event_new
|
||||
[]
|
||||
end
|
||||
|
||||
def _dispatch_event_push(list, item)
|
||||
list << item
|
||||
list
|
||||
end
|
||||
|
||||
Ripper::PARSER_EVENT_TABLE.each do |event, arity|
|
||||
case event
|
||||
when /_new\z/
|
||||
alias :"on_#{event}" :_dispatch_event_new if arity == 0
|
||||
when /_add\z/
|
||||
alias :"on_#{event}" :_dispatch_event_push
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :source, :lineno, :column
|
||||
|
||||
def initialize(source)
|
||||
@source = source
|
||||
@result = nil
|
||||
@lineno = nil
|
||||
@column = nil
|
||||
end
|
||||
|
||||
############################################################################
|
||||
# Public interface
|
||||
############################################################################
|
||||
|
||||
def error?
|
||||
result.errors.any?
|
||||
end
|
||||
|
||||
def parse
|
||||
result.value.accept(self) unless error?
|
||||
end
|
||||
|
||||
############################################################################
|
||||
# Visitor methods
|
||||
############################################################################
|
||||
|
||||
def visit(node)
|
||||
node&.accept(self)
|
||||
end
|
||||
|
||||
def visit_call_node(node)
|
||||
if !node.opening_loc && node.arguments.arguments.length == 1
|
||||
bounds(node.receiver.location)
|
||||
left = visit(node.receiver)
|
||||
|
||||
bounds(node.arguments.arguments.first.location)
|
||||
right = visit(node.arguments.arguments.first)
|
||||
|
||||
on_binary(left, source[node.message_loc.start_offset...node.message_loc.end_offset].to_sym, right)
|
||||
else
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
|
||||
def visit_integer_node(node)
|
||||
bounds(node.location)
|
||||
on_int(source[node.location.start_offset...node.location.end_offset])
|
||||
end
|
||||
|
||||
def visit_statements_node(node)
|
||||
bounds(node.location)
|
||||
node.body.inject(on_stmts_new) do |stmts, stmt|
|
||||
on_stmts_add(stmts, visit(stmt))
|
||||
end
|
||||
end
|
||||
|
||||
def visit_token(node)
|
||||
bounds(node.location)
|
||||
|
||||
case node.type
|
||||
when :MINUS
|
||||
on_op(node.value)
|
||||
when :PLUS
|
||||
on_op(node.value)
|
||||
else
|
||||
raise NotImplementedError, "Unknown token: #{node.type}"
|
||||
end
|
||||
end
|
||||
|
||||
def visit_program_node(node)
|
||||
bounds(node.location)
|
||||
on_program(visit(node.statements))
|
||||
end
|
||||
|
||||
############################################################################
|
||||
# Entrypoints for subclasses
|
||||
############################################################################
|
||||
|
||||
# This is a convenience method that runs the SexpBuilder subclass parser.
|
||||
def self.sexp_raw(source)
|
||||
SexpBuilder.new(source).parse
|
||||
end
|
||||
|
||||
# This is a convenience method that runs the SexpBuilderPP subclass parser.
|
||||
def self.sexp(source)
|
||||
SexpBuilderPP.new(source).parse
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# This method is responsible for updating lineno and column information
|
||||
# to reflect the current node.
|
||||
#
|
||||
# This method could be drastically improved with some caching on the start
|
||||
# of every line, but for now it's good enough.
|
||||
def bounds(location)
|
||||
start_offset = location.start_offset
|
||||
|
||||
@lineno = source[0..start_offset].count("\n") + 1
|
||||
@column = start_offset - (source.rindex("\n", start_offset) || 0)
|
||||
end
|
||||
|
||||
def result
|
||||
@result ||= YARP.parse(source)
|
||||
end
|
||||
|
||||
def _dispatch0; end
|
||||
def _dispatch1(_); end
|
||||
def _dispatch2(_, _); end
|
||||
def _dispatch3(_, _, _); end
|
||||
def _dispatch4(_, _, _, _); end
|
||||
def _dispatch5(_, _, _, _, _); end
|
||||
def _dispatch7(_, _, _, _, _, _, _); end
|
||||
|
||||
(Ripper::SCANNER_EVENT_TABLE.merge(Ripper::PARSER_EVENT_TABLE)).each do |event, arity|
|
||||
alias :"on_#{event}" :"_dispatch#{arity}"
|
||||
end
|
||||
end
|
||||
end
|
367
lib/yarp/serialize.rb
Normal file
367
lib/yarp/serialize.rb
Normal file
@ -0,0 +1,367 @@
|
||||
# frozen_string_literal: true
|
||||
=begin
|
||||
This file is generated by the bin/template script and should not be
|
||||
modified manually. See templates/lib/yarp/serialize.rb.erb
|
||||
if you are looking to modify the template
|
||||
=end
|
||||
|
||||
require "stringio"
|
||||
|
||||
module YARP
|
||||
module Serialize
|
||||
def self.load(source, serialized)
|
||||
io = StringIO.new(serialized)
|
||||
io.set_encoding(Encoding::BINARY)
|
||||
|
||||
Loader.new(source, serialized, io).load
|
||||
end
|
||||
|
||||
class Loader
|
||||
attr_reader :encoding, :source, :serialized, :io
|
||||
attr_reader :constant_pool_offset, :constant_pool
|
||||
|
||||
def initialize(source, serialized, io)
|
||||
@encoding = Encoding::UTF_8
|
||||
|
||||
@source = source.dup
|
||||
@serialized = serialized
|
||||
@io = io
|
||||
|
||||
@constant_pool_offset = nil
|
||||
@constant_pool = nil
|
||||
end
|
||||
|
||||
def load
|
||||
io.read(4) => "YARP"
|
||||
io.read(3).unpack("C3") => [0, 4, 0]
|
||||
|
||||
@encoding = Encoding.find(io.read(load_varint))
|
||||
@source = source.force_encoding(@encoding).freeze
|
||||
|
||||
@constant_pool_offset = io.read(4).unpack1("L")
|
||||
@constant_pool = Array.new(load_varint, nil)
|
||||
|
||||
load_node
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# variable-length integer using https://en.wikipedia.org/wiki/LEB128
|
||||
# This is also what protobuf uses: https://protobuf.dev/programming-guides/encoding/#varints
|
||||
def load_varint
|
||||
n = io.getbyte
|
||||
if n < 128
|
||||
n
|
||||
else
|
||||
n -= 128
|
||||
shift = 0
|
||||
while (b = io.getbyte) >= 128
|
||||
n += (b - 128) << (shift += 7)
|
||||
end
|
||||
n + (b << (shift + 7))
|
||||
end
|
||||
end
|
||||
|
||||
def load_serialized_length
|
||||
io.read(4).unpack1("L")
|
||||
end
|
||||
|
||||
def load_optional_node
|
||||
if io.getbyte != 0
|
||||
io.pos -= 1
|
||||
load_node
|
||||
end
|
||||
end
|
||||
|
||||
def load_string
|
||||
io.read(load_varint).force_encoding(encoding)
|
||||
end
|
||||
|
||||
def load_location
|
||||
Location.new(load_varint, load_varint)
|
||||
end
|
||||
|
||||
def load_optional_location
|
||||
load_location if io.getbyte != 0
|
||||
end
|
||||
|
||||
def load_constant
|
||||
index = load_varint - 1
|
||||
constant = constant_pool[index]
|
||||
|
||||
unless constant
|
||||
offset = constant_pool_offset + index * 8
|
||||
|
||||
start = serialized.unpack1("L", offset: offset)
|
||||
length = serialized.unpack1("L", offset: offset + 4)
|
||||
|
||||
constant = source.byteslice(start, length).to_sym
|
||||
constant_pool[index] = constant
|
||||
end
|
||||
|
||||
constant
|
||||
end
|
||||
|
||||
def load_node
|
||||
type = io.getbyte
|
||||
start_offset, length = load_varint, load_varint
|
||||
|
||||
case type
|
||||
when 1 then
|
||||
AliasNode.new(load_node, load_node, load_location, start_offset, length)
|
||||
when 2 then
|
||||
AlternationPatternNode.new(load_node, load_node, load_location, start_offset, length)
|
||||
when 3 then
|
||||
AndNode.new(load_node, load_node, load_location, start_offset, length)
|
||||
when 4 then
|
||||
ArgumentsNode.new(Array.new(load_varint) { load_node }, start_offset, length)
|
||||
when 5 then
|
||||
ArrayNode.new(Array.new(load_varint) { load_node }, load_optional_location, load_optional_location, start_offset, length)
|
||||
when 6 then
|
||||
ArrayPatternNode.new(load_optional_node, Array.new(load_varint) { load_node }, load_optional_node, Array.new(load_varint) { load_node }, load_optional_location, load_optional_location, start_offset, length)
|
||||
when 7 then
|
||||
AssocNode.new(load_node, load_optional_node, load_optional_location, start_offset, length)
|
||||
when 8 then
|
||||
AssocSplatNode.new(load_optional_node, load_location, start_offset, length)
|
||||
when 9 then
|
||||
BackReferenceReadNode.new(start_offset, length)
|
||||
when 10 then
|
||||
BeginNode.new(load_optional_location, load_optional_node, load_optional_node, load_optional_node, load_optional_node, load_optional_location, start_offset, length)
|
||||
when 11 then
|
||||
BlockArgumentNode.new(load_optional_node, load_location, start_offset, length)
|
||||
when 12 then
|
||||
BlockNode.new(Array.new(load_varint) { load_constant }, load_optional_node, load_optional_node, load_location, load_location, start_offset, length)
|
||||
when 13 then
|
||||
BlockParameterNode.new(load_optional_location, load_location, start_offset, length)
|
||||
when 14 then
|
||||
BlockParametersNode.new(load_optional_node, Array.new(load_varint) { load_location }, load_optional_location, load_optional_location, start_offset, length)
|
||||
when 15 then
|
||||
BreakNode.new(load_optional_node, load_location, start_offset, length)
|
||||
when 16 then
|
||||
CallNode.new(load_optional_node, load_optional_location, load_optional_location, load_optional_location, load_optional_node, load_optional_location, load_optional_node, load_varint, load_string, start_offset, length)
|
||||
when 17 then
|
||||
CallOperatorAndWriteNode.new(load_node, load_location, load_node, start_offset, length)
|
||||
when 18 then
|
||||
CallOperatorOrWriteNode.new(load_node, load_node, load_location, start_offset, length)
|
||||
when 19 then
|
||||
CallOperatorWriteNode.new(load_node, load_location, load_node, load_constant, start_offset, length)
|
||||
when 20 then
|
||||
CapturePatternNode.new(load_node, load_node, load_location, start_offset, length)
|
||||
when 21 then
|
||||
CaseNode.new(load_optional_node, Array.new(load_varint) { load_node }, load_optional_node, load_location, load_location, start_offset, length)
|
||||
when 22 then
|
||||
ClassNode.new(Array.new(load_varint) { load_constant }, load_location, load_node, load_optional_location, load_optional_node, load_optional_node, load_location, start_offset, length)
|
||||
when 23 then
|
||||
ClassVariableOperatorAndWriteNode.new(load_location, load_location, load_node, start_offset, length)
|
||||
when 24 then
|
||||
ClassVariableOperatorOrWriteNode.new(load_location, load_location, load_node, start_offset, length)
|
||||
when 25 then
|
||||
ClassVariableOperatorWriteNode.new(load_location, load_location, load_node, load_constant, start_offset, length)
|
||||
when 26 then
|
||||
ClassVariableReadNode.new(start_offset, length)
|
||||
when 27 then
|
||||
ClassVariableWriteNode.new(load_location, load_optional_node, load_optional_location, start_offset, length)
|
||||
when 28 then
|
||||
ConstantOperatorAndWriteNode.new(load_location, load_location, load_node, start_offset, length)
|
||||
when 29 then
|
||||
ConstantOperatorOrWriteNode.new(load_location, load_location, load_node, start_offset, length)
|
||||
when 30 then
|
||||
ConstantOperatorWriteNode.new(load_location, load_location, load_node, load_constant, start_offset, length)
|
||||
when 31 then
|
||||
ConstantPathNode.new(load_optional_node, load_node, load_location, start_offset, length)
|
||||
when 32 then
|
||||
ConstantPathOperatorAndWriteNode.new(load_node, load_location, load_node, start_offset, length)
|
||||
when 33 then
|
||||
ConstantPathOperatorOrWriteNode.new(load_node, load_location, load_node, start_offset, length)
|
||||
when 34 then
|
||||
ConstantPathOperatorWriteNode.new(load_node, load_location, load_node, load_constant, start_offset, length)
|
||||
when 35 then
|
||||
ConstantPathWriteNode.new(load_node, load_optional_location, load_optional_node, start_offset, length)
|
||||
when 36 then
|
||||
ConstantReadNode.new(start_offset, length)
|
||||
when 37 then
|
||||
load_serialized_length
|
||||
DefNode.new(load_location, load_optional_node, load_optional_node, load_optional_node, Array.new(load_varint) { load_constant }, load_location, load_optional_location, load_optional_location, load_optional_location, load_optional_location, load_optional_location, start_offset, length)
|
||||
when 38 then
|
||||
DefinedNode.new(load_optional_location, load_node, load_optional_location, load_location, start_offset, length)
|
||||
when 39 then
|
||||
ElseNode.new(load_location, load_optional_node, load_optional_location, start_offset, length)
|
||||
when 40 then
|
||||
EmbeddedStatementsNode.new(load_location, load_optional_node, load_location, start_offset, length)
|
||||
when 41 then
|
||||
EmbeddedVariableNode.new(load_location, load_node, start_offset, length)
|
||||
when 42 then
|
||||
EnsureNode.new(load_location, load_optional_node, load_location, start_offset, length)
|
||||
when 43 then
|
||||
FalseNode.new(start_offset, length)
|
||||
when 44 then
|
||||
FindPatternNode.new(load_optional_node, load_node, Array.new(load_varint) { load_node }, load_node, load_optional_location, load_optional_location, start_offset, length)
|
||||
when 45 then
|
||||
FloatNode.new(start_offset, length)
|
||||
when 46 then
|
||||
ForNode.new(load_node, load_node, load_optional_node, load_location, load_location, load_optional_location, load_location, start_offset, length)
|
||||
when 47 then
|
||||
ForwardingArgumentsNode.new(start_offset, length)
|
||||
when 48 then
|
||||
ForwardingParameterNode.new(start_offset, length)
|
||||
when 49 then
|
||||
ForwardingSuperNode.new(load_optional_node, start_offset, length)
|
||||
when 50 then
|
||||
GlobalVariableOperatorAndWriteNode.new(load_location, load_location, load_node, start_offset, length)
|
||||
when 51 then
|
||||
GlobalVariableOperatorOrWriteNode.new(load_location, load_location, load_node, start_offset, length)
|
||||
when 52 then
|
||||
GlobalVariableOperatorWriteNode.new(load_location, load_location, load_node, load_constant, start_offset, length)
|
||||
when 53 then
|
||||
GlobalVariableReadNode.new(start_offset, length)
|
||||
when 54 then
|
||||
GlobalVariableWriteNode.new(load_location, load_optional_location, load_optional_node, start_offset, length)
|
||||
when 55 then
|
||||
HashNode.new(load_location, Array.new(load_varint) { load_node }, load_location, start_offset, length)
|
||||
when 56 then
|
||||
HashPatternNode.new(load_optional_node, Array.new(load_varint) { load_node }, load_optional_node, load_optional_location, load_optional_location, start_offset, length)
|
||||
when 57 then
|
||||
IfNode.new(load_optional_location, load_node, load_optional_node, load_optional_node, load_optional_location, start_offset, length)
|
||||
when 58 then
|
||||
ImaginaryNode.new(load_node, start_offset, length)
|
||||
when 59 then
|
||||
InNode.new(load_node, load_optional_node, load_location, load_optional_location, start_offset, length)
|
||||
when 60 then
|
||||
InstanceVariableOperatorAndWriteNode.new(load_location, load_location, load_node, start_offset, length)
|
||||
when 61 then
|
||||
InstanceVariableOperatorOrWriteNode.new(load_location, load_location, load_node, start_offset, length)
|
||||
when 62 then
|
||||
InstanceVariableOperatorWriteNode.new(load_location, load_location, load_node, load_constant, start_offset, length)
|
||||
when 63 then
|
||||
InstanceVariableReadNode.new(start_offset, length)
|
||||
when 64 then
|
||||
InstanceVariableWriteNode.new(load_location, load_optional_node, load_optional_location, start_offset, length)
|
||||
when 65 then
|
||||
IntegerNode.new(start_offset, length)
|
||||
when 66 then
|
||||
InterpolatedRegularExpressionNode.new(load_location, Array.new(load_varint) { load_node }, load_location, load_varint, start_offset, length)
|
||||
when 67 then
|
||||
InterpolatedStringNode.new(load_optional_location, Array.new(load_varint) { load_node }, load_optional_location, start_offset, length)
|
||||
when 68 then
|
||||
InterpolatedSymbolNode.new(load_optional_location, Array.new(load_varint) { load_node }, load_optional_location, start_offset, length)
|
||||
when 69 then
|
||||
InterpolatedXStringNode.new(load_location, Array.new(load_varint) { load_node }, load_location, start_offset, length)
|
||||
when 70 then
|
||||
KeywordHashNode.new(Array.new(load_varint) { load_node }, start_offset, length)
|
||||
when 71 then
|
||||
KeywordParameterNode.new(load_location, load_optional_node, start_offset, length)
|
||||
when 72 then
|
||||
KeywordRestParameterNode.new(load_location, load_optional_location, start_offset, length)
|
||||
when 73 then
|
||||
LambdaNode.new(Array.new(load_varint) { load_constant }, load_location, load_optional_node, load_optional_node, start_offset, length)
|
||||
when 74 then
|
||||
LocalVariableOperatorAndWriteNode.new(load_location, load_location, load_node, load_constant, start_offset, length)
|
||||
when 75 then
|
||||
LocalVariableOperatorOrWriteNode.new(load_location, load_location, load_node, load_constant, start_offset, length)
|
||||
when 76 then
|
||||
LocalVariableOperatorWriteNode.new(load_location, load_location, load_node, load_constant, load_constant, start_offset, length)
|
||||
when 77 then
|
||||
LocalVariableReadNode.new(load_constant, load_varint, start_offset, length)
|
||||
when 78 then
|
||||
LocalVariableWriteNode.new(load_constant, load_varint, load_optional_node, load_location, load_optional_location, start_offset, length)
|
||||
when 79 then
|
||||
MatchPredicateNode.new(load_node, load_node, load_location, start_offset, length)
|
||||
when 80 then
|
||||
MatchRequiredNode.new(load_node, load_node, load_location, start_offset, length)
|
||||
when 81 then
|
||||
MissingNode.new(start_offset, length)
|
||||
when 82 then
|
||||
ModuleNode.new(Array.new(load_varint) { load_constant }, load_location, load_node, load_optional_node, load_location, start_offset, length)
|
||||
when 83 then
|
||||
MultiWriteNode.new(Array.new(load_varint) { load_node }, load_optional_location, load_optional_node, load_optional_location, load_optional_location, start_offset, length)
|
||||
when 84 then
|
||||
NextNode.new(load_optional_node, load_location, start_offset, length)
|
||||
when 85 then
|
||||
NilNode.new(start_offset, length)
|
||||
when 86 then
|
||||
NoKeywordsParameterNode.new(load_location, load_location, start_offset, length)
|
||||
when 87 then
|
||||
NumberedReferenceReadNode.new(start_offset, length)
|
||||
when 88 then
|
||||
OptionalParameterNode.new(load_constant, load_location, load_location, load_node, start_offset, length)
|
||||
when 89 then
|
||||
OrNode.new(load_node, load_node, load_location, start_offset, length)
|
||||
when 90 then
|
||||
ParametersNode.new(Array.new(load_varint) { load_node }, Array.new(load_varint) { load_node }, Array.new(load_varint) { load_node }, load_optional_node, Array.new(load_varint) { load_node }, load_optional_node, load_optional_node, start_offset, length)
|
||||
when 91 then
|
||||
ParenthesesNode.new(load_optional_node, load_location, load_location, start_offset, length)
|
||||
when 92 then
|
||||
PinnedExpressionNode.new(load_node, load_location, load_location, load_location, start_offset, length)
|
||||
when 93 then
|
||||
PinnedVariableNode.new(load_node, load_location, start_offset, length)
|
||||
when 94 then
|
||||
PostExecutionNode.new(load_optional_node, load_location, load_location, load_location, start_offset, length)
|
||||
when 95 then
|
||||
PreExecutionNode.new(load_optional_node, load_location, load_location, load_location, start_offset, length)
|
||||
when 96 then
|
||||
ProgramNode.new(Array.new(load_varint) { load_constant }, load_node, start_offset, length)
|
||||
when 97 then
|
||||
RangeNode.new(load_optional_node, load_optional_node, load_location, load_varint, start_offset, length)
|
||||
when 98 then
|
||||
RationalNode.new(load_node, start_offset, length)
|
||||
when 99 then
|
||||
RedoNode.new(start_offset, length)
|
||||
when 100 then
|
||||
RegularExpressionNode.new(load_location, load_location, load_location, load_string, load_varint, start_offset, length)
|
||||
when 101 then
|
||||
RequiredDestructuredParameterNode.new(Array.new(load_varint) { load_node }, load_location, load_location, start_offset, length)
|
||||
when 102 then
|
||||
RequiredParameterNode.new(load_constant, start_offset, length)
|
||||
when 103 then
|
||||
RescueModifierNode.new(load_node, load_location, load_node, start_offset, length)
|
||||
when 104 then
|
||||
RescueNode.new(load_location, Array.new(load_varint) { load_node }, load_optional_location, load_optional_node, load_optional_node, load_optional_node, start_offset, length)
|
||||
when 105 then
|
||||
RestParameterNode.new(load_location, load_optional_location, start_offset, length)
|
||||
when 106 then
|
||||
RetryNode.new(start_offset, length)
|
||||
when 107 then
|
||||
ReturnNode.new(load_location, load_optional_node, start_offset, length)
|
||||
when 108 then
|
||||
SelfNode.new(start_offset, length)
|
||||
when 109 then
|
||||
SingletonClassNode.new(Array.new(load_varint) { load_constant }, load_location, load_location, load_node, load_optional_node, load_location, start_offset, length)
|
||||
when 110 then
|
||||
SourceEncodingNode.new(start_offset, length)
|
||||
when 111 then
|
||||
SourceFileNode.new(load_string, start_offset, length)
|
||||
when 112 then
|
||||
SourceLineNode.new(start_offset, length)
|
||||
when 113 then
|
||||
SplatNode.new(load_location, load_optional_node, start_offset, length)
|
||||
when 114 then
|
||||
StatementsNode.new(Array.new(load_varint) { load_node }, start_offset, length)
|
||||
when 115 then
|
||||
StringConcatNode.new(load_node, load_node, start_offset, length)
|
||||
when 116 then
|
||||
StringNode.new(load_optional_location, load_location, load_optional_location, load_string, start_offset, length)
|
||||
when 117 then
|
||||
SuperNode.new(load_location, load_optional_location, load_optional_node, load_optional_location, load_optional_node, start_offset, length)
|
||||
when 118 then
|
||||
SymbolNode.new(load_optional_location, load_location, load_optional_location, load_string, start_offset, length)
|
||||
when 119 then
|
||||
TrueNode.new(start_offset, length)
|
||||
when 120 then
|
||||
UndefNode.new(Array.new(load_varint) { load_node }, load_location, start_offset, length)
|
||||
when 121 then
|
||||
UnlessNode.new(load_location, load_node, load_optional_node, load_optional_node, load_optional_location, start_offset, length)
|
||||
when 122 then
|
||||
UntilNode.new(load_location, load_node, load_optional_node, start_offset, length)
|
||||
when 123 then
|
||||
WhenNode.new(load_location, Array.new(load_varint) { load_node }, load_optional_node, start_offset, length)
|
||||
when 124 then
|
||||
WhileNode.new(load_location, load_node, load_optional_node, start_offset, length)
|
||||
when 125 then
|
||||
XStringNode.new(load_location, load_location, load_location, load_string, start_offset, length)
|
||||
when 126 then
|
||||
YieldNode.new(load_location, load_optional_location, load_optional_node, load_optional_location, start_offset, length)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
47
test/yarp/comments_test.rb
Normal file
47
test/yarp/comments_test.rb
Normal file
@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "yarp_test_helper"
|
||||
|
||||
class CommentsTest < Test::Unit::TestCase
|
||||
include ::YARP::DSL
|
||||
|
||||
def test_comment_inline
|
||||
assert_comment "# comment", :inline
|
||||
end
|
||||
|
||||
def test_comment___END__
|
||||
source = <<~RUBY
|
||||
__END__
|
||||
comment
|
||||
RUBY
|
||||
|
||||
assert_comment source, :__END__
|
||||
end
|
||||
|
||||
def test_comment_embedded_document
|
||||
source = <<~RUBY
|
||||
=begin
|
||||
comment
|
||||
=end
|
||||
RUBY
|
||||
|
||||
assert_comment source, :embdoc
|
||||
end
|
||||
|
||||
def test_comment_embedded_document_with_content_on_same_line
|
||||
source = <<~RUBY
|
||||
=begin other stuff
|
||||
=end
|
||||
RUBY
|
||||
|
||||
assert_comment source, :embdoc
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_comment(source, type)
|
||||
result = YARP.parse(source)
|
||||
assert result.errors.empty?, result.errors.map(&:message).join("\n")
|
||||
result => YARP::ParseResult[comments: [YARP::Comment[type: type]]]
|
||||
end
|
||||
end
|
212
test/yarp/compile_test.rb
Normal file
212
test/yarp/compile_test.rb
Normal file
@ -0,0 +1,212 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "yarp_test_helper"
|
||||
|
||||
class CompileTest < Test::Unit::TestCase
|
||||
def test_AliasNode
|
||||
assert_compiles("alias foo bar")
|
||||
end
|
||||
|
||||
def test_AndNode
|
||||
assert_compiles("true && false")
|
||||
end
|
||||
|
||||
def test_ArrayNode
|
||||
assert_compiles("[]")
|
||||
assert_compiles("[foo, bar, baz]")
|
||||
end
|
||||
|
||||
def test_AssocNode
|
||||
assert_compiles("{ foo: bar }")
|
||||
end
|
||||
|
||||
def test_BlockNode
|
||||
assert_compiles("foo { bar }")
|
||||
end
|
||||
|
||||
def test_BlockNode_with_optionals
|
||||
assert_compiles("foo { |x = 1| bar }")
|
||||
end
|
||||
|
||||
def test_CallNode
|
||||
assert_compiles("foo")
|
||||
assert_compiles("foo(bar)")
|
||||
end
|
||||
|
||||
def test_ClassVariableReadNode
|
||||
assert_compiles("@@foo")
|
||||
end
|
||||
|
||||
def test_ClassVariableWriteNode
|
||||
assert_compiles("@@foo = 1")
|
||||
end
|
||||
|
||||
def test_FalseNode
|
||||
assert_compiles("false")
|
||||
end
|
||||
|
||||
def test_GlobalVariableReadNode
|
||||
assert_compiles("$foo")
|
||||
end
|
||||
|
||||
def test_GlobalVariableWriteNode
|
||||
assert_compiles("$foo = 1")
|
||||
end
|
||||
|
||||
def test_HashNode
|
||||
assert_compiles("{ foo: bar }")
|
||||
end
|
||||
|
||||
def test_InstanceVariableReadNode
|
||||
assert_compiles("@foo")
|
||||
end
|
||||
|
||||
def test_InstanceVariableWriteNode
|
||||
assert_compiles("@foo = 1")
|
||||
end
|
||||
|
||||
def test_IntegerNode
|
||||
assert_compiles("1")
|
||||
assert_compiles("1_000")
|
||||
end
|
||||
|
||||
def test_InterpolatedStringNode
|
||||
assert_compiles("\"foo \#{bar} baz\"")
|
||||
end
|
||||
|
||||
def test_LocalVariableWriteNode
|
||||
assert_compiles("foo = 1")
|
||||
end
|
||||
|
||||
def test_LocalVariableReadNode
|
||||
assert_compiles("[foo = 1, foo]")
|
||||
end
|
||||
|
||||
def test_NilNode
|
||||
assert_compiles("nil")
|
||||
end
|
||||
|
||||
def test_OrNode
|
||||
assert_compiles("true || false")
|
||||
end
|
||||
|
||||
def test_ParenthesesNode
|
||||
assert_compiles("()")
|
||||
end
|
||||
|
||||
def test_ProgramNode
|
||||
assert_compiles("")
|
||||
end
|
||||
|
||||
def test_RangeNode
|
||||
assert_compiles("foo..bar")
|
||||
assert_compiles("foo...bar")
|
||||
assert_compiles("(foo..)")
|
||||
assert_compiles("(foo...)")
|
||||
assert_compiles("(..bar)")
|
||||
assert_compiles("(...bar)")
|
||||
end
|
||||
|
||||
def test_SelfNode
|
||||
assert_compiles("self")
|
||||
end
|
||||
|
||||
def test_StringNode
|
||||
assert_compiles("\"foo\"")
|
||||
end
|
||||
|
||||
def test_SymbolNode
|
||||
assert_compiles(":foo")
|
||||
end
|
||||
|
||||
def test_TrueNode
|
||||
assert_compiles("true")
|
||||
end
|
||||
|
||||
def test_UndefNode
|
||||
assert_compiles("undef :foo, :bar, :baz")
|
||||
end
|
||||
|
||||
def test_XStringNode
|
||||
assert_compiles("`foo`")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_compiles(source)
|
||||
assert_equal_iseqs(rubyvm_compile(source), YARP.compile(source))
|
||||
end
|
||||
|
||||
# Instruction sequences have 13 elements in their lists. We don't currently
|
||||
# support all of the fields, so we can't compare the iseqs directly. Instead,
|
||||
# we compare the elements that we do support.
|
||||
def assert_equal_iseqs(expected, actual)
|
||||
# The first element is the magic comment string.
|
||||
assert_equal expected[0], actual[0]
|
||||
|
||||
# The next three elements are the major, minor, and patch version numbers.
|
||||
# TODO: Insert this check once Ruby 3.3 is released, and the TruffleRuby
|
||||
# GitHub workflow also checks against Ruby 3.3
|
||||
# assert_equal expected[1...4], actual[1...4]
|
||||
|
||||
# The next element is a set of options for the iseq. It has lots of
|
||||
# different information, some of which we support and some of which we
|
||||
# don't.
|
||||
assert_equal expected[4][:arg_size], actual[4][:arg_size], "Unexpected difference in arg_size"
|
||||
assert_equal expected[4][:stack_max], actual[4][:stack_max], "Unexpected difference in stack_max"
|
||||
|
||||
assert_kind_of Integer, actual[4][:local_size]
|
||||
assert_kind_of Integer, actual[4][:node_id]
|
||||
|
||||
assert_equal expected[4][:code_location].length, actual[4][:code_location].length, "Unexpected difference in code_location length"
|
||||
assert_equal expected[4][:node_ids].length, actual[4][:node_ids].length, "Unexpected difference in node_ids length"
|
||||
|
||||
# Then we have the name of the iseq, the relative file path, the absolute
|
||||
# file path, and the line number. We don't have this working quite yet.
|
||||
assert_kind_of String, actual[5]
|
||||
assert_kind_of String, actual[6]
|
||||
assert_kind_of String, actual[7]
|
||||
assert_kind_of Integer, actual[8]
|
||||
|
||||
# Next we have the type of the iseq.
|
||||
assert_equal expected[9], actual[9]
|
||||
|
||||
# Next we have the list of local variables. We don't support this yet.
|
||||
assert_kind_of Array, actual[10]
|
||||
|
||||
# Next we have the argument options. These are used in block and method
|
||||
# iseqs to reflect how the arguments are passed.
|
||||
assert_equal expected[11], actual[11], "Unexpected difference in argument options"
|
||||
|
||||
# Next we have the catch table entries. We don't have this working yet.
|
||||
assert_kind_of Array, actual[12]
|
||||
|
||||
# Finally we have the actual instructions. We support some of this, but omit
|
||||
# line numbers and some tracepoint events.
|
||||
expected[13].each do |insn|
|
||||
case insn
|
||||
in [:send, opnds, expected_block] unless expected_block.nil?
|
||||
actual[13].shift => [:send, ^(opnds), actual_block]
|
||||
assert_equal_iseqs expected_block, actual_block
|
||||
in Array | :RUBY_EVENT_B_CALL | :RUBY_EVENT_B_RETURN | /^label_\d+/
|
||||
assert_equal insn, actual[13].shift
|
||||
in Integer | /^RUBY_EVENT_/
|
||||
# skip these for now
|
||||
else
|
||||
flunk "Unexpected instruction: #{insn.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def rubyvm_compile(source)
|
||||
options = {
|
||||
peephole_optimization: false,
|
||||
specialized_instruction: false,
|
||||
operands_unification: false,
|
||||
instructions_unification: false,
|
||||
frozen_string_literal: false
|
||||
}
|
||||
|
||||
RubyVM::InstructionSequence.compile(source, **options).to_a
|
||||
end
|
||||
end
|
70
test/yarp/encoding_test.rb
Normal file
70
test/yarp/encoding_test.rb
Normal file
@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "yarp_test_helper"
|
||||
|
||||
class EncodingTest < Test::Unit::TestCase
|
||||
%w[
|
||||
ascii
|
||||
ascii-8bit
|
||||
big5
|
||||
binary
|
||||
euc-jp
|
||||
gbk
|
||||
iso-8859-1
|
||||
iso-8859-2
|
||||
iso-8859-3
|
||||
iso-8859-4
|
||||
iso-8859-5
|
||||
iso-8859-6
|
||||
iso-8859-7
|
||||
iso-8859-8
|
||||
iso-8859-9
|
||||
iso-8859-10
|
||||
iso-8859-11
|
||||
iso-8859-13
|
||||
iso-8859-14
|
||||
iso-8859-15
|
||||
iso-8859-16
|
||||
koi8-r
|
||||
shift_jis
|
||||
sjis
|
||||
us-ascii
|
||||
utf-8
|
||||
windows-31j
|
||||
windows-1251
|
||||
windows-1252
|
||||
CP1251
|
||||
CP1252
|
||||
].each do |encoding|
|
||||
define_method "test_encoding_#{encoding}" do
|
||||
result = YARP.parse("# encoding: #{encoding}\nident")
|
||||
actual = result.value.statements.body.first.name.encoding
|
||||
assert_equal Encoding.find(encoding), actual
|
||||
end
|
||||
end
|
||||
|
||||
def test_coding
|
||||
result = YARP.parse("# coding: utf-8\nident")
|
||||
actual = result.value.statements.body.first.name.encoding
|
||||
assert_equal Encoding.find("utf-8"), actual
|
||||
end
|
||||
|
||||
def test_emacs_style
|
||||
result = YARP.parse("# -*- coding: utf-8 -*-\nident")
|
||||
actual = result.value.statements.body.first.name.encoding
|
||||
assert_equal Encoding.find("utf-8"), actual
|
||||
end
|
||||
|
||||
def test_utf_8_variations
|
||||
%w[
|
||||
utf-8-unix
|
||||
utf-8-dos
|
||||
utf-8-mac
|
||||
utf-8-*
|
||||
].each do |encoding|
|
||||
result = YARP.parse("# coding: #{encoding}\nident")
|
||||
actual = result.value.statements.body.first.name.encoding
|
||||
assert_equal Encoding.find("utf-8"), actual
|
||||
end
|
||||
end
|
||||
end
|
984
test/yarp/errors_test.rb
Normal file
984
test/yarp/errors_test.rb
Normal file
@ -0,0 +1,984 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "yarp_test_helper"
|
||||
|
||||
class ErrorsTest < Test::Unit::TestCase
|
||||
include ::YARP::DSL
|
||||
|
||||
def test_constant_path_with_invalid_token_after
|
||||
assert_error_messages "A::$b", [
|
||||
"Expected identifier or constant after '::'",
|
||||
"Expected a newline or semicolon after statement."
|
||||
]
|
||||
end
|
||||
|
||||
def test_module_name_recoverable
|
||||
expected = ModuleNode(
|
||||
[],
|
||||
Location(),
|
||||
ConstantReadNode(),
|
||||
StatementsNode(
|
||||
[ModuleNode([], Location(), MissingNode(), nil, Location())]
|
||||
),
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "module Parent module end", [
|
||||
"Expected to find a module name after `module`."
|
||||
]
|
||||
end
|
||||
|
||||
def test_for_loops_index_missing
|
||||
expected = ForNode(
|
||||
MissingNode(),
|
||||
expression("1..10"),
|
||||
StatementsNode([expression("i")]),
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "for in 1..10\ni\nend", ["Expected index after for."]
|
||||
end
|
||||
|
||||
def test_for_loops_only_end
|
||||
expected = ForNode(
|
||||
MissingNode(),
|
||||
MissingNode(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "for end", ["Expected index after for.", "Expected keyword in.", "Expected collection."]
|
||||
end
|
||||
|
||||
def test_pre_execution_missing_brace
|
||||
expected = PreExecutionNode(
|
||||
StatementsNode([expression("1")]),
|
||||
Location(),
|
||||
Location(),
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "BEGIN 1 }", ["Expected '{' after 'BEGIN'."]
|
||||
end
|
||||
|
||||
def test_pre_execution_context
|
||||
expected = PreExecutionNode(
|
||||
StatementsNode([
|
||||
CallNode(
|
||||
expression("1"),
|
||||
nil,
|
||||
Location(),
|
||||
nil,
|
||||
ArgumentsNode([MissingNode()]),
|
||||
nil,
|
||||
nil,
|
||||
0,
|
||||
"+"
|
||||
)
|
||||
]),
|
||||
Location(),
|
||||
Location(),
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "BEGIN { 1 + }", ["Expected a value after the operator."]
|
||||
end
|
||||
|
||||
def test_unterminated_embdoc
|
||||
assert_errors expression("1"), "1\n=begin\n", ["Unterminated embdoc"]
|
||||
end
|
||||
|
||||
def test_unterminated_i_list
|
||||
assert_errors expression("%i["), "%i[", ["Expected a closing delimiter for a `%i` list."]
|
||||
end
|
||||
|
||||
def test_unterminated_w_list
|
||||
assert_errors expression("%w["), "%w[", ["Expected a closing delimiter for a `%w` list."]
|
||||
end
|
||||
|
||||
def test_unterminated_W_list
|
||||
assert_errors expression("%W["), "%W[", ["Expected a closing delimiter for a `%W` list."]
|
||||
end
|
||||
|
||||
def test_unterminated_regular_expression
|
||||
assert_errors expression("/hello"), "/hello", ["Expected a closing delimiter for a regular expression."]
|
||||
end
|
||||
|
||||
def test_unterminated_xstring
|
||||
assert_errors expression("`hello"), "`hello", ["Expected a closing delimiter for an xstring."]
|
||||
end
|
||||
|
||||
def test_unterminated_string
|
||||
assert_errors expression('"hello'), '"hello', ["Expected a closing delimiter for an interpolated string."]
|
||||
end
|
||||
|
||||
def test_unterminated_s_symbol
|
||||
assert_errors expression("%s[abc"), "%s[abc", ["Expected a closing delimiter for a dynamic symbol."]
|
||||
end
|
||||
|
||||
def test_unterminated_parenthesized_expression
|
||||
assert_errors expression('(1 + 2'), '(1 + 2', ["Expected to be able to parse an expression.", "Expected a closing parenthesis."]
|
||||
end
|
||||
|
||||
def test_1_2_3
|
||||
assert_errors expression("(1, 2, 3)"), "(1, 2, 3)", [
|
||||
"Expected to be able to parse an expression.",
|
||||
"Expected a closing parenthesis.",
|
||||
"Expected a newline or semicolon after statement.",
|
||||
"Expected to be able to parse an expression.",
|
||||
"Expected a newline or semicolon after statement.",
|
||||
"Expected to be able to parse an expression.",
|
||||
"Expected a newline or semicolon after statement.",
|
||||
"Expected to be able to parse an expression."
|
||||
]
|
||||
end
|
||||
|
||||
def test_return_1_2_3
|
||||
assert_error_messages "return(1, 2, 3)", [
|
||||
"Expected to be able to parse an expression.",
|
||||
"Expected a closing parenthesis.",
|
||||
"Expected a newline or semicolon after statement.",
|
||||
"Expected to be able to parse an expression."
|
||||
]
|
||||
end
|
||||
|
||||
def test_return_1
|
||||
assert_errors expression("return 1,;"), "return 1,;", ["Expected to be able to parse an argument."]
|
||||
end
|
||||
|
||||
def test_next_1_2_3
|
||||
assert_errors expression("next(1, 2, 3)"), "next(1, 2, 3)", [
|
||||
"Expected to be able to parse an expression.",
|
||||
"Expected a closing parenthesis.",
|
||||
"Expected a newline or semicolon after statement.",
|
||||
"Expected to be able to parse an expression."
|
||||
]
|
||||
end
|
||||
|
||||
def test_next_1
|
||||
assert_errors expression("next 1,;"), "next 1,;", ["Expected to be able to parse an argument."]
|
||||
end
|
||||
|
||||
def test_break_1_2_3
|
||||
errors = [
|
||||
"Expected to be able to parse an expression.",
|
||||
"Expected a closing parenthesis.",
|
||||
"Expected a newline or semicolon after statement.",
|
||||
"Expected to be able to parse an expression."
|
||||
]
|
||||
|
||||
assert_errors expression("break(1, 2, 3)"), "break(1, 2, 3)", errors
|
||||
end
|
||||
|
||||
def test_break_1
|
||||
assert_errors expression("break 1,;"), "break 1,;", ["Expected to be able to parse an argument."]
|
||||
end
|
||||
|
||||
def test_argument_forwarding_when_parent_is_not_forwarding
|
||||
assert_errors expression('def a(x, y, z); b(...); end'), 'def a(x, y, z); b(...); end', ["unexpected ... when parent method is not forwarding."]
|
||||
end
|
||||
|
||||
def test_argument_forwarding_only_effects_its_own_internals
|
||||
assert_errors expression('def a(...); b(...); end; def c(x, y, z); b(...); end'), 'def a(...); b(...); end; def c(x, y, z); b(...); end', ["unexpected ... when parent method is not forwarding."]
|
||||
end
|
||||
|
||||
def test_top_level_constant_with_downcased_identifier
|
||||
assert_error_messages "::foo", [
|
||||
"Expected a constant after ::.",
|
||||
"Expected a newline or semicolon after statement."
|
||||
]
|
||||
end
|
||||
|
||||
def test_top_level_constant_starting_with_downcased_identifier
|
||||
assert_error_messages "::foo::A", [
|
||||
"Expected a constant after ::.",
|
||||
"Expected a newline or semicolon after statement."
|
||||
]
|
||||
end
|
||||
|
||||
def test_aliasing_global_variable_with_non_global_variable
|
||||
assert_errors expression("alias $a b"), "alias $a b", ["Expected a global variable."]
|
||||
end
|
||||
|
||||
def test_aliasing_non_global_variable_with_global_variable
|
||||
assert_errors expression("alias a $b"), "alias a $b", ["Expected a bare word or symbol argument."]
|
||||
end
|
||||
|
||||
def test_aliasing_global_variable_with_global_number_variable
|
||||
assert_errors expression("alias $a $1"), "alias $a $1", ["Can't make alias for number variables."]
|
||||
end
|
||||
|
||||
def test_def_with_expression_receiver_and_no_identifier
|
||||
assert_errors expression("def (a); end"), "def (a); end", [
|
||||
"Expected '.' or '::' after receiver"
|
||||
]
|
||||
end
|
||||
|
||||
def test_def_with_multiple_statements_receiver
|
||||
assert_errors expression("def (\na\nb\n).c; end"), "def (\na\nb\n).c; end", [
|
||||
"Expected closing ')' for receiver.",
|
||||
"Expected '.' or '::' after receiver",
|
||||
"Expected to be able to parse an expression.",
|
||||
"Expected to be able to parse an expression."
|
||||
]
|
||||
end
|
||||
|
||||
def test_def_with_empty_expression_receiver
|
||||
assert_errors expression("def ().a; end"), "def ().a; end", ["Expected to be able to parse receiver."]
|
||||
end
|
||||
|
||||
def test_block_beginning_with_brace_and_ending_with_end
|
||||
assert_error_messages "x.each { x end", [
|
||||
"Expected a newline or semicolon after statement.",
|
||||
"Expected to be able to parse an expression.",
|
||||
"Expected to be able to parse an expression.",
|
||||
"Expected block beginning with '{' to end with '}'."
|
||||
]
|
||||
end
|
||||
|
||||
def test_double_splat_followed_by_splat_argument
|
||||
expected = CallNode(
|
||||
nil,
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
ArgumentsNode(
|
||||
[KeywordHashNode(
|
||||
[AssocSplatNode(
|
||||
CallNode(
|
||||
nil,
|
||||
nil,
|
||||
Location(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
0,
|
||||
"kwargs"
|
||||
),
|
||||
Location()
|
||||
)]
|
||||
),
|
||||
SplatNode(
|
||||
Location(),
|
||||
CallNode(nil, nil, Location(), nil, nil, nil, nil, 0, "args")
|
||||
)]
|
||||
),
|
||||
Location(),
|
||||
nil,
|
||||
0,
|
||||
"a"
|
||||
)
|
||||
|
||||
assert_errors expected, "a(**kwargs, *args)", ["Unexpected splat argument after double splat."]
|
||||
end
|
||||
|
||||
def test_arguments_after_block
|
||||
expected = CallNode(
|
||||
nil,
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
ArgumentsNode([
|
||||
BlockArgumentNode(expression("block"), Location()),
|
||||
expression("foo")
|
||||
]),
|
||||
Location(),
|
||||
nil,
|
||||
0,
|
||||
"a"
|
||||
)
|
||||
|
||||
assert_errors expected, "a(&block, foo)", ["Unexpected argument after block argument."]
|
||||
end
|
||||
|
||||
def test_arguments_binding_power_for_and
|
||||
assert_error_messages "foo(*bar and baz)", [
|
||||
"Expected a ')' to close the argument list.",
|
||||
"Expected a newline or semicolon after statement.",
|
||||
"Expected to be able to parse an expression."
|
||||
]
|
||||
end
|
||||
|
||||
def test_splat_argument_after_keyword_argument
|
||||
expected = CallNode(
|
||||
nil,
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
ArgumentsNode(
|
||||
[KeywordHashNode(
|
||||
[AssocNode(
|
||||
SymbolNode(nil, Location(), Location(), "foo"),
|
||||
CallNode(nil, nil, Location(), nil, nil, nil, nil, 0, "bar"),
|
||||
nil
|
||||
)]
|
||||
),
|
||||
SplatNode(
|
||||
Location(),
|
||||
CallNode(nil, nil, Location(), nil, nil, nil, nil, 0, "args")
|
||||
)]
|
||||
),
|
||||
Location(),
|
||||
nil,
|
||||
0,
|
||||
"a"
|
||||
)
|
||||
|
||||
assert_errors expected, "a(foo: bar, *args)", ["Unexpected splat argument after double splat."]
|
||||
end
|
||||
|
||||
def test_module_definition_in_method_body
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
nil,
|
||||
StatementsNode([ModuleNode([], Location(), ConstantReadNode(), nil, Location())]),
|
||||
[],
|
||||
Location(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo;module A;end;end", ["Module definition in method body"]
|
||||
end
|
||||
|
||||
def test_module_definition_in_method_body_within_block
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
nil,
|
||||
StatementsNode(
|
||||
[CallNode(
|
||||
nil,
|
||||
nil,
|
||||
Location(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
BlockNode(
|
||||
[],
|
||||
nil,
|
||||
StatementsNode([ModuleNode([], Location(), ConstantReadNode(), nil, Location())]),
|
||||
Location(),
|
||||
Location()
|
||||
),
|
||||
0,
|
||||
"bar"
|
||||
)]
|
||||
),
|
||||
[],
|
||||
Location(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "
|
||||
def foo
|
||||
bar do
|
||||
module Foo;end
|
||||
end
|
||||
end
|
||||
", ["Module definition in method body"]
|
||||
end
|
||||
|
||||
def test_class_definition_in_method_body
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
nil,
|
||||
StatementsNode(
|
||||
[ClassNode(
|
||||
[],
|
||||
Location(),
|
||||
ConstantReadNode(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
Location()
|
||||
)]
|
||||
),
|
||||
[],
|
||||
Location(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo;class A;end;end", ["Class definition in method body"]
|
||||
end
|
||||
|
||||
def test_bad_arguments
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode([], [], [], nil, [], nil, nil),
|
||||
nil,
|
||||
[],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(A, @a, $A, @@a);end", [
|
||||
"Formal argument cannot be a constant",
|
||||
"Formal argument cannot be an instance variable",
|
||||
"Formal argument cannot be a global variable",
|
||||
"Formal argument cannot be a class variable",
|
||||
]
|
||||
end
|
||||
|
||||
def test_cannot_assign_to_a_reserved_numbered_parameter
|
||||
expected = BeginNode(
|
||||
Location(),
|
||||
StatementsNode([
|
||||
LocalVariableWriteNode(:_1, 0, SymbolNode(Location(), Location(), nil, "a"), Location(), Location()),
|
||||
LocalVariableWriteNode(:_2, 0, SymbolNode(Location(), Location(), nil, "a"), Location(), Location()),
|
||||
LocalVariableWriteNode(:_3, 0, SymbolNode(Location(), Location(), nil, "a"), Location(), Location()),
|
||||
LocalVariableWriteNode(:_4, 0, SymbolNode(Location(), Location(), nil, "a"), Location(), Location()),
|
||||
LocalVariableWriteNode(:_5, 0, SymbolNode(Location(), Location(), nil, "a"), Location(), Location()),
|
||||
LocalVariableWriteNode(:_6, 0, SymbolNode(Location(), Location(), nil, "a"), Location(), Location()),
|
||||
LocalVariableWriteNode(:_7, 0, SymbolNode(Location(), Location(), nil, "a"), Location(), Location()),
|
||||
LocalVariableWriteNode(:_8, 0, SymbolNode(Location(), Location(), nil, "a"), Location(), Location()),
|
||||
LocalVariableWriteNode(:_9, 0, SymbolNode(Location(), Location(), nil, "a"), Location(), Location()),
|
||||
LocalVariableWriteNode(:_10, 0, SymbolNode(Location(), Location(), nil, "a"), Location(), Location())
|
||||
]),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, <<~RUBY, Array.new(9, "reserved for numbered parameter")
|
||||
begin
|
||||
_1=:a;_2=:a;_3=:a;_4=:a;_5=:a
|
||||
_6=:a;_7=:a;_8=:a;_9=:a;_10=:a
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
def test_do_not_allow_trailing_commas_in_method_parameters
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode(
|
||||
[RequiredParameterNode(:a), RequiredParameterNode(:b), RequiredParameterNode(:c)],
|
||||
[],
|
||||
[],
|
||||
nil,
|
||||
[],
|
||||
nil,
|
||||
nil
|
||||
),
|
||||
nil,
|
||||
[:a, :b, :c],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(a,b,c,);end", [
|
||||
"Unexpected ','."
|
||||
]
|
||||
end
|
||||
|
||||
def test_do_not_allow_trailing_commas_in_lambda_parameters
|
||||
expected = LambdaNode(
|
||||
[:a, :b],
|
||||
Location(),
|
||||
BlockParametersNode(
|
||||
ParametersNode([RequiredParameterNode(:a), RequiredParameterNode(:b)], [], [], nil, [], nil, nil),
|
||||
[],
|
||||
Location(),
|
||||
Location()
|
||||
),
|
||||
nil
|
||||
)
|
||||
assert_errors expected, "-> (a, b, ) {}", [
|
||||
"Unexpected ','."
|
||||
]
|
||||
end
|
||||
|
||||
def test_do_not_allow_multiple_codepoints_in_a_single_character_literal
|
||||
expected = StringNode(Location(), Location(), nil, "\u0001\u0002")
|
||||
|
||||
assert_errors expected, '?\u{0001 0002}', [
|
||||
"Multiple codepoints at single character literal"
|
||||
]
|
||||
end
|
||||
|
||||
def test_do_not_allow_more_than_6_hexadecimal_digits_in_u_Unicode_character_notation
|
||||
expected = StringNode(Location(), Location(), Location(), "\u0001")
|
||||
|
||||
assert_errors expected, '"\u{0000001}"', [
|
||||
"invalid Unicode escape.",
|
||||
"invalid Unicode escape."
|
||||
]
|
||||
end
|
||||
|
||||
def test_do_not_allow_characters_other_than_0_9_a_f_and_A_F_in_u_Unicode_character_notation
|
||||
expected = StringNode(Location(), Location(), Location(), "\u0000z}")
|
||||
|
||||
assert_errors expected, '"\u{000z}"', [
|
||||
"unterminated Unicode escape",
|
||||
"unterminated Unicode escape"
|
||||
]
|
||||
end
|
||||
|
||||
def test_method_parameters_after_block
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode(
|
||||
[],
|
||||
[],
|
||||
[RequiredParameterNode(:a)],
|
||||
nil,
|
||||
[],
|
||||
nil,
|
||||
BlockParameterNode(Location(), Location())
|
||||
),
|
||||
nil,
|
||||
[:block, :a],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
assert_errors expected, "def foo(&block, a)\nend", ["Unexpected parameter order"]
|
||||
end
|
||||
|
||||
def test_method_with_arguments_after_anonymous_block
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode([], [], [RequiredParameterNode(:a)], nil, [], nil, BlockParameterNode(nil, Location())),
|
||||
nil,
|
||||
[:&, :a],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(&, a)\nend", ["Unexpected parameter order"]
|
||||
end
|
||||
|
||||
def test_method_parameters_after_arguments_forwarding
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode(
|
||||
[],
|
||||
[],
|
||||
[RequiredParameterNode(:a)],
|
||||
nil,
|
||||
[],
|
||||
ForwardingParameterNode(),
|
||||
nil
|
||||
),
|
||||
nil,
|
||||
[:"...", :a],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
assert_errors expected, "def foo(..., a)\nend", ["Unexpected parameter order"]
|
||||
end
|
||||
|
||||
def test_keywords_parameters_before_required_parameters
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode(
|
||||
[],
|
||||
[],
|
||||
[RequiredParameterNode(:a)],
|
||||
nil,
|
||||
[KeywordParameterNode(Location(), nil)],
|
||||
nil,
|
||||
nil
|
||||
),
|
||||
nil,
|
||||
[:b, :a],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
assert_errors expected, "def foo(b:, a)\nend", ["Unexpected parameter order"]
|
||||
end
|
||||
|
||||
def test_rest_keywords_parameters_before_required_parameters
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode(
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
nil,
|
||||
[KeywordParameterNode(Location(), nil)],
|
||||
KeywordRestParameterNode(Location(), Location()),
|
||||
nil
|
||||
),
|
||||
nil,
|
||||
[:rest, :b],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
assert_errors expected, "def foo(**rest, b:)\nend", ["Unexpected parameter order"]
|
||||
end
|
||||
|
||||
def test_double_arguments_forwarding
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode([], [], [], nil, [], ForwardingParameterNode(), nil),
|
||||
nil,
|
||||
[:"..."],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(..., ...)\nend", ["Unexpected parameter order"]
|
||||
end
|
||||
|
||||
def test_multiple_error_in_parameters_order
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode(
|
||||
[],
|
||||
[],
|
||||
[RequiredParameterNode(:a)],
|
||||
nil,
|
||||
[KeywordParameterNode(Location(), nil)],
|
||||
KeywordRestParameterNode(Location(), Location()),
|
||||
nil
|
||||
),
|
||||
nil,
|
||||
[:args, :a, :b],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(**args, a, b:)\nend", ["Unexpected parameter order", "Unexpected parameter order"]
|
||||
end
|
||||
|
||||
def test_switching_to_optional_arguments_twice
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode(
|
||||
[],
|
||||
[],
|
||||
[RequiredParameterNode(:a)],
|
||||
nil,
|
||||
[KeywordParameterNode(Location(), nil)],
|
||||
KeywordRestParameterNode(Location(), Location()),
|
||||
nil
|
||||
),
|
||||
nil,
|
||||
[:args, :a, :b],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(**args, a, b:)\nend", ["Unexpected parameter order", "Unexpected parameter order"]
|
||||
end
|
||||
|
||||
def test_switching_to_named_arguments_twice
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode(
|
||||
[],
|
||||
[],
|
||||
[RequiredParameterNode(:a)],
|
||||
nil,
|
||||
[KeywordParameterNode(Location(), nil)],
|
||||
KeywordRestParameterNode(Location(), Location()),
|
||||
nil
|
||||
),
|
||||
nil,
|
||||
[:args, :a, :b],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(**args, a, b:)\nend", ["Unexpected parameter order", "Unexpected parameter order"]
|
||||
end
|
||||
|
||||
def test_returning_to_optional_parameters_multiple_times
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode(
|
||||
[RequiredParameterNode(:a)],
|
||||
[
|
||||
OptionalParameterNode(:b, Location(), Location(), IntegerNode()),
|
||||
OptionalParameterNode(:d, Location(), Location(), IntegerNode())
|
||||
],
|
||||
[RequiredParameterNode(:c), RequiredParameterNode(:e)],
|
||||
nil,
|
||||
[],
|
||||
nil,
|
||||
nil
|
||||
),
|
||||
nil,
|
||||
[:a, :b, :c, :d, :e],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(a, b = 1, c, d = 2, e)\nend", ["Unexpected parameter order"]
|
||||
end
|
||||
|
||||
def test_case_without_when_clauses_errors_on_else_clause
|
||||
expected = CaseNode(
|
||||
SymbolNode(Location(), Location(), nil, "a"),
|
||||
[],
|
||||
ElseNode(Location(), nil, Location()),
|
||||
Location(),
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "case :a\nelse\nend", ["Unexpected else without no when clauses in case statement."]
|
||||
end
|
||||
|
||||
def test_setter_method_cannot_be_defined_in_an_endless_method_definition
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
nil,
|
||||
StatementsNode([IntegerNode()]),
|
||||
[],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
Location(),
|
||||
nil
|
||||
)
|
||||
|
||||
assert_errors expected, "def a=() = 42", ["Setter method cannot be defined in an endless method definition"]
|
||||
end
|
||||
|
||||
def test_do_not_allow_forward_arguments_in_lambda_literals
|
||||
expected = LambdaNode(
|
||||
[:"..."],
|
||||
Location(),
|
||||
BlockParametersNode(ParametersNode([], [], [], nil, [], ForwardingParameterNode(), nil), [], Location(), Location()),
|
||||
nil
|
||||
)
|
||||
|
||||
assert_errors expected, "->(...) {}", ["Unexpected ..."]
|
||||
end
|
||||
|
||||
def test_do_not_allow_forward_arguments_in_blocks
|
||||
expected = CallNode(
|
||||
nil,
|
||||
nil,
|
||||
Location(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
BlockNode(
|
||||
[:"..."],
|
||||
BlockParametersNode(ParametersNode([], [], [], nil, [], ForwardingParameterNode(), nil), [], Location(), Location()),
|
||||
nil,
|
||||
Location(),
|
||||
Location()
|
||||
),
|
||||
0,
|
||||
"a"
|
||||
)
|
||||
|
||||
assert_errors expected, "a {|...|}", ["Unexpected ..."]
|
||||
end
|
||||
|
||||
def test_dont_allow_return_inside_class_body
|
||||
expected = ClassNode(
|
||||
[],
|
||||
Location(),
|
||||
ConstantReadNode(),
|
||||
nil,
|
||||
nil,
|
||||
StatementsNode([ReturnNode(Location(), nil)]),
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "class A; return; end", ["Invalid return in class/module body"]
|
||||
end
|
||||
|
||||
def test_dont_allow_return_inside_module_body
|
||||
expected = ModuleNode(
|
||||
[],
|
||||
Location(),
|
||||
ConstantReadNode(),
|
||||
StatementsNode([ReturnNode(Location(), nil)]),
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "module A; return; end", ["Invalid return in class/module body"]
|
||||
end
|
||||
|
||||
def test_dont_allow_setting_to_back_and_nth_reference
|
||||
expected = BeginNode(
|
||||
Location(),
|
||||
StatementsNode([
|
||||
GlobalVariableWriteNode(Location(), Location(), NilNode()),
|
||||
GlobalVariableWriteNode(Location(), Location(), NilNode())
|
||||
]),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "begin\n$+ = nil\n$1466 = nil\nend", ["Can't set variable", "Can't set variable"]
|
||||
end
|
||||
|
||||
def test_duplicated_parameter_names
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode([RequiredParameterNode(:a), RequiredParameterNode(:b), RequiredParameterNode(:a)], [], [], nil, [], nil, nil),
|
||||
nil,
|
||||
[:a, :b],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(a,b,a);end", ["Duplicated parameter name."]
|
||||
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode([RequiredParameterNode(:a), RequiredParameterNode(:b)], [], [], RestParameterNode(Location(), Location()), [], nil, nil),
|
||||
nil,
|
||||
[:a, :b],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(a,b,*a);end", ["Duplicated parameter name."]
|
||||
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode([RequiredParameterNode(:a), RequiredParameterNode(:b)], [], [], nil, [], KeywordRestParameterNode(Location(), Location()), nil),
|
||||
nil,
|
||||
[:a, :b],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(a,b,**a);end", ["Duplicated parameter name."]
|
||||
|
||||
expected = DefNode(
|
||||
Location(),
|
||||
nil,
|
||||
ParametersNode([RequiredParameterNode(:a), RequiredParameterNode(:b)], [], [], nil, [], nil, BlockParameterNode(Location(), Location())),
|
||||
nil,
|
||||
[:a, :b],
|
||||
Location(),
|
||||
nil,
|
||||
Location(),
|
||||
Location(),
|
||||
nil,
|
||||
Location()
|
||||
)
|
||||
|
||||
assert_errors expected, "def foo(a,b,&a);end", ["Duplicated parameter name."]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_errors(expected, source, errors)
|
||||
assert_nil Ripper.sexp_raw(source)
|
||||
|
||||
result = YARP.parse(source)
|
||||
result => YARP::ParseResult[value: YARP::ProgramNode[statements: YARP::StatementsNode[body: [*, node]]]]
|
||||
|
||||
assert_equal_nodes(expected, node, compare_location: false)
|
||||
assert_equal(errors, result.errors.map(&:message))
|
||||
end
|
||||
|
||||
def assert_error_messages(source, errors)
|
||||
assert_nil Ripper.sexp_raw(source)
|
||||
result = YARP.parse(source)
|
||||
assert_equal(errors, result.errors.map(&:message))
|
||||
end
|
||||
|
||||
def expression(source)
|
||||
YARP.parse(source) => YARP::ParseResult[value: YARP::ProgramNode[statements: YARP::StatementsNode[body: [*, node]]]]
|
||||
node
|
||||
end
|
||||
end
|
23
test/yarp/fixtures/alias.txt
Normal file
23
test/yarp/fixtures/alias.txt
Normal file
@ -0,0 +1,23 @@
|
||||
alias :foo :bar
|
||||
|
||||
alias %s[abc] %s[def]
|
||||
|
||||
alias :'abc' :'def'
|
||||
|
||||
alias :"abc#{1}" :'def'
|
||||
|
||||
alias $a $'
|
||||
|
||||
alias foo bar
|
||||
|
||||
alias $foo $bar
|
||||
|
||||
alias foo if
|
||||
|
||||
alias foo <=>
|
||||
|
||||
alias :== :eql?
|
||||
|
||||
alias A B
|
||||
|
||||
alias :A :B
|
8
test/yarp/fixtures/arithmetic.txt
Normal file
8
test/yarp/fixtures/arithmetic.txt
Normal file
@ -0,0 +1,8 @@
|
||||
foo !bar
|
||||
|
||||
-foo*bar
|
||||
|
||||
+foo**bar
|
||||
|
||||
foo ~bar
|
||||
|
80
test/yarp/fixtures/arrays.txt
Normal file
80
test/yarp/fixtures/arrays.txt
Normal file
@ -0,0 +1,80 @@
|
||||
[*a]
|
||||
|
||||
foo[bar, baz] = 1, 2, 3
|
||||
|
||||
[a: [:b, :c]]
|
||||
|
||||
|
||||
|
||||
[:a, :b,
|
||||
:c,1,
|
||||
|
||||
|
||||
|
||||
:d,
|
||||
]
|
||||
|
||||
|
||||
[:a, :b,
|
||||
:c,1,
|
||||
|
||||
|
||||
|
||||
:d
|
||||
|
||||
|
||||
]
|
||||
|
||||
[foo => bar]
|
||||
|
||||
foo[bar][baz] = qux
|
||||
|
||||
foo[bar][baz]
|
||||
|
||||
[
|
||||
]
|
||||
|
||||
foo[bar, baz]
|
||||
|
||||
foo[bar, baz] = qux
|
||||
|
||||
foo[0], bar[0] = 1, 2
|
||||
|
||||
foo[bar[baz] = qux]
|
||||
|
||||
foo[bar]
|
||||
|
||||
foo[bar] = baz
|
||||
|
||||
[**{}]
|
||||
|
||||
[**kw]
|
||||
|
||||
[1, **kw]
|
||||
|
||||
[1, **kw, **{}, **kw]
|
||||
|
||||
[
|
||||
foo => bar,
|
||||
]
|
||||
|
||||
|
||||
%i#one two three#
|
||||
|
||||
%w#one two three#
|
||||
|
||||
%x#one two three#
|
||||
|
||||
|
||||
%i@one two three@
|
||||
|
||||
%w@one two three@
|
||||
|
||||
%x@one two three@
|
||||
|
||||
|
||||
%i{one two three}
|
||||
|
||||
%w{one two three}
|
||||
|
||||
%x{one two three}
|
14
test/yarp/fixtures/begin_ensure.txt
Normal file
14
test/yarp/fixtures/begin_ensure.txt
Normal file
@ -0,0 +1,14 @@
|
||||
begin
|
||||
a
|
||||
ensure
|
||||
b
|
||||
end
|
||||
|
||||
begin; a; ensure; b; end
|
||||
|
||||
begin a
|
||||
ensure b
|
||||
end
|
||||
|
||||
begin a; ensure b; end
|
||||
|
79
test/yarp/fixtures/begin_rescue.txt
Normal file
79
test/yarp/fixtures/begin_rescue.txt
Normal file
@ -0,0 +1,79 @@
|
||||
begin; a; rescue; b; else; c; end
|
||||
|
||||
begin; a; rescue; b; else; c; ensure; d; end
|
||||
|
||||
begin
|
||||
a
|
||||
end
|
||||
|
||||
begin; a; end
|
||||
|
||||
begin a
|
||||
end
|
||||
|
||||
begin a; end
|
||||
|
||||
begin
|
||||
a
|
||||
rescue
|
||||
b
|
||||
rescue
|
||||
c
|
||||
rescue
|
||||
d
|
||||
end
|
||||
|
||||
begin
|
||||
a
|
||||
rescue Exception => ex
|
||||
b
|
||||
rescue AnotherException, OneMoreException => ex
|
||||
c
|
||||
end
|
||||
|
||||
begin
|
||||
a
|
||||
rescue Exception => ex
|
||||
b
|
||||
ensure
|
||||
b
|
||||
end
|
||||
|
||||
%!abc!
|
||||
|
||||
begin
|
||||
a
|
||||
rescue
|
||||
b
|
||||
end
|
||||
|
||||
begin;a;rescue;b;end
|
||||
|
||||
begin
|
||||
a;rescue
|
||||
b;end
|
||||
|
||||
begin
|
||||
a
|
||||
rescue Exception
|
||||
b
|
||||
end
|
||||
|
||||
begin
|
||||
a
|
||||
rescue Exception, CustomException
|
||||
b
|
||||
end
|
||||
|
||||
begin
|
||||
a
|
||||
rescue Exception, CustomException => ex
|
||||
b
|
||||
end
|
||||
|
||||
begin
|
||||
a
|
||||
rescue Exception => ex
|
||||
b
|
||||
end
|
||||
|
54
test/yarp/fixtures/blocks.txt
Normal file
54
test/yarp/fixtures/blocks.txt
Normal file
@ -0,0 +1,54 @@
|
||||
foo[bar] { baz }
|
||||
|
||||
foo[bar] do
|
||||
baz
|
||||
end
|
||||
|
||||
x.reduce(0) { |x, memo| memo += x }
|
||||
|
||||
foo do end
|
||||
|
||||
foo bar, (baz do end)
|
||||
|
||||
foo bar do end
|
||||
|
||||
foo bar baz do end
|
||||
|
||||
foo do |a = b[1]|
|
||||
end
|
||||
|
||||
foo do
|
||||
rescue
|
||||
end
|
||||
|
||||
foo do
|
||||
bar do
|
||||
baz do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
foo[bar] { baz }
|
||||
|
||||
foo { |x, y = 2, z:| x }
|
||||
|
||||
foo { |x| }
|
||||
|
||||
fork = 1
|
||||
fork do |a|
|
||||
end
|
||||
|
||||
fork { |a| }
|
||||
|
||||
C do
|
||||
end
|
||||
|
||||
C {}
|
||||
|
||||
foo lambda { |
|
||||
a: 1,
|
||||
b: 2
|
||||
|
|
||||
}
|
||||
|
||||
foo do |bar,| end
|
5
test/yarp/fixtures/boolean_operators.txt
Normal file
5
test/yarp/fixtures/boolean_operators.txt
Normal file
@ -0,0 +1,5 @@
|
||||
a &&= b
|
||||
|
||||
a += b
|
||||
|
||||
a ||= b
|
3
test/yarp/fixtures/booleans.txt
Normal file
3
test/yarp/fixtures/booleans.txt
Normal file
@ -0,0 +1,3 @@
|
||||
false
|
||||
|
||||
true
|
25
test/yarp/fixtures/break.txt
Normal file
25
test/yarp/fixtures/break.txt
Normal file
@ -0,0 +1,25 @@
|
||||
break
|
||||
|
||||
break (1), (2), (3)
|
||||
|
||||
break 1
|
||||
|
||||
break 1, 2,
|
||||
3
|
||||
|
||||
break 1, 2, 3
|
||||
|
||||
break [1, 2, 3]
|
||||
|
||||
break(
|
||||
1
|
||||
2
|
||||
)
|
||||
|
||||
break()
|
||||
|
||||
break(1)
|
||||
|
||||
foo { break 42 } == 42
|
||||
|
||||
foo { |a| break } == 42
|
30
test/yarp/fixtures/case.txt
Normal file
30
test/yarp/fixtures/case.txt
Normal file
@ -0,0 +1,30 @@
|
||||
case :hi
|
||||
when :hi
|
||||
end
|
||||
|
||||
case true; when true; puts :hi; when false; puts :bye; end
|
||||
|
||||
case; when *foo; end
|
||||
|
||||
case :hi
|
||||
when :hi
|
||||
else
|
||||
:b
|
||||
end
|
||||
|
||||
case this; when FooBar, BazBonk; end
|
||||
|
||||
case
|
||||
when foo == bar
|
||||
end
|
||||
|
||||
case
|
||||
when a
|
||||
else
|
||||
# empty
|
||||
end
|
||||
|
||||
case type;
|
||||
;when :b;
|
||||
; else;
|
||||
end
|
35
test/yarp/fixtures/classes.txt
Normal file
35
test/yarp/fixtures/classes.txt
Normal file
@ -0,0 +1,35 @@
|
||||
class A a = 1 end
|
||||
|
||||
class A; ensure; end
|
||||
|
||||
class A; rescue; else; ensure; end
|
||||
|
||||
class A < B
|
||||
a = 1
|
||||
end
|
||||
|
||||
class << not foo
|
||||
end
|
||||
|
||||
class A; class << self; ensure; end; end
|
||||
|
||||
class A; class << self; rescue; else; ensure; end; end
|
||||
|
||||
class << foo.bar
|
||||
end
|
||||
|
||||
class << foo.bar;end
|
||||
|
||||
class << self
|
||||
end
|
||||
|
||||
class << self;end
|
||||
|
||||
class << self
|
||||
1 + 2
|
||||
end
|
||||
|
||||
class << self;1 + 2;end
|
||||
|
||||
class A < B[1]
|
||||
end
|
24
test/yarp/fixtures/comments.txt
Normal file
24
test/yarp/fixtures/comments.txt
Normal file
@ -0,0 +1,24 @@
|
||||
a
|
||||
# Comment
|
||||
b
|
||||
|
||||
c # Comment
|
||||
d
|
||||
|
||||
e
|
||||
# Comment
|
||||
.f
|
||||
|
||||
g
|
||||
# Comment
|
||||
.h
|
||||
|
||||
i # Comment
|
||||
.j
|
||||
|
||||
k # Comment
|
||||
.l
|
||||
|
||||
m
|
||||
# Comment
|
||||
&.n
|
169
test/yarp/fixtures/constants.txt
Normal file
169
test/yarp/fixtures/constants.txt
Normal file
@ -0,0 +1,169 @@
|
||||
A::B
|
||||
|
||||
A::B::C
|
||||
|
||||
a::B
|
||||
|
||||
A::B = 1
|
||||
|
||||
A = 1
|
||||
|
||||
ABC
|
||||
|
||||
Foo 1
|
||||
|
||||
::A::foo
|
||||
|
||||
::A = 1
|
||||
|
||||
::A::B = 1
|
||||
|
||||
::A::B
|
||||
|
||||
::A
|
||||
|
||||
A::false
|
||||
|
||||
A::B::true
|
||||
|
||||
A::&
|
||||
|
||||
A::`
|
||||
|
||||
A::!
|
||||
|
||||
A::!=
|
||||
|
||||
A::^
|
||||
|
||||
A::==
|
||||
|
||||
A::===
|
||||
|
||||
A::=~
|
||||
|
||||
A::>
|
||||
|
||||
A::>=
|
||||
|
||||
A::>>
|
||||
|
||||
A::<<
|
||||
|
||||
A::\
|
||||
|
||||
A::alias
|
||||
|
||||
A::and
|
||||
|
||||
A::begin
|
||||
|
||||
A::BEGIN
|
||||
|
||||
A::break
|
||||
|
||||
A::class
|
||||
|
||||
A::def
|
||||
|
||||
A::defined
|
||||
|
||||
A::do
|
||||
|
||||
A::else
|
||||
|
||||
A::elsif
|
||||
|
||||
A::end
|
||||
|
||||
A::END
|
||||
|
||||
A::ensure
|
||||
|
||||
A::false
|
||||
|
||||
A::for
|
||||
|
||||
A::if
|
||||
|
||||
A::in
|
||||
|
||||
A::next
|
||||
|
||||
A::nil
|
||||
|
||||
A::not
|
||||
|
||||
A::or
|
||||
|
||||
A::redo
|
||||
|
||||
A::rescue
|
||||
|
||||
A::retry
|
||||
|
||||
A::return
|
||||
|
||||
A::self
|
||||
|
||||
A::super
|
||||
|
||||
A::then
|
||||
|
||||
A::true
|
||||
|
||||
A::undef
|
||||
|
||||
A::unless
|
||||
|
||||
A::until
|
||||
|
||||
A::when
|
||||
|
||||
A::while
|
||||
|
||||
A::yield
|
||||
|
||||
A::__ENCODING__
|
||||
|
||||
A::__FILE__
|
||||
|
||||
A::__LINE__
|
||||
|
||||
A::<
|
||||
|
||||
A::<=>
|
||||
|
||||
A::<<
|
||||
|
||||
A::-
|
||||
|
||||
A::%
|
||||
|
||||
A::%i
|
||||
|
||||
A::%w
|
||||
|
||||
A::%x
|
||||
|
||||
A::%I
|
||||
|
||||
A::%W
|
||||
|
||||
A::|
|
||||
|
||||
A::+
|
||||
|
||||
A::/
|
||||
|
||||
A::*
|
||||
|
||||
A::**
|
||||
|
||||
A::~
|
||||
|
||||
A::_::
|
||||
|
||||
A::_..
|
||||
|
||||
A::__END__
|
44
test/yarp/fixtures/dash_heredocs.txt
Normal file
44
test/yarp/fixtures/dash_heredocs.txt
Normal file
@ -0,0 +1,44 @@
|
||||
<<-EOF
|
||||
a
|
||||
EOF
|
||||
|
||||
<<-FIRST + <<-SECOND
|
||||
a
|
||||
FIRST
|
||||
b
|
||||
SECOND
|
||||
|
||||
<<-`EOF`
|
||||
a
|
||||
#{b}
|
||||
EOF
|
||||
|
||||
<<-EOF #comment
|
||||
a
|
||||
EOF
|
||||
|
||||
<<-EOF
|
||||
a
|
||||
b
|
||||
EOF
|
||||
|
||||
<<-"EOF"
|
||||
a
|
||||
#{b}
|
||||
EOF
|
||||
|
||||
<<-EOF
|
||||
a
|
||||
#{b}
|
||||
EOF
|
||||
|
||||
%#abc#
|
||||
|
||||
<<-EOF
|
||||
a
|
||||
b
|
||||
EOF
|
||||
|
||||
<<-'EOF'
|
||||
a #{1}
|
||||
EOF
|
7
test/yarp/fixtures/defined.txt
Normal file
7
test/yarp/fixtures/defined.txt
Normal file
@ -0,0 +1,7 @@
|
||||
defined? 1 and defined? 2
|
||||
|
||||
defined?(x %= 2)
|
||||
|
||||
defined?(foo and bar)
|
||||
|
||||
defined? 1
|
20
test/yarp/fixtures/dos_endings.txt
Normal file
20
test/yarp/fixtures/dos_endings.txt
Normal file
@ -0,0 +1,20 @@
|
||||
puts "hi"\
|
||||
"there"
|
||||
|
||||
%I{a\
|
||||
b}
|
||||
|
||||
<<-E
|
||||
1 \
|
||||
2
|
||||
3
|
||||
E
|
||||
|
||||
x = %
|
||||
|
||||
|
||||
|
||||
a = foo(<<~EOF.chop)
|
||||
|
||||
baz
|
||||
EOF
|
2
test/yarp/fixtures/embdoc_no_newline_at_end.txt
Normal file
2
test/yarp/fixtures/embdoc_no_newline_at_end.txt
Normal file
@ -0,0 +1,2 @@
|
||||
=begin
|
||||
=end
|
19
test/yarp/fixtures/for.txt
Normal file
19
test/yarp/fixtures/for.txt
Normal file
@ -0,0 +1,19 @@
|
||||
for i in 1..10
|
||||
i
|
||||
end
|
||||
|
||||
for i in 1..10; i; end
|
||||
|
||||
for i,j in 1..10
|
||||
i
|
||||
end
|
||||
|
||||
for i,j,k in 1..10
|
||||
i
|
||||
end
|
||||
|
||||
for i in 1..10 do
|
||||
i
|
||||
end
|
||||
|
||||
for i in 1..10; i; end
|
89
test/yarp/fixtures/global_variables.txt
Normal file
89
test/yarp/fixtures/global_variables.txt
Normal file
@ -0,0 +1,89 @@
|
||||
$global_variable
|
||||
|
||||
$_
|
||||
|
||||
$-w
|
||||
|
||||
$LOAD_PATH
|
||||
|
||||
$stdin
|
||||
|
||||
$stdout
|
||||
|
||||
$stderr
|
||||
|
||||
$!
|
||||
|
||||
$?
|
||||
|
||||
$~
|
||||
|
||||
$&
|
||||
|
||||
$`
|
||||
|
||||
$'
|
||||
|
||||
$+
|
||||
|
||||
$:
|
||||
|
||||
$;
|
||||
|
||||
$,
|
||||
|
||||
$DEBUG
|
||||
|
||||
$FILENAME
|
||||
|
||||
$-0
|
||||
|
||||
$LOADED_FEATURES
|
||||
|
||||
$VERBOSE
|
||||
|
||||
$-K
|
||||
|
||||
:$global_variable
|
||||
|
||||
:$_
|
||||
|
||||
:$-w
|
||||
|
||||
:$LOAD_PATH
|
||||
|
||||
:$stdin
|
||||
|
||||
:$stdout
|
||||
|
||||
:$stderr
|
||||
|
||||
:$!
|
||||
|
||||
:$?
|
||||
|
||||
:$~
|
||||
|
||||
:$&
|
||||
|
||||
:$`
|
||||
|
||||
:$'
|
||||
|
||||
:$+
|
||||
|
||||
:$:
|
||||
|
||||
:$;
|
||||
|
||||
:$DEBUG
|
||||
|
||||
:$FILENAME
|
||||
|
||||
:$-0
|
||||
|
||||
:$LOADED_FEATURES
|
||||
|
||||
:$VERBOSE
|
||||
|
||||
:$-K
|
20
test/yarp/fixtures/hashes.txt
Normal file
20
test/yarp/fixtures/hashes.txt
Normal file
@ -0,0 +1,20 @@
|
||||
{}
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
{ a => b, c => d }
|
||||
|
||||
{ a => b, **c }
|
||||
|
||||
{
|
||||
a: b,
|
||||
c: d
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
{ a: b, c: d, **e, f: g }
|
||||
|
||||
{ "a": !b? }
|
2
test/yarp/fixtures/heredoc_with_trailing_newline.txt
Normal file
2
test/yarp/fixtures/heredoc_with_trailing_newline.txt
Normal file
@ -0,0 +1,2 @@
|
||||
<<-END
|
||||
END
|
9
test/yarp/fixtures/heredocs_nested.txt
Normal file
9
test/yarp/fixtures/heredocs_nested.txt
Normal file
@ -0,0 +1,9 @@
|
||||
<<~RUBY
|
||||
pre
|
||||
#{
|
||||
<<RUBY
|
||||
hello
|
||||
RUBY
|
||||
}
|
||||
post
|
||||
RUBY
|
14
test/yarp/fixtures/heredocs_with_ignored_newlines.txt
Normal file
14
test/yarp/fixtures/heredocs_with_ignored_newlines.txt
Normal file
@ -0,0 +1,14 @@
|
||||
<<-HERE\
|
||||
HERE
|
||||
|
||||
<<~THERE\
|
||||
way over
|
||||
<<HERE
|
||||
not here
|
||||
HERE
|
||||
|
||||
<<~BUT\
|
||||
but
|
||||
BUT
|
||||
there
|
||||
THERE
|
@ -0,0 +1,4 @@
|
||||
<<-EOE
|
||||
some
|
||||
heredocs
|
||||
EOE
|
31
test/yarp/fixtures/if.txt
Normal file
31
test/yarp/fixtures/if.txt
Normal file
@ -0,0 +1,31 @@
|
||||
if true; 1; end
|
||||
|
||||
if true
|
||||
1 else 2 end
|
||||
|
||||
if true then true elsif false then false elsif nil then nil else self end
|
||||
|
||||
1 if true
|
||||
|
||||
break if true
|
||||
|
||||
next if true
|
||||
|
||||
return if true
|
||||
|
||||
if exit_loop then break 42 end
|
||||
|
||||
if foo
|
||||
then bar
|
||||
end
|
||||
|
||||
a if b if c
|
||||
|
||||
if true
|
||||
a b:
|
||||
else
|
||||
end
|
||||
|
||||
if type in 1
|
||||
elsif type in B
|
||||
end
|
63
test/yarp/fixtures/integer_operations.txt
Normal file
63
test/yarp/fixtures/integer_operations.txt
Normal file
@ -0,0 +1,63 @@
|
||||
!1
|
||||
|
||||
~1
|
||||
|
||||
1 != 2
|
||||
|
||||
1 !~ 2
|
||||
|
||||
1 % 2
|
||||
|
||||
1 & 2
|
||||
|
||||
1 * 2
|
||||
|
||||
1**2
|
||||
|
||||
1 + 2
|
||||
|
||||
1 - 2
|
||||
|
||||
1 / 2
|
||||
|
||||
1/2/3
|
||||
|
||||
1 < 2
|
||||
|
||||
1 << 2
|
||||
|
||||
1 <= 2
|
||||
|
||||
1 <=> 2
|
||||
|
||||
1 == 2
|
||||
|
||||
1 === 2
|
||||
|
||||
1 =~ 2
|
||||
|
||||
1 > 2
|
||||
|
||||
1 >= 2
|
||||
|
||||
1 >> 2
|
||||
|
||||
1 ^ 2
|
||||
|
||||
1 | 2
|
||||
|
||||
1 && 2
|
||||
|
||||
1 and 2
|
||||
|
||||
1 * 2 ** 3
|
||||
|
||||
1 * 2 + 3
|
||||
|
||||
1 or 2
|
||||
|
||||
1 || 2
|
||||
|
||||
1 + 2 * 3
|
||||
|
||||
(1 + 1)
|
29
test/yarp/fixtures/keyword_method_names.txt
Normal file
29
test/yarp/fixtures/keyword_method_names.txt
Normal file
@ -0,0 +1,29 @@
|
||||
def def
|
||||
end
|
||||
|
||||
def self.ensure
|
||||
end
|
||||
|
||||
private def foo
|
||||
bar do
|
||||
end
|
||||
end
|
||||
|
||||
def m(a, **nil)
|
||||
end
|
||||
|
||||
def __ENCODING__.a
|
||||
end
|
||||
|
||||
%{abc}
|
||||
|
||||
%"abc"
|
||||
|
||||
def __FILE__.a
|
||||
end
|
||||
|
||||
def __LINE__.a
|
||||
end
|
||||
|
||||
def nil::a
|
||||
end
|
11
test/yarp/fixtures/keywords.txt
Normal file
11
test/yarp/fixtures/keywords.txt
Normal file
@ -0,0 +1,11 @@
|
||||
redo
|
||||
|
||||
retry
|
||||
|
||||
self
|
||||
|
||||
__ENCODING__
|
||||
|
||||
__FILE__
|
||||
|
||||
__LINE__
|
7
test/yarp/fixtures/lambda.txt
Normal file
7
test/yarp/fixtures/lambda.txt
Normal file
@ -0,0 +1,7 @@
|
||||
->(
|
||||
foo
|
||||
) {}
|
||||
|
||||
->(x: "b#{a}") { }
|
||||
|
||||
->(a: b * 3) {}
|
139
test/yarp/fixtures/method_calls.txt
Normal file
139
test/yarp/fixtures/method_calls.txt
Normal file
@ -0,0 +1,139 @@
|
||||
foo.bar %{baz}
|
||||
|
||||
a.b(c, d)
|
||||
|
||||
a.b()
|
||||
|
||||
foo
|
||||
.bar
|
||||
&.baz
|
||||
|
||||
a!
|
||||
|
||||
a.()
|
||||
|
||||
a.(1, 2, 3)
|
||||
|
||||
a::b
|
||||
|
||||
foo.bar = 1
|
||||
|
||||
a?
|
||||
|
||||
a(&block)
|
||||
|
||||
a(**kwargs)
|
||||
|
||||
a.b.c
|
||||
|
||||
a(b, c)
|
||||
|
||||
a()
|
||||
|
||||
a(*args)
|
||||
|
||||
a b, c
|
||||
|
||||
a.b c, d
|
||||
|
||||
foo.foo, bar.bar = 1, 2
|
||||
|
||||
a&.b
|
||||
|
||||
a&.()
|
||||
|
||||
a&.b(c)
|
||||
|
||||
a&.b()
|
||||
|
||||
foo :a, :b if bar? or baz and qux
|
||||
|
||||
foo(:a,
|
||||
|
||||
:b
|
||||
)
|
||||
|
||||
foo(*rest)
|
||||
|
||||
foo(:a, :h => [:x, :y], :a => :b, &:bar)
|
||||
|
||||
hi 123, { :there => :friend, **{}, whatup: :dog }
|
||||
|
||||
foo :a, b: true do |a, b| puts a end
|
||||
|
||||
hi there: :friend
|
||||
|
||||
hi :there => :friend, **{}, whatup: :dog
|
||||
|
||||
hi(:there => :friend, **{}, whatup: :dog)
|
||||
|
||||
foo({ a: true, b: false, }, &:block)
|
||||
|
||||
hi :there => :friend
|
||||
|
||||
foo(:a,
|
||||
:b,
|
||||
)
|
||||
|
||||
foo(
|
||||
:a,
|
||||
b: :c,
|
||||
)
|
||||
|
||||
foo a: true, b: false, &:block
|
||||
|
||||
some_func 1, kwarg: 2
|
||||
|
||||
Kernel.Integer(10)
|
||||
|
||||
x.each { }
|
||||
|
||||
foo.map { $& }
|
||||
|
||||
A::B::C :foo
|
||||
|
||||
A::B::C(:foo)
|
||||
|
||||
A::B::C(:foo) { }
|
||||
|
||||
foo("a": -1)
|
||||
|
||||
foo bar: { baz: qux do end }
|
||||
|
||||
foo bar: { **kw do end }
|
||||
|
||||
foo "#{bar.map do "baz" end}" do end
|
||||
|
||||
foo class Bar baz do end end
|
||||
|
||||
foo module Bar baz do end end
|
||||
|
||||
foo [baz do end]
|
||||
|
||||
p begin 1.times do 1 end end
|
||||
|
||||
foo :a,
|
||||
if x
|
||||
bar do |a|
|
||||
a
|
||||
end
|
||||
end
|
||||
|
||||
foo :a,
|
||||
while x
|
||||
bar do |a|
|
||||
a
|
||||
end
|
||||
end,
|
||||
until x
|
||||
baz do
|
||||
end
|
||||
end
|
||||
|
||||
{} + A {}
|
||||
|
||||
{} + A { |a| a }
|
||||
|
||||
A {} + A {}
|
||||
|
||||
lst << A {}
|
165
test/yarp/fixtures/methods.txt
Normal file
165
test/yarp/fixtures/methods.txt
Normal file
@ -0,0 +1,165 @@
|
||||
def foo((bar, baz))
|
||||
end
|
||||
|
||||
def foo((bar, baz), optional = 1, (bin, bag))
|
||||
end
|
||||
|
||||
|
||||
def a; ensure; end
|
||||
|
||||
def (b).a
|
||||
end
|
||||
|
||||
def (a)::b
|
||||
end
|
||||
|
||||
def false.a
|
||||
end
|
||||
|
||||
def a(...)
|
||||
end
|
||||
|
||||
def $var.a
|
||||
end
|
||||
|
||||
def a.b
|
||||
end
|
||||
|
||||
def @var.a
|
||||
end
|
||||
|
||||
def a b:; end
|
||||
|
||||
%,abc,
|
||||
|
||||
def a(b:)
|
||||
end
|
||||
|
||||
def a(**b)
|
||||
end
|
||||
|
||||
def a(**)
|
||||
end
|
||||
|
||||
a = 1; def a
|
||||
end
|
||||
|
||||
def a b, c, d
|
||||
end
|
||||
|
||||
def nil.a
|
||||
end
|
||||
|
||||
def a b:, c: 1
|
||||
end
|
||||
|
||||
def a(b:, c: 1)
|
||||
end
|
||||
|
||||
def a(b:
|
||||
1, c:)
|
||||
end
|
||||
|
||||
%.abc.
|
||||
|
||||
def a b = 1, c = 2
|
||||
end
|
||||
|
||||
def a()
|
||||
end
|
||||
|
||||
def a b, c = 2
|
||||
end
|
||||
|
||||
def a b
|
||||
end
|
||||
|
||||
def a; rescue; else; ensure; end
|
||||
|
||||
def a *b
|
||||
end
|
||||
|
||||
def a(*)
|
||||
end
|
||||
|
||||
def a
|
||||
b = 1
|
||||
end
|
||||
|
||||
def self.a
|
||||
end
|
||||
|
||||
def true.a
|
||||
end
|
||||
|
||||
def a
|
||||
end
|
||||
|
||||
def hi
|
||||
return :hi if true
|
||||
:bye
|
||||
end
|
||||
|
||||
def foo = 1
|
||||
def bar = 2
|
||||
|
||||
def foo(bar) = 123
|
||||
|
||||
def foo = 123
|
||||
|
||||
def a(*); b(*); end
|
||||
|
||||
def a(...); b(...); end
|
||||
|
||||
def a(...); b(1, 2, ...); end
|
||||
|
||||
def (c = b).a
|
||||
end
|
||||
|
||||
def a &b
|
||||
end
|
||||
|
||||
def a(&)
|
||||
end
|
||||
|
||||
def @@var.a
|
||||
end
|
||||
|
||||
def (a = b).C
|
||||
end
|
||||
|
||||
def self.Array_function; end
|
||||
|
||||
Const = 1; def Const.a
|
||||
end
|
||||
|
||||
def a(...); "foo#{b(...)}"; end
|
||||
|
||||
def foo
|
||||
{}.merge **bar, **baz, **qux
|
||||
end
|
||||
|
||||
def bar(a: (1...10))
|
||||
end
|
||||
|
||||
def bar(a: (...10))
|
||||
end
|
||||
|
||||
def bar(a: (1...))
|
||||
end
|
||||
|
||||
def bar(a = (1...10))
|
||||
end
|
||||
|
||||
def bar(a = (...10))
|
||||
end
|
||||
|
||||
def bar(a = (1...))
|
||||
end
|
||||
|
||||
def method(a)
|
||||
item >> a {}
|
||||
end
|
||||
|
||||
def foo(_a, _a, b, c)
|
||||
end
|
18
test/yarp/fixtures/modules.txt
Normal file
18
test/yarp/fixtures/modules.txt
Normal file
@ -0,0 +1,18 @@
|
||||
module A a = 1 end
|
||||
|
||||
%Q{aaa #{bbb} ccc}
|
||||
|
||||
module m::M
|
||||
end
|
||||
|
||||
module A
|
||||
x = 1; rescue; end
|
||||
|
||||
module ::A
|
||||
end
|
||||
|
||||
module A[]::B
|
||||
end
|
||||
|
||||
module A[1]::B
|
||||
end
|
24
test/yarp/fixtures/next.txt
Normal file
24
test/yarp/fixtures/next.txt
Normal file
@ -0,0 +1,24 @@
|
||||
next
|
||||
|
||||
next (1), (2), (3)
|
||||
|
||||
next 1
|
||||
|
||||
next 1, 2,
|
||||
3
|
||||
|
||||
next 1, 2, 3
|
||||
|
||||
next [1, 2, 3]
|
||||
|
||||
next(
|
||||
1
|
||||
2
|
||||
)
|
||||
|
||||
next
|
||||
1
|
||||
|
||||
next()
|
||||
|
||||
next(1)
|
13
test/yarp/fixtures/nils.txt
Normal file
13
test/yarp/fixtures/nils.txt
Normal file
@ -0,0 +1,13 @@
|
||||
nil
|
||||
|
||||
()
|
||||
|
||||
(
|
||||
;
|
||||
;
|
||||
)
|
||||
|
||||
END { 1 }
|
||||
|
||||
BEGIN { 1 }
|
||||
|
105
test/yarp/fixtures/non_alphanumeric_methods.txt
Normal file
105
test/yarp/fixtures/non_alphanumeric_methods.txt
Normal file
@ -0,0 +1,105 @@
|
||||
def !
|
||||
end
|
||||
|
||||
def !=
|
||||
end
|
||||
|
||||
def !~
|
||||
end
|
||||
|
||||
def %
|
||||
end
|
||||
|
||||
def self.+
|
||||
end
|
||||
|
||||
def &
|
||||
end
|
||||
|
||||
def *
|
||||
end
|
||||
|
||||
def **
|
||||
end
|
||||
|
||||
%|abc|
|
||||
|
||||
def + **b
|
||||
end
|
||||
|
||||
def +()
|
||||
end
|
||||
|
||||
def + b
|
||||
end
|
||||
|
||||
def self.+
|
||||
end
|
||||
|
||||
def +
|
||||
end
|
||||
|
||||
def +@
|
||||
end
|
||||
|
||||
def -
|
||||
end
|
||||
|
||||
def a.-;end
|
||||
|
||||
def -@
|
||||
end
|
||||
|
||||
def /
|
||||
end
|
||||
|
||||
def <
|
||||
end
|
||||
|
||||
def <<
|
||||
end
|
||||
|
||||
def <=
|
||||
end
|
||||
|
||||
def <=>
|
||||
end
|
||||
|
||||
def ==
|
||||
end
|
||||
|
||||
def ===
|
||||
end
|
||||
|
||||
def =~
|
||||
end
|
||||
|
||||
def >
|
||||
end
|
||||
|
||||
def >=
|
||||
end
|
||||
|
||||
def >>
|
||||
end
|
||||
|
||||
def []
|
||||
end
|
||||
|
||||
def []=
|
||||
end
|
||||
|
||||
def ^
|
||||
end
|
||||
|
||||
def `
|
||||
end
|
||||
|
||||
def self.`
|
||||
end
|
||||
|
||||
def |
|
||||
end
|
||||
|
||||
def ~
|
||||
end
|
20
test/yarp/fixtures/not.txt
Normal file
20
test/yarp/fixtures/not.txt
Normal file
@ -0,0 +1,20 @@
|
||||
not foo and not bar
|
||||
|
||||
not(foo and bar)
|
||||
|
||||
not foo
|
||||
|
||||
not foo and not
|
||||
bar
|
||||
|
||||
|
||||
not foo and
|
||||
not
|
||||
bar
|
||||
|
||||
|
||||
not foo and
|
||||
not
|
||||
|
||||
|
||||
bar
|
63
test/yarp/fixtures/numbers.txt
Normal file
63
test/yarp/fixtures/numbers.txt
Normal file
@ -0,0 +1,63 @@
|
||||
0
|
||||
|
||||
1
|
||||
|
||||
1.0
|
||||
|
||||
2
|
||||
|
||||
0b0
|
||||
|
||||
0b1
|
||||
|
||||
0b10
|
||||
|
||||
0d0
|
||||
|
||||
0d1
|
||||
|
||||
0d2
|
||||
|
||||
00
|
||||
|
||||
01
|
||||
|
||||
02
|
||||
|
||||
0o0
|
||||
|
||||
0o1
|
||||
|
||||
0o2
|
||||
|
||||
0x0
|
||||
|
||||
0x1
|
||||
|
||||
0x2
|
||||
|
||||
1i
|
||||
|
||||
1r
|
||||
|
||||
-1
|
||||
|
||||
1ri
|
||||
|
||||
1.2ri
|
||||
|
||||
-1ri
|
||||
|
||||
-1.2ri
|
||||
|
||||
0o1r
|
||||
|
||||
0o1i
|
||||
|
||||
0o1ri
|
||||
|
||||
0d1r
|
||||
|
||||
0d1i
|
||||
|
||||
0b1ri
|
191
test/yarp/fixtures/patterns.txt
Normal file
191
test/yarp/fixtures/patterns.txt
Normal file
@ -0,0 +1,191 @@
|
||||
foo => bar
|
||||
foo => 1
|
||||
foo => 1.0
|
||||
foo => 1i
|
||||
foo => 1r
|
||||
foo => :foo
|
||||
foo => %s[foo]
|
||||
foo => :"foo"
|
||||
foo => /foo/
|
||||
foo => `foo`
|
||||
foo => %x[foo]
|
||||
foo => %i[foo]
|
||||
foo => %I[foo]
|
||||
foo => %w[foo]
|
||||
foo => %W[foo]
|
||||
foo => %q[foo]
|
||||
foo => %Q[foo]
|
||||
foo => "foo"
|
||||
foo => nil
|
||||
foo => self
|
||||
foo => true
|
||||
foo => false
|
||||
foo => __FILE__
|
||||
foo => __LINE__
|
||||
foo => __ENCODING__
|
||||
foo => -> { bar }
|
||||
|
||||
foo => 1 .. 1
|
||||
foo => 1.0 .. 1.0
|
||||
foo => 1i .. 1i
|
||||
foo => 1r .. 1r
|
||||
foo => :foo .. :foo
|
||||
foo => %s[foo] .. %s[foo]
|
||||
foo => :"foo" .. :"foo"
|
||||
foo => /foo/ .. /foo/
|
||||
foo => `foo` .. `foo`
|
||||
foo => %x[foo] .. %x[foo]
|
||||
foo => %i[foo] .. %i[foo]
|
||||
foo => %I[foo] .. %I[foo]
|
||||
foo => %w[foo] .. %w[foo]
|
||||
foo => %W[foo] .. %W[foo]
|
||||
foo => %q[foo] .. %q[foo]
|
||||
foo => %Q[foo] .. %Q[foo]
|
||||
foo => "foo" .. "foo"
|
||||
foo => nil .. nil
|
||||
foo => self .. self
|
||||
foo => true .. true
|
||||
foo => false .. false
|
||||
foo => __FILE__ .. __FILE__
|
||||
foo => __LINE__ .. __LINE__
|
||||
foo => __ENCODING__ .. __ENCODING__
|
||||
foo => -> { bar } .. -> { bar }
|
||||
|
||||
foo => ^bar
|
||||
foo => ^@bar
|
||||
foo => ^@@bar
|
||||
foo => ^$bar
|
||||
|
||||
foo => ^(1)
|
||||
foo => ^(nil)
|
||||
foo => ^("bar" + "baz")
|
||||
|
||||
foo => Foo
|
||||
foo => Foo::Bar::Baz
|
||||
foo => ::Foo
|
||||
foo => ::Foo::Bar::Baz
|
||||
|
||||
foo => Foo()
|
||||
foo => Foo(1)
|
||||
foo => Foo(1, 2, 3)
|
||||
foo => Foo(bar)
|
||||
foo => Foo(*bar, baz)
|
||||
foo => Foo(bar, *baz)
|
||||
foo => Foo(*bar, baz, *qux)
|
||||
|
||||
foo => Foo[]
|
||||
foo => Foo[1]
|
||||
foo => Foo[1, 2, 3]
|
||||
foo => Foo[bar]
|
||||
foo => Foo[*bar, baz]
|
||||
foo => Foo[bar, *baz]
|
||||
foo => Foo[*bar, baz, *qux]
|
||||
|
||||
foo => *bar
|
||||
foo => *bar, baz, qux
|
||||
foo => bar, *baz, qux
|
||||
foo => bar, baz, *qux
|
||||
foo => *bar, baz, *qux
|
||||
|
||||
foo => []
|
||||
foo => [[[[[]]]]]
|
||||
|
||||
foo => [*bar]
|
||||
foo => [*bar, baz, qux]
|
||||
foo => [bar, *baz, qux]
|
||||
foo => [bar, baz, *qux]
|
||||
foo => [*bar, baz, *qux]
|
||||
|
||||
foo in bar
|
||||
foo in 1
|
||||
foo in 1.0
|
||||
foo in 1i
|
||||
foo in 1r
|
||||
foo in :foo
|
||||
foo in %s[foo]
|
||||
foo in :"foo"
|
||||
foo in /foo/
|
||||
foo in `foo`
|
||||
foo in %x[foo]
|
||||
foo in %i[foo]
|
||||
foo in %I[foo]
|
||||
foo in %w[foo]
|
||||
foo in %W[foo]
|
||||
foo in %q[foo]
|
||||
foo in %Q[foo]
|
||||
foo in "foo"
|
||||
foo in nil
|
||||
foo in self
|
||||
foo in true
|
||||
foo in false
|
||||
foo in __FILE__
|
||||
foo in __LINE__
|
||||
foo in __ENCODING__
|
||||
foo in -> { bar }
|
||||
|
||||
case foo; in bar then end
|
||||
case foo; in 1 then end
|
||||
case foo; in 1.0 then end
|
||||
case foo; in 1i then end
|
||||
case foo; in 1r then end
|
||||
case foo; in :foo then end
|
||||
case foo; in %s[foo] then end
|
||||
case foo; in :"foo" then end
|
||||
case foo; in /foo/ then end
|
||||
case foo; in `foo` then end
|
||||
case foo; in %x[foo] then end
|
||||
case foo; in %i[foo] then end
|
||||
case foo; in %I[foo] then end
|
||||
case foo; in %w[foo] then end
|
||||
case foo; in %W[foo] then end
|
||||
case foo; in %q[foo] then end
|
||||
case foo; in %Q[foo] then end
|
||||
case foo; in "foo" then end
|
||||
case foo; in nil then end
|
||||
case foo; in self then end
|
||||
case foo; in true then end
|
||||
case foo; in false then end
|
||||
case foo; in __FILE__ then end
|
||||
case foo; in __LINE__ then end
|
||||
case foo; in __ENCODING__ then end
|
||||
case foo; in -> { bar } then end
|
||||
|
||||
case foo; in bar if baz then end
|
||||
case foo; in 1 if baz then end
|
||||
case foo; in 1.0 if baz then end
|
||||
case foo; in 1i if baz then end
|
||||
case foo; in 1r if baz then end
|
||||
case foo; in :foo if baz then end
|
||||
case foo; in %s[foo] if baz then end
|
||||
case foo; in :"foo" if baz then end
|
||||
case foo; in /foo/ if baz then end
|
||||
case foo; in `foo` if baz then end
|
||||
case foo; in %x[foo] if baz then end
|
||||
case foo; in %i[foo] if baz then end
|
||||
case foo; in %I[foo] if baz then end
|
||||
case foo; in %w[foo] if baz then end
|
||||
case foo; in %W[foo] if baz then end
|
||||
case foo; in %q[foo] if baz then end
|
||||
case foo; in %Q[foo] if baz then end
|
||||
case foo; in "foo" if baz then end
|
||||
case foo; in nil if baz then end
|
||||
case foo; in self if baz then end
|
||||
case foo; in true if baz then end
|
||||
case foo; in false if baz then end
|
||||
case foo; in __FILE__ if baz then end
|
||||
case foo; in __LINE__ if baz then end
|
||||
case foo; in __ENCODING__ if baz then end
|
||||
case foo; in -> { bar } if baz then end
|
||||
|
||||
if a in []
|
||||
end
|
||||
|
||||
a => [
|
||||
b
|
||||
]
|
||||
|
||||
foo in A[
|
||||
bar: B[
|
||||
value: a
|
||||
]
|
||||
]
|
27
test/yarp/fixtures/procs.txt
Normal file
27
test/yarp/fixtures/procs.txt
Normal file
@ -0,0 +1,27 @@
|
||||
-> (a; b, c, d) { b }
|
||||
|
||||
-> do
|
||||
ensure
|
||||
end
|
||||
|
||||
-> do
|
||||
rescue
|
||||
else
|
||||
ensure
|
||||
end
|
||||
|
||||
-> { foo }
|
||||
|
||||
-> do; foo; end
|
||||
|
||||
-> a, b = 1, c:, d:, &e { a }
|
||||
|
||||
-> (a, b = 1, *c, d:, e:, **f, &g) { a }
|
||||
|
||||
-> (a, b = 1, *c, d:, e:, **f, &g) do
|
||||
a
|
||||
end
|
||||
|
||||
-> (a) { -> b { a * b } }
|
||||
|
||||
-> ((a, b), *c) { }
|
1
test/yarp/fixtures/range_begin_open_exclusive.txt
Normal file
1
test/yarp/fixtures/range_begin_open_exclusive.txt
Normal file
@ -0,0 +1 @@
|
||||
...2
|
1
test/yarp/fixtures/range_begin_open_inclusive.txt
Normal file
1
test/yarp/fixtures/range_begin_open_inclusive.txt
Normal file
@ -0,0 +1 @@
|
||||
..2
|
1
test/yarp/fixtures/range_end_open_exclusive.txt
Normal file
1
test/yarp/fixtures/range_end_open_exclusive.txt
Normal file
@ -0,0 +1 @@
|
||||
2...
|
1
test/yarp/fixtures/range_end_open_inclusive.txt
Normal file
1
test/yarp/fixtures/range_end_open_inclusive.txt
Normal file
@ -0,0 +1 @@
|
||||
2..
|
17
test/yarp/fixtures/ranges.txt
Normal file
17
test/yarp/fixtures/ranges.txt
Normal file
@ -0,0 +1,17 @@
|
||||
(...2)
|
||||
|
||||
(..2)
|
||||
|
||||
1...2
|
||||
|
||||
foo[...2]
|
||||
|
||||
{ foo: ...bar }
|
||||
|
||||
(1...)
|
||||
|
||||
1..2
|
||||
|
||||
{ foo: ..bar }
|
||||
|
||||
(1..)
|
28
test/yarp/fixtures/regex.txt
Normal file
28
test/yarp/fixtures/regex.txt
Normal file
@ -0,0 +1,28 @@
|
||||
foo /bar/
|
||||
|
||||
%r{abc}i
|
||||
|
||||
/a\b/
|
||||
|
||||
/aaa #$bbb/
|
||||
|
||||
/aaa #{bbb} ccc/
|
||||
|
||||
[/(?<foo>bar)/ =~ baz, foo]
|
||||
|
||||
/abc/i
|
||||
|
||||
%r/[a-z$._?][\w$.?#@~]*:/i
|
||||
|
||||
%r/([a-z$._?][\w$.?#@~]*)(\s+)(equ)/i
|
||||
|
||||
%r/[a-z$._?][\w$.?#@~]*/i
|
||||
|
||||
%r(
|
||||
(?:[\w#$%_']|\(\)|\(,\)|\[\]|[0-9])*
|
||||
(?:[\w#$%_']+)
|
||||
)
|
||||
|
||||
/(?#\))/ =~ "hi"
|
||||
|
||||
%r#pound#
|
31
test/yarp/fixtures/rescue.txt
Normal file
31
test/yarp/fixtures/rescue.txt
Normal file
@ -0,0 +1,31 @@
|
||||
foo rescue nil
|
||||
|
||||
foo rescue
|
||||
nil
|
||||
|
||||
break rescue nil
|
||||
|
||||
next rescue nil
|
||||
|
||||
return rescue nil
|
||||
|
||||
foo rescue nil || 1
|
||||
|
||||
foo rescue nil ? 1 : 2
|
||||
|
||||
begin; a; rescue *b; end
|
||||
|
||||
foo do |x|
|
||||
bar(y) rescue ArgumentError fail "baz"
|
||||
end
|
||||
|
||||
if a = foo rescue nil
|
||||
bar
|
||||
end
|
||||
|
||||
def some_method = other_method 42 rescue nil
|
||||
|
||||
def a
|
||||
a b:
|
||||
rescue
|
||||
end
|
24
test/yarp/fixtures/return.txt
Normal file
24
test/yarp/fixtures/return.txt
Normal file
@ -0,0 +1,24 @@
|
||||
return
|
||||
|
||||
return (1), (2), (3)
|
||||
|
||||
return *1
|
||||
|
||||
return 1
|
||||
|
||||
return 1, 2,
|
||||
3
|
||||
|
||||
return 1, 2, 3
|
||||
|
||||
return [1, 2, 3]
|
||||
|
||||
return(
|
||||
1
|
||||
2
|
||||
)
|
||||
|
||||
return()
|
||||
|
||||
return(1)
|
||||
|
1
test/yarp/fixtures/seattlerb/BEGIN.txt
Normal file
1
test/yarp/fixtures/seattlerb/BEGIN.txt
Normal file
@ -0,0 +1 @@
|
||||
BEGIN { 42 }
|
113
test/yarp/fixtures/seattlerb/README.rdoc
Normal file
113
test/yarp/fixtures/seattlerb/README.rdoc
Normal file
@ -0,0 +1,113 @@
|
||||
= ruby_parser
|
||||
|
||||
home :: https://github.com/seattlerb/ruby_parser
|
||||
bugs :: https://github.com/seattlerb/ruby_parser/issues
|
||||
rdoc :: http://docs.seattlerb.org/ruby_parser
|
||||
|
||||
== DESCRIPTION:
|
||||
|
||||
ruby_parser (RP) is a ruby parser written in pure ruby (utilizing
|
||||
racc--which does by default use a C extension). It outputs
|
||||
s-expressions which can be manipulated and converted back to ruby via
|
||||
the ruby2ruby gem.
|
||||
|
||||
As an example:
|
||||
|
||||
def conditional1 arg1
|
||||
return 1 if arg1 == 0
|
||||
return 0
|
||||
end
|
||||
|
||||
becomes:
|
||||
|
||||
s(:defn, :conditional1, s(:args, :arg1),
|
||||
s(:if,
|
||||
s(:call, s(:lvar, :arg1), :==, s(:lit, 0)),
|
||||
s(:return, s(:lit, 1)),
|
||||
nil),
|
||||
s(:return, s(:lit, 0)))
|
||||
|
||||
Tested against 801,039 files from the latest of all rubygems (as of 2013-05):
|
||||
|
||||
* 1.8 parser is at 99.9739% accuracy, 3.651 sigma
|
||||
* 1.9 parser is at 99.9940% accuracy, 4.013 sigma
|
||||
* 2.0 parser is at 99.9939% accuracy, 4.008 sigma
|
||||
* 2.6 parser is at 99.9972% accuracy, 4.191 sigma
|
||||
* 3.0 parser has a 100% parse rate.
|
||||
* Tested against 2,672,412 unique ruby files across 167k gems.
|
||||
* As do all the others now, basically.
|
||||
|
||||
== FEATURES/PROBLEMS:
|
||||
|
||||
* Pure ruby, no compiles.
|
||||
* Includes preceding comment data for defn/defs/class/module nodes!
|
||||
* Incredibly simple interface.
|
||||
* Output is 100% equivalent to ParseTree.
|
||||
* Can utilize PT's SexpProcessor and UnifiedRuby for language processing.
|
||||
* Known Issue: Speed is now pretty good, but can always improve:
|
||||
* RP parses a corpus of 3702 files in 125s (avg 108 Kb/s)
|
||||
* MRI+PT parsed the same in 67.38s (avg 200.89 Kb/s)
|
||||
* Known Issue: Code is much better, but still has a long way to go.
|
||||
* Known Issue: Totally awesome.
|
||||
* Known Issue: line number values can be slightly off. Parsing LR sucks.
|
||||
|
||||
== SYNOPSIS:
|
||||
|
||||
RubyParser.new.parse "1+1"
|
||||
# => s(:call, s(:lit, 1), :+, s(:lit, 1))
|
||||
|
||||
You can also use Ruby19Parser, Ruby18Parser, or RubyParser.for_current_ruby:
|
||||
|
||||
RubyParser.for_current_ruby.parse "1+1"
|
||||
# => s(:call, s(:lit, 1), :+, s(:lit, 1))
|
||||
|
||||
== DEVELOPER NOTES:
|
||||
|
||||
To add a new version:
|
||||
|
||||
* New parser should be generated from lib/ruby[3]_parser.yy.
|
||||
* Extend lib/ruby[3]_parser.yy with new class name.
|
||||
* Add new version number to V2/V3 in Rakefile for rule creation.
|
||||
* Add new `ruby_parse "x.y.z"` line to Rakefile for rake compare (line ~300).
|
||||
* Require generated parser in lib/ruby_parser.rb.
|
||||
* Add new V## = ::Ruby##Parser; end to ruby_parser.rb (bottom of file).
|
||||
* Add empty TestRubyParserShared##Plus module and TestRubyParserV## to test/test_ruby_parser.rb.
|
||||
* Extend Manifest.txt with generated file names.
|
||||
* Add new version number to sexp_processor's pt_testcase.rb in all_versions
|
||||
|
||||
Until all of these are done, you won't have a clean test run.
|
||||
|
||||
== REQUIREMENTS:
|
||||
|
||||
* ruby. woot.
|
||||
* sexp_processor for Sexp and SexpProcessor classes, and testing.
|
||||
* racc full package for parser development (compiling .y to .rb).
|
||||
|
||||
== INSTALL:
|
||||
|
||||
* sudo gem install ruby_parser
|
||||
|
||||
== LICENSE:
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) Ryan Davis, seattle.rb
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
test/yarp/fixtures/seattlerb/__ENCODING__.txt
Normal file
1
test/yarp/fixtures/seattlerb/__ENCODING__.txt
Normal file
@ -0,0 +1 @@
|
||||
__ENCODING__
|
1
test/yarp/fixtures/seattlerb/alias_gvar_backref.txt
Normal file
1
test/yarp/fixtures/seattlerb/alias_gvar_backref.txt
Normal file
@ -0,0 +1 @@
|
||||
alias $MATCH $&
|
1
test/yarp/fixtures/seattlerb/alias_resword.txt
Normal file
1
test/yarp/fixtures/seattlerb/alias_resword.txt
Normal file
@ -0,0 +1 @@
|
||||
alias in out
|
3
test/yarp/fixtures/seattlerb/and_multi.txt
Normal file
3
test/yarp/fixtures/seattlerb/and_multi.txt
Normal file
@ -0,0 +1,3 @@
|
||||
true and
|
||||
not false and
|
||||
true
|
1
test/yarp/fixtures/seattlerb/aref_args_assocs.txt
Normal file
1
test/yarp/fixtures/seattlerb/aref_args_assocs.txt
Normal file
@ -0,0 +1 @@
|
||||
[1 => 2]
|
1
test/yarp/fixtures/seattlerb/aref_args_lit_assocs.txt
Normal file
1
test/yarp/fixtures/seattlerb/aref_args_lit_assocs.txt
Normal file
@ -0,0 +1 @@
|
||||
[1, 2 => 3]
|
1
test/yarp/fixtures/seattlerb/args_kw_block.txt
Normal file
1
test/yarp/fixtures/seattlerb/args_kw_block.txt
Normal file
@ -0,0 +1 @@
|
||||
def f(a: 1, &b); end
|
4
test/yarp/fixtures/seattlerb/array_line_breaks.txt
Normal file
4
test/yarp/fixtures/seattlerb/array_line_breaks.txt
Normal file
@ -0,0 +1,4 @@
|
||||
[
|
||||
'a',
|
||||
'b']
|
||||
1
|
@ -0,0 +1,3 @@
|
||||
%w[].b
|
||||
|
||||
[].b
|
1
test/yarp/fixtures/seattlerb/assoc__bare.txt
Normal file
1
test/yarp/fixtures/seattlerb/assoc__bare.txt
Normal file
@ -0,0 +1 @@
|
||||
{ y: }
|
1
test/yarp/fixtures/seattlerb/assoc_label.txt
Normal file
1
test/yarp/fixtures/seattlerb/assoc_label.txt
Normal file
@ -0,0 +1 @@
|
||||
a(b:1)
|
1
test/yarp/fixtures/seattlerb/attr_asgn_colon_id.txt
Normal file
1
test/yarp/fixtures/seattlerb/attr_asgn_colon_id.txt
Normal file
@ -0,0 +1 @@
|
||||
A::b = 1
|
1
test/yarp/fixtures/seattlerb/attrasgn_array_arg.txt
Normal file
1
test/yarp/fixtures/seattlerb/attrasgn_array_arg.txt
Normal file
@ -0,0 +1 @@
|
||||
a[[1, 2]] = 3
|
1
test/yarp/fixtures/seattlerb/attrasgn_array_lhs.txt
Normal file
1
test/yarp/fixtures/seattlerb/attrasgn_array_lhs.txt
Normal file
@ -0,0 +1 @@
|
||||
[1, 2, 3, 4][from .. to] = ["a", "b", "c"]
|
@ -0,0 +1 @@
|
||||
a.B = 1
|
@ -0,0 +1 @@
|
||||
x `#{y}`
|
1
test/yarp/fixtures/seattlerb/bang_eq.txt
Normal file
1
test/yarp/fixtures/seattlerb/bang_eq.txt
Normal file
@ -0,0 +1 @@
|
||||
1 != 2
|
3
test/yarp/fixtures/seattlerb/bdot2.txt
Normal file
3
test/yarp/fixtures/seattlerb/bdot2.txt
Normal file
@ -0,0 +1,3 @@
|
||||
..10
|
||||
; ..a
|
||||
; c
|
3
test/yarp/fixtures/seattlerb/bdot3.txt
Normal file
3
test/yarp/fixtures/seattlerb/bdot3.txt
Normal file
@ -0,0 +1,3 @@
|
||||
...10
|
||||
; ...a
|
||||
; c
|
3
test/yarp/fixtures/seattlerb/begin_ensure_no_bodies.txt
Normal file
3
test/yarp/fixtures/seattlerb/begin_ensure_no_bodies.txt
Normal file
@ -0,0 +1,3 @@
|
||||
begin
|
||||
ensure
|
||||
end
|
@ -0,0 +1,9 @@
|
||||
begin
|
||||
1
|
||||
rescue
|
||||
2
|
||||
else
|
||||
3
|
||||
ensure
|
||||
4
|
||||
end
|
@ -0,0 +1,9 @@
|
||||
begin
|
||||
|
||||
rescue
|
||||
|
||||
else
|
||||
|
||||
ensure
|
||||
|
||||
end
|
@ -0,0 +1,4 @@
|
||||
begin
|
||||
rescue
|
||||
ensure
|
||||
end
|
1
test/yarp/fixtures/seattlerb/block_arg__bare.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_arg__bare.txt
Normal file
@ -0,0 +1 @@
|
||||
def x(&); end
|
1
test/yarp/fixtures/seattlerb/block_arg_kwsplat.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_arg_kwsplat.txt
Normal file
@ -0,0 +1 @@
|
||||
a { |**b| }
|
1
test/yarp/fixtures/seattlerb/block_arg_opt_arg_block.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_arg_opt_arg_block.txt
Normal file
@ -0,0 +1 @@
|
||||
a { |b, c=1, d, &e| }
|
1
test/yarp/fixtures/seattlerb/block_arg_opt_splat.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_arg_opt_splat.txt
Normal file
@ -0,0 +1 @@
|
||||
a { |b, c = 1, *d| }
|
@ -0,0 +1 @@
|
||||
a { |b, c=1, *d, e, &f| }
|
1
test/yarp/fixtures/seattlerb/block_arg_optional.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_arg_optional.txt
Normal file
@ -0,0 +1 @@
|
||||
a { |b = 1| }
|
1
test/yarp/fixtures/seattlerb/block_arg_scope.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_arg_scope.txt
Normal file
@ -0,0 +1 @@
|
||||
a { |b; c| }
|
1
test/yarp/fixtures/seattlerb/block_arg_scope2.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_arg_scope2.txt
Normal file
@ -0,0 +1 @@
|
||||
a {|b; c, d| }
|
1
test/yarp/fixtures/seattlerb/block_arg_splat_arg.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_arg_splat_arg.txt
Normal file
@ -0,0 +1 @@
|
||||
a { |b, *c, d| }
|
1
test/yarp/fixtures/seattlerb/block_args_kwargs.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_args_kwargs.txt
Normal file
@ -0,0 +1 @@
|
||||
f { |**kwargs| kwargs }
|
1
test/yarp/fixtures/seattlerb/block_args_no_kwargs.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_args_no_kwargs.txt
Normal file
@ -0,0 +1 @@
|
||||
f { |**nil| }
|
1
test/yarp/fixtures/seattlerb/block_args_opt1.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_args_opt1.txt
Normal file
@ -0,0 +1 @@
|
||||
f { |a, b = 42| [a, b] }
|
1
test/yarp/fixtures/seattlerb/block_args_opt2.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_args_opt2.txt
Normal file
@ -0,0 +1 @@
|
||||
a { | b=1, c=2 | }
|
1
test/yarp/fixtures/seattlerb/block_args_opt2_2.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_args_opt2_2.txt
Normal file
@ -0,0 +1 @@
|
||||
f { |a, b = 42, c = 24| [a, b, c] }
|
1
test/yarp/fixtures/seattlerb/block_args_opt3.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_args_opt3.txt
Normal file
@ -0,0 +1 @@
|
||||
f { |a, b = 42, c = 24, &d| [a, b, c, d] }
|
1
test/yarp/fixtures/seattlerb/block_break.txt
Normal file
1
test/yarp/fixtures/seattlerb/block_break.txt
Normal file
@ -0,0 +1 @@
|
||||
break foo arg do |bar| end
|
@ -0,0 +1,4 @@
|
||||
a def b(c)
|
||||
d
|
||||
end
|
||||
e.f do end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user