11

我已经写了一个生成器来实现这个技巧,但我想知道实现偏离规则的最佳方法。你将如何执行偏离规则?

不久:Off-side rule意味着在这种情况下,缩进被认为是一个句法元素。

这里是伪越位规则进行断词,在可用的形式捕捉压痕,我不想用语言来限制答案:

token NEWLINE 
    matches r"\n\ *" 
    increase line count 
    pick up and store the indentation level 
    remember to also record the current level of parenthesis 

procedure layout tokens 
    level = stack of indentation levels 
    push 0 to level 
    last_newline = none 
    per each token 
     if it is NEWLINE put it to last_newline and get next token 
     if last_newline contains something 
      extract new_level and parenthesis_count from last_newline 
      - if newline was inside parentheses, do nothing 
      - if new_level > level.top 
       push new_level to level 
       emit last_newline as INDENT token and clear last_newline 
      - if new_level == level.top 
       emit last_newline and clear last_newline 
      - otherwise 
       while new_level < level.top 
        pop from level 
        if new_level > level.top 
         freak out, indentation is broken. 
        emit last_newline as DEDENT token 
       clear last_newline 
     emit token 
    while level.top != 0 
     emit token as DEDENT token 
     pop from level 

comments are ignored before they are getting into the layouter 
layouter lies between a lexer and a parser 

这制图器在不产生超过一个NEWLINE时间,并且在缩进出现时不会生成NEWLINE。因此解析规则很简单。我觉得这很不错,但是告知是否有更好的方法来完成它。

虽然使用了这一段时间,但我注意到,在DEDENTs之后,无论如何都可以很好地发出newline,这样,您可以将NEWLINE的表达式分开,同时将INDENT DEDENT作为表达的预告片。

回答

8

在过去几年里,我已经为几种以缩进为中心的领域特定语言编写了标记器和解析器,并且您在那里看到的东西对我来说看起来相当合理,无论值多少钱。如果我没有弄错,那么你的方法和Python所做的很相似,例如,它看起来应该有一定的权重。

将NEWLINE NEWLINE INDENT转换为INDENT在命中解析器之前绝对看起来像是正确的做事方式 - 在解析器中始终在前面窥视它是一种痛苦(IME)!实际上,我已经将这一步作为一个单独的层来完成,最终成为一个三步过程:第一个步骤结合了您的词法分析器和layouter所做的减去所有NEWLINE lookahead的东西(这使得它非常简单),第二个(也非常简单)层连续折叠NEWLINE并将NEWLINE INDENT转换为INDENT(或实际上,COLON NEWLINE INDENT转换为INDENT,因为在这种情况下,所有缩进块总是以冒号开头),那么解析器就是第三个阶段。但是对于我来说,按照您描述的方式进行操作也是非常有意义的,特别是如果您想将词法分析器与layouter分开时,假设您使用的是代码生成工具例如,按照惯例,制作词法分析器。

我确实有有点更加灵活的缩进规则,留下基本解析器在需要时强制执行他们需要一个应用 - 下面的需要是在某些情况下有效,例如:

this line introduces an indented block of literal text: 
    this line of the block is indented four spaces 
    but this line is only indented two spaces 

INDENT/DEDENT标记不能很好地工作,因为您最终需要为每列缩进生成一个INDENT,并在返回时生成相同数量的DEDENT,除非您想方设法找出哪里缩进级别最终会出现,这似乎并不像一个标记器那样。在这种情况下,我尝试了一些不同的事情,最后只在每个NEWLINE标记中存储了一个计数器,以便为以下逻辑行改变缩进(正值或负值)。 (每个标记还存储所有尾随空白,以防需要保留;对于NEWLINE,存储的空白包括EOL本身,任何中间空白行和下一个逻辑行上的缩进。)根本没有单独的INDENT或DEDENT标记。让解析器处理这个问题比嵌套INDENT和DEDENT要多一点,而且可能是一个复杂的语法,需要一个花哨的解析器生成器,但它并没有我担心的那么糟糕,无论是。同样,不需要解析器从NEWLINE向前看,看看是否有INDENT出现在这个方案中。

尽管如此,我认为你会同意在标记器/布局器中允许和保留所有疯狂的空格,并让解析器决定什么是文字,什么是代码是一个不寻常的要求!例如,如果您只是想解析Python代码,那么您肯定不希望解析器受到缩进计数器的影响。你做事情的方式几乎肯定是你的应用程序和其他许多方面的正确方法。虽然如果任何人有想法如何最好做这种事情,我显然喜欢听到他们......

3

我最近一直在试验这个,我得出结论,为我的需要至少,我希望NEWLINES标记每个“声明”的结尾,不管它是否是最后一个声明,也就是我在DEDENT之前需要换行符。

我的解决方案是打开它的头,而不是NEWLINES标记行的末尾,我使用LINE令牌来标记行的开始。

我有一个词法分析器,用于折叠空行(包括仅包含注释的行)并发出一个带有关于最后一行缩进信息的LINE标记。然后我的预处理函数接受这个标记流,并在缩进改变的任何行之间添加INDENT或DEDENT“in”。所以

line1 
    line2 
    line3 
line4 

会给标流

LINE "line1" INDENT LINE "line2" LINE "line3" DEDENT LINE "line4" EOF 

这让我写的声明明确语法产生,而不用担心检测报告结束时竟然以嵌套的,凹陷的子块,东西结束如果您将NEWLINES(和DEDENTS)匹配,那么这可能很难。

这里是预处理,写在O'Caml的核心:

match next_token() with 
     LINE indentation -> 
     if indentation > !current_indentation then 
      (
      Stack.push !current_indentation indentation_stack; 
      current_indentation := indentation; 
      INDENT 
     ) 
     else if indentation < !current_indentation then 
      (
      let prev = Stack.pop indentation_stack in 
       if indentation > prev then 
       (
        current_indentation := indentation; 
        BAD_DEDENT 
       ) 
       else 
       (
        current_indentation := prev; 
        DEDENT 
       ) 
     ) 
     else (* indentation = !current_indentation *) 
      let token = remove_next_token() in 
      if next_token() = EOF then 
       remove_next_token() 
      else 
       token 
    | _ -> 
     remove_next_token() 

我还没有添加括号还支持,但应该是一个简单的扩展。它确实,但是避免在文件末尾发出一个流浪的LINE。

+0

您的代码无法发出多个DEDENT,它在EOF之前都没有考虑过缩进。它对某些事物可能有用,但这些事情比括号支持更重要。 – Cheery 2009-06-04 09:31:51

1

标记者在红宝石的乐趣:

def tokenize(input) 
    result, prev_indent, curr_indent, line = [""], 0, 0, "" 
    line_started = false 

    input.each_char do |char| 

    case char 
    when ' ' 
     if line_started 
     # Content already started, add it. 
     line << char 
     else 
     # No content yet, just count. 
     curr_indent += 1 
     end 
    when "\n" 
     result.last << line + "\n" 
     curr_indent, line = 0, "" 
     line_started = false 
    else 
     # Check if we are at the first non-space character. 
     unless line_started 
     # Insert indent and dedent tokens if indentation changed. 
     if prev_indent > curr_indent 
      # 2 spaces dedentation 
      ((prev_indent - curr_indent)/2).times do 
      result << :DEDENT 
      end 
      result << "" 
     elsif prev_indent < curr_indent 
      result << :INDENT 
      result << "" 
     end 

     prev_indent = curr_indent 
     end 

     # Mark line as started and add char to line. 
     line_started = true; line << char 
    end 

    end 

    result 
end 

确实为两个空间缩进唯一的工作。结果是类似于["Hello there from level 0\n", :INDENT, "This\nis level\ntwo\n", :DEDENT, "This is level0 again\n"]