当前位置: 首页 > 知识库问答 >
问题:

python解释器的ANTLR4缩进管理

温峻熙
2023-03-14

我正在使用ANTLR4实现一个python解释器,如lexer和解析器生成器。我使用了在此链接上定义的BNF:https://github.com/antlr/grammars-v4/blob/master/python3/Python3.g4.但是,当我定义复合语句时,在lexer::成员中使用INDENT和DEDENT令牌实现缩进不起作用。例如,如果我定义以下语句:

x=10
while x>2 :
      print("hello")
            x=x-3

所以在重新分配x变量值的行中,我应该有一个在currest状态下没有的缩进错误。我应该在lexer代码中编辑一些东西还是什么?这是我在lexer::成员和上面链接中定义的NEWLINE规则中使用的BNF。

grammar python;

tokens { INDENT, DEDENT }

@lexer::members {

  // A queue where extra tokens are pushed on (see the NEWLINE lexer rule).
  private java.util.LinkedList<Token> tokens = new java.util.LinkedList<>();

  // The stack that keeps track of the indentation level.
  private java.util.Stack<Integer> indents = new java.util.Stack<>();

  // The amount of opened braces, brackets and parenthesis.
  private int opened = 0;

  // The most recently produced token.
  private Token lastToken = null;

  @Override
  public void emit(Token t) {
    super.setToken(t);
    tokens.offer(t);
  }

  @Override
  public Token nextToken() {

    // Check if the end-of-file is ahead and there are still some DEDENTS expected.
    if (_input.LA(1) == EOF && !this.indents.isEmpty()) {

      // Remove any trailing EOF tokens from our buffer.
      for (int i = tokens.size() - 1; i >= 0; i--) {
        if (tokens.get(i).getType() == EOF) {
          tokens.remove(i);
        }
      }

      // First emit an extra line break that serves as the end of the statement.
      this.emit(commonToken(pythonParser.NEWLINE, "\n"));

      // Now emit as much DEDENT tokens as needed.
      while (!indents.isEmpty()) {
        this.emit(createDedent());
        indents.pop();
      }

      // Put the EOF back on the token stream.
      this.emit(commonToken(pythonParser.EOF, "<EOF>"));
      //throw new Exception("indentazione inaspettata in riga "+this.getLine());
    }

    Token next = super.nextToken();

    if (next.getChannel() == Token.DEFAULT_CHANNEL) {
      // Keep track of the last token on the default channel.
      this.lastToken = next;
    }

    return tokens.isEmpty() ? next : tokens.poll();
  }

  private Token createDedent() {
    CommonToken dedent = commonToken(pythonParser.DEDENT, "");
    dedent.setLine(this.lastToken.getLine());
    return dedent;
  }

  private CommonToken commonToken(int type, String text) {
    int stop = this.getCharIndex() - 1;
    int start = text.isEmpty() ? stop : stop - text.length() + 1;
    return new CommonToken(this._tokenFactorySourcePair, type, DEFAULT_TOKEN_CHANNEL, start, stop);
  }

  // Calculates the indentation of the provided spaces, taking the
  // following rules into account:
  //
  // "Tabs are replaced (from left to right) by one to eight spaces
  //  such that the total number of characters up to and including
  //  the replacement is a multiple of eight [...]"
  //
  //  -- https://docs.python.org/3.1/reference/lexical_analysis.html#indentation
  static int getIndentationCount(String spaces) {

    int count = 0;

    for (char ch : spaces.toCharArray()) {
      switch (ch) {
        case '\t':
          count += 8 - (count % 8);
          break;
        default:
          // A normal space char.
          count++;
      }
    }

    return count;
  }

  boolean atStartOfInput() {
    return super.getCharPositionInLine() == 0 && super.getLine() == 1;
    }
}

parse
 :( NEWLINE parse 
 | block ) EOF 
 ;

block
 : (statement NEWLINE?| functionDecl)*  
 ;

statement
 : assignment 
 | functionCall 
 | ifStatement
 | forStatement
 | whileStatement
 | arithmetic_expression
 ;

assignment
 : IDENTIFIER indexes? '=' expression 
 ;

functionCall
 : IDENTIFIER OPAREN exprList? CPAREN   #identifierFunctionCall
 | PRINT OPAREN? exprList? CPAREN?     #printFunctionCall
 ;

 arithmetic_expression
 : expression
 ;

ifStatement
 : ifStat elifStat* elseStat? 
 ;

ifStat
 : IF expression COLON NEWLINE INDENT block DEDENT
 ;

elifStat
 : ELIF expression COLON NEWLINE INDENT block DEDENT 
 ;

elseStat
 : ELSE COLON NEWLINE INDENT block DEDENT
 ;

functionDecl
 : DEF IDENTIFIER OPAREN idList? CPAREN COLON NEWLINE INDENT block DEDENT 
 ;

forStatement
 : FOR IDENTIFIER IN expression COLON NEWLINE INDENT block DEDENT elseStat?
 ;

whileStatement
 : WHILE expression COLON NEWLINE INDENT block DEDENT elseStat?
 ;

idList
 : IDENTIFIER (',' IDENTIFIER)*
 ;

exprList
 : expression (COMMA expression)*
 ;

expression
 : '-' expression                           #unaryMinusExpression
 | '!' expression                           #notExpression
 | expression '**' expression               #powerExpression
 | expression '*' expression                #multiplyExpression
 | expression '/' expression                #divideExpression
 | expression '%' expression                #modulusExpression
 | expression '+' expression                #addExpression
 | expression '-' expression                #subtractExpression
 | expression '>=' expression               #gtEqExpression
 | expression '<=' expression               #ltEqExpression
 | expression '>' expression                #gtExpression
 | expression '<' expression                #ltExpression
 | expression '==' expression               #eqExpression
 | expression '!=' expression               #notEqExpression
 | expression '&&' expression               #andExpression
 | expression '||' expression               #orExpression
 | expression '?' expression ':' expression #ternaryExpression
 | expression IN expression                 #inExpression
 | NUMBER                                   #numberExpression
 | BOOL                                     #boolExpression
 | NULL                                     #nullExpression
 | functionCall indexes?                    #functionCallExpression
 | list indexes?                            #listExpression
 | IDENTIFIER indexes?                      #identifierExpression
 | STRING indexes?                          #stringExpression
 | '(' expression ')' indexes?              #expressionExpression
 | INPUT '(' STRING? ')'                    #inputExpression
 ;

list
 : '[' exprList? ']'
 ;

indexes
 : ('[' expression ']')+
 ;

PRINT    : 'print';
INPUT    : 'input';
DEF      : 'def';
IF       : 'if';
ELSE     : 'else';
ELIF     : 'elif';
RETURN   : 'return';
FOR      : 'for';
WHILE    : 'while';
IN       : 'in';
NULL     : 'null';

OR       : '||';
AND      : '&&';
EQUALS   : '==';
NEQUALS  : '!=';
GTEQUALS : '>=';
LTEQUALS : '<=';
POW      : '**';
EXCL     : '!';
GT       : '>';
LT       : '<';
ADD      : '+';
SUBTRACT : '-';
MULTIPLY : '*';
DIVIDE   : '/';
MODULE  : '%';
OBRACE   : '{' {opened++;};
CBRACE   : '}' {opened--;};
OBRACKET : '[' {opened++;};
CBRACKET : ']' {opened--;};
OPAREN   : '(' {opened++;};
CPAREN   : ')' {opened--;};
SCOLON   : ';';
ASSIGN   : '=';
COMMA    : ',';
QMARK    : '?';
COLON    : ':';

BOOL
 : 'true' 
 | 'false'
 ;

NUMBER
 : INT ('.' DIGIT*)?
 ;

IDENTIFIER
 : [a-zA-Z_] [a-zA-Z_0-9]*
 ;

STRING
 : ["] (~["\r\n] | '\\\\' | '\\"')* ["]
 | ['] (~['\r\n] | '\\\\' | '\\\'')* [']
 ;

 SKIPS
 : ( SPACES | COMMENT | LINE_JOINING ){firstLine();} -> skip
 ;

 NEWLINE
 : ( {atStartOfInput()}?   SPACES
   | ( '\r'? '\n' | '\r' | '\f' ) SPACES?
   )
   {
     String newLine = getText().replaceAll("[^\r\n\f]+", "");
     String spaces = getText().replaceAll("[\r\n\f]+", "");
     int next = _input.LA(1);

     if (opened > 0 || next == '\r' || next == '\n' || next == '\f' || next == '#') {
       // If we're inside a list or on a blank line, ignore all indents, 
       // dedents and line breaks.
       skip();
     }
     else {
       emit(commonToken(NEWLINE, newLine));

       int indent = getIndentationCount(spaces);
       int previous = indents.isEmpty() ? 0 : indents.peek();

       if (indent == previous) {
         // skip indents of the same size as the present indent-size
         skip();
       }
       else if (indent > previous) {
         indents.push(indent);
         emit(commonToken(pythonParser.INDENT, spaces));
       }
       else {
         // Possibly emit more than 1 DEDENT token.
         while(!indents.isEmpty() && indents.peek() > indent) {
           this.emit(createDedent());
           indents.pop();
         }
       }
     }
   }
 ;

fragment INT
 : [1-9] DIGIT*
 | '0'
 ;

fragment DIGIT 
 : [0-9]
 ;

 fragment SPACES
 : [ \t]+
 ;

 fragment COMMENT
 : '#' ~[\r\n\f]*
 ;

 fragment LINE_JOINING
 : '\\' SPACES? ( '\r'? '\n' | '\r' | '\f' )
 ; 

共有1个答案

万喜
2023-03-14

不,这不应该在语法中处理。lexer应该简单地发出(错误的)INDENT标记。解析器应该在运行时产生错误。类似于这样:

String source = "x=10\n" +
        "while x>2 :\n" +
        "    print(\"hello\")\n" +
        "        x=x-3\n";

Python3Lexer lexer = new Python3Lexer(CharStreams.fromString(source));
Python3Parser parser = new Python3Parser(new CommonTokenStream(lexer));

// Remove default error-handling
parser.removeErrorListeners();

// Add custom error-handling
parser.addErrorListener(new BaseErrorListener() {
  @Override
  public void syntaxError(Recognizer<?, ?> recognizer, Object o, int i, int i1, String s, RecognitionException e) {

    CommonToken token = (CommonToken) o;

    if (token.getType() == Python3Parser.INDENT) {
      // The parser encountered an unexpected INDENT token
      // TODO throw your exception
    }

    // TODO handle other errors
  }
});

// Trigger the error
parser.file_input();
 类似资料:
  • 问题内容: 我决定学习一点Python。第一部分介绍说它使用缩进来对语句进行分组。虽然最好的习惯显然是仅使用其中一种,但是如果我互换它们会发生什么呢?多少个空格将被视为等于一个制表符?如果将制表符和空格混合使用,还是根本无法工作? 问题答案: 空格不等同于制表符。用制表符缩进的行与用1、2、4 或8个 空格缩进的行的缩进不同。 通过反例证明( 错误或至多限制-tab!= 4个空格 ): 的‘显示一

  • 当我们编写Python代码时,我们得到的是一个包含Python代码的以.py为扩展名的文本文件。要运行代码,就需要Python解释器去执行.py文件。 由于整个Python语言从规范到解释器都是开源的,所以理论上,只要水平够高,任何人都可以编写Python解释器来执行Python代码(当然难度很大)。事实上,确实存在多种Python解释器。 CPython 当我们从Python官方网站下载并安装好

  • 当我们编写Python代码时,我们得到的是一个包含Python代码的以.py为扩展名的文本文件。要运行代码,就需要Python解释器去执行.py文件。 由于整个Python语言从规范到解释器都是开源的,所以理论上,只要水平够高,任何人都可以编写Python解释器来执行Python代码(当然难度很大)。事实上,确实存在多种Python解释器。 CPython 当我们从Python官方网站下载并安装好

  • 2. Python 解释器 2.1 调用解释器 在可用的机器上,Python解释器通常安装成/usr/local/bin/python;请将/usr/local/bin放在您的Unix shell搜索路径,以使得可以通过在shell中键入命令 python 来启动它。由于解释器放置的目录是一个安装选项,其它地方也是可能的;请与您本地的Python专家或系统管理员联系。(例如,/usr/local

  • 问题内容: 我正在使用Vim并编辑Python脚本。 通常,自动缩进效果很好,但是当我开始换行并输入’#’来输入注释时,Vim会为我取消缩进。 例如,如果有 然后按回车,Vim会正确缩进 但是,如果我没有输入,而是输入,它会自动缩进 我的.vimrc文件如下。有人知道为什么会这样吗? 问题答案: 设置使Vim表现得像您为我描述的那样,而使用(这就是我倾向于使用的)Vim表现得像您希望的那样。 更新

  • 在我的作业中,我对字符串Lexer有以下描述: “字符串文字由零个或多个用双引号(”“)括起的字符组成。使用转义序列(如下所列)表示字符串中的特殊字符。在字符串文本中出现新行或EOF字符是编译时错误。 所有支持的转义序列如下: \b退格 \f formfeed \r回车 \n换行符 \t水平选项卡 \“双引号 \反斜杠 以下是字符串文字的有效示例: "这是一个包含制表符\t的字符串" "他问我:\