2008-09-25 7 views
14

में कई रेगेक्स से कुशलतापूर्वक मिलान करें जब आपके पास रेगेक्स होता है तो लेक्सिकल विश्लेषक लिखने में काफी आसान होते हैं। आज मैं अजगर में एक साधारण सामान्य विश्लेषक लिखना चाहते थे, और के साथ आया था:पाइथन

import re 
import sys 

class Token(object): 
    """ A simple Token structure. 
     Contains the token type, value and position. 
    """ 
    def __init__(self, type, val, pos): 
     self.type = type 
     self.val = val 
     self.pos = pos 

    def __str__(self): 
     return '%s(%s) at %s' % (self.type, self.val, self.pos) 


class LexerError(Exception): 
    """ Lexer error exception. 

     pos: 
      Position in the input line where the error occurred. 
    """ 
    def __init__(self, pos): 
     self.pos = pos 


class Lexer(object): 
    """ A simple regex-based lexer/tokenizer. 

     See below for an example of usage. 
    """ 
    def __init__(self, rules, skip_whitespace=True): 
     """ Create a lexer. 

      rules: 
       A list of rules. Each rule is a `regex, type` 
       pair, where `regex` is the regular expression used 
       to recognize the token and `type` is the type 
       of the token to return when it's recognized. 

      skip_whitespace: 
       If True, whitespace (\s+) will be skipped and not 
       reported by the lexer. Otherwise, you have to 
       specify your rules for whitespace, or it will be 
       flagged as an error. 
     """ 
     self.rules = [] 

     for regex, type in rules: 
      self.rules.append((re.compile(regex), type)) 

     self.skip_whitespace = skip_whitespace 
     self.re_ws_skip = re.compile('\S') 

    def input(self, buf): 
     """ Initialize the lexer with a buffer as input. 
     """ 
     self.buf = buf 
     self.pos = 0 

    def token(self): 
     """ Return the next token (a Token object) found in the 
      input buffer. None is returned if the end of the 
      buffer was reached. 
      In case of a lexing error (the current chunk of the 
      buffer matches no rule), a LexerError is raised with 
      the position of the error. 
     """ 
     if self.pos >= len(self.buf): 
      return None 
     else: 
      if self.skip_whitespace: 
       m = self.re_ws_skip.search(self.buf[self.pos:]) 

       if m: 
        self.pos += m.start() 
       else: 
        return None 

      for token_regex, token_type in self.rules: 
       m = token_regex.match(self.buf[self.pos:]) 

       if m: 
        value = self.buf[self.pos + m.start():self.pos + m.end()] 
        tok = Token(token_type, value, self.pos) 
        self.pos += m.end() 
        return tok 

      # if we're here, no rule matched 
      raise LexerError(self.pos) 

    def tokens(self): 
     """ Returns an iterator to the tokens found in the buffer. 
     """ 
     while 1: 
      tok = self.token() 
      if tok is None: break 
      yield tok 


if __name__ == '__main__': 
    rules = [ 
     ('\d+',    'NUMBER'), 
     ('[a-zA-Z_]\w+', 'IDENTIFIER'), 
     ('\+',    'PLUS'), 
     ('\-',    'MINUS'), 
     ('\*',    'MULTIPLY'), 
     ('\/',    'DIVIDE'), 
     ('\(',    'LP'), 
     ('\)',    'RP'), 
     ('=',    'EQUALS'), 
    ] 

    lx = Lexer(rules, skip_whitespace=True) 
    lx.input('erw = _abc + 12*(R4-623902) ') 

    try: 
     for tok in lx.tokens(): 
      print tok 
    except LexerError, err: 
     print 'LexerError at position', err.pos 

यह सिर्फ ठीक काम करता है, लेकिन मैं थोड़ा चिंतित है कि यह भी अक्षम हूँ। क्या कोई रेगेक्स चाल है जो मुझे इसे अधिक कुशल/सुरुचिपूर्ण तरीके से लिखने की अनुमति देगी?

विशेष रूप से, क्या फिट बैठने वाले किसी भी व्यक्ति को खोजने के लिए सभी रेगेक्स नियमों पर लूपिंग से बचने का कोई तरीका है?

उत्तर

7

आप अपने सभी regexes को "|" का उपयोग करके एक में विलय कर सकते हैं ऑपरेटर और रेगेक्स लाइब्रेरी टोकन के बीच समझदार काम करते हैं। टोकन की वरीयता सुनिश्चित करने के लिए कुछ देखभाल की जानी चाहिए (उदाहरण के लिए किसी कीवर्ड को पहचानकर्ता के रूप में मिलान करने से बचें)।

+1

मैं इसे प्रत्येक विकल्प के लिए सही प्रकार कैसे वापस कर सकता हूं? –

+0

कैप्चरिंग समूहों का उपयोग करें। कोष्ठक में रेगेक्स का एक हिस्सा संलग्न करना इसे कैप्चरिंग समूह बनाता है जिसे मिलान ऑब्जेक्ट से पुनर्प्राप्त किया जा सकता है, उदाहरण के लिए re.match ("(ए) | (बी)", "बी")। समूह() = (कोई नहीं, "बी")। पहला समूह मेल नहीं खाता, दूसरा मैच "बी" से मेल खाता था। –

+0

लेकिन मुझे अभी भी कैप्चर समूहों पर रैखिक रूप से चलना होगा? –

3

re.match लंगर है। आप इसे एक स्थिति तर्क दे सकते हैं:

pos = 0 
end = len(text) 
while pos < end: 
    match = regexp.match(text, pos) 
    # do something with your match 
    pos = match.end() 

pygments जो जहाजों वाक्य रचना विभिन्न कार्यान्वयन के साथ उद्देश्यों पर प्रकाश डाला के लिए lexers की एक shitload, सबसे नियमित अभिव्यक्ति के आधार पर एक नज़र डालें।

+0

यह कैसे मदद करता है? –

+0

क्या मदद करता है? एंकरिंग? पाठ को टुकड़ा करने की कोई ज़रूरत नहीं है। –

+0

मैं देखता हूं। तो मुझे लगता है कि मैं समय slicing लेने में सक्षम हो जाएगा? –

1

यह आपके प्रश्न का सही उत्तर नहीं है, लेकिन आप ANTLR पर देख सकते हैं। this दस्तावेज़ के अनुसार पाइथन कोड जनरेशन लक्ष्य अद्यतित होना चाहिए।

आपके regexes के रूप में, यदि आप regexes चिपके हुए हैं तो इसे तेज करने के लिए वास्तव में दो तरीके हैं। सबसे पहले आपके regexes को एक डिफ़ॉल्ट पाठ में खोजने की संभावना के क्रम में आदेश देना होगा। आप उस कोड में एक सरल प्रोफाइलर जोड़ना चाहते हैं जो प्रत्येक टोकन प्रकार के लिए टोकन गणना एकत्र करता है और काम के शरीर पर लेक्सर चलाता है। दूसरा समाधान बाल्टी को आपके रेगेक्स को सॉर्ट करना होगा (क्योंकि आपकी मुख्य जगह, एक चरित्र होने के नाते, अपेक्षाकृत छोटा है) और उसके बाद पहले वर्ण पर एकल भेदभाव करने के बाद आवश्यक रेगेक्स को निष्पादित करने के लिए सरणी या शब्दकोश का उपयोग करें।

हालांकि, मुझे लगता है कि यदि आप इस मार्ग पर जा रहे हैं, तो आपको वास्तव में ANTLR जैसे कुछ प्रयास करना चाहिए जो कि बग रखने के लिए तेज़ी से, तेज़ और कम होने की संभावना है।

3

यह संभव है कि टोकन रेगेक्स का संयोजन काम करेगा, लेकिन आपको इसे बेंचमार्क करना होगा। कुछ की तरह:

x = re.compile('(?P<NUMBER>[0-9]+)|(?P<VAR>[a-z]+)') 
a = x.match('9999').groupdict() # => {'VAR': None, 'NUMBER': '9999'} 
if a: 
    token = [a for a in a.items() if a[1] != None][0] 

फिल्टर है कि आप कुछ बेंच मार्किंग करना होगा जहां ...

अद्यतन: मैं इस परीक्षण किया है, और ऐसा लगता है जैसे कि अगर आप सभी टोकन गठबंधन के रूप में कहा गया है और एक समारोह लिखें:

def find_token(lst): 
    for tok in lst: 
     if tok[1] != None: return tok 
    raise Exception 

इसके लिए आपको मोटे तौर पर एक ही गति (शायद एक किशोरी तेज) मिल जाएगी। मेरा मानना ​​है कि गति को मिलान करने के लिए कॉल की संख्या में होना चाहिए, लेकिन टोकन भेदभाव के लिए लूप अभी भी वहां है, जो निश्चित रूप से इसे मारता है।

+0

आप उस समूह के लिए मिलान करने वाली स्ट्रिंग प्राप्त करने के लिए अंतिम मिलान किए गए समूह का नाम पाने के लिए 'a.lastgroup' और 'a.group (a.lastgroup)' का उपयोग कर सकते हैं। पूरे शब्दकोश को बनाने और उस प्रविष्टि को खोजने की आवश्यकता नहीं है जो 'कोई नहीं' है। –

0

इन इतना आसान नहीं हैं, लेकिन को देख लायक हो सकता है ...

अजगर मॉड्यूल pyparsing (pyparsing.wikispaces.com) की अनुमति देता है व्याकरण को निर्दिष्ट - तो यह उपयोग करते हुए पाठ पार्स करने के लिए।डगलस, एएनटीएलआर के पोस्ट के लिए धन्यवाद, मैंने इसके बारे में नहीं सुना है। इसके अलावा PLY - python2 और python3 लेक्स/yacc के संगत कार्यान्वयन है।

मैं एक तदर्थ regex आधारित पार्सर अपने आप को पहले, लेकिन बाद में एहसास हुआ कि मैं कुछ परिपक्व पार्स उपकरण का उपयोग और संदर्भ स्वतंत्र व्याकरण की अवधारणाओं को सीखने से लाभ हो सकता है, आदि

उपयोग करने का लाभ लिखा है पार्सिंग के लिए व्याकरण यह है कि आप नियमों को आसानी से संशोधित कर सकते हैं और जो कुछ भी आप पार्स कर रहे हैं उसके लिए जटिल वाक्यविन्यास को औपचारिक रूप से कार्यान्वित कर सकते हैं।

11

मैं re.Scanner क्लास का उपयोग करने का सुझाव देता हूं, यह मानक लाइब्रेरी में प्रलेखित नहीं है, लेकिन इसका उपयोग करने योग्य है। यहां एक उदाहरण दिया गया है:

import re 

scanner = re.Scanner([ 
    (r"-?[0-9]+\.[0-9]+([eE]-?[0-9]+)?", lambda scanner, token: float(token)), 
    (r"-?[0-9]+", lambda scanner, token: int(token)), 
    (r" +", lambda scanner, token: None), 
]) 

>>> scanner.scan("0 -1 4.5 7.8e3")[0] 
[0, -1, 4.5, 7800.0] 
+1

मुझे लगता है कि टोकन (टेक्स्ट, टैग) जोड़े की एक सूची होनी चाहिए। रिटर्निंग सिर्फ मिलान किए गए मान अनुक्रम बाद के पार्सिंग के लिए बहुत उपयोगी नहीं होंगे। – Meow

+0

यह भी दुर्भाग्यपूर्ण है कि इसे जनरेटर के रूप में लागू नहीं किया गया है। यह पूरी चीज को एक बार में पार करता है और एक सूची देता है। –

6

मुझे पाइथन दस्तावेज़ में this मिला। यह सिर्फ सरल और सुरुचिपूर्ण है।

tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification) 

यहाँ (?P<ID>PATTERN) एक नाम ID द्वारा निर्दिष्ट के साथ मिलान परिणाम का प्रतीक होगा:

import collections 
import re 

Token = collections.namedtuple('Token', ['typ', 'value', 'line', 'column']) 

def tokenize(s): 
    keywords = {'IF', 'THEN', 'ENDIF', 'FOR', 'NEXT', 'GOSUB', 'RETURN'} 
    token_specification = [ 
     ('NUMBER', r'\d+(\.\d*)?'), # Integer or decimal number 
     ('ASSIGN', r':='),   # Assignment operator 
     ('END',  r';'),   # Statement terminator 
     ('ID',  r'[A-Za-z]+'), # Identifiers 
     ('OP',  r'[+*\/\-]'), # Arithmetic operators 
     ('NEWLINE', r'\n'),   # Line endings 
     ('SKIP', r'[ \t]'),  # Skip over spaces and tabs 
    ] 
    tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification) 
    get_token = re.compile(tok_regex).match 
    line = 1 
    pos = line_start = 0 
    mo = get_token(s) 
    while mo is not None: 
     typ = mo.lastgroup 
     if typ == 'NEWLINE': 
      line_start = pos 
      line += 1 
     elif typ != 'SKIP': 
      val = mo.group(typ) 
      if typ == 'ID' and val in keywords: 
       typ = val 
      yield Token(typ, val, line, mo.start()-line_start) 
     pos = mo.end() 
     mo = get_token(s, pos) 
    if pos != len(s): 
     raise RuntimeError('Unexpected character %r on line %d' %(s[pos], line)) 

statements = ''' 
    IF quantity THEN 
     total := total + price * quantity; 
     tax := price * 0.05; 
    ENDIF; 
''' 

for token in tokenize(statements): 
    print(token) 

यहाँ चाल रेखा है।