2011-12-27 9 views
17

मैं इंडेंट-निर्भर व्याकरण की तरह पाइथन का एहसास करने की कोशिश कर रहा हूं।एएनटीएलआर इंडेंट-निर्भर व्याकरण की तरह पाइथन को समझने का सबसे आसान तरीका क्या है?

स्रोत उदाहरण:

ABC QWE 
    CDE EFG 
    EFG CDE 
    ABC 
    QWE ZXC 

तक मैं देख, मैं क्या जरूरत है दो टोकन इंडेंट और DEDENT साकार करने के लिए है, इसलिए मैं कुछ ऐसा लिख ​​सकते हैं: वहाँ के लिए किसी भी सरल तरीका है

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

एएनटीएलआर का उपयोग करके इसका एहसास है?

(मैं पसंद करते हैं, यदि यह संभव है, मानक ANTLR lexer उपयोग करने के लिए।)

उत्तर

17

मैं सबसे आसान तरीका क्या इसे संभाल करने के लिए पता नहीं है है टोकन, लेकिन अगले एक अपेक्षाकृत आसान तरीका है। जब भी आप अपने लेक्सर में लाइन ब्रेक से मेल खाते हैं, वैकल्पिक रूप से एक या अधिक रिक्त स्थान से मेल खाते हैं। यदि लाइन ब्रेक के बाद रिक्त स्थान हैं, तो मौजूदा रिक्त स्थान के साथ इन रिक्त स्थान की लंबाई की तुलना करें। यदि यह वर्तमान इंडेंट आकार से अधिक है, तो Indent टोकन उत्सर्जित करें, यदि यह वर्तमान इंडेंट-साइज से कम है, तो Dedent टोकन उत्सर्जित करें और यदि यह वही है, तो कुछ भी न करें।

आप फ़ाइल के अंत में Dedent टोकन को भी छोड़ना चाहते हैं ताकि प्रत्येक Indent मिलान Dedent टोकन हो।

इस ठीक से काम करने के लिए, आपचाहिए अपने इनपुट स्रोत फ़ाइल के लिए एक प्रमुख और अनुगामी लाइन ब्रेक जोड़ने!

ANTRL3

एक त्वरित प्रदर्शन:

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 : ; 

आप वर्ग के साथ पार्सर का परीक्षण कर सकते हैं:

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); 
    } 
}  

आप अब in.txt नामक एक फ़ाइल में निम्न डाल दिया:

 

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

(अग्रणी और पिछली लाइन ब्रेक नोट करें!(2 dedent

enter image description here

नोट है कि मेरे प्रदर्शन लगातार पर्याप्त dedents का उत्पादन नहीं होता है, ccc से aaa को dedenting की तरह:)

तो आप उत्पादन जो निम्न एएसटी से मेल खाती है देखेंगे टोकन की आवश्यकता होती है):

aaa 
    bbb 
    ccc 
aaa 

आप संभवतः 1 से अधिक dedent टोकन बस फेंकना else if(n < previousIndents) { ... } अंदर कोड को समायोजित करने की आवश्यकता होगी n और previousIndents के बीच अंतर पर एड करें। मेरे सिर के ऊपर से, कि ऐसा दिखाई दे सकता:

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

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

हाय @ बार्ट कियर्स, मैं अग्रणी और पिछली लाइनब्रेक्स सीमा को कैसे दूर कर सकता हूं? मैंने पार्सिंग शुरू करने से पहले इसे इंडेंट टोकन प्रोग्रामेटिक रूप से उत्सर्जित करने की कोशिश की, लेकिन कोई भाग्य नहीं। – ains

3

आप Python ANTLR grammar को देखा है?

संपादित करें: जोड़ा गया छद्म अजगर मांगपत्र/DEDENT बनाने के लिए कोड

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 ठीक है, [इंडेंटेशन के लिए पायथन के लेक्सिकल विश्लेषण दृष्टिकोण] को देखा है (http://docs.python.org/reference/lexical_analysis.html#indentation), उनके इंडेंट और डेडेंट टोकन एक अलग प्रक्रिया में उत्पादित होते हैं (जिसे एएनटीएलआर से गुजरने से पहले किया जा सकता है)। जब आप इसे अपने तरीके से देखते हैं, तो यह बहुत आसान है। उत्तर के लिए –

+0

Thanx, JSPerf अज्ञात। खैर, एएनटीएलआर से गुजरने से पहले इंडेंट और डेडेंट टोकन परफॉर्मिंग एक अच्छा मुद्दा है। मैं इसके बारे में सोचूंगा। अभी के लिए मैं केवल मानक लेक्सर का उपयोग करना पसंद करूंगा, इसलिए बार्ट के जवाब को स्वीकार करें। – Astronavigator

4

नहीं है एएनटीएलआर v4 के लिए एक ओपन-सोर्स लाइब्रेरी antlr-denter जो आपके लिए इंडेंट और समर्पण पार्स में मदद करता है। इसका उपयोग कैसे करें इसके README देखें।

चूंकि यह आपके व्याकरण में कॉपी-एंड-पेस्ट करने के लिए कोड स्निपेट की बजाय लाइब्रेरी है, इसलिए इसके इंडेंटेशन-हैंडलिंग को आपके शेष व्याकरण से अलग से अपडेट किया जा सकता है।

1

इस एएनटीएलआर को करने का एक अपेक्षाकृत सरल तरीका है, जिसे मैंने एक प्रयोग के रूप में लिखा था: Dent.g4। यह समाधान इस पृष्ठ पर उल्लिखित अन्य लोगों से अलग है जो कियर्स और शावित द्वारा लिखे गए थे। यह केवल लेक्सर की nextToken() विधि के ओवरराइड के माध्यम से रनटाइम के साथ एकीकृत करता है। यह टोकन की जांच करके इसका काम करता है: (1) NEWLINE टोकन "इंडेंटेशन का ट्रैक रखें" चरण की शुरुआत को ट्रिगर करता है; (2) व्हाइटस्पेस और टिप्पणियां, दोनों चैनल HIDDEN पर सेट की गई हैं, क्रमशः उस चरण के दौरान गिना और अनदेखा की जाती हैं; और, (3) कोई गैर- HIDDEN टोकन चरण समाप्त होता है। इस प्रकार इंडेंटेशन तर्क को नियंत्रित करना टोकन के चैनल को सेट करने का एक साधारण मामला है।

इस पृष्ठ पर उल्लिखित दोनों समाधानों को NEWLINE टोकन की आवश्यकता होती है ताकि वे सभी बाद के व्हाइटस्पेस को भी पकड़ सकें, लेकिन ऐसा करने में बहु-पंक्ति टिप्पणियां हैंडल स्पेस को बाधित नहीं कर सकती हैं। इसके बजाए, NEWLINE और व्हाइटस्पेस टोकन अलग रहता है और बहु-पंक्ति टिप्पणियों को संभाल सकता है।

आपका व्याकरण नीचे जैसा कुछ स्थापित किया जाएगा। ध्यान दें कि NEWLINE और WS Lexer नियमों में ऐसे क्रियाएं हैं जो 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... 
संबंधित मुद्दे