【问题标题】:Parser in Ruby: dealing with sticky comments and quotesRuby 中的解析器:处理粘性注释和引号
【发布时间】:2010-07-29 13:56:57
【问题描述】:

我正在尝试在 Ruby 中为语法创建递归下降解析器,该语法由以下规则定义

  1. 输入空格分隔的卡片组成,以停用词开头, 其中 white-space 是正则表达式 /[ \n\t]+/
  2. 卡片可能包含 Keywords 或/和 Values 也由空格分隔, 具有特定于卡片的顺序/模式
  3. 所有停用词和关键字都不区分大小写,即:/^[a-z]+[a-z0-9]*$/i
  4. 值可以是一个双引号的字符串,可以不分开 用空格表示的其他词,例如:

    word"quoted string"word
    
  5. 值也可以是 word /^[a-z]+[a-z0-9]*$/,或 integer,或 float(例如 -1.15,或 @ 987654328@)

  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"

由于大多数单词都用空格分隔,有一段时间我在考虑拆分整个输入并逐字解析。为了处理 cmets 和引号,我打算这样做

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

但是,以这种方式修改了字符串(和 cmets,如果我想保留它们)的内容。您将如何处理这些粘性 cmets 和引号?

【问题讨论】:

  • 我不认为在空格上分割文本对于解析除了最简单的语法之外的任何内容都是一个好主意。我不想在这里写一篇关于创建解析器的文章......无论如何,谷歌搜索“compiler compiler ruby​​”,“parser generation ruby​​”......这是一个例子treetop.rubyforge.org
  • 嗯,树顶对我来说有点难以理解。也许你能告诉我如何将它应用到我的语法中?我认为对于这么简单的语法,我可以在 SO 用户的帮助下自己制作一些东西。

标签: ruby parsing recursive-descent


【解决方案1】:

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

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 进一步解析单词。

【讨论】:

    猜你喜欢
    • 2022-01-10
    • 2012-09-26
    • 2020-12-29
    • 2013-06-04
    • 1970-01-01
    • 1970-01-01
    • 2017-07-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多