खोज…


परिचय

PLY लोकप्रिय संकलक निर्माण उपकरण lex और yacc का शुद्ध-पायथन कार्यान्वयन है।

टिप्पणियों

अतिरिक्त लिंक:

  1. आधिकारिक डॉक्स
  2. Github

PLY के साथ शुरुआत करना

Python2 / 3 के लिए अपनी मशीन पर PLY स्थापित करने के लिए, नीचे दिए गए चरणों का पालन करें:

  1. स्रोत कोड यहाँ से डाउनलोड करें
  2. डाउनलोड की गई ज़िप फ़ाइल को अनज़िप करें
  3. अनज़िप किए गए ply-3.10 फ़ोल्डर में नेविगेट करें
  4. अपने टर्मिनल में निम्न कमांड चलाएँ: python setup.py install

यदि आपने उपरोक्त सभी को पूरा कर लिया है, तो आपको अब PLY मॉड्यूल का उपयोग करने में सक्षम होना चाहिए। आप अजगर इंटरप्रेटर खोलकर और import ply.lex टाइप करके इसका परीक्षण कर सकते हैं।

नोट: PLY को स्थापित करने के लिए pip का उपयोग करें, यह आपकी मशीन पर टूटा हुआ वितरण स्थापित करेगा।

"हैलो, विश्व!" PLY का - एक साधारण कैलकुलेटर

आइए एक साधारण उदाहरण के साथ PLY की शक्ति प्रदर्शित करें: यह प्रोग्राम एक स्ट्रिंग इनपुट के रूप में एक अंकगणितीय अभिव्यक्ति लेगा, और इसे हल करने का प्रयास करेगा।

अपना पसंदीदा संपादक खोलें और निम्नलिखित कोड कॉपी करें:

from ply import lex
import ply.yacc as yacc

tokens = (
    'PLUS',
    'MINUS',
    'TIMES',
    'DIV',
    'LPAREN',
    'RPAREN',
    'NUMBER',
)

t_ignore = ' \t'

t_PLUS    = r'\+'
t_MINUS   = r'-'
t_TIMES   = r'\*'
t_DIV     = r'/'
t_LPAREN  = r'\('
t_RPAREN  = r'\)'

def t_NUMBER( t ) :
    r'[0-9]+'
    t.value = int( t.value )
    return t

def t_newline( t ):
  r'\n+'
  t.lexer.lineno += len( t.value )

def t_error( t ):
  print("Invalid Token:",t.value[0])
  t.lexer.skip( 1 )

lexer = lex.lex()

precedence = (
    ( 'left', 'PLUS', 'MINUS' ),
    ( 'left', 'TIMES', 'DIV' ),
    ( 'nonassoc', 'UMINUS' )
)

def p_add( p ) :
    'expr : expr PLUS expr'
    p[0] = p[1] + p[3]

def p_sub( p ) :
    'expr : expr MINUS expr'
    p[0] = p[1] - p[3]

def p_expr2uminus( p ) :
    'expr : MINUS expr %prec UMINUS'
    p[0] = - p[2]

def p_mult_div( p ) :
    '''expr : expr TIMES expr
            | expr DIV expr'''

    if p[2] == '*' :
        p[0] = p[1] * p[3]
    else :
        if p[3] == 0 :
            print("Can't divide by 0")
            raise ZeroDivisionError('integer division by 0')
        p[0] = p[1] / p[3]

def p_expr2NUM( p ) :
    'expr : NUMBER'
    p[0] = p[1]

def p_parens( p ) :
    'expr : LPAREN expr RPAREN'
    p[0] = p[2]

def p_error( p ):
    print("Syntax error in input!")

parser = yacc.yacc()

res = parser.parse("-4*-(3-5)") # the input
print(res)

इस फ़ाइल को calc.py रूप में calc.py और चलाएँ।

आउटपुट:

-8

जो -4 * - (3 - 5) लिए सही उत्तर है।

भाग 1: लेक्स के साथ इनपुट को टोकन करना

वहाँ दो चरण हैं, जो उदाहरण 1 से कोड किए गए हैं: एक इनपुट, जिसका अर्थ है कि यह प्रतीक है कि अंकगणित अभिव्यक्ति का गठन के लिए देखा tokenizing गया था, और दूसरे चरण के लिए, पार्स करने गया था जो निकाले टोकन का विश्लेषण करने और परिणाम का मूल्यांकन करना शामिल है।

यह अनुभाग उपयोगकर्ता इनपुट को टोकन करने के तरीके का एक सरल उदाहरण प्रदान करता है, और फिर इसे लाइन द्वारा लाइन को तोड़ता है।

    import ply.lex as lex

    # List of token names. This is always required
    tokens = [
       'NUMBER',
       'PLUS',
       'MINUS',
       'TIMES',
       'DIVIDE',
       'LPAREN',
       'RPAREN',
    ]

    # Regular expression rules for simple tokens
    t_PLUS    = r'\+'
    t_MINUS   = r'-'
    t_TIMES   = r'\*'
    t_DIVIDE  = r'/'
    t_LPAREN  = r'\('
    t_RPAREN  = r'\)'

    # A regular expression rule with some action code
    def t_NUMBER(t):
        r'\d+'
        t.value = int(t.value)    
        return t

    # Define a rule so we can track line numbers
    def t_newline(t):
        r'\n+'
        t.lexer.lineno += len(t.value)

    # A string containing ignored characters (spaces and tabs)
    t_ignore  = ' \t'

    # Error handling rule
    def t_error(t):
        print("Illegal character '%s'" % t.value[0])
        t.lexer.skip(1)

    # Build the lexer
    lexer = lex.lex()

    # Give the lexer some input
    lexer.input(data)

    # Tokenize
    while True:
        tok = lexer.token()
        if not tok: 
            break      # No more input
        print(tok)

इस फ़ाइल को calclex.py रूप में calclex.py । हमारे याक पार्सर के निर्माण के दौरान हम इसका उपयोग करेंगे।


टूट - फूट

  1. मॉड्यूल import ply.lex का उपयोग कर import ply.lex

  2. सभी लेक्सर्स को tokens नामक एक सूची प्रदान करनी चाहिए जो कि सभी संभावित टोकन नामों को परिभाषित करती है जो कि लेक्सर द्वारा उत्पादित की जा सकती हैं। इस सूची की हमेशा आवश्यकता होती है।

     tokens = [
        'NUMBER',
        'PLUS',
        'MINUS',
        'TIMES',
        'DIVIDE',
        'LPAREN',
        'RPAREN',
     ]
    

tokens स्ट्रिंग्स का एक टपल भी हो सकता है (स्ट्रिंग के बजाय), जहां प्रत्येक स्ट्रिंग पहले की तरह टोकन को दर्शाता है।

  1. प्रत्येक स्ट्रिंग के लिए रेगेक्स नियम को स्ट्रिंग के रूप में या फ़ंक्शन के रूप में परिभाषित किया जा सकता है। या तो मामले में, चर नाम को t_ से उपसर्ग किया जाना चाहिए ताकि यह सूचित किया जा सके कि यह टोकन से मेल खाने के लिए एक नियम है।

    • सरल टोकन के लिए, नियमित अभिव्यक्ति को स्ट्रिंग्स के रूप में निर्दिष्ट किया जा सकता है: t_PLUS = r'\+'

    • यदि किसी प्रकार की कार्रवाई करने की आवश्यकता है, तो एक टोकन नियम को एक फ़ंक्शन के रूप में निर्दिष्ट किया जा सकता है।

         def t_NUMBER(t):
             r'\d+'
             t.value = int(t.value)
             return t
      

      ध्यान दें, नियम फ़ंक्शन के भीतर डॉक्टर स्ट्रिंग के रूप में निर्दिष्ट है। फ़ंक्शन एक तर्क को स्वीकार करता है जो कि LexToken का एक उदाहरण है, कुछ कार्रवाई करता है और फिर तर्क को वापस करता है।

      यदि आप डॉक स्ट्रिंग निर्दिष्ट करने के बजाय फ़ंक्शन के लिए रेगेक्स नियम के रूप में एक बाहरी स्ट्रिंग का उपयोग करना चाहते हैं, तो निम्न उदाहरण पर विचार करें:

         @TOKEN(identifier)         # identifier is a string holding the regex
         def t_ID(t):
             ...      # actions
      
    • LexToken ऑब्जेक्ट का उदाहरण (चलो इस ऑब्जेक्ट को t ) में निम्नलिखित विशेषताएं हैं:

      1. t.type जो एक टोकन प्रकार (एक स्ट्रिंग के रूप में) है (उदाहरण के लिए: 'NUMBER' , 'PLUS' , आदि)। डिफ़ॉल्ट रूप से, t.type t_ prefix के बाद वाले नाम पर सेट है।
      2. t.value जो कि लेक्मे है (वास्तविक पाठ मेल खाता है)
      3. t.lineno जो कि वर्तमान लाइन नंबर है (यह स्वचालित रूप से अपडेट नहीं होता है, क्योंकि t.lineno लाइन नंबर का कुछ भी पता नहीं है)। t_newline को t_newline नामक फ़ंक्शन का उपयोग करके अपडेट करें।

        def t_newline(t):
            r'\n+'
            t.lexer.lineno += len(t.value)
      

      1. t.lexpos जो इनपुट टेक्स्ट की शुरुआत के सापेक्ष टोकन की स्थिति है।
    • यदि कोई भी regex नियम फ़ंक्शन से वापस नहीं किया जाता है, तो टोकन को छोड़ दिया जाता है। यदि आप टोकन को त्यागना चाहते हैं, तो आप वैकल्पिक रूप से t_ignore_ को प्रीफ़िक्स जोड़ सकते हैं और उसी नियम के लिए फ़ंक्शन को परिभाषित करने के बजाय regex नियम चर में जोड़ सकते हैं।

         def t_COMMENT(t):
             r'\#.*'
             pass
             # No return value. Token discarded
      

      ...के समान है:

         t_ignore_COMMENT = r'\#.*'
      

      यदि आप कोई टिप्पणी करते हैं तो कुछ कार्रवाई कर रहे हैं तो यह निश्चित रूप से अमान्य है। जिस स्थिति में, रेगेक्स नियम को परिभाषित करने के लिए एक फ़ंक्शन का उपयोग करें।

      यदि आपने कुछ वर्णों के लिए एक टोकन निर्धारित नहीं किया है, लेकिन फिर भी इसे अनदेखा करना चाहते हैं, तो t_ignore = "<characters to ignore>" (ये उपसर्ग आवश्यक हैं):

         t_ignore_COMMENT = r'\#.*'
         t_ignore  = ' \t'    # ignores spaces and tabs
      

    • मास्टर रेगेक्स का निर्माण करते समय, lex फाइल में निर्दिष्ट रेगेक्स को निम्न प्रकार से जोड़ेगा:

      1. फ़ंक्शंस द्वारा परिभाषित टोकन उसी क्रम में जोड़े जाते हैं जैसे वे फ़ाइल में दिखाई देते हैं।
      2. स्ट्रिंग द्वारा परिभाषित टोकन स्ट्रिंग की लंबाई को परिभाषित करने वाले स्ट्रिंग की लंबाई के घटते क्रम में जोड़े जाते हैं।

      यदि आप एक ही फ़ाइल में == और = मिलान कर रहे हैं, तो इन नियमों का लाभ उठाएं।

    • साहित्य वे टोकन हैं, जो जैसे हैं वैसे ही वापस आ जाते हैं। t.type और t.value दोनों ही चरित्र के लिए सेट t.value जाएंगे। शाब्दिक सूची को इस प्रकार परिभाषित करें:

      literals = [ '+', '-', '*', '/' ]
      

      या,

      literals = "+-*/"
      

      टोकन कार्यों को लिखना संभव है जो कि शाब्दिक रूप से मेल खाने पर अतिरिक्त क्रिया करते हैं। हालाँकि, आपको टोकन टाइप उचित रूप से सेट करना होगा। उदाहरण के लिए:

      literals = [ '{', '}' ]
      
      def t_lbrace(t):
          r'\{'
          t.type = '{'  # Set token type to the expected literal (ABSOLUTE MUST if this is a literal)
          return t
      

    • T_error फ़ंक्शन के साथ त्रुटियों को संभालें।

      # Error handling rule
      def t_error(t):
          print("Illegal character '%s'" % t.value[0])
          t.lexer.skip(1) # skip the illegal token (don't process it)
      

      सामान्य तौर पर, t.lexer.skip(n) इनपुट स्ट्रिंग में n वर्णों को छोड़ देता है।

  2. अंतिम तैयारी:

    lexer = lex.lex() का उपयोग करके lexer = lex.lex() का निर्माण करें।

    आप एक कक्षा के अंदर भी सब कुछ डाल सकते हैं और लेसर को परिभाषित करने के लिए कक्षा के उपयोग उदाहरण को कॉल कर सकते हैं। उदाहरण के लिए:

     import ply.lex as lex  
     class MyLexer(object):            
           ...     # everything relating to token rules and error handling comes here as usual 
    
           # Build the lexer
           def build(self, **kwargs):
               self.lexer = lex.lex(module=self, **kwargs)
    
           def test(self, data):
               self.lexer.input(data)
               for token in self.lexer.token():
                   print(token)
    
           # Build the lexer and try it out
    
     m = MyLexer()
     m.build()           # Build the lexer
     m.test("3 + 4")     #
    

    lexer.input(data) का उपयोग करके इनपुट प्रदान करें जहां डेटा एक स्ट्रिंग है

    टोकन प्राप्त करने के लिए, lexer.token() उपयोग करें, जो मिलान किए गए टोकन लौटाता है। आप एक लूप में लेक्सर को पुन: टाइप कर सकते हैं:

    for i in lexer: 
        print(i)
    

भाग 2: पार्सिंग टोकन इनपुट को याक के साथ

यह खंड बताता है कि भाग 1 से टोकन इनपुट कैसे संसाधित किया जाता है - यह संदर्भ मुक्त व्याकरण (सीएफजी) का उपयोग करके किया जाता है। व्याकरण को निर्दिष्ट किया जाना चाहिए, और व्याकरण के अनुसार टोकन संसाधित किए जाते हैं। हुड के तहत, पार्सर एक LALR पार्सर का उपयोग करता है।

# Yacc example

import ply.yacc as yacc

# Get the token map from the lexer. This is required.
from calclex import tokens

def p_expression_plus(p):
    'expression : expression PLUS term'
    p[0] = p[1] + p[3]

def p_expression_minus(p):
    'expression : expression MINUS term'
    p[0] = p[1] - p[3]

def p_expression_term(p):
    'expression : term'
    p[0] = p[1]

def p_term_times(p):
    'term : term TIMES factor'
    p[0] = p[1] * p[3]

def p_term_div(p):
    'term : term DIVIDE factor'
    p[0] = p[1] / p[3]

def p_term_factor(p):
    'term : factor'
    p[0] = p[1]

def p_factor_num(p):
    'factor : NUMBER'
    p[0] = p[1]

def p_factor_expr(p):
    'factor : LPAREN expression RPAREN'
    p[0] = p[2]

# Error rule for syntax errors
def p_error(p):
    print("Syntax error in input!")

# Build the parser
parser = yacc.yacc()

while True:
   try:
       s = raw_input('calc > ')
   except EOFError:
       break
   if not s: continue
   result = parser.parse(s)
   print(result)

टूट - फूट

  • प्रत्येक व्याकरण नियम को एक फ़ंक्शन द्वारा परिभाषित किया जाता है जहां उस फ़ंक्शन के लिए docstring में उपयुक्त संदर्भ-मुक्त व्याकरण विनिर्देश होते हैं। फ़ंक्शन बॉडी बनाने वाले कथन नियम के शब्दार्थ कार्यों को कार्यान्वित करते हैं। प्रत्येक फ़ंक्शन एकल तर्क p को स्वीकार करता है जो कि एक अनुक्रम होता है जिसमें संबंधित नियम में प्रत्येक व्याकरण चिन्ह के मान होते हैं। p[i] मूल्य p[i] व्याकरण प्रतीकों के लिए मैप किए गए हैं जैसा कि यहां दिखाया गया है:

      def p_expression_plus(p):
          'expression : expression PLUS term'
          #   ^            ^        ^    ^
          #  p[0]         p[1]     p[2] p[3]
    
          p[0] = p[1] + p[3]
    
  • टोकन के लिए, संबंधित p[i] का "मान" p.value मॉड्यूल में निर्दिष्ट p.value विशेषता के समान है। तो, PLUS का मान +

  • गैर-टर्मिनलों के लिए, मूल्य जो भी p[0] में रखा गया है, द्वारा निर्धारित किया जाता है। यदि कुछ नहीं रखा गया है, तो मूल्य कोई भी नहीं है। इसके अलावा, p[-1] p[3] के समान नहीं है, क्योंकि p एक सरल सूची नहीं है ( p[-1] एम्बेडेड कार्यों को निर्दिष्ट कर सकता है (यहां चर्चा नहीं की गई है)।

ध्यान दें कि फ़ंक्शन का कोई भी नाम हो सकता है, जब तक कि यह p_ द्वारा p_

  • p_error(p) नियम को सिंटैक्स त्रुटियों को पकड़ने के लिए परिभाषित किया गया है (जैसे yacc / bison में yyerror )।

  • कई व्याकरण के नियमों को एक एकल फ़ंक्शन में जोड़ा जा सकता है, जो एक अच्छा विचार है यदि प्रस्तुतियों में एक समान संरचना है।

      def p_binary_operators(p):
          '''expression : expression PLUS term
                        | expression MINUS term
             term       : term TIMES factor
                        | term DIVIDE factor'''
          if p[2] == '+':
              p[0] = p[1] + p[3]
          elif p[2] == '-':
              p[0] = p[1] - p[3]
          elif p[2] == '*':
              p[0] = p[1] * p[3]
          elif p[2] == '/':
              p[0] = p[1] / p[3] 
    
  • टोकन के बजाय चरित्र शाब्दिक का उपयोग किया जा सकता है।

      def p_binary_operators(p):
          '''expression : expression '+' term
                        | expression '-' term
             term       : term '*' factor
                        | term '/' factor'''
          if p[2] == '+':
              p[0] = p[1] + p[3]
          elif p[2] == '-':
              p[0] = p[1] - p[3]
          elif p[2] == '*':
              p[0] = p[1] * p[3]
          elif p[2] == '/':
              p[0] = p[1] / p[3]
    

    बेशक, शाब्दिक लेक्सर मॉड्यूल में निर्दिष्ट होना चाहिए।

  • खाली प्रस्तुतियों के पास '''symbol : '''

  • प्रारंभ प्रतीक को स्पष्ट रूप से सेट करने के लिए, start = 'foo' उपयोग करें, जहां foo कुछ गैर-टर्मिनल है।

  • पूर्ववर्ती चर का उपयोग करके पूर्ववर्तीता और सहानुभूति की स्थापना की जा सकती है।

      precedence = (
          ('nonassoc', 'LESSTHAN', 'GREATERTHAN'),  # Nonassociative operators
          ('left', 'PLUS', 'MINUS'),
          ('left', 'TIMES', 'DIVIDE'),
          ('right', 'UMINUS'),            # Unary minus operator
      )
    

    सबसे कम वरीयता से टोकन का आदेश दिया जाता है। nonassoc मतलब है कि उन टोकन का कोई संबंध नहीं है। इसका अर्थ है कि a < b < c जैसा कुछ अवैध है जबकि a < b अभी भी कानूनी है।

  • parser.out एक डीबगिंग फ़ाइल है जो पहली बार yacc प्रोग्राम निष्पादित होने पर बनाई जाती है। जब भी एक पारी / कम संघर्ष होता है, तो पार्सर हमेशा बदलता रहता है।



Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow