# encoding: utf-8 # frozen_string_literal: true module Carbon module Compiler # A base-level node. Contains all of the basic behaviors that a node # needs. module Node # A base-level node. Contains all of the basic behaviors that a node # needs. class Base # Creates a "mapping." This contructs a mapping between a child # and a name. For example, a for loop has four children: `initial`, # `condition`, `increment`, and `body`. This creates the association # between the numerical index of the child and the symbolic name # of the child. # # @example # Statement::For.mapping(initial: 0, condition: 1, increment: 2, # body: 3) # Statement::For.new(children).initial # => children[0] # @param data [Hash?] The data for the mapping. If this is nil, # the function returns the mapping instead of setting it. # @return [Hash, void] def self.mapping(data = nil) if data.nil? @mapping ||= Concurrent::Hash.new else @mapping = Concurrent::Hash.new.merge(data) data.each do |key, value| fail if key == :klass define_method(key) { @children[value] } end end end class << self alias_method :attribute, :mapping alias_method :attributes, :mapping end include Enumerable extend Forwardable # The node's children. This is most often an array of nodes, but can # sometimes include Scanner tokens as well. # # @return [Array] attr_reader :children # The location describing the node. This is derived solely from the # children that make up the node, and so is considered auxillary. # # @return [Location] attr_reader :location # The attributes of a node. This is mainly used for associating # directives to definitions. This is empty for most of the lifetime # of a node. # # @return [Array] attr_reader :attributes # @!method [](index) # Access a specific child at the given index. If a child exists, # it is returned; otherwise, `nil` is returned. # # @return [Node, Scanner::Token, nil] def_delegator :children, :[] # @!method each # Yields all children one by one, if a block is given. It # then returns an enumerator that enumerates over all of the # children. # # @yield [child] # @yieldparam child [Node, Scanner::Token] # @return [Enumerator] def_delegator :children, :each def_delegator :children, :to_a # Initialize the node with the given children and the data. # # @param children [Array, Scanner::Token] The children of this # node. # @param data [Hash] The associated data for this node. # @option data [Type] :type (nil) The type of the node. # @option data [Array] :attributes ([]) The attributes of the node. # @option data [Location] :location (nil) The location of the node. # @option data [Array] :components (children) The nodes to derive # the location of the node from. def initialize(children, data = {}) @children = children.dup.freeze @type = data[:type] @attributes = data.fetch(:attributes, []) if data.key?(:location) @location = data[:location] else derive_location(data.fetch(:components, children)) end freeze end # Sets the attributes of a copy of this node to the given attributes. # It then returns the copy. # # @param attributes [Array] # @return [Base] def attributes!(attributes) self.class.new(children, data.merge(attributes: attributes)) end # Sets the type of a copy of this node to the given type. # It then returns the copy. # # @param type [Type] # @return [Base] def type!(type) self.class.new(children, data.merge(type: type)) end # Sets the children of a copy of this node to the given children. # It then returns the copy. # # @param children [Array] # @return [Base] def update!(children) self.class.new(children, data) end alias_method :update, :update! # Maps the children using the given block. It then returns a copy of # the node with the new children. # # @param children [::Array] # @return [Base] def map! update!(children.map(&Proc.new)) end # Using the {ClassMethods#mapping}, it merges the given hash into # the children. # # @example # Statement::For.mapping(initial: 0, condition: 1, increment: 2, # body: 3) # stmt = Statement::For.new(children) # stmt.merge!(initial: initial).initial # => initial # @param options [{Symbol => Base}] # @return [Base] def merge!(options) merged = @children.dup options.each do |key, value| merged[attributes.fetch(key)] = value end update!(merged) end alias_method :merge, :merge! # Accepts the current visitor unto itself. # # @param visitor [#visit] # @return [Object] def accept(visitor, *arguments) visitor.visit(self, *arguments) end # Returns true if this node is a data definition. # # @return [Boolean] def data? false end # Returns true if this node is a behavior definition. # # @return [Boolean] def behavior? false end # Returns true if this node is an identity definition. # # @return [Boolean] def identity? false end def data { location: @location, attributes: @attributes, type: @type } end private def attributes self.class.attributes end def derive_location(children) @location = children.flatten.compact .select { |c| c.respond_to?(:location) } .map(&:location).inject(:|) end end end end end