lib/unparser/emitter.rb in unparser-0.4.9 vs lib/unparser/emitter.rb in unparser-0.5.0
- old
+ new
@@ -2,38 +2,24 @@
module Unparser
UnknownNodeError = Class.new(ArgumentError)
# Emitter base class
- #
- # buggy, argument values are sends to self
- #
- # ignore :reek:TooManyMethods
class Emitter
- include Adamantium::Flat, AbstractType, Constants, NodeHelpers
- include Concord.new(:node, :parent)
+ include Adamantium::Flat, AbstractType, Constants, Generation, NodeHelpers
+ include Anima.new(:buffer, :comments, :node, :local_variable_scope)
+
+ public :node
+
extend DSL
# Registry for node emitters
- REGISTRY = {} # rubocop:disable MutableConstant
+ REGISTRY = {} # rubocop:disable Style/MutableConstant
- NOINDENT = %i[rescue ensure].to_set.freeze
+ NO_INDENT = %i[ensure rescue].freeze
- module Unterminated
- def terminated?
- false
- end
- end
-
- module Terminated
- def terminated?
- true
- end
- end
-
module LocalVariableRoot
-
# Return local variable root
#
# @return [Parser::AST::Node]
#
# @api private
@@ -45,37 +31,12 @@
def self.included(descendant)
descendant.class_eval do
memoize :local_variable_scope
end
end
-
end # LocalVariableRoot
- # Return local variable root
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def local_variable_scope
- parent.local_variable_scope
- end
-
- # Return assigned lvars
- #
- # @return [Array<Symbol>]
- #
- # @api private
- #
- abstract_method :local_variables
-
- # Return node type
- #
- # @return [Symbol]
- #
- # @api private
- #
def node_type
node.type
end
# Register emitter for type
@@ -86,412 +47,49 @@
#
# @api private
#
def self.handle(*types)
types.each do |type|
+ fail "Handler for type: #{type} already registered" if REGISTRY.key?(type)
+
REGISTRY[type] = self
end
end
private_class_method :handle
- # Trigger write to buffer
- #
- # @return [self]
- #
- # @api private
- #
- def write_to_buffer
- emit_comments_before if buffer.fresh_line?
+ def emit_mlhs
dispatch
- comments.consume(node)
- emit_eof_comments if parent.is_a?(Root)
- self
end
- memoize :write_to_buffer
# Return emitter
#
# @return [Emitter]
#
# @api private
#
- def self.emitter(node, parent)
+ # rubocop:disable Metrics/ParameterLists
+ def self.emitter(buffer:, comments:, node:, local_variable_scope:)
type = node.type
+
klass = REGISTRY.fetch(type) do
- raise UnknownNodeError, "Unknown node type: #{type.inspect}"
+ fail UnknownNodeError, "Unknown node type: #{type.inspect}"
end
- klass.new(node, parent)
- end
- # Dispatch node
- #
- # @return [undefined]
- #
- # @api private
- #
- abstract_method :dispatch
-
- # Test if node is emitted as terminated expression
- #
- # @return [Boolean]
- #
- # @api private
- #
- abstract_method :terminated?
-
- protected
-
- # Return buffer
- #
- # @return [Buffer] buffer
- #
- # @api private
- #
- def buffer
- parent.buffer
+ klass.new(
+ buffer: buffer,
+ comments: comments,
+ local_variable_scope: local_variable_scope,
+ node: node
+ )
end
- memoize :buffer, freezer: :noop
+ # rubocop:enable Metrics/ParameterLists
- # Return comments
+ # Dispatch node write as statement
#
- # @return [Comments] comments
- #
- # @api private
- #
- def comments
- parent.comments
- end
- memoize :comments, freezer: :noop
-
- private
-
- # Emit contents of block within parentheses
- #
# @return [undefined]
#
# @api private
#
- def parentheses(open = M_PO, close = M_PC)
- write(open)
- yield
- write(close)
- end
-
- # Visit node
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def visit_plain(node)
- emitter = emitter(node)
- emitter.write_to_buffer
- end
-
- # Visit ambiguous node
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def visit(node)
- emitter = emitter(node)
- conditional_parentheses(!emitter.terminated?) do
- emitter.write_to_buffer
- end
- end
-
- # Visit within parentheses
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def visit_parentheses(node, *arguments)
- parentheses(*arguments) do
- visit_plain(node)
- end
- end
-
- # Call block in optional parentheses
- #
- # @param [true, false] flag
- #
- # @return [undefined]
- #
- # @api private
- #
- # ignore :reek:ControlParameter
- def conditional_parentheses(flag)
- if flag
- parentheses { yield }
- else
- yield
- end
- end
-
- # Return emitter for node
- #
- # @param [Parser::AST::Node] node
- #
- # @return [Emitter]
- #
- # @api private
- #
- def emitter(node)
- self.class.emitter(node, self)
- end
-
- # Emit delimited body
- #
- # @param [Enumerable<Parser::AST::Node>] nodes
- #
- # @return [undefined]
- #
- # @api private
- #
- def delimited_plain(nodes)
- delimited(nodes, &method(:visit_plain))
- end
-
- # Emit delimited body
- #
- # @param [Enumerable<Parser::AST::Node>] nodes
- #
- # @return [undefined]
- #
- # @api private
- #
- def delimited(nodes, &block)
- return if nodes.empty?
-
- block ||= method(:visit)
- head, *tail = nodes
- block.call(head)
- tail.each do |node|
- write(DEFAULT_DELIMITER)
- block.call(node)
- end
- end
-
- # Return children of node
- #
- # @return [Array<Parser::AST::Node>]
- #
- # @api private
- #
- def children
- node.children
- end
-
- # Write newline
- #
- # @return [undefined]
- #
- # @api private
- #
- def nl
- emit_eol_comments
- buffer.nl
- end
-
- # Write comments that appeared before source_part in the source
- #
- # @param [Symbol] source_part
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_comments_before(source_part = :expression)
- comments_before = comments.take_before(node, source_part)
- return if comments_before.empty?
-
- emit_comments(comments_before)
- buffer.nl
- end
-
- # Write end-of-line comments
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_eol_comments
- comments.take_eol_comments.each do |comment|
- write(WS, comment.text)
- end
- end
-
- # Write end-of-file comments
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_eof_comments
- emit_eol_comments
- comments_left = comments.take_all
- return if comments_left.empty?
-
- buffer.nl
- emit_comments(comments_left)
- end
-
- # Write each comment to a separate line
- #
- # @param [Array] comments
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_comments(comments)
- max = comments.size - 1
- comments.each_with_index do |comment, index|
- if comment.type.equal?(:document)
- buffer.append_without_prefix(comment.text.chomp)
- else
- write(comment.text)
- end
- buffer.nl if index < max
- end
- end
-
- # Write strings into buffer
- #
- # @return [undefined]
- #
- # @api private
- #
- def write(*strings)
- strings.each do |string|
- buffer.append(string)
- end
- end
-
- # Write end keyword
- #
- # @return [undefined]
- #
- # @api private
- #
- def k_end
- buffer.indent
- emit_comments_before(:end)
- buffer.unindent
- write(K_END)
- end
-
- # Return first child
- #
- # @return [Parser::AST::Node]
- # if present
- #
- # @return [nil]
- # otherwise
- #
- # @api private
- #
- def first_child
- children.first
- end
-
- # Write whitespace
- #
- # @return [undefined]
- #
- # @api private
- #
- def ws
- write(WS)
- end
-
- # Call emit contents of block indented
- #
- # @return [undefined]
- #
- # @api private
- #
- # False positive:
- #
- def indented
- buffer = buffer()
- buffer.indent
- nl
- yield
- nl
- buffer.unindent
- end
-
- # Emit non nil body
- #
- # @param [Parser::AST::Node] body
- #
- # @return [undefined]
- #
- # @api private
- #
- # rubocop:disable MethodCallWithoutArgsParentheses
- def emit_body(body = body())
- unless body
- buffer.indent
- nl
- buffer.unindent
- return
- end
- visit_indented(body)
- end
- # rubocop:enable MethodCallWithoutArgsParentheses
-
- # Visit indented node
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def visit_indented(node)
- if NOINDENT.include?(node.type)
- visit_plain(node)
- else
- indented { visit_plain(node) }
- end
- end
-
- # Return parent type
- #
- # @return [Symbol]
- # if parent is present
- #
- # @return [nil]
- # otherwise
- #
- # @api private
- #
- def parent_type
- parent.node_type
- end
-
- # Delegate to emitter
- #
- # @param [Class:Emitter] emitter
- #
- # @return [undefined]
- #
- # @api private
- #
- # rubocop:disable MethodCallWithoutArgsParentheses
- def run(emitter, node = node())
- emitter.new(node, self).write_to_buffer
- end
- # rubocop:enable MethodCallWithoutArgsParentheses
+ abstract_method :dispatch
end # Emitter
end # Unparser