代码之家  ›  专栏  ›  技术社区  ›  Andy

Ruby中的解析器:处理粘性注释和引号

  •  1
  • Andy  · 技术社区  · 14 年前

    我试图用Ruby为一种语法创建一个递归的下降解析器,它由以下规则定义

    1. 输入 包括 白色空间 分离的 从A开始 停止字 , 在哪里? 白色空间 是正则表达式 /[ \n\t]+/
    2. 卡可以包括 关键词 或/和 价值观 也被空白隔开, 有卡特定的顺序/模式
    3. 所有停止词和关键字都不区分大小写,即: /^[a-z]+[a-z0-9]*$/i
    4. 值可以是 双引号字符串 ,不能与 用空格表示的其他单词,例如:

      word"quoted string"word
      
    5. 值也可以是 单词 /^[a-z]+[a-z0-9]*$/ 整数 浮动 (例如) -1.15 1.0e+2 )

    6. 单行注释 # 不可与 其他词,例如:

      word#single-line comment\n
      
    7. 多行注释 /* */ 也许不是 与其他词分开,例如:

      word/*multi-line 
      comment*/word
      

    # Input example. Stop-words are chosen just to highlight them: set, object
    set title"Input example"set objects 2#not-separated by white-space. test: "/*
    set test "#/*"
    object 1 shape box/* shape is a Keyword, 
    box is a Value. test: "#*/object 2 shape sphere
    set data # message and complete are Values
    0 0 0 0 1 18 18 18 1 35 35 35 72 35 35 # all numbers are Values of the Card "set"
    

    因为大多数单词都是用空格分隔的,所以有一段时间我一直在考虑将整个输入拆分并逐字解析。为了处理评论和引用,我打算

    words = input_text.gsub( /([\"\#\n]|\/\*|\*\/)/, ' \1 ' ).split( /[ \t]+/ )
    

    但是,以这种方式修改字符串(和注释,如果我想保留它们的话)的内容。你会如何处理这些粘乎乎的评论和引用?

    1 回复  |  直到 14 年前
        1
  •  0
  •   Community miroxlav    7 年前

    好的,我自己做的。如果不需要可读性,可以最小化以下代码

    class WordParser
      attr_reader :words
    
      def initialize text
        @text = text
      end
    
      def parse
        reset_parser
        until eof?
          case curr_char
            when '"' then
              start_word and add_chars_until? '"'
              close_word
            when '#','%' then
              start_word and add_chars_until? "\n"
              close_word
            when '/' then
              if next_is? '*' then
                start_word and 2.times { add_char }
                add_char until curr_is? '*' and next_is? '/' or eof?
                2.times { add_char } unless eof?
                close_word
              else
                # parser_error "unexpected symbol '/'" # if not allowed in the grammar
                start_word unless word_already_started?
                add_char
              end
            when /[^\s]/ then
              start_word unless word_already_started?
              add_char
          else # skip whitespaces etc. between words
            move and close_word
          end
        end
        return @words
      end
    
    private
    
      def reset_parser
        @position = 0
        @line, @column = 1, 1
        @words = []
        @word_started = false
      end
    
      def parser_error s
        Kernel.puts 'Parser error on line %d, col %d: ' + s
        raise 'Parser error'
      end
    
      def word_already_started?
        @word_started
      end
    
      def close_word
        @word_started = false
      end
    
      def add_chars_until? ch
        add_char until next_is? ch or eof?
        2.times { add_char } unless eof?
      end
    
      def add_char
        @words.last[:to] = @position
        # @words.last[:length] += 1
        # @word.last += curr_char # if one just collects words
        move
      end
    
      def start_word
        @words.push from: @position, to: @position, line: @line, column: @column
        # @words.push '' unless @words.last.empty? # if one just collects words
        @word_started = true
      end
    
      def move
        increase :@position
        return if eof?
        if prev_is? "\n"
          increase :@line
          reset :@column
        else
          increase :@column
        end
      end
    
      def reset var; instance_variable_set(var, 1) end
      def increase var; instance_variable_set(var, instance_variable_get(var)+1) end
    
      def eof?; @position >= @text.length end
    
      def prev_is? ch; prev_char == ch end
      def curr_is? ch; curr_char == ch end
      def next_is? ch; next_char == ch end
    
      def prev_char; @text[ @position-1 ] end
      def curr_char; @text[ @position   ] end
      def next_char; @text[ @position+1 ] end
    end
    

    使用我问题中的示例进行测试

    words = WordParser.new(text).parse
    p words.collect { |w| text[ w[:from]..w[:to] ] } .to_a
    
    # >> ["# Input example. Stop-words are chosen just to highlight them: set, object\n", 
    # >>  "set", "title", "\"Input example\"", "set", "objects", "2", 
    # >>  "#not-separated by white-space. test: \"/*\n", "set", "test", "\"#/*\"", 
    # >>  "object", "1", "shape", "box", "/* shape is a Keyword, \nbox is a Value. test: \"#*/", 
    # >>  "object", "2", "shape", "sphere", "set", "data", "# message and complete are Values\n", 
    # >>  "0", "0", "0", "0", "1", "18", "18", "18", "1", "35", "35", "35", "72", 
    # >>  "35", "35", "# all numbers are Values of the Card \"set\"\n"]
    

    所以现在我可以用 something like this 进一步分析单词。