Simple Top-Down Parsing in Python

Fredrik Lundh | July 2008

In Simple Iterator-based Parsing, I described a way to write simple recursive-descent parsers in Python, by passing around the current token and a token generator function.

A recursive-descent parser consists of a series of functions, usually one for each grammar rule. Such parsers are easy to write, and are reasonably efficient, as long as the grammar is “prefix-heavy”; that is, that it’s usually sufficient to look at a token at the beginning of a construct to figure out what parser function to call. For example, if you’re parsing Python code, you can identify most statements simply by looking at the first token.

However, recursive-descent is less efficient for expression syntaxes, especially for languages with lots of operators at different precedence levels. With one function per rule, you can easily end up with lots of calls even for short, trivial expressions, just to get to the right level in the grammar.

For example, here’s an excerpt from Python’s expression grammar. The “test” rule is a basic expression element:

 
test: or_test ['if' or_test 'else' test] | lambdef or_test: and_test ('or' and_test)* and_test: not_test ('and' not_test)* not_test: 'not' not_test | comparison comparison: expr (comp_op expr)* expr: xor_expr ('|' xor_expr)* xor_expr: and_expr ('^' and_expr)* and_expr: shift_expr ('&' shift_expr)* shift_expr: arith_expr (('<<'|'>>') arith_expr)* arith_expr: term (('+'|'-') term)* term: factor (('*'|'/'|'%'|'//') factor)* factor: ('+'|'-'|'~') factor | power power: atom trailer* ['**' factor] trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME 

With a naive recursive-descent implementation of this grammar, the parser would have to recurse all the way from “test” down to “trailer” in order to parse a simple function call (of the form “expression(arglist)”).

In the early seventies, Vaughan Pratt published an elegant improvement to recursive-descent in his paper Top-down Operator Precedence. Pratt’s algorithm associates semantics with tokens instead of grammar rules, and uses a simple “binding power” mechanism to handle precedence levels. Traditional recursive-descent parsing is then used to handle odd or irregular portions of the syntax.

In an article (and book chapter) with the same name, Douglas Crockford shows how to implement the algorithm in a subset of JavaScript, and uses it to develop a parser that can parse itself in the process.

In this article, I’ll be a bit more modest: I’ll briefly explain how the algorithm works, discuss different ways to implement interpreters and translators with it in Python, and finally use it to implement a parser for Python’s expression syntax. And yes, there will be benchmarks too.

Introducing The Algorithm #

Like most other parsers, a topdown parser operates on a stream of distinct syntax elements, or tokens. For example, the expression “1 + 2” could correspond to the following tokens:

literal with value 1 add operator literal with value 2 end of program 

In the topdown algorithm, each token has two associated functions, called “nud” and “led”, and an integer value called “lbp”.

The “nud” function (for null denotation) is used when a token appears at the beginning of a language construct, and the “led” function (left denotation) when it appears inside the construct (to the left of the rest of the construct, that is).

The “lbp” value is a binding power, and it controls operator precedence; the higher the value, the tighter a token binds to the tokens that follow.

Given this brief introduction, we’re ready to look at the core of Pratt’s algorithm, the expression parser:

def expression(rbp=0):
    global token
    t = token
    token = next()
    left = t.nud()
    while rbp < token.lbp:
        t = token
        token = next()
        left = t.led(left)
    return left

(Pratt calls this function “parse”, but we’ll use the name from Crockford’s article instead.)

Here, “token” is a global variable that contains the current token, and “next” is a global helper that fetches the next token. The “nud” and “led” functions are represented as methods, and the “lbp” is an attribute. The “left” variable, finally, is used to pass some value that represents the “left” part of the expression through to the “led” method; this can be any object, such as an intermediate result (for an interpreter) or a portion of a parse tree (for a compiler).

If applied to the simple expression shown earlier, the parser will start by calling the “nud” method on the first token. In our example, that’s a literal token, which can be represented by something like the following class:

class literal_token:
    def __init__(self, value):
        self.value = int(value)
    def nud(self):
        return self.value

Next, the parser checks if the binding power of the next token is at least as large as the given binding power (the “rbp” argument, for “right binding power”). If it is, it calls the “led” method for that token. Here, the right binding power is zero, and the next token is an operator, the implementation of which could look like:

class operator_add_token:
    lbp = 10
    def led(self, left):
        right = expression(10)
        return left + right

The operator has a binding power of 10, and a “led” method that calls the expression parser again, passing in a right binding power that’s the same as the operator’s own power. This causes the expression parser to treat everything with a higher power as a subexpression, and return its result. The method then adds the left value (from the literal, in this case) to the return value from the expression parser, and returns the result.

The end of the program is indicated by a special marker token, with binding power zero (lower than any other token). This makes sure that the expression parser stops when it reaches the end of the program.

class end_token:
    lbp = 0

And that’s the entire parser. To use it, we need a tokenizer that can generate the right kind of token objects for a given source program. Here’s a simple regular expression-based version that handles the minimal language we’ve used this far:

import re

token_pat = re.compile("\s*(?:(\d+)|(.))")

def tokenize(program):
    for number, operator in token_pat.findall(program):
        if number:
            yield literal_token(number)
        elif operator == "+":
            yield operator_add_token()
        else:
            raise SyntaxError("unknown operator")
    yield end_token()

Now, let’s wire this up and try it out:

def parse(program):
    global token, next
    next = tokenize(program).next
    token = next()
    return expression()

>>> parse("1 + 2")
3

Not counting the calls to the tokenizer, the parser algorithm will make a total of four calls to parse this expression; one for each token, and one extra for the recursive call to the expression parser in the “led” method.

Extending the Parser #

To see how this scales, let’s add support for a few more math operations. We need a few more token classes:

class operator_sub_token:
    lbp = 10
    def led(self, left):
        return left - expression(10)

class operator_mul_token:
    lbp = 20
    def led(self, left):
        return left * expression(20)

class operator_div_token:
    lbp = 20
    def led(self, left):
        return left / expression(20)

Note that “mul” and “div” uses a higher binding power than the other operators; this guarantees that when the “mul” operator is invoked in the expression “1 * 2 + 3”, it only gets the literal “2”, instead of treating “2 + 3” as a subexpression.

We also need to add the classes to the tokenizer:

def tokenize(program): for number, operator in token_pat.findall(program): if number: yield literal_token(number) elif operator == "+": yield operator_add_token() elif operator == "-": yield operator_sub_token() elif operator == "*": yield operator_mul_token() elif operator == "/": yield operator_div_token() else: raise SyntaxError("unknown operator) yield end_token() 

but that’s it. The parser now understands the four basic math operators, and handles their precedence correctly.

>>> parse("1+2")
3
>>> parse("1+2*3")
7
>>> parse("1+2-3*4/5")
1

Despite the fact that we’ve added more grammar rules, the parser still makes the same number of calls as before; the expression “1+2” is still handled by four calls inside the parser.

However, codewise, this isn’t that different from a recursive-descent parser. We still need to write code for each token class, and while we’ve moved most of the dispatching from individual rules to the expression parser, most of that ended up in a big if/else statement in the tokenizer.

Before we look at ways to get rid of some of that code, let’s add two more features to the parser: unary plus and minus operators, and a Python-style exponentiation operator (**).

To support the unary operators, all we need to do is to add “nud” implementations to the relevant tokens:

class operator_add_token:
    lbp = 10
    def nud(self):
        return expression(100)
    def led(self, left):
        return left + expression(10)

class operator_sub_token:
    lbp = 10
    def nud(self):
        return -expression(100)
    def led(self, left):
        return left - expression(10)

Note that the recursive call to expression uses a high binding power, to make sure that the unary operator binds to the token immediately to the right, instead of to the rest of the expression (“(-1)-2” and “-(1-2)” are two different things).

Adding exponentiation is a bit trickier; first, we need to tweak the tokenizer to identify the two-character operator:

token_pat = re.compile("\s*(?:(\d+)|(\*\*|.))") ... elif operator == "**": yield operator_pow_token() ... 

A bigger problem is that the operator is right-associative (that it, it binds to the right). If you type “2**3**4” into a Python prompt, Python will evaluate the “3**4” part first:

>>> 2**3**4
2417851639229258349412352L
>>> (2**3)**4
4096
>>> 2**(3**4)
2417851639229258349412352L
>>>

Luckily, the binding power mechanism makes it easy to implement this; to get right associativity, just subtract one from the operator’s binding power when doing the recursive call:

class operator_pow_token:
    lbp = 30
    def led(self, left):
        return left ** expression(30-1)

In this way, the parser will treat subsequent exponentiation operators (with binding power 30) as subexpressions to the current one, which is exactly what we want.

Building Parse Trees #

A nice side-effect of the top-down approach is that it’s easy to build parse trees, without much extra overhead; since the tokenizer is creating a new object for each token anyway, we can reuse these objects as nodes in the parse tree.

To do this, the “nud” and “led” methods have to add syntax tree information to the objects, and then return the objects themselves. In the following example, the literal leaf nodes has a “value” attribute, and the operator nodes have “first” and “second” attributes. The classes also have __repr__ methods to make it easier to look at the resulting tree:

class literal_token:
    def __init__(self, value):
        self.value = value
    def nud(self):
        return self
    def __repr__(self):
        return "(literal %s)" % self.value

class operator_add_token:
    lbp = 10
    def nud(self):
        self.first = expression(100)
        self.second = None
        return self
    def led(self, left):
        self.first = left
        self.second = expression(10)
        return self
    def __repr__(self):
        return "(add %s %s)" % (self.first, self.second)

class operator_mul_token:
    lbp = 20
    def led(self, left):
        self.first = left
        self.second = expression(20)
        return self
    def __repr__(self):
        return "(mul %s %s)" % (self.first, self.second)

(implementing “sub”, “div”, and “pow” is left as an exercise.)

With the new token implementations, the parser will return parse trees:

>>> parse("1")
(literal 1)
>>> parse("+1")
(add (literal 1) None)
>>> parse("1+2+3")
(add (add (literal 1) (literal 2)) (literal 3))
>>> parse("1+2*3")
(add (literal 1) (mul (literal 2) (literal 3)))
>>> parse("1*2+3")
(add (mul (literal 1) (literal 2)) (literal 3))

The unary plus inserts a “unary add” node in the tree (with the “second” attribute set to None). If you prefer, you can skip the extra node, simply by returning the inner expression from “nud”:

class operator_add_token:
    lbp = 10
    def nud(self):
        return expression(100)

    ...

>>> parse("1")
(literal 1)
>>> parse("+1")
(literal 1)

Whether this is a good idea or not depends on your language definition (Python, for one, won’t optimize them away in the general case, in case you’re using unary plus on something that’s not a number.)

Streamlining Token Class Generation #

The simple parsers we’ve used this far all consist of a number of classes, one for each token type, and a tokenizer that knows about them all. Pratt uses associative arrays instead, and associates the operations with their tokens. In Python, it could look something like:

nud = {}; led = {}; lbp = {}

nud["+"] = lambda: +expression(100)
led["+"] = lambda left: left + expression(10)
lbp["+"] = 10

This is a bit unwieldy, and feels somewhat backwards from a Python perspective. Crockford’s JavaScript implementation uses a different approach; he uses a single “token class registry” (which he calls “symbol table”), with a factory function that creates new classes on the fly. JavaScript’s prototype model makes that ludicrously simple, but it’s not that hard to generate classes on the fly in Python either.

First, let’s introduce a base class for token types, to get a place to stuff all common behaviour. I’ve added default attributes for storing the token type name (the “id” attribute) and the token value (for literal and name tokens), as well as a few attributes for the syntax tree. This class is also a convenient place to provide default implementations of the “nud” and “led” methods.

 
class symbol_base(object):

    id = None # node/token type name
    value = None # used by literals
    first = second = third = None # used by tree nodes

    def nud(self):
        raise SyntaxError(
            "Syntax error (%r)." % self.id
        )

    def led(self, left):
        raise SyntaxError(
            "Unknown operator (%r)." % self.id
        )

    def __repr__(self):
        if self.id == "(name)" or self.id == "(literal)":
            return "(%s %s)" % (self.id[1:-1], self.value)
        out = [self.id, self.first, self.second, self.third]
        out = map(str, filter(None, out))
        return "(" + " ".join(out) + ")"

Next, we need a token type factory:

symbol_table = {}

def symbol(id, bp=0):
    try:
        s = symbol_table[id]
    except KeyError:
        class s(symbol_base):
            pass
        s.__name__ = "symbol-" + id # for debugging
        s.id = id
        s.lbp = bp
        symbol_table[id] = s
    else:
        s.lbp = max(bp, s.lbp)
    return s

This function takes a token identifier and an optional binding power, and creates a new class if necessary. The identifier and the binding power are inserted as class attributes, and will thus be available in all instances of that class. If the function is called for a symbol that’s already registered, it just updates the binding power; this allows us to define different parts of the symbol’s behaviour in different places, as we’ll see later.

We can now populate the registry with the symbols we’re going to use:

symbol("(literal)")
symbol("+", 10); symbol("-", 10)
symbol("*", 20); symbol("/", 20)
symbol("**", 30)
symbol("(end)")

To simplify dispatching, we’re using the token strings as identifiers; the identifiers for the “(literal)” and “(end)” symbols (which replaces the literal_token and end_token classes used earlier) are strings that won’t appear as ordinary tokens.

We also need to update the tokenizer, to make it use classes from the registry:

def tokenize(program):
    for number, operator in token_pat.findall(program):
        if number:
            symbol = symbol_table["(literal)"]
            s = symbol()
            s.value = number
            yield s
        else:
            symbol = symbol_table.get(operator)
            if not symbol:
                raise SyntaxError("Unknown operator")
            yield symbol()
    symbol = symbol_table["(end)"]
    yield symbol()

Like before, the literal class is used as a common class for all literal values. All other tokens have their own classes.

Now, all that’s left is to define “nud” and “led” methods for the symbols that need additional behavior. To do that, we can define them as ordinary functions, and then simply plug them into the symbol classes, one by one. For example, here’s the “led” method for addition:

def led(self, left):
    self.first = left
    self.second = expression(10)
    return self
symbol("+").led = led

That last line fetches the class from the symbol registry, and adds the function to it. Here are a few more “led” methods:

def led(self, left):
    self.first = left
    self.second = expression(10)
    return self
symbol("-").led = led

def led(self, left):
    self.first = left
    self.second = expression(20)
    return self
symbol("*").led = led

def led(self, left):
    self.first = left
    self.second = expression(20)
    return self
symbol("/").led = led

They do look pretty similar, don’t they? The only thing that differs is the binding power, so we can simplify things quite a bit by moving the repeated code into a helper function:

def infix(id, bp):
    def led(self, left):
        self.first = left
        self.second = expression(bp)
        return self
    symbol(id, bp).led = led

Given this helper, we can now replace the “led” functions above with four simple calls:

infix("+", 10); infix("-", 10)
infix("*", 20); infix("/", 20)

Likewise, we can provide helper functions for the “nud” methods, and for operators with right associativity:

def prefix(id, bp):
    def nud(self):
        self.first = expression(bp)
        self.second = None
        return self
    symbol(id).nud = nud

prefix("+", 100); prefix("-", 100)

def infix_r(id, bp):
    def led(self, left):
        self.first = left
        self.second = expression(bp-1)
        return self
    symbol(id, bp).led = led

infix_r("**", 30)

Finally, the literal symbol must be fitted with a “nud” method that returns the symbol itself. We can use a plain lambda for this:

symbol("(literal)").nud = lambda self: self

Note that most of the above is general-purpose plumbing; given the helper functions, the actual parser definition boils down to the following six lines:

infix("+", 10); infix("-", 10)
infix("*", 20); infix("/", 20)
infix_r("**", 30)
prefix("+", 100); prefix("-", 100)

symbol("(literal)").nud = lambda self: self
symbol("(end)")

Running this produces the same result as before:

>>> parse("1")
(literal 1)
>>> parse("+1")
(+ (literal 1))
>>> parse("1+2")
(+ (literal 1) (literal 2))
>>> parse("1+2+3")
(+ (+ (literal 1) (literal 2)) (literal 3))
>>> parse("1+2*3")
(+ (literal 1) (* (literal 2) (literal 3)))
>>> parse("1*2+3")
(+ (* (literal 1) (literal 2)) (literal 3))

Parsing Python Expressions #

To get a somewhat larger example, let’s tweak the parser so it can parse a subset of the Python expression syntax, similar to the syntax shown in the grammar snippet at the start of this article.

To do this, we first need a fancier tokenizer. The obvious choice is to build on Python’s tokenize module:

 
def tokenize_python(program):
    import tokenize
    from cStringIO import StringIO
    type_map = {
        tokenize.NUMBER: "(literal)",
        tokenize.STRING: "(literal)",
        tokenize.OP: "(operator)",
        tokenize.NAME: "(name)",
        }
    for t in tokenize.generate_tokens(StringIO(program).next):
        try:
            yield type_map[t[0]], t[1]
        except KeyError:
            if t[0] == tokenize.ENDMARKER:
                break
            else:
                raise SyntaxError("Syntax error")
    yield "(end)", "(end)"

def tokenize(program):
    for id, value in tokenize_python(program):
        if id == "(literal)":
            symbol = symbol_table[id]
            s = symbol()
            s.value = value
        else:
            # name or operator
            symbol = symbol_table.get(value)
            if symbol:
                s = symbol()
            elif id == "(name)":
                symbol = symbol_table[id]
                s = symbol()
                s.value = value
            else:
                raise SyntaxError("Unknown operator (%r)" % id)
        yield s

This tokenizer is split into two parts; one language-specific parser that turns the source program into a stream of literals, names, and operators, and a second part that turns those into a token instances. The latter checks both operators and names against the symbol table (to handle keyword operators), and uses a psuedo-symbol (“(name)”) for all other names.

You could combine the two tasks into a single function, but the separation makes it a bit easier to test the parser, and also makes it possible to reuse the second part for other syntaxes.

We can test the new tokenizer with the old parser definition:

>>> parse("1+2") (+ (literal 1) (literal 2)) >>> parse(1+2+3") (+ (+ (literal 1) (literal 2)) (literal 3)) >>> parse(1+2*3") (+ (literal 1) (* (literal 2) (literal 3))) >>> parse(1.0*2+3") (+ (* (literal 1.0) (literal 2)) (literal 3)) >>> parse("'hello'+'world'") (+ (literal 'hello') (literal 'world')) 

The new tokenizer supports more literals, so our parser does that too, without any extra work. And we’re still using the 10-line expression implementation we introduced at the beginning of this article.

The Python Expression Grammar #

So, let’s do something about the grammar. We could figure out the correct expression grammar from the grammar snippet shown earlier, but there’s a more practical description in the section “Evaluation order” in Python’s language reference. The table in that section lists all expression operators in precedence order, from lowest to highest. Here are the corresponding definitions (starting at binding power 20):

symbol("lambda", 20)
symbol("if", 20) # ternary form

infix_r("or", 30); infix_r("and", 40); prefix("not", 50)

infix("in", 60); infix("not", 60) # in, not in
infix("is", 60) # is, is not
infix("<", 60); infix("<=", 60)
infix(">", 60); infix(">=", 60)
infix("<>", 60); infix("!=", 60); infix("==", 60)

infix("|", 70); infix("^", 80); infix("&", 90)

infix("<<", 100); infix(">>", 100)

infix("+", 110); infix("-", 110)

infix("*", 120); infix("/", 120); infix("//", 120)
infix("%", 120)

prefix("-", 130); prefix("+", 130); prefix("~", 130)

infix_r("**", 140)

symbol(".", 150); symbol("[", 150); symbol("(", 150)

These 16 lines define the syntax for 35 operators, and also provide behaviour for most of them.

However, tokens defined by the symbol helper have no intrinsic behaviour; to make them work, additional code is needed. There are also some intricacies caused by limitations in Python’s tokenizer; more about those later.

But before we start working on those symbols, we need to add behaviour to the pseudo-tokens too:

symbol("(literal)").nud = lambda self: self
symbol("(name)").nud = lambda self: self
symbol("(end)")

We can now do a quick sanity check:

>>> parse("1+2")
(+ (literal 1) (literal 2))
>>> parse("2<<3")
(<< (literal 2) (literal 3))

Parenthesized Expressions #

Let’s turn our focus to the remaining symbols, and start with something simple: parenthesized expressions. They can be implemented by a “nud” method on the “(” token:

def nud(self):
    expr = expression()
    advance(")")
    return expr
symbol("(").nud = nud

The “advance” function used here is a helper function that checks that the current token has a given value, before fetching the next token.

def advance(id=None):
    global token
    if id and token.id != id:
        raise SyntaxError("Expected %r" % id)
    token = next()

The “)” token must be registered; if not, the tokenizer will report it as an invalid token. To register it, just call the symbol function:

symbol(")")

Let’s try it out:

>>> 1+2*3
(+ (literal 1) (* (literal 2) (literal 3)))
>>> (1+2)*3
(* (+ (literal 1) (literal 2)) (literal 3))

Note that the “nud” method returns the inner expression, so the “(” node won’t appear in the resulting syntax tree.

Also note that we’re cheating here, for a moment: the “(” prefix has two meanings in Python; it can either be used for grouping, as above, or to create tuples. We’ll fix this below.

Ternary Operators #

Most custom methods look more or less exactly like their recursive-descent counterparts, and the code for inline if-else is no different:

def led(self, left):
    self.first = left
    self.second = expression()
    advance("else")
    self.third = expression()
    return self
symbol("if").led = led

Again, we need to register the extra token before we can try it out:

symbol("else")

>>> parse("1 if 2 else 3")
(if (literal 1) (literal 2) (literal 3))

Attribute and Item Lookups #

To handle attribute lookups, the “.” operator needs a “led” method. For convenience, this version verifies that the period is followed by a proper name token (this check could be made at a later stage as well):

def led(self, left):
    if token.id != "(name)":
        SyntaxError("Expected an attribute name.")
    self.first = left
    self.second = token
    advance()
    return self
symbol(".").led = led

>>> parse("foo.bar")
(. (name foo) (name bar))

Item access is similar; just add a “led” method to the “[” operator. And since “]” is part of the syntax, we need to register that symbol as well.

symbol("]") def led(self, left): self.first = left self.second = expression() advance("]") return self symbol("[").led = led >>> parse("'hello'[0]") ([ (literal 'hello') (literal 0)) 

Note that we’re ending up with lots of code of the form:

def led(self, left):
    ...
symbol(id).led = led

which is a bit inconvenient, if not else because it violates the “don’t repeat yourself” rule (the name of the method appears three times). A simple decorator solves this:

def method(s):
    assert issubclass(s, symbol_base)
    def bind(fn):
        setattr(s, fn.__name__, fn)
    return bind

This decorator picks up the function name, and attaches that to the given symbol. This puts the symbol name before the method definition, and only requires you to write the method name once.

@method(symbol(id))
def led(self, left):
    ...

We’ll use this in the following examples. The other approach isn’t much longer, so you can still use it if you need to target Python 2.3 or older. Just watch out for typos.

Function Calls #

A function call consists of an expression followed by a comma-separated expression list, in parentheses. By treating the left parentesis as a binary operator, parsing this is straight-forward:

symbol(")"); symbol(",") @method(symbol("(")) def led(self, left): self.first = left self.second = [] if token.id != ")": while 1: self.second.append(expression()) if token.id != ",": break advance(",") advance(")") return self >>> parse("hello(1,2,3)") (( (name hello) [(literal 1), (literal 2), (literal 3)]) 

This is a bit simplified; keyword arguments and the “*” and “**” forms are not supported by this version. To handle keyword arguments, look for an “=” after the first expression, and if that’s found, check that the subtree is a plain name, and then call expression again to get the default value. The other forms could be handled by “nud” methods on the corresponding operators, but it’s probably easier to handle these too in this method.

Lambdas #

Lambdas are also quite simple. Since the “lambda” keyword is a prefix operator, we’ll implement it using a “nud” method:

symbol(":")

@method(symbol("lambda"))
def nud(self):
    self.first = []
    if token.id != ":":
        argument_list(self.first)
    advance(":")
    self.second = expression()
    return self

def argument_list(list):
    while 1:
        if token.id != "(name)":
            SyntaxError("Expected an argument name.")
        list.append(token)
        advance()
        if token.id != ",":
            break
        advance(",")

>>> parse("lambda a, b, c: a+b+c")
(lambda [(name a), (name b), (name c)]
    (+ (+ (name a) (name b)) (name c)))

Again, the argument list parsing is a bit simplified; it doesn’t handle default values and the “*” and “**” forms. See above for implementation hints. Also note that there’s no scope handling at the parser level in this implementation. See Crockford’s article for more on that topic.

Constants #

Constants can be handled as literals; the following “nud” method changes the token instance to a literal node, and inserts the token itself as the literal value:

def constant(id):
    @method(symbol(id))
    def nud(self):
        self.id = "(literal)"
        self.value = id
        return self

constant("None")
constant("True")
constant("False")

>>> parse("1 is None")
(is (literal 1) (literal None))
>>> parse("True or False")
(or (literal True) (literal False))

Multi-Token Operators #

Python has two multi-token operators, “is not” and “not in”, but our parser doesn’t quite treat them correctly:

>>> parse("1 is not 2")
(is (literal 1) (not (literal 2)))

The problem is that the standard tokenize module doesn’t understand this syntax, so it happily returns these operators as two separate tokens:

>>> list(tokenize("1 is not 2"))
[(literal 1), (is), (not), (literal 2), ((end))]

In other words, “1 is not 2” is handled as “1 is (not 2)”, which isn’t the same thing:

>>> 1 is not 2
True
>>> 1 is (not 2)
False

One way to fix this is to tweak the tokenizer (e.g. by inserting a combining filter between the raw Python parser and the token instance factory), but it’s probably easier to fix this with custom “led” methods on the “is” and “not” operators:

@method(symbol("not"))
def led(self, left):
    if token.id != "in":
        raise SyntaxError("Invalid syntax")
    advance()
    self.id = "not in"
    self.first = left
    self.second = expression(60)
    return self

@method(symbol("is"))
def led(self, left):
    if token.id == "not":
        advance()
        self.id = "is not"
    self.first = left
    self.second = expression(60)
    return self

>>> parse("1 in 2")
(in (literal 1) (literal 2))
>>> parse("1 not in 2")
(not in (literal 1) (literal 2))
>>> parse("1 is 2")
(is (literal 1) (literal 2))
>>> parse("1 is not 2")
(is not (literal 1) (literal 2))

This means that the “not” operator handles both unary “not” and binary “not in”.

Tuples, Lists, and Dictionary Displays #

As noted above, the “(” prefix serves two purposes in Python; it’s used for grouping, and to create tuples (it’s also used as a binary operator, for function calls). To handle tuples, we need to replace the “nud” method with a version that can distinguish between tuples and a plain parenthesized expression.

Python’s tuple-forming rules are simple; if a pair of parenteses are empty, or contain at least one comma, it’s a tuple. Otherwise, it’s an expression. Or in other words:

  • () is a tuple
  • (1) is a parenthesized expression
  • (1,) is a tuple
  • (1, 2) is a tuple

Here’s a “nud” replacement that implements these rules:

@method(symbol("(")) def nud(self): self.first = [] comma = False if token.id != ")": while 1: if token.id == ")": break self.first.append(expression()) if token.id != ",": break comma = True advance(",") advance(")") if not self.first or comma: return self # tuple else: return self.first[0] >>> parse("()") (() >>> parse("(1)") (literal 1) >>> parse("(1,)") (( [(literal 1)]) >>> parse("(1, 2)") (( [(literal 1), (literal 2)]) 

Lists and dictionaries are a bit simpler; they’re just plain lists of expressions or expression pairs. Don’t forget to register the extra tokens.

symbol("]") @method(symbol("[")) def nud(self): self.first = [] if token.id != "]": while 1: if token.id == "]": break self.first.append(expression()) if token.id != ",": break advance(",") advance("]") return self >>> parse("[1, 2, 3]") ([ [(literal 1), (literal 2), (literal 3)]) symbol("}"); symbol(":") @method(symbol("{")) def nud(self): self.first = [] if token.id != "}": while 1: if token.id == "}": break self.first.append(expression()) advance(":") self.first.append(expression()) if token.id != ",": break advance(",") advance("}") return self >>> {1: 'one', 2: 'two'} ({ [(literal 1), (literal 'one'), (literal 2), (literal 'two')]) 

Note that Python allows you to use optional trailing commas when creating lists, tuples, and dictionaries; an extra if-statement at the beginning of the collection loop takes care of that case.

Summary #

At roughly 250 lines of code (including the entire parser machinery), there are still a few things left to add before we can claim to fully support the Python 2.5 expression syntax, but we’ve covered a remarkably large part of the syntax with very little work.

And as we’ve seen thoughout this article, parsers using this algorithm and implementation approach are readable, easy to extend, and, as we’ll see in a moment, surprisingly fast. While this article has focussed on expressions, the algorithm can be easily extended for statement-oriented syntaxes. See Crockford’s article for one way to do that.

All in all, Pratt’s parsing algorithm is a great addition to the Python parsing toolbox, and the implementation strategy outlined in this article is a simple way to quickly implement such parsers.

Performance #

As we’ve seen, the parser makes only a few Python calls per token, which means that it should be pretty efficient (or as Pratt put it, “efficient in practice if not in theory”).

To test practical performance, I picked a 456 character long Python expression (about 300 tokens) from the Python FAQ, and parsed it with a number of different tools. Here are some typical results under Python 2.5:

  • topdown parse (to abstract syntax tree): 4.0 ms
  • built-in parse (to tuple tree): 0.60 ms
  • built-in compile (to code object): 0.68 ms
  • compiler parse (to abtract syntax tree): 4.8 ms
  • compiler compile (to code object): 18 ms

If we tweak the parser to work on a precomputed list of tokens (obtained by running “list(tokenize_python(program))”), the parsing time drops to just under 0.9 ms. In other words, only about one fourth of the time for the full parse is spent on token instance creation, parsing, and tree building. The rest is almost entirely spent in Python’s tokenize module. With a faster tokenizer, this algorithm would get within 2x or so from Python’s built-in tokenizer/parser.

The built-in parse test is in itself quite interesting; it uses Python’s internal tokenizer and parser module (both of which are written in C), and uses the parser module (also written in C) to convert the internal syntax tree object to a tuple tree. This is fast, but results in a remarkably undecipherable low-level tree:

>>> parser.st2tuple(parser.expr("1+2"))
(258, (326, (303, (304, (305, (306, (307, (309,
(310, (311, (312, (313, (314, (315, (316, (317,
(2, '1'))))), (14, '+'), (314, (315, (316, (317,
(2, '2')))))))))))))))), (4, ''), (0, ''))

(In this example, 2 means number, 14 means plus, 4 is newline, and 0 is end of program. The 3-digit numbers represent intermediate rules in the Python grammar.)

The compiler parse test uses the parse function from the compiler package instead; this function uses Python’s internal tokenizer and parser, and then turns the resulting low-level structure into a much nicer abstract tree:

>>> import compiler
>>> compiler.parse("1+2", "eval")
Expression(Add((Const(1), Const(2))))

This conversion (done in Python) turns out to be more work than parsing the expression with the topdown parser; with the code in this article, we get an abstract tree in about 85% of the time, despite using a really slow tokenizer.

Code Notes #

The code in this article uses global variables to hold parser state (the “token” variable and the “next” helper). If you need a thread-safe parser, these should be moved to a context object. This will result in a slight performance hit, but there are some surprising ways to compensate for that, by trading a little memory for performance. More on that in a later article.

All code for the interpreters and translators shown in this article is included in the article itself. Assorted code samples are also available from:

http://svn.effbot.org/public/stuff/sandbox/topdown

 

A Django site. rendered by a django application. hosted by webfaction.

avanafil
купить диплом института
https://originality-diplomx24.com
купить свидетельство о браке
https://originality-diploms24.com
купить диплом фармацевта
https://originality-diploma24.com
купить диплом техникума
https://originality-diploman24.com
купить диплом
https://originality-diplomans24.com
купить аттестат за 9 класс
https://originality-diplomik24.com
купить аттестат за 11 класс
https://originality-diplomix24.com
купить аттестат за 11 класс
https://originality-diplomas24.com
купить диплом в москве
https://originality-diplomiks24.com
купить диплом ссср
https://originality-diplomiki24.com
купить аттестат за 9 класс
купить диплом колледжа
ru-diplomirovan.com
купить диплом
www.ru-diplomirovany.com
купить диплом автомеханика
ru-diplomirovanay.com
купить диплом колледжа
http://www.ru-diplomirovannie.com
купить диплом техникума
www.rudiplomir.com
купить диплом врача
https://rudiplomirs.com
купить диплом колледжа
https://rudiplomira.com
купить диплом университета
http://www.rudiplomiry.com
купить диплом института
https://rudiplomiru.com
купить диплом кандидата наук
https://diplom-msk24.ru
купить диплом ссср
http://www.rudik-attestats365.com
где купить диплом
купить диплом магистра
https://premialnie-diploms24.com
купить диплом ссср
www.premialnie-diplomy24.com
купить дипломы о высшем
http://www.premialnie-diplomas24.com
купить аттестат
http://www.premialnie-diploman24.com
купить аттестат за 9 класс
http://www.premialnie-diplomx24.com
купить диплом о среднем специальном
premialnie-diplomix24.com
купить диплом вуза
https://premialnie-diplomik24.com
купить диплом в москве
premialnie-diplomans24.com
купить диплом бакалавра
originality-diplomix.com
купить диплом в москве
https://russiany-diplomx.com
купить диплом кандидата наук
http://www.russiany-diplomix.com
купить аттестат
https://premialnie-diplomx.com
купить диплом колледжа
http://www.premialnie-diplomix.com
купить аттестат за 9 класс
http://www.diplomx-asx.com
купить аттестат за 11 класс
www.diplomix-asx.com купить диплом о среднем образовании цена
купить свидетельство о браке
можно ли купить диплом вуза
купить диплом бакалавра
https://premialnie-diplomansy.com/купить-диплом-института/
купить дипломы о высшем
https://premialnie-diplomansy.com/купить-диплом-колледжа/
купить диплом
https://russiany-diplomans.com/attestat-9-klassov
купить диплом
https://russiany-diplomans.com/kupit-diplom-tehnikuma
купить свидетельство о браке
https://russiany-diplomans.com/srednee-spetsialnoe-obrazovanie
купить аттестат за 11 класс
http://lands-diploms.com/attestat-11-klassov.html http://lands-diploms.com/specialnosti/yurist.html http://lands-diploms.com/goroda/novosibirsk.html http://lands-diploms.com/goroda/omsk.html http://lands-diploms.com/goroda/tula.html
купить диплом техникума
http://radiploma.com/kupit-diplom-s-reestrom http://radiploma.com/kupit-attestat-11-klassov http://radiploma.com/kupit-diplom-o-vysshem-obrazovanii http://radiploma.com/kupit-diplom-kosmetologa
купить диплом бакалавра
http://rudiplomisty24.com/диплом-техникума rudiplomisty24.com/диплом-специалиста-российского-вуза http://www.rudiplomisty24.com/купить-диплом-пермь http://rudiplomisty24.com/купить-диплом-воронеж
купить диплом фармацевта
купить аттестат за 9 класс
http://www.diploman-dok.com
купить диплом института
https://diploman-doks.com
купить диплом о среднем специальном
https://diploman-doky.com
купить свидетельство о рождении
diploman-doku.com
купить диплом автомеханика
https://diplomy-servise.com
купить диплом магистра
www.education-ua.com/kupit-legalnyij-diplom-v-ukraine.html
купить дипломы о высшем
http://education-ua.com/attestat-diplom.html
купить диплом о среднем специальном
http://education-ua.com/zaporozhe.html
купить диплом врача
http://www.education-ua.com/kieve.html
купить диплом автомеханика
http://education-ua.com/odesse.html
купить диплом техникума
education-ua.com/summah.html
купить аттестат
www.education-ua.com/ternopole.html
купить аттестат за 11 класс
www.education-ua.com/diplom-o-mediczinskom-obrazovanii.html
купить диплом о среднем образовании
http://education-ua.com/diplom-vyisshee.html
купить диплом колледжа
www.education-ua.com/kupit-nastoyashhij-diplom-goznak.html
купить диплом нового образца
http://www.education-ua.com/
купить аттестат за 11 класс
www.education-ua.com/diplom-texnikuma-kolledzha.html
купить диплом магистра
http://www.education-ua.com/diplom-magistra-ukrainskogo-vuza.html
купить диплом автомеханика
http://education-ua.com/diplom-o-mediczinskom-obrazovanii.html
купить диплом врача
http://www.education-ua.com/diplom-medsestry.html
купить диплом
www.education-ua.com/diplom-nedorogo.html
купить диплом автомеханика
www.education-ua.com/kupit-diplom-novogo-obrazca.html
где купить диплом
http://education-ua.com/otzivi-pogelania.html
купить диплом бакалавра
http://education-ua.com/diplom-vyisshee.html
купить свидетельство о рождении
http://www.education-ua.com/diplom-uchilishha.html
купить диплом о среднем образовании
http://education-ua.com/diplom-s-registracziej.html
купить дипломы о высшем
http://www.education-ua.com/kupit-diplom-s-provodkoj-v-ukraine.html
купить диплом университета
http://www.education-ua.com/diplom-specialista-ukrainskogo-vuza.html
купить диплом
http://www.education-ua.com/diplom-uchilishha.html
купить диплом фармацевта
www.education-ua.com/kupit-diplom-sssr-na-nastoyashhem-blanke.html
купить диплом нового образца
education-ua.com/svidetelstvo-o-rozhdenii.html
купить диплом автомеханика
http://education-ua.com/svidetelstvo-o-smerti.html
купить диплом о среднем образовании
http://education-ua.com/svidetelstvo-o-brake.html
купить дипломы о высшем
http://education-ua.com/svidetelstvo-o-razvode.html диплом купить купить аттестат купить диплом запорожье купить диплом киев купить диплом одесса купить диплом сумы купить диплом тернополь купить диплом врача купить диплом вуза купить диплом высшее купить диплом гознак купить диплом института купить диплом колледжа купить диплом магистра купить диплом медицинского училища купить диплом медсестры купить диплом недорого купить диплом нового образца купить диплом отзывы купить диплом о высшем образовании купить диплом о среднем образовании купить диплом с занесением в реестр купить диплом с проводкой купить диплом специалиста купить диплом средне специального образования купить диплом ссср купить свидетельство о рождении купить свидетельство о смерти купить свидетельство о браке купить свидетельство о разводе http://rudiplomisty24.com/аттестат-11-классов https://diplomy-servise.com http://dipplomy.com http://www.diplomansy.com/diplom-spetsialista diplomansy.com/diplom-magistra diplomansy.com/kupit-diplom-s-reestrom www.diplomansy.com/diplom-tekhnikuma-ili-kolledzha http://www.diplomansy.com/kupit-diplom-o-srednem-obrazovanii www.diplomansy.com/kupit-diplom-v-gorode https://diplomansy.com/kupit-diplom-ekaterinburg https://diplomansy.com/kupit-diplom-kazan https://diplomansy.com/kupit-diplom-volgograd diplomansy.com/diplom-o-vysshem-obrazovanii www.diplomansy.com/diplom-magistra www.diplomansy.com/kupit-diplom-o-srednem-obrazovanii https://diplomansy.com/attestat-za-11-klass www.diplomansy.com/kupit-diplom-v-gorode www.diplomansy.com/kupit-diplom-novosibirsk www.diplomansy.com/kupit-diplom-ekaterinburg https://diplomansy.com/kupit-diplom-omsk www.diplomansy.com/kupit-diplom-chelyabinsk https://diplomansy.com/kupit-diplom-volgograd eonline-diploma.com/kupit-diplom-o-vysshem-obrazovanii-oficialno https://eonline-diploma.com eonline-diploma.com/kupit-diplom.-otzyvy https://eonline-diploma.com/diplom-o-vysshem-obrazovanii-nizhnij-tagil www.eonline-diploma.com/diplom-o-vysshem-obrazovanii-v-tule
купить диплом вуза
http://http://egpu.ru/ http://http://vina-net.ru/ http://cursovik.ru/ http://http://spravkipiter.ru/ www.alcoself.ru school625.ru http://http://moek-college.ru/ mmcmedclinic.ru http://intimvisit.ru/ http://med-pravo.ru/ moskva-medcentr.ru http://intim-72.ru/ http://http://razigrushki.ru/ www.dgartschool.ru www.ftbteam.ru http://sb26.ru/ https://originaly-dokuments.com/attestaty/attestat-za-9-klass http://www.originaly-dokuments.com/diplom-o-srednem-spetsialnom-obrazovanii https://originaly-dokuments.com/gorod/nizhnij-tagil https://originaly-dokuments.com/gorod/vologda www.originaly-dokuments.com/gorod/saratov www.originaly-dokuments.com/spetsialnosti/yurist www.originaly-dokuments.com/spetsialnosti/povar http://www.originaly-dokuments.com/gorod/krasnodar www.originaly-dokuments.com/attestaty/attestat-za-11-klass http://www.originaly-dokuments.com/gorod/ekaterinburg http://www.originaly-dokuments.com/attestaty originaly-dokuments.com/spetsialnosti/bukhgalter originaly-dokuments.com/gorod/omsk http://www.originaly-dokuments.com/gorod/novosibirsk http://www.originaly-dokuments.com/gorod/sankt-peterburg www.originaly-dokuments.com/gorod/chelyabinsk www.originaly-dokuments.com/gorod/orjol http://www.originaly-dokuments.com/diplom-o-vysshem-obrazovanii/diplom-bakalavra https://originaly-dokuments.com/diplom-o-vysshem-obrazovanii/diplom-magistra http://www.originaly-dokuments.com/spetsialnosti/menedzher www.originaly-dokuments.com/diplom-o-srednem-spetsialnom-obrazovanii/diplom-kolledzha www.originaly-dokuments.com/diplom-o-srednem-spetsialnom-obrazovanii
купить диплом о среднем специальном
http://http://egpu.ru/ vina-net.ru cursovik.ru www.spravkipiter.ru http://http://alcoself.ru/ school625.ru http://http://moek-college.ru/ http://mmcmedclinic.ru/ intimvisit.ru http://med-pravo.ru/ www.moskva-medcentr.ru http://intim-72.ru/ http://http://razigrushki.ru/ www.dgartschool.ru http://http://ftbteam.ru/ http://http://sb26.ru/ https://originaly-dokuments.com/attestaty/attestat-za-9-klass originaly-dokuments.com/diplom-o-srednem-spetsialnom-obrazovanii originaly-dokuments.com/gorod/nizhnij-tagil http://www.originaly-dokuments.com/gorod/vologda originaly-dokuments.com/gorod/saratov originaly-dokuments.com/spetsialnosti/yurist http://www.originaly-dokuments.com/spetsialnosti/povar originaly-dokuments.com/gorod/krasnodar originaly-dokuments.com/attestaty/attestat-za-11-klass originaly-dokuments.com/gorod/ekaterinburg originaly-dokuments.com/attestaty www.originaly-dokuments.com/spetsialnosti/bukhgalter www.originaly-dokuments.com/gorod/omsk http://www.originaly-dokuments.com/gorod/novosibirsk originaly-dokuments.com/gorod/sankt-peterburg https://originaly-dokuments.com/gorod/chelyabinsk https://originaly-dokuments.com/gorod/orjol originaly-dokuments.com/diplom-o-vysshem-obrazovanii/diplom-bakalavra www.originaly-dokuments.com/diplom-o-vysshem-obrazovanii/diplom-magistra originaly-dokuments.com/spetsialnosti/menedzher www.originaly-dokuments.com/diplom-o-srednem-spetsialnom-obrazovanii/diplom-kolledzha https://originaly-dokuments.com/diplom-o-srednem-spetsialnom-obrazovanii
купить диплом врача
http://http://aurus-diploms.com/geography/kupit-diplom-v-toljatti.html lands-diploms.com www.lands-diploms.com aurus-diploms.com lands-diploms.com http://http://lands-diploms.com/goroda/samara.html http://aurus-diploms.com/geography/zarechnyj.html http://aurus-diploms.com/geography/kupit-diplom-v-saratove.html lands-diploms.com www.lands-diploms.com http://http://aurus-diploms.com/kupit-diplom-tehnika.html http://http://aurus-diploms.com/geography/kupit-diplom-v-cherepovce.html http://http://aurus-diploms.com/kupit-diplom-kosmetologa.html aurus-diploms.com http://http://aurus-diploms.com/geography/michurinsk.html http://aurus-diploms.com/geography/kupit-diplom-volzhskij.html www.lands-diploms.com http://http://aurus-diploms.com/geography/volsk.html http://aurus-diploms.com/kupit-diplom-santekhnika.html www.lands-diploms.com lands-diploms.com aurus-diploms.com http://aurus-diploms.com/geography/kupit-diplom-v-permi.html aurus-diploms.com http://lands-diploms.com/goroda/vladimir.html www.lands-diploms.com aurus-diploms.com http://http://aurus-diploms.com/kupit-diplom-svarshhika.html aurus-diploms.com http://http://lands-diploms.com/goroda/chita.html http://aurus-diploms.com/geography/kupit-diplom-v-novosibirske.html aurus-diploms.com http://http://aurus-diploms.com/geography/novoaltajsk.html http://lands-diploms.com/goroda/lipeck.html www.lands-diploms.com http://http://aurus-diploms.com/geography/kupit-diplom-v-kurske.html lands-diploms.com lands-diploms.com http://lands-diploms.com/goroda/voronezh.html http://aurus-diploms.com/geography/kupit-diplom-v-surgute.html http://lands-diploms.com/goroda/belgorod.html lands-diploms.com www.aurus-diploms.com www.lands-diploms.com lands-diploms.com http://lands-diploms.com/attestat-11-klassov.html http://aurus-diploms.com/kupit-diplom-biologa.html http://http://lands-diploms.com/goroda/yaroslavl.html http://lands-diploms.com/specialnosti/ekonomist.html www.lands-diploms.com www.lands-diploms.com www.lands-diploms.com http://aurus-diploms.com/geography/kupit-diplom-rostov-na-donu.html http://http://lands-diploms.com/goroda/barnaul.html aurus-diploms.com http://http://lands-diploms.com/goroda/kazan.html http://aurus-diploms.com/geography/kupit-diplom-v-saranske.html www.lands-diploms.com http://http://aurus-diploms.com/geography/sevastopol.html http://lands-diploms.com/goroda/balashiha.html lands-diploms.com http://http://aurus-diploms.com/geography/kupit-diplom-v-ivanovo.html www.lands-diploms.com http://http://aurus-diploms.com/geography/kupit-diplom-v-rjazani.html http://aurus-diploms.com/geography/diplom-v-tomske.html http://lands-diploms.com/goroda/irkutsk.html www.lands-diploms.com aurus-diploms.com http://http://lands-diploms.com/goroda/ryazan.html http://http://aurus-diploms.com/kupit-diplom-avtomehanika.html www.aurus-diploms.com www.aurus-diploms.com www.aurus-diploms.com www.lands-diploms.com купить диплом с занесением в реестр https://archive-diplom.com диплом купить с занесением в реестр москва куплю диплом техникума реестр archive-diploman.com купить диплом с внесением в реестр https://archive-diploms24.com archive-diplomy24.com www.diploms-ukraine.com http://http://diploms-ukraine.com/diplom-spetsialista diploms-ukraine.com diploms-ukraine.com diploms-ukraine.com http://http://diploms-ukraine.com/kupit-diplom-belaya-tserkov www.diploms-ukraine.com diploms-ukraine.com www.diploms-ukraine.com www.diploms-ukraine.com diploms-ukraine.com diploms-ukraine.com diploms-ukraine.com http://http://diploms-ukraine.com/kupit-diplom-nikolaev www.diploms-ukraine.com diploms-ukraine.com http://diploms-ukraine.com/kupit-diplom-ternopol http://http://diploms-ukraine.com/kupit-diplom-vuza www.diploms-ukraine.com http://diploms-ukraine.com/meditsinskij-diplom diploms-ukraine.com http://diploms-ukraine.com/svidetelstvo-o-razvode www.diploms-ukraine.com http://https://6landik-diploms.ru www.7arusak-diploms.ru www.8saksx-diploms.ru http://https://8arusak-diploms24.ru www.9saksx-diploms24.ru www.4russkiy365-diploms.ru www.5gruppa365-diploms.ru www.6rudik-diploms365.ru купить диплом училища купить диплом харьков купить диплом кривой рог купить диплом ужгород купить аттестат школы
купить аттестат
купить диплом в челябинске купить диплом в благовещенске купить диплом киев купить диплом в волгограде купить диплом кандидата наук купить диплом о высшем образовании реестр купить диплом провизора купить диплом университета купить диплом в туапсе купить диплом с занесением в реестр купить диплом педагога купить диплом альчевск купить диплом в черногорске купить диплом дизайнера купить диплом учителя купить диплом фельдшера купить диплом в набережных челнах купить диплом в старом осколе купить диплом в заречном купить диплом в ишимбае купить диплом в омске купить диплом в санкт-петербурге купить диплом в сургуте купить диплом кировоград купить диплом менеджера купить диплом в екатеринбурге купить диплом в саратове купить диплом в нижнем новгороде купить диплом инженера механика купить диплом в нижнем новгороде купить диплом тренера купить диплом ивано-франковск купить диплом в перми купить диплом в череповце купить диплом фитнес инструктора купить диплом в белогорске купить диплом в каменске-шахтинском купить диплом учителя купить свидетельство о разводе купить диплом в томске купить диплом специалиста купить диплом воспитателя купить диплом в воронеже купить диплом экономиста купить диплом энергетика купить диплом в буйнакске купить диплом медсестры купить диплом массажиста купить диплом кривой рог купить диплом в томске купить диплом бурильщика купить диплом механика купить диплом железнодорожника купить диплом менеджера купить диплом медсестры купить диплом с занесением в реестр купить диплом высшем образовании занесением реестр купить диплом училища купить диплом в нижним тагиле купить диплом в калуге купить диплом парикмахера купить диплом в минеральных водах купить диплом сварщика купить диплом в махачкале купить диплом с внесением в реестр купить диплом в архангельске купить диплом экономиста купить аттестат за классов купить диплом средне техническое купить диплом керчь купить диплом николаев купить диплом массажиста купить диплом штукатура купить диплом автомеханика купить диплом в ставрополе купить диплом хмельницкий купить диплом энергетика купить диплом в ярославле купить диплом в белгороде купить диплом сварщика купить свидетельство о рождении купить диплом в рязани купить диплом винница купить диплом в липецке купить диплом в туле купить диплом программиста купить диплом сантехника купить диплом в оренбурге купить диплом днепропетровск купить диплом в балашихе купить диплом в самаре купить диплом в ростове-на-дону купить диплом в серове купить диплом средне специального образования купить диплом фармацевта купить диплом в севастополе купить диплом полтава купить диплом биолога купить диплом в казани купить диплом в смоленске купить диплом в сочи купить диплом реестром москве купить диплом житомир купить диплом в чите купить диплом в краснодаре купить диплом александрия купить аттестат купить диплом в саранске купить диплом в курске https://premialnie-diplomansy.com/отзывы-клиентов/ premialnie-diplomansy.com http://https://premialnie-diplomansy.com/инженер-строитель/ https://originality-diploman.com/свидетельство-о-разводе originality-diploman.com http://https://originality-diploman.com/купить-диплом-юриста http://https://originality-diploman.com/купить-диплом-медсестры https://originality-diploman.com/купить-диплом-врача http://https://originality-diploman.com/купить-диплом-института-в-рф https://originality-diploman.com/купить-диплом-красноярск http://https://originality-diploman.com/купить-диплом-ссср premialnie-diplomansy.com www.originality-diploman.com http://https://premialnie-diplomansy.com/купить-диплом-пермь/ www.premialnie-diplomansy.com premialnie-diplomansy.com www.premialnie-diplomansy.com premialnie-diplomansy.com www.premialnie-diplomansy.com https://originality-diploman.com/купить-диплом-москва
купить диплом о среднем специальном
https://originality-diplomas.com/
купить аттестат
rudiplomista24.com
купить диплом о среднем образовании
www.lands-diplom.com
купить диплом университета
http://https://gosznac-diplom24.com/ aurus-diplom.com
купить диплом о среднем образовании
originality-diplomans.com
купить диплом врача
http://https://rudiplomis24.com/
купить аттестат за 9 класс
diploma-asx.com
купить дипломы о высшем
gosznac-diplom24.com www.aurus-diplom.com
купить свидетельство о рождении
originality-diplomas.com
купить диплом кандидата наук
https://rudiplomista24.com/
купить диплом ссср
www.diploma-asx.com
купить свидетельство о рождении
www.gosznac-diplom24.com www.diploman-doci.com
купить диплом фармацевта
originality-diplomans.com
где купить диплом
rudiplomista24.com
купить дипломы о высшем
diploma-asx.com
купить диплом колледжа
www.gosznac-diplom24.com www.diploman-doci.com
купить диплом техникума
http://diplomsagroups.com/kupit-diplom-v-gorode/bryansk.html https://diploma-asx.com/ www.originality-diploman24.com Купить диплом Екатеринбург radiploms.com https://radiplom.com/kupit-diplom-stomatologa radiplomy.com www.radiplomas.com https://frees-diplom.com/diplom-feldshera купить свидетельство о рождении Купить диплом СССР Купить диплом в Москве Купить диплом университета Купить диплом о среднем образовании bitcoin casinos for usa players www.russiany-diplomix.com www.russiany-diplomx.com http://https://russiany-diplomana.com/diplom-o-srednem-tehnicheskom-obrazovanii https://russiany-diploman.com/kupit-diplom-novosibirsk https://russiany-diplomany.com/kupit-diplom-farmatsevta http://https://russiany-diplomas.com/kupit-diplom-novosibirsk купить диплом о среднем образовании Купить аттестат 11 классов Купить диплом в казани Купить диплом Воронеж Купить аттестат 11 классов купить диплом в Красноярске rudiplomirovany.com www.rudiplomirovans.com http://http://rudiplomirovana.com/аттестат-11-классов www.rudiplomisty.com http://http://rudiplomis.com/купить-диплом-омск www.rudiplomista.com www.diploman-doci.com rudiplomista24.com http://http://rudiplomis24.com/диплом-колледжа http://rudiplomirovan.com/свидетельства-и-справки/свидетельство-о-разводе http://http://ru-diplomirovanie.com/купить-диплом-томск www.ru-diplomirovan.com Купить свидетельство о разводе Купить диплом Екатеринбург Купить диплом Екатеринбург Купить аттестат 11 классов Купить диплом в Краснодаре Купить диплом в СПБ Купить диплом строителя купить диплом в екатеринбурге Купить диплом Воронеж Купить диплом Москва Купить диплом техникума Купить диплом для иностранцев Купить диплом колледжа https://diploman-doci.com/diplom-kandidata-nauk lands-diplomy.com купить диплом университета купить аттестат https://ru-diplomirovans.com/аттестат-11-классов www.ru-diplomirovana.com ru-diplomirovany.com ru-diplomirovanay.com https://ru-diplomirovannie.com/купить-диплом-томск https://gosznac-diplomy.com/kupit-diplom-v-krasnodare Купить диплом с реестром Купить диплом Казань Купить диплом врача Купить диплом в СПБ Купить аттестат за 9 классов Купить диплом Екатеринбург www.diplomsagroups.com купить диплом в хабаровске deep nudify www.i-medic.com.ua http://https://renault-club.kiev.ua/germetik-dlya-avto-zahist-ta-doglyad-za-vashym-avtomobilem tehnoprice.in.ua http://https://lifeinvest.com.ua/kley-dlya-far https://warfare.com.ua/kupity-germetik-dlya-far-gid-pokuptsya www.05161.com.ua http://https://brightwallpapers.com.ua/butilovyy-germetik-dlya-far http://http://3dlevsha.com.ua/kupit-germetik-avtomobilnyy https://abank.com.ua/chernyy-germetik-dlya-far abshop.com.ua http://https://alicegood.com.ua/linzy-v-faru artflo.com.ua www.atlantic-club.com.ua http://https://atelierdesdelices.com.ua/sekrety-vyboru-idealnyh-led-linz-dlya-vashoho-avto www.510.com.ua www.autostill.com.ua https://autodoctor.com.ua/led-linzi-v-fari-de-kupiti-yak-vibrati-vstanoviti-dlya-krashchogo-osvitlennya-vashogo-avto http://babyphotostar.com.ua/linzi-v-fari-vibir-kupivlya-i-vstanovlennya https://bagit.com.ua/linzi-v-faru-vibir-kupivlya-i-vstanovlennya bagstore.com.ua befirst.com.ua https://bike-drive.com.ua/perevagi-led-far-chomu-varto-pereyti-na-novitnye-osvitlennya http://billiard-classic.com.ua/yak-vybraty-ta-kupyty-pnevmostepler-porady-ta-rekomendaciyi ch-z.com.ua www.bestpeople.com.ua www.daicond.com.ua delavore.com.ua https://jiraf.com.ua/stekla-far-yak-pokrashchyty-zovnishniy-vyglyad-vashogo-avto www.itware.com.ua http://http://logotypes.com.ua/osvitlennya-yak-klyuch-do-bezpeki-yak-vazhlyvi-linzi-dlya-far http://https://naduvnie-lodki.com.ua/jak-vibrati-idealni-stekla-far-dlya-vashogo-avto-poradi-vid-ekspertiv www.nagrevayka.com.ua http://https://repetitory.com.ua/chomu-yakisni-stekla-far-vazhlivi-dlya-vashogo-avtomobilya www.optimapharm.com.ua http://https://rockradio.com.ua/korpusy-dlya-far-vid-klasyky-do-suchasnykh-trendiv www.renenergy.com.ua shop4me.in.ua http://https://tops.net.ua/vibir-linz-dlya-far-osnovni-kriterii-ta-rekomendacii http://https://comfortdeluxe.com.ua/linzi-v-avtomobilnih-farah-vazhlivist-i-vpliv-na-yakist-osvitlennya www.companion.com.ua http://http://vlada.dp.ua/ekspluataciyni-harakteristiki-ta-znosostiikist-polikarbonatnih-ta-sklyanih-stekol-far www.tennis-club.kiev.ua metabo-partner.com.ua hr.com.ua www.dvernoyolimp.org.ua http://i-medic.com.ua/naykrashche-sklo-dlya-far-yak-obraty-dlya-vashogo-avto https://renault-club.kiev.ua/steklo-dlya-far-yak-obraty-vstanovyty-ta-doglyadat www.tehnoprice.in.ua https://lifeinvest.com.ua/yak-vybraty-korpus-fary-dlya-vashogo-avtomobilya-porady-ta-rekomendaciyi http://daicond.com.ua/yak-doglyadaty-za-sklom-dlya-far-porady-ta-sekrety 05161.com.ua http://https://brightwallpapers.com.ua/sklo-far-dlya-staryh-avtomobiliv-de-znayty-i-yak-pidibraty http://3dlevsha.com.ua/yak-bi-led-24v-linzi-pidvyshchuyut-bezpeku-na-dorozi-doslidzhennya-ta-fakty http://https://abank.com.ua/zamina-skla-far-na-mazda-6-gh-pokrokove-kerivnytstvo svetiteni.com.ua www.startupline.com.ua http://http://unasoft.com.ua/termostiikyy-kley-dlya-stekla-far-yak-vybraty-naykrashchyy-variant https://apartments.dp.ua/bi-led-24v-linzi-v-fary-yak-vony-pratsyuyut-i-chomu-vony-efektyvni www.sun-shop.com.ua http://https://ital-parts.com.ua/yak-vibraty-idealni-bi-led-linzi-24v-dlya-vashogo-avto-poradi-ta-rekomendatsiyi http://http://corpnews.com.ua/led-lampy-z-linzamy-innovatsiyi-u-sviti-avtomobilnogo-osvitlennya www.brides.com.ua www.grim.in.ua www.mega-m.com.ua www.hr.com.ua https://gazeta.sebastopol.ua/chomu-obraty-bi-led-linzy-3-dyuyma-dlya-vashogo-avto https://interpen.com.ua/bi-led-linzy-3-dyuyma-pokrashchennya-osvitlennya bookidoc.com.ua vps.com.ua https://dvernoyolimp.org.ua/linzy-dlya-far-porady-z-vyboru-ta-vstanovlennya www.interpen.com.ua fairspin blockchain casino 5 popular bitcoin casino blockchain online casino online australian casinos that accept neosurf gold ira companies – gold ira companies compared https://05161.com.ua/yak-pidvishchiti-bezpeku-na-dorozi-za-dopomogoyu-yakisnih-linz-u-farah https://cancer.com.ua/yak-vibrati-yakisne-sklo-korpus-ta-inshi-skladovi-dlya-far https://dvernoyolimp.org.ua/vidguki-pro-sklo-dlya-far-shcho-kazhut-koristuvachi https://smotri.com.ua/perevagi-ta-nedoliki-riznih-tipiv-led-linz https://05161.com.ua/materiali-korpusiv-far-shcho-obrati-dlya-vashogo-avto https://vps.com.ua/oglyad-novitnih-tehnologiy-u-virobnitstvi-skla-dlya-far https://diamond-gallery.in.ua/perevagi-zamini-skla-far-pokrashchennya-vidimosti-ta-bezpeki https://cancer.com.ua/vse-shcho-potribno-znati-pro-sklo-korpusi-ta-skladovi-fari-avtomobilya https://warfare.com.ua/vartist-bi-led-linz-shcho-vplivaie-na-cinu-i-yak-znayti-optimalniy-variant https://firma.com.ua/perevagi-kupivli-led-linz-pokrashchennya-osvitlennya-ta-stylyu https://slovakia.kiev.ua/poshireni-problemi-zi-sklom-far-ta-yih-virishennya https://eterna.in.ua/yak-vibrati-chaynik-dlya-himichnogo-poliruvannya-avtomobilnih-far-poradi-vid-ekspertiv https://geliosfireworks.com.ua/idealne-osvitlennya-yak-obrati-optimalni-bi-led-linzi https://eebc.net.ua/idealne-osvitlennya-yak-obrati-optimalni-bi-led-linzi https://omurp.org.ua/oglyad-populyarnih-brendiv-skla-ta-korpusiv-dlya-far https://vwclub.org.ua/novitni-tehnologiyi-osvitlennya-vibor-bi-led-linz-dlya-vashogo-avto https://thecrimea.org.ua/perevagi-vikoristannya-bi-led-linz-u-farah-avtomobilya https://510.com.ua/chomu-vazhlivo-zaminyuvati-poshkodzene-sklo-far-poradi-vid-ekspertiv https://lifeinvest.com.ua/oglyad-naykrashchih-bi-led-linz-u-2024-roci https://alicegood.com.ua/remont-far-zamina-skla-korpusu-ta-inshih-skladovih https://atelierdesdelices.com.ua/top-10-virobnikiv-skla-dlya-far-avtomobiliv-u-2024-roci https://atlantic-club.com.ua/yak-obrati-khoroshogo-ryepyetitora-z-angliyskoyi-movi-klyuchovi-aspyekti-viboru https://tehnoprice.in.ua/trivalist-zhittya-bi-led-linz-shcho-ochikuvati https://artflo.com.ua/yak-vibrati-naykrashche-sklo-dlya-far-vashogo-avtomobilya https://i-medic.com.ua/bi-led-linzi-tehnologiyi-maybutnogo-dlya-suchasnih-avtomobiliv https://renault-club.kiev.ua/yak-bi-led-linzi-pokrashuyut-vidimist-na-dorozi https://elektromotor.net.ua/butiloviy-germetik-dlya-far-vse-scho-vam-potribno-znati https://cpaday.com.ua/yak-zakhistiti-steklo-fari-vid-vologi-i-pilu-za-dopomogoyu-germetika https://ameli-studio.com.ua/vidguki-ta-reytingi-yakiy-germetik-dlya-far-varto-kupiti https://blooms.com.ua/perevagi-bi-led-linz-chomu-varto-pereyti-na-novi-tehnologiyi-osvitlennya https://dnmagazine.com.ua/yak-vibrati-naykrashchiy-germetik-dlya-far-vashogo-avtomobilya https://cocoshop.com.ua/novitni-rishennya-v-oblasti-stekol-dlya-far-yak-obrati-pravilno https://salle.com.ua/vidnovlennya-stekla-fari-vse-scho-vam-potribno-znati https://reklamist.com.ua/zahist-stekla-fari-avto-vid-pogodnih-umov-i-vplivu-vid-shlyahu https://synergize.com.ua/yak-obrati-bi-led-linzi-dlya-far-poradi-ekspertiv https://brandwatches.com.ua/germetik-dlya-far-navishcho-vin-potriben-i-yak-pravilno-vikoristovuvati https://abshop.com.ua/perevagi-yakisnogo-skla-korpusiv-ta-skladovih-far-dlya-vashogo-avto https://tyres.com.ua/yak-led-linzi-pokrashchuyut-vidimist-na-dorozi https://tm-marmelad.com.ua/perevagi-bi-led-linz-dlya-vashogo-avtomobilya-chomu-varto-pereyti-na-novu-tehnologiyu https://pravoslavnews.com.ua/top-5-stekol-dlya-far-avtomobilya-porivnyannya-i-vidguki https://salonsharm.com.ua/yak-zaminiti-steklo-fari-na-avtomobili-samostiyno-pokrokova-instruktsiya https://tayger.com.ua/perevagi-led-linz-chomu-varto-zrobiti-vibir-na-korist-novitnoyi-tehnologiyi
накрутка поведенческих факторов накрутка пф купить https://keepstyle.com.ua/kak-pravilno-ispolzovat-germetik-dlya-far-avto https://aurus-diploms.com/geography/kupit-diplom-v-nizhnem-tagile.html купить диплом в курске купить свидетельство о рождении купить диплом переводчика https://ru-diplomirovans.com/школьный-аттестат купить диплом в красноярске купить диплом о среднем специальном https://diploman-dok.com/kupit-diplom-habarovsk https://russiany-diplomans.com/diplom-sssr https://radiplomy.com/kupit-diplom-s-reestrom купить диплом института https://diplomix-asx.com/kupit-diplom-sssr https://rusd-diploms.com/diplom-medsestryi.html куплю диплом высшего образования https://try-kolduna.com.ua/where-to-buy-bilead-lens.html https://silvestry.com.ua/top-5-powerful-bilead.html http://apartments.dp.ua/optima-bilead-review.html http://companion.com.ua/laser-bilead-future.html http://slovakia.kiev.ua/h7-bilead-lens-guide.html https://join.com.ua/h4-bilead-lens-guide.html https://kfek.org.ua/focus2-bilead-install.html https://lift-load.com.ua/dual-chip-bilead-lens.html http://davinci-design.com.ua/bolt-mount-bilead.html http://funhost.org.ua/bilead-test-drive.html http://comfortdeluxe.com.ua/bilead-selection-criteria.html http://shopsecret.com.ua/bilead-principles.html https://firma.com.ua/bilead-lens-revolution.html http://sun-shop.com.ua/bilead-lens-price-comparison.html https://para-dise.com.ua/bilead-lens-guide.html https://geliosfireworks.com.ua/bilead-installation-guide.html https://tops.net.ua/bilead-buyers-guide.html https://degustator.net.ua/bilead-2024-review.html https://oncology.com.ua/bilead-2022-rating.html https://shop4me.in.ua/bestselling-bilead-2023.html https://crazy-professor.com.ua/aozoom-bilead-review.html http://reklama-sev.com.ua/angel-eyes-bilead.html http://gollos.com.ua/angel-eyes-bilead.html http://jokes.com.ua/ams-bilead-review.html https://greenap.com.ua/adaptive-bilead-future.html http://kvn-tehno.com.ua/3-inch-bilead-market-review.html https://salesup.in.ua/3-inch-bilead-lens-guide.html http://compromat.in.ua/2-5-inch-bilead-lens-guide.html http://vlada.dp.ua/24v-bilead-truck.html