Compiler construction 在扫描仪中备份的替代方案?

Compiler construction 在扫描仪中备份的替代方案?,compiler-construction,language-theory,Compiler Construction,Language Theory,我有时发现自己在扫描仪中备份。下面是一个典型的例子: private static final JSON_Value readArray( StringBuffer sbContent, StringBuffer sbError ){ JSON_Value value_array = JSON_Value.createNull(); char c; BeginningOfArray: while( true ){ p

我有时发现自己在扫描仪中备份。下面是一个典型的例子:

    private static final JSON_Value readArray( StringBuffer sbContent, StringBuffer sbError ){
        JSON_Value value_array = JSON_Value.createNull();
        char c;
BeginningOfArray:
        while( true ){
            pos++;
            c = sbContent.charAt( pos - 1 );
            switch( c ){
                case '\n': case '\r': case '\t': case ' ': case '\f': // blank space at beginning of array
                    continue;
                case ']':
                    return JSON_Value.createEmptyArray();
                case ',':
                    value_array.size = 1;
                    break BeginningOfArray; // process next value
                default:
                    pos--;  <-- BACKING UP
                    value_array.size = 0;
                    break BeginningOfArray; // process next value
            }
        }
        while( true ){
            pos++;
            c = sbContent.charAt( pos - 1 );
            switch( c ){
            ... etc
私有静态最终JSON_值readArray(StringBuffer sbContent,StringBuffer sbError){
JSON_Value_数组=JSON_Value.createNull();
字符c;
开始法拉利:
while(true){
pos++;
c=sbContent.charAt(位置-1);
开关(c){
案例“\n”:案例“\r”:案例“\t”:案例“”:案例“\f”://数组开头的空格
持续
案例']':
返回JSON_值。createEmptyArray();
案例',':
值_array.size=1;
break BeginingOfarray;//处理下一个值
违约:

pos--;这取决于所分析语言的词法结构的精确性质,但通常可以消除备份。但是,将其保留在其中通常更方便,从而避免一定数量的代码重复和/或笨拙的控制结构

<> P>精确地描述你所说的“备份”是有用的,因为我通常不会把你的问题中的例子看作是一个例子。绝大多数令牌都不能被识别,而不必看标记后面的字符(例如,标识符一直延伸到下一个字符不是字母数字)。虽然在某些工作中可以避免,但终止字符的两次考虑是正常的:一次确定终止字符不能成为前一个令牌的一部分,然后再次确定它可能发起什么样的令牌。 这看起来是否像是备份,在某种程度上取决于您用于读取输入的范例。一般来说,有两种可能的范例(以及许多不完全适合任何一种模型的混合匹配实现):

  • peek
    accept

    peek
    返回下一个输入字符而不修改读取光标的位置,因此两次连续的peek将看到同一个字符。
    accept
    将读取光标移动到下一个位置而不返回任何内容。因此,处理一个字符将需要一个(或多个)字符
    peek
    操作,并且只有一个
    accept

    这可能是更有效的范例,但是手工编写有点麻烦,因为需要显式的
    accept

    对于一个简单的内存输入缓冲区,
    peek()
    基本上是
    返回inputBuffer.charAt(pos);
    accept()
    ++pos;
    ;这两者通常都是由编译器或程序员排列的

  • get
    unaccept

    get
    返回下一个输入字符并将读取光标向前移动一个位置,以便下一个
    get
    将读取下一个字符。
    uncept
    将读取光标向后移动一个字符,以便下一个
    get
    将重新读取上一个字符。(这与标准C库的
    ungect
    不太一样,后者允许在读取缓冲区中插入不同的字符,并且可能允许取消一个以上的字符。稍后将详细介绍。)

    对于简单的内存输入缓冲区,
    get()
    基本上是
    c=peek();accept();return c;
    unaccept()
    --pos
    。同样,它们通常是内联的

  • 由于
    get();uncept();
    序列与
    peek();
    序列完全相同,因此这两种范式是完全同构的。尽管
    uncept()
    包含一个减量到
    pos
    ,但我很难将其视为“备份”它只是简单地重读一个字符,就像使用
    peek/accept
    范式一样

    高度简化的部分词法分析器(非上下文)的摘录可能如下所示:

    c = peek();
    while (isblank(c))   { accept(); c = peek(); }
    if (c == '<') {
      accept();
      c = peek();
      if (c == '=')      { accept(); return Token_LessEquals; }
      else if (c == '<') { accept(); return Token_LeftShift; }
      else return Token_Less;
    }
    if (isalpha(c)) {
      tstart = pos;
      accept();
      while (isalnum(peek())) { accept(); }
      return makeIdToken(tstart, pos);
    }
    // etc.
    

    现在,唯一的
    unaccept()
    被安全地隐藏在tokeniser的序言中。(实际上,这是Flex使用的样式。“冗余的”
    accept()
    s实际上隐藏在转换表中。没有“token accept”动作,所以在看到我喜欢你的答案的第二个字符之后,没有想到令牌终止符的双扫描。你的推解析器想法很可爱。你可以考虑处理它的另一种方式是通过启动状态转换来替换接受状态并标记为接受状态,然后将路径扩展为FSA。通过转换将接受状态返回FSA。然后lexer只需在发现令牌时保持“当前状态”,重新启动时,它将根据启动状态转换的结果重新启动。只需检查一个字符。必须尝试以下操作:-}@ira:这在简单的情况下可以正常工作,但在许多标准的flex操作中,尤其是在启动条件下,它不能很好地发挥作用。
    less()
    也是有问题的。
    ^
    如果您对dfa做一点工作,就可以处理模式。我已经通过让特殊操作设置一个标志来解决这些问题,以便条目可以重新计算状态;因为理论上您可以在外部调用
    开始
    较少
    ,所以无法在codegen中执行。这有点复杂essy但是codegen总是这样。我认为这是一个小胜利。是否值得争论是另一个问题。在同一个操作中将令牌终止符作为第二个令牌来处理是一个推式解析器改编,这是我在SGML解析器中见过的一个hack。最初的hack向解析器返回了一个复合令牌,这显然使语法变得复杂。但是更大的表并不会让事情慢下来,就像避免使用switch语句会让事情加快一样。我想,我不需要担心Flex或它的混乱
    c = get();
    while (isblank(c))     { c = get(); }
    if (c == '<') {
      c = get();
      if (c == '=')        { return Token_LessEquals; }
      else if (c == '<')   { return Token_LeftShift; }
      else                 { unaccept(); return Token_Less; }
    }
    if (isalpha(c)) {
      tstart = pos - 1;
      while (isalnum(get())) { }
      unaccept();
      return makeIdToken(tstart, pos);
    }
    // etc.
    
    unaccept();
    c = get();
    while (isblank(c))     { c = get(); }
    if (c == '<') {
      c = get();
      if (c == '=')        { accept(); return Token_LessEquals; }
      else if (c == '<')   { accept(); return Token_LeftShift; }
      else                 { return Token_Less; }
    }
    if (isalpha(c)) {
      tstart = pos - 1;
      while (isalnum(get())) { }
      return makeIdToken(tstart, pos);
    }
    // etc.
    
    c = get();   /* Start by reading the first character */
    for (;;) {   /* Token loop assumes that every action will read a lookahead */
      while (isblank(c))   { c = get(); }
      if (c == '<') {
        c = get();
        if (c == '=')      { c = get(); parse(Token_LessEquals); }
        else if (c == '<') { c = get(); parse(Token_LeftShift); }
        else               {            parse(Token_Less); }
      }
      if (isalpha(c)) {
        tstart = pos - 1;
        while (isalnum(get())) { }
        parse(makeIdToken(tstart, pos));
      }
      // etc.
    }
    
    ["][^"\n]*["]    { yylval = strndup(yytext + 1, yyleng - 2); return STRING; }
      // ...
    .                { return *yytext; /* Standard fallback rule */ }