# frozen_string_literal: true module SlimLint # Represents an atomic, childless, literal value within an S-expression. # # This creates a light wrapper around literal values of S-expressions so we # can make an {Atom} quack like a {Sexp} without being an {Sexp}. class Atom # Stores the line number of the code in the original document that this Atom # came from. attr_accessor :value, :start, :finish # Creates an atom from the specified value. # # @param value [Object] def initialize(value, pos:) @value = value @start = pos if value.is_a?(String) lines = value.count("\n") chars = 0 chars = value.lines.last.size unless value.empty? @finish = [pos[0] + lines, chars + (lines == 0 ? pos[1] : 0)] end end def line start[0] if start end def location SourceLocation.new( start_line: start[0], start_column: start[1], last_line: (finish || start)[0], last_column: (finish || start)[1] ) end # Returns whether this atom is equivalent to another object. # # This defines a helper which unwraps the inner value of the atom to compare # against a literal value, saving us having to do it ourselves everywhere # else. # # @param other [Object] # @return [Boolean] def ==(other) @value == (other.is_a?(Atom) ? other.instance_variable_get(:@value) : other) end # Returns whether this atom matches the given Sexp pattern. # # This exists solely to make an {Atom} quack like a {Sexp}, so we don't have # to manually check the type when doing comparisons elsewhere. # # @param [Array, Object] # @return [Boolean] def match?(pattern) # Delegate matching logic if we're comparing against a matcher if pattern.is_a?(SlimLint::Matcher::Base) return pattern.match?(@value) end @value == pattern end # Displays the string representation the value this {Atom} wraps. # # @return [String] def to_s @value.to_s end def to_array @value end # Displays a string representation of this {Atom} suitable for debugging. # # @return [String] def inspect range = +"" range << start.join(":") if start range << " => " if start && finish range << finish.join(":") if finish "A(#{range}) #{@value.inspect}" end # Redirect methods to the value this {Atom} wraps. # # Again, this is for convenience so we don't need to manually unwrap the # value ourselves. It's pretty magical, but results in much DRYer code. # # @param method_sym [Symbol] method that was called # @param args [Array] # @yield block that was passed to the method def method_missing(method_sym, *args, &block) if @value.respond_to?(method_sym) @value.send(method_sym, *args, &block) else super end end # @param method_name [String,Symbol] method name # @param args [Array] def respond_to_missing?(method_name, *args) @value.__send__(:respond_to_missing?, method_name, *args) || super end # Return whether this {Atom} or the value it wraps responds to the given # message. # # @param method_sym [Symbol] # @param include_private [Boolean] # @return [Boolean] def respond_to?(method_sym, include_private = false) if super true else @value.respond_to?(method_sym, include_private) end end end end