2011-12-27 11 views
17

私はインデント依存の文法のようなPythonを実現しようとしています。ANTLRインデント依存文法のような、Pythonを実現する最も単純な方法は何ですか?

ソースの例は:

ABC QWE 
    CDE EFG 
    EFG CDE 
    ABC 
    QWE ZXC 

私は見ての通り、私は必要なものを2つのトークンのインデントとDEDENTを実現することであるので、私のような何か書くことができます:任意の簡単な方法にはあり

grammar mygrammar; 
text: (ID | block)+; 
block: INDENT (ID|block)+ DEDENT; 
INDENT: ????; 
DEDENT: ????; 

をANTLRを使用してこれを実現しますか?

(それが可能だ場合、私は、標準ANTLRのレクサーを使用することを、好むと思います。)

答えて

17

INDENT/DEDENTトークンを作成するための擬似のPythonコード私はそれを処理するための最も簡単な方法は何であるか知らないが、以下では、比較的簡単な方法です。レクサーの改行にマッチするたびに、必要に応じて1つ以上のスペースにマッチします。改行の後にスペースがある場合は、これらのスペースの長さと現在のインデントサイズを比較します。現在のインデントサイズより大きい場合はIndentトークンを発行し、現在のインデントサイズより小さい場合はDedentトークンを出力し、同じ場合は何もしないでください。

Dedentのトークンをファイルの最後に付けて、Indentのすべてに一致するDedentトークンがあるようにする必要があります。

正しく、あなた必見があなたの入力ソース・ファイルに先頭と末尾の改行を追加し、これが機能するために!

ANTRL3

迅速なデモ:あなたは今in.txtと呼ばれるファイルに次のように置く場合

import org.antlr.runtime.*; 
import org.antlr.runtime.tree.*; 
import org.antlr.stringtemplate.*; 

public class Main { 
    public static void main(String[] args) throws Exception { 
    PyEsqueLexer lexer = new PyEsqueLexer(new ANTLRFileStream("in.txt")); 
    PyEsqueParser parser = new PyEsqueParser(new CommonTokenStream(lexer)); 
    CommonTree tree = (CommonTree)parser.parse().getTree(); 
    DOTTreeGenerator gen = new DOTTreeGenerator(); 
    StringTemplate st = gen.toDOT(tree); 
    System.out.println(st); 
    } 
}  

:あなたはクラスでパーサをテストすることができ

grammar PyEsque; 

options { 
    output=AST; 
} 

tokens { 
    BLOCK; 
} 

@lexer::members { 

    private int previousIndents = -1; 
    private int indentLevel = 0; 
    java.util.Queue<Token> tokens = new java.util.LinkedList<Token>(); 

    @Override 
    public void emit(Token t) { 
    state.token = t; 
    tokens.offer(t); 
    } 

    @Override 
    public Token nextToken() { 
    super.nextToken(); 
    return tokens.isEmpty() ? Token.EOF_TOKEN : tokens.poll(); 
    } 

    private void jump(int ttype) { 
    indentLevel += (ttype == Dedent ? -1 : 1); 
    emit(new CommonToken(ttype, "level=" + indentLevel)); 
    } 
} 

parse 
: block EOF -> block 
; 

block 
: Indent block_atoms Dedent -> ^(BLOCK block_atoms) 
; 

block_atoms 
: (Id | block)+ 
; 

NewLine 
: NL SP? 
    { 
    int n = $SP.text == null ? 0 : $SP.text.length(); 
    if(n > previousIndents) { 
     jump(Indent); 
     previousIndents = n; 
    } 
    else if(n < previousIndents) { 
     jump(Dedent); 
     previousIndents = n; 
    } 
    else if(input.LA(1) == EOF) { 
     while(indentLevel > 0) { 
     jump(Dedent); 
     } 
    } 
    else { 
     skip(); 
    } 
    } 
; 

Id 
: ('a'..'z' | 'A'..'Z')+ 
; 

SpaceChars 
: SP {skip();} 
; 

fragment NL  : '\r'? '\n' | '\r'; 
fragment SP  : (' ' | '\t')+; 
fragment Indent : ; 
fragment Dedent : ; 

 

AAA AAAAA 
    BBB BB B 
    BB BBBBB BB 
    CCCCCC C CC 
    BB BBBBBB 
    C CCC 
     DDD DD D 
     DDD D DDD 

(先頭と末尾の改行に注意してください!

enter image description here

注意私のデモがaaacccからdedentingのように、連続して十分なdedentsを生成しない(2 DEDENT:)

は、次のASTに対応した出力が表示されますトークン)が必要とされています

aaa 
    bbb 
    ccc 
aaa 

あなたはおそらく以上1個のDEDENTトークンのBASを放出するelse if(n < previousIndents) { ... }内のコードを調整する必要がありますnpreviousIndentsの違いについて教えてください。このようになります。私の頭の上、オフ:ANTLR4について

else if(n < previousIndents) { 
    // Note: assuming indent-size is 2. Jumping from previousIndents=6 
    // to n=2 will result in emitting 2 `Dedent` tokens 
    int numDedents = (previousIndents - n)/2; 
    while(numDedents-- > 0) { 
    jump(Dedent); 
    } 
    previousIndents = n; 
} 

ANTLR4

、このような何か:から撮影

grammar Python3; 

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(Python3Parser.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(Python3Parser.EOF, "<EOF>")); 
    } 

    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(Python3Parser.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; 
    } 
} 

single_input 
: NEWLINE 
| simple_stmt 
| compound_stmt NEWLINE 
; 

// more parser rules 

NEWLINE 
: ({atStartOfInput()}? SPACES 
    | ('\r'? '\n' | '\r') SPACES? 
    ) 
    { 
    String newLine = getText().replaceAll("[^\r\n]+", ""); 
    String spaces = getText().replaceAll("[\r\n]+", ""); 
    int next = _input.LA(1); 
    if (opened > 0 || next == '\r' || next == '\n' || 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(Python3Parser.INDENT, spaces)); 
     } 
     else { 
     // Possibly emit more than 1 DEDENT token. 
     while(!indents.isEmpty() && indents.peek() > indent) { 
      this.emit(createDedent()); 
      indents.pop(); 
     } 
     } 
    } 
    } 
; 

// more lexer rules 

https://github.com/antlr/grammars-v4/blob/master/python3/Python3.g4

+0

Thanx、それは動作します:) – Astronavigator

+0

ようこそ@Astronavigator。 –

+0

こんにちは@Bart Kiersさん、どうやって先行および後続の改行制限を克服できますか?私はそれが、構文解析を開始する前にインデントトークンをプログラム的に発行するようにしましたが、運はありません。 – ains

3

あなたはPython ANTLR grammarで見たことがありますか?

編集:追加されました

UNKNOWN_TOKEN = 0 
INDENT_TOKEN = 1 
DEDENT_TOKEN = 2 

# filestream has already been processed so that each character is a newline and 
# every tab outside of quotations is converted to 8 spaces. 
def GetIndentationTokens(filestream): 
    # Stores (indentation_token, line, character_index) 
    indentation_record = list() 
    line = 0 
    character_index = 0 
    column = 0 
    counting_whitespace = true 
    indentations = list() 
    for c in filestream: 
     if IsNewLine(c): 
      character_index = 0 
      column = 0 
      line += 1 
      counting_whitespace = true 
     elif c != ' ' and counting_whitespace: 
      counting_whitespace = false 
      if(len(indentations) == 0): 
       indentation_record.append((token, line, character_index)) 
      else: 
       while(len(indentations) > 0 and indentations[-1] != column: 
        if(column < indentations[-1]): 
         indentations.pop() 
         indentation_record.append((
          DEDENT, line, character_index)) 
        elif(column > indentations[-1]): 
         indentations.append(column) 
         indentation_record.append((
          INDENT, line, character_index)) 

     if not IsNewLine(c): 
      column += 1 

     character_index += 1 
    while(len(indentations) > 0): 
     indentations.pop() 
     indentation_record.append((DEDENT_TOKEN, line, character_index)) 
    return indentation_record 
+0

イエップ。この文法には、インデントとデデントの規則は実装されていません。この文法は標準的なレクサーではないようです... – Astronavigator

+0

@Astronavigatorまあ、[インデントに対するPythonのレキシカル分析のアプローチ](http://docs.python.org/reference/lexical_analysis.html#indentation)を見て、それらのインデントDEDENTトークンは別のプロセスで生成されます(これはANTLRに渡す前に実行できます)。彼らの姿を見ると、はるかに簡単です。 –

+0

答えのためのThanx、JSPerfUnknownさて、ANTLRに渡す前に、慣れ親しんでいる洞察と辞めのトークンが良い点です。考えておく。今のところ私は標準的なレクサーだけを使いたいので、バートの答えを受け入れてください。 – Astronavigator

4

がありますがインデントと献辞を解析するのに役立つANTLR v4のオープンソースライブラリantlr-denterがあります。使用方法についてはREADMEをご覧ください。

文法にコピー&ペーストするコードスニペットではなく、ライブラリであるため、インデント処理は残りの文法とは別に更新できます。

1

私は実験として書いたこのANTLRを行う簡単な方法があります:Dent.g4。この解決策は、このページでKiersとShavitによって書かれたものとは異なります。 LexerのnextToken()メソッドをオーバーライドするだけでランタイムと統合されます。それはトークンを調べることによってその仕事をします:(1)NEWLINEトークンが「インデントの維持」フェーズの開始をトリガーします。 (2)チャネルHIDDENに設定された空白とコメントは、それぞれそのフェーズでカウントされ、無視されます。 (3)いずれの非トークンもフェーズを終了させる。したがって、インデント論理を制御することは、トークンのチャネルを設定することの簡単な問題である。

このページで言及されている両方の解決策では、後続のすべての空白を取得するためにトークンがNEWLINE必要ですが、空白を中断する複数行のコメントを処理できません。代わりに、DentはNEWLINEと空白トークンを分離して保持し、複数行のコメントを処理できます。

あなたの文法は以下のように設定されます。 NEWLINEルールとWSレクサールールには、pendingDentの状態を制御し、indentCount変数を使用してインデントレベルを追跡するアクションがあることに注意してください。

grammar MyGrammar; 

tokens { INDENT, DEDENT } 

@lexer::members { 
    // override of nextToken(), see Dent.g4 grammar on github 
    // https://github.com/wevrem/wry/blob/master/grammars/Dent.g4 
} 

script : (NEWLINE | statement)* EOF ; 

statement 
    : simpleStatement 
    | blockStatements 
    ; 

simpleStatement : LEGIT+ NEWLINE ; 

blockStatements : LEGIT+ NEWLINE INDENT statement+ DEDENT ; 

NEWLINE : ('\r'? '\n' | '\r') { 
    if (pendingDent) { setChannel(HIDDEN); } 
    pendingDent = true; 
    indentCount = 0; 
    initialIndentToken = null; 
} ; 

WS : [ \t]+ { 
    setChannel(HIDDEN); 
    if (pendingDent) { indentCount += getText().length(); } 
} ; 

BlockComment : '/*' (BlockComment | .)*? '*/' -> channel(HIDDEN) ; // allow nesting comments 
LineComment : '//' ~[\r\n]* -> channel(HIDDEN) ; 

LEGIT : ~[ \t\r\n]+ ~[\r\n]*; // Replace with your language-specific rules... 
関連する問題