kiyonori-matsumoto/tcl-ruby

View on GitHub
lib/tcl/ruby/parser.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'strscan'

module Tcl
  module Ruby
    class Parser
      EX_CHAR_CHECK = lambda do |s, t|
        (t && !s.check(/\s|\z/)) || (!t && !s.check(/\s|\z|;/))
      end.curry

      class << self

        def parse(str, to_list = false)
          init(str)
          until @s.empty?
            if @s.scan(/\\./) then @buffer << @s[0]
            elsif @s.scan(/\#/) then parse_comments
            elsif !to_list && @s.scan(/\r\n|\r|\n|;/) then parse_command_ends
            elsif @s.scan(/\s/) then parse_blanks
            elsif @s.scan(/\S/) then parse_chars(to_list)
            end
          end
          check_pstack
          to_list ? @list_array.to_string : @commands
        end

        private

        def init(str)
          str2 = (str + "\n").gsub(/\\\n\s*/, ' ') # replace \ & \n & extra ws
          @s = StringScanner.new(str2)
          @list_array = ListArray.new
          @pstack = [] # stack for brace, bracket, quote
          @buffer = '' # ListElement.new('')'' # buffer for list element
          @commands = []
        end

        def check_pstack
          raise(ParseError, "unmatched #{@pstack.last}s") if @pstack.any?
        end

        def parse_command_ends
          bl = @s[0]
          if @pstack.empty?
            @list_array << @buffer
            @list_array.to_string
            @commands << @list_array.dup
            @list_array.clear
          else
            @buffer << bl
          end
        end

        def parse_blanks
          @pstack.empty? ? @list_array << @buffer : @buffer << @s[0]
        end

        def parse_comments
          if @buffer.empty? && @list_array.empty?
            @s.scan(/.+$/)
          else
            @buffer << @s[0]
          end
        end

        def parse_chars(to_list)
          @buffer << @s[0]
          analyze_parentheses(to_list, EX_CHAR_CHECK[@s]) if
            @buffer.parenthesis?
        end

        def matched_parentheses(id, has_ex_char)
          if @pstack.last == id
            r = @pstack.pop
            if @pstack.empty? && has_ex_char
              raise(ParseError, "extra characters after close-#{id}")
            end
            r
          end
        end

        def analyze_braces(to_list, extra_characters_check)
          if @buffer[-1] == '{'
            @pstack.push(:brace) if @pstack.last != :quote
          else
            matched_parentheses(:brace, extra_characters_check[to_list])
          end
        end

        def analyze_quotes(to_list, extra_characters_check)
          unless matched_parentheses(:quote, extra_characters_check[to_list])
            @pstack.push :quote if @pstack.last != :brace
          end
        end

        def analyze_brackets
          if @buffer[-1] == '[' && @pstack.last != :brace
            @pstack.push :bracket
          elsif @pstack.last == :bracket
            @pstack.pop
          end
        end

        def analyze_parentheses(to_list, extra_characters_check)
          bl = @buffer[-1]
          case bl
          when '{', '}' then analyze_braces(to_list, extra_characters_check)
          when '[', ']' then analyze_brackets unless to_list
          when '"' then analyze_quotes(to_list, extra_characters_check)
          end
        end
      end
    end
  end
end