lib/dentaku/ast/functions/string_functions.rb



require_relative '../function'

module Dentaku
  module AST
    module StringFunctions
      class Base < Function
        def type
          :string
        end

        def negative_argument_failure(fun, arg = 'length')
          raise Dentaku::ArgumentError.for(
            :invalid_value,
            function_name: "#{fun}()"
          ), "#{fun}() requires #{arg} to be positive"
        end
      end

      class Left < Base
        def self.min_param_count
          2
        end

        def self.max_param_count
          2
        end

        def initialize(*args)
          super
          @string, @length = *@args
        end

        def value(context = {})
          string = @string.value(context).to_s
          length = Dentaku::AST::Function.numeric(@length.value(context)).to_i
          negative_argument_failure('LEFT') if length < 0
          string[0, length]
        end
      end

      class Right < Base
        def self.min_param_count
          2
        end

        def self.max_param_count
          2
        end

        def initialize(*args)
          super
          @string, @length = *@args
        end

        def value(context = {})
          string = @string.value(context).to_s
          length = Dentaku::AST::Function.numeric(@length.value(context)).to_i
          negative_argument_failure('RIGHT') if length < 0
          string[length * -1, length] || string
        end
      end

      class Mid < Base
        def self.min_param_count
          3
        end

        def self.max_param_count
          3
        end

        def initialize(*args)
          super
          @string, @offset, @length = *@args
        end

        def value(context = {})
          string = @string.value(context).to_s
          offset = Dentaku::AST::Function.numeric(@offset.value(context)).to_i
          negative_argument_failure('MID', 'offset') if offset < 0
          length = Dentaku::AST::Function.numeric(@length.value(context)).to_i
          negative_argument_failure('MID') if length < 0
          string[offset - 1, length].to_s
        end
      end

      class Len < Base
        def self.min_param_count
          1
        end

        def self.max_param_count
          1
        end

        def initialize(*args)
          super
          @string = @args[0]
        end

        def value(context = {})
          string = @string.value(context).to_s
          string.length
        end

        def type
          :numeric
        end
      end

      class Find < Base
        def self.min_param_count
          2
        end

        def self.max_param_count
          2
        end

        def initialize(*args)
          super
          @needle, @haystack = *@args
        end

        def value(context = {})
          needle = @needle.value(context)
          needle = needle.to_s unless needle.is_a?(Regexp)
          haystack = @haystack.value(context).to_s
          pos = haystack.index(needle)
          pos && pos + 1
        end

        def type
          :numeric
        end
      end

      class Substitute < Base
        def self.min_param_count
          3
        end

        def self.max_param_count
          3
        end

        def initialize(*args)
          super
          @original, @search, @replacement = *@args
        end

        def value(context = {})
          original = @original.value(context).to_s
          search = @search.value(context)
          search = search.to_s unless search.is_a?(Regexp)
          replacement = @replacement.value(context).to_s
          original.sub(search, replacement)
        end
      end

      class Concat < Base
        def self.min_param_count
          1
        end

        def self.max_param_count
          Float::INFINITY
        end

        def initialize(*args)
          super
        end

        def value(context = {})
          @args.map { |arg| arg.value(context).to_s }.join
        end
      end

      class Contains < Base
        def self.min_param_count
          2
        end

        def self.max_param_count
          2
        end

        def initialize(*args)
          super
          @needle, @haystack = *args
        end

        def value(context = {})
          @haystack.value(context).to_s.include? @needle.value(context).to_s
        end

        def type
          :logical
        end
      end
    end
  end
end

Dentaku::AST::Function.register_class(:left,       Dentaku::AST::StringFunctions::Left)
Dentaku::AST::Function.register_class(:right,      Dentaku::AST::StringFunctions::Right)
Dentaku::AST::Function.register_class(:mid,        Dentaku::AST::StringFunctions::Mid)
Dentaku::AST::Function.register_class(:len,        Dentaku::AST::StringFunctions::Len)
Dentaku::AST::Function.register_class(:find,       Dentaku::AST::StringFunctions::Find)
Dentaku::AST::Function.register_class(:substitute, Dentaku::AST::StringFunctions::Substitute)
Dentaku::AST::Function.register_class(:concat,     Dentaku::AST::StringFunctions::Concat)
Dentaku::AST::Function.register_class(:contains,   Dentaku::AST::StringFunctions::Contains)