класс Prism::Translation::Parser
Этот класс служит точкой входа для преобразования синтаксического дерева prism в синтаксическое дерево гема whitequark/parser. Он наследуется от базового парсера для гема parser и переопределяет методы parse*, чтобы выполнять разбор с помощью prism, а затем преобразовывать результат.
Публичные методы класса
# File lib/prism/translation/parser.rb, line 67
def initialize(builder = Prism::Translation::Parser::Builder.new, parser: Prism)
if !builder.is_a?(Prism::Translation::Parser::Builder)
warn(<<~MSG, uplevel: 1, category: :deprecated)
[deprecation]: The builder passed to `Prism::Translation::Parser.new` is not a \
`Prism::Translation::Parser::Builder` subclass. This will raise in the next major version.
MSG
end
@parser = parser
super(builder)
end Аргумент builder по умолчанию используется для создания парсера с помощью нашего пользовательского класса builder.
С помощью именованного аргумента :parser можно выполнять преобразование, совместимое с гемом Parser, используя любой парсер.
Например, в RuboCop для Ruby LSP можно использовать следующий подход, чтобы повысить производительность за счёт повторного использования предварительно разобранного Prism::ParseLexResult:
class PrismPreparsed def initialize(prism_result) @prism_result = prism_result end
def parse_lex(source, **options) @prism_result end
end
prism_preparsed = PrismPreparsed.new(prism_result)
Prism::Translation::Ruby34.new(builder, parser: prism_preparsed)
В объекте, переданном в именованный аргумент :parser, при необходимости должны быть реализованы методы parse и parse_lex.
Публичные методы экземпляра
# File lib/prism/translation/parser.rb, line 84 def default_encoding Encoding::UTF_8 end
Кодировка Ruby-файлов по умолчанию — UTF-8.
# File lib/prism/translation/parser.rb, line 92 def parse(source_buffer) @source_buffer = source_buffer source = source_buffer.source offset_cache = build_offset_cache(source) result = unwrap(@parser.parse(source, **prism_options), offset_cache) build_ast(result.value, offset_cache) ensure @source_buffer = nil end
Разбирает буфер исходного кода и возвращает AST.
# File lib/prism/translation/parser.rb, line 105
def parse_with_comments(source_buffer)
@source_buffer = source_buffer
source = source_buffer.source
offset_cache = build_offset_cache(source)
result = unwrap(@parser.parse(source, **prism_options), offset_cache)
[
build_ast(result.value, offset_cache),
build_comments(result.comments, offset_cache)
]
ensure
@source_buffer = nil
end Разбирает буфер исходного кода и возвращает AST и комментарии исходного кода.
# File lib/prism/translation/parser.rb, line 122
def tokenize(source_buffer, recover = false)
@source_buffer = source_buffer
source = source_buffer.source
offset_cache = build_offset_cache(source)
result =
begin
unwrap(@parser.parse_lex(source, **prism_options), offset_cache)
rescue ::Parser::SyntaxError
raise if !recover
end
program, tokens = result.value
ast = build_ast(program, offset_cache) if result.success?
[
ast,
build_comments(result.comments, offset_cache),
build_tokens(tokens, offset_cache)
]
ensure
@source_buffer = nil
end Разбирает буфер исходного кода и возвращает AST, комментарии исходного кода и токены, выданные лексером.
# File lib/prism/translation/parser.rb, line 148 def try_declare_numparam(node) node.children[0].match?(/\A_[1-9]\z/) end
Поскольку prism разрешает num-параметры за нас, нам не нужно поддерживать здесь такую логику.
Приватные методы экземпляра
# File lib/prism/translation/parser.rb, line 306 def build_ast(program, offset_cache) program.accept(Compiler.new(self, offset_cache)) end
Создаёт AST гема parser на основе AST prism.
# File lib/prism/translation/parser.rb, line 311
def build_comments(comments, offset_cache)
comments.map do |comment|
::Parser::Source::Comment.new(build_range(comment.location, offset_cache))
end
end Создаёт комментарии гема parser на основе комментариев prism.
# File lib/prism/translation/parser.rb, line 289
def build_offset_cache(source)
if source.bytesize == source.length
-> (offset) { offset }
else
offset_cache = []
offset = 0
source.each_char do |char|
char.bytesize.times { offset_cache << offset }
offset += 1
end
offset_cache << offset
end
end Prism работает со смещениями в байтах, тогда как гем parser работает со смещениями в символах. Чтобы построить AST гема parser, нам нужно выполнить это преобразование.
Если размер исходного кода в байтах совпадает с его длиной, можно просто использовать смещение напрямую. В противном случае мы создаём массив, в котором индекс — это смещение в байтах, а значение — смещение в символах.
# File lib/prism/translation/parser.rb, line 323
def build_range(location, offset_cache)
::Parser::Source::Range.new(
source_buffer,
offset_cache[location.start_offset],
offset_cache[location.end_offset]
)
end Создаёт диапазон на основе местоположения в prism.
# File lib/prism/translation/parser.rb, line 318 def build_tokens(tokens, offset_cache) Lexer.new(source_buffer, tokens, offset_cache).to_a end
Создаёт токены гема parser на основе токенов prism.
# File lib/prism/translation/parser.rb, line 167
def error_diagnostic(error, offset_cache)
location = error.location
diagnostic_location = build_range(location, offset_cache)
case error.type
when :argument_block_multi
Diagnostic.new(:error, :block_and_blockarg, {}, diagnostic_location, [])
when :argument_formal_constant
Diagnostic.new(:error, :argument_const, {}, diagnostic_location, [])
when :argument_formal_class
Diagnostic.new(:error, :argument_cvar, {}, diagnostic_location, [])
when :argument_formal_global
Diagnostic.new(:error, :argument_gvar, {}, diagnostic_location, [])
when :argument_formal_ivar
Diagnostic.new(:error, :argument_ivar, {}, diagnostic_location, [])
when :argument_no_forwarding_amp
Diagnostic.new(:error, :no_anonymous_blockarg, {}, diagnostic_location, [])
when :argument_no_forwarding_star
Diagnostic.new(:error, :no_anonymous_restarg, {}, diagnostic_location, [])
when :argument_no_forwarding_star_star
Diagnostic.new(:error, :no_anonymous_kwrestarg, {}, diagnostic_location, [])
when :begin_lonely_else
location = location.copy(length: 4)
diagnostic_location = build_range(location, offset_cache)
Diagnostic.new(:error, :useless_else, {}, diagnostic_location, [])
when :class_name, :module_name
Diagnostic.new(:error, :module_name_const, {}, diagnostic_location, [])
when :class_in_method
Diagnostic.new(:error, :class_in_def, {}, diagnostic_location, [])
when :def_endless_setter
Diagnostic.new(:error, :endless_setter, {}, diagnostic_location, [])
when :embdoc_term
Diagnostic.new(:error, :embedded_document, {}, diagnostic_location, [])
when :incomplete_variable_class, :incomplete_variable_class_3_3
location = location.copy(length: location.length + 1)
diagnostic_location = build_range(location, offset_cache)
Diagnostic.new(:error, :cvar_name, { name: location.slice }, diagnostic_location, [])
when :incomplete_variable_instance, :incomplete_variable_instance_3_3
location = location.copy(length: location.length + 1)
diagnostic_location = build_range(location, offset_cache)
Diagnostic.new(:error, :ivar_name, { name: location.slice }, diagnostic_location, [])
when :invalid_variable_global, :invalid_variable_global_3_3
Diagnostic.new(:error, :gvar_name, { name: location.slice }, diagnostic_location, [])
when :module_in_method
Diagnostic.new(:error, :module_in_def, {}, diagnostic_location, [])
when :numbered_parameter_ordinary
Diagnostic.new(:error, :ordinary_param_defined, {}, diagnostic_location, [])
when :numbered_parameter_outer_scope
Diagnostic.new(:error, :numparam_used_in_outer_scope, {}, diagnostic_location, [])
when :parameter_circular
Diagnostic.new(:error, :circular_argument_reference, { var_name: location.slice }, diagnostic_location, [])
when :parameter_name_repeat
Diagnostic.new(:error, :duplicate_argument, {}, diagnostic_location, [])
when :parameter_numbered_reserved
Diagnostic.new(:error, :reserved_for_numparam, { name: location.slice }, diagnostic_location, [])
when :regexp_unknown_options
Diagnostic.new(:error, :regexp_options, { options: location.slice[1..] }, diagnostic_location, [])
when :singleton_for_literals
Diagnostic.new(:error, :singleton_literal, {}, diagnostic_location, [])
when :string_literal_eof
Diagnostic.new(:error, :string_eof, {}, diagnostic_location, [])
when :unexpected_token_ignore
Diagnostic.new(:error, :unexpected_token, { token: location.slice }, diagnostic_location, [])
when :write_target_in_method
Diagnostic.new(:error, :dynamic_const, {}, diagnostic_location, [])
else
PrismDiagnostic.new(error.message, :error, error.type, diagnostic_location)
end
end Создаёт диагностику на основе указанной ошибки разбора prism.
# File lib/prism/translation/parser.rb, line 332
def prism_options
options = {
filepath: @source_buffer.name,
version: convert_for_prism(version),
partial_script: true,
}
# The parser gem always encodes to UTF-8, unless it is binary.
# https://github.com/whitequark/parser/blob/v3.3.6.0/lib/parser/source/buffer.rb#L80-L107
options[:encoding] = false if @source_buffer.source.encoding != Encoding::BINARY
options
end Параметры, определяющие, как prism должен выполнять разбор и лексический анализ исходного кода.
# File lib/prism/translation/parser.rb, line 267
def unwrap(result, offset_cache)
result.errors.each do |error|
next unless valid_error?(error)
diagnostics.process(error_diagnostic(error, offset_cache))
end
result.warnings.each do |warning|
next unless valid_warning?(warning)
diagnostic = warning_diagnostic(warning, offset_cache)
diagnostics.process(diagnostic) if diagnostic
end
result
end Если при разборе возникла ошибка, выбрасывает соответствующую синтаксическую ошибку. В противном случае возвращает результат.
# File lib/prism/translation/parser.rb, line 156 def valid_error?(error) true end
Этот хук позволяет пользователям отключать некоторые ошибки, если они не хотят, чтобы те препятствовали созданию синтаксического дерева.
# File lib/prism/translation/parser.rb, line 162 def valid_warning?(warning) true end
Этот хук позволяет пользователям отключать некоторые предупреждения, если они не хотят, чтобы те препятствовали созданию синтаксического дерева.
# File lib/prism/translation/parser.rb, line 240
def warning_diagnostic(warning, offset_cache)
diagnostic_location = build_range(warning.location, offset_cache)
case warning.type
when :ambiguous_first_argument_plus
Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "+" }, diagnostic_location, [])
when :ambiguous_first_argument_minus
Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "-" }, diagnostic_location, [])
when :ambiguous_prefix_ampersand
Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "&" }, diagnostic_location, [])
when :ambiguous_prefix_star
Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "*" }, diagnostic_location, [])
when :ambiguous_prefix_star_star
Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "**" }, diagnostic_location, [])
when :ambiguous_slash
Diagnostic.new(:warning, :ambiguous_regexp, {}, diagnostic_location, [])
when :dot_dot_dot_eol
Diagnostic.new(:warning, :triple_dot_at_eol, {}, diagnostic_location, [])
when :duplicated_hash_key
# skip, parser does this on its own
else
PrismDiagnostic.new(warning.message, :warning, warning.type, diagnostic_location)
end
end Создаёт диагностику на основе указанного предупреждения о разборе prism.
Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.