سؤال

المعجمية تحليل من السهل جدا أن أكتب عندما يكون لديك regexes.اليوم أردت أن أكتب بسيط العامة محلل في بيثون ، وجاء مع:

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

فإنه يعمل على ما يرام, ولكن أنا قلق قليلا أنها غير فعالة جدا.هل هناك أي regex الحيل التي من شأنها أن تسمح لي أن أكتب في أكثر كفاءة / أنيقة الطريقة ؟

على وجه التحديد, هل هناك طريقة لتجنب حلقات على كل regex قواعد خطيا إلى العثور على واحد الذي يناسبك ..

هل كانت مفيدة؟

المحلول

يمكنك دمج كل ما تبذلونه من regexes في واحدة باستخدام "|" المشغل السماح باستخدام التعابير المنطقية المكتبة للقيام بهذا العمل من المميزين بين الرموز.بعض وينبغي الحرص على ضمان تفضيل من الرموز (على سبيل المثال تجنب مطابقة الكلمة كمعرف).

نصائح أخرى

أقترح استخدام إعادة.الماسح الضوئي فئة ليست موثقة في المكتبة القياسية ، لكنها تستحق باستخدام.هنا مثال:

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]

وجدت هذا في بيثون الوثيقة.انها مجرد بسيطة وأنيقة.

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)

الخدعة هنا هو خط:

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

هنا (?P<ID>PATTERN) بمناسبة تتطابق النتيجة مع اسم محدد ID.

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 من أجل تسليط الضوء على بناء الجملة الأغراض مع تطبيقات مختلفة ، معظم استنادا إلى التعبيرات العادية.

فمن الممكن أن الجمع بين رمز regexes العمل ، ولكن يجب أن المعيار هو.شيء من هذا القبيل:

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

سوف تحصل على تقريبا نفس السرعة (ربما teensy أسرع) من أجل هذا.أعتقد تسريع يجب أن يكون في عدد المكالمات إلى المباراة, ولكن حلقة رمزية التمييز لا يزال هناك ، والتي بالطبع يقتل.

هذه ليست إجابة مباشرة على سؤالك, ولكن قد ترغب في النظر في ANTLR.وفقا هذا الوثيقة الثعبان رمز جيل الهدف ينبغي أن يكون ما يصل إلى تاريخ.

كما regexes, حقا هناك طريقتان للذهاب حول لتسريع ذلك إذا كنت التمسك regexes.الأولى أن النظام الخاص بك regexes في أمر من احتمال العثور عليها في النص الافتراضي.هل يمكن أن الشكل إضافة بسيطة التعريف إلى التعليمات البرمجية التي تم جمعها رمزية التهم لكل رمز نوع وتشغيل lexer على العمل.الحل الآخر سيكون دلو فرز regexes (منذ الفضاء الرئيسية ، لكونها حرف صغير نسبيا) ثم استخدام مجموعة أو قاموس لأداء اللازمة regexes بعد أداء واحد التمييز على الحرف الأول.

ومع ذلك, أعتقد أنه إذا كنت تريد أن تذهب في هذا الطريق, عليك أن تحاول حقا شيء مثل ANTLR والتي سوف يكون من الأسهل للحفاظ على وأسرع وأقل احتمالا أن يكون الخلل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top