Парсинг

Знову про синтаксис

Ми визначили синтаксис таким чином:

    classes ::= rootdef classdefs
    rootdef ::= rootclass { params }
    classdefs ::= classdef
    classdefs ::= classdefs classdef
    classdef ::= ordclass { params }
    params ::= ratedef
    params ::= ceildef
    params ::= descrdef
    params ::= params params
    ratedef ::= rate = number
    ceildef ::= ceil = number
    descrdef ::= descr = string
    rootclass ::= class classid root
    ordclass ::= class classid parent classid

Пам’ятаємо, що ми визначили базові типи («rate«, «=«, «classid» тощо) раніше. Ще на етапі сканування.

Парсинг

Дуже приємна особливість «ідеології» модуля SPARK — визначати синтаксис у рядках документації (docstrings) (пам’ятаєте, ми таким саме чином визначали регурярні вирази для сканування).

#!/usr/bin/env python
 
import spark
import ast
 
class SimpleParser(spark.GenericParser):
    def __init__(self, start='classes'):
        spark.GenericParser.__init__(self, start)
 
    def p_classes(self, args):
        """ classes ::= rootdef classdefs """
        return ast.AST(type='classes', kids=args)
 
    def p_rootdef(self, args):
        """ rootdef ::= rootclass { params } """
        a = ast.AST('root')
        a.classid = args[0][1]
        a.params = args[2]
        return a
 
    def p_classdefs(self, args):
        """
            classdefs ::= classdef
            classdefs ::= classdefs classdef
        """
        a = ast.AST(type='classdefs', kids=args)
        return a
 
    def p_classdef(self, args):
        """ classdef ::= ordclass { params } """
        a = ast.AST('classdef')
        a.classid = args[0][1]
        a.parent = args[0][3]
        a.params = args[2]
        return a
 
    def p_params(self, args):
        """
            params ::= ratedef
            params ::= ceildef
            params ::= descrdef
            params ::= params params
        """
        ret = []
        if len(args) > 1:
            for p in args:
                ret.extend([x for x in p])
            return ret
 
        return args
 
 
    def p_rate(self, args):
        """ ratedef ::= rate = number """
        args[0].value = args[2]
        return args[0]
 
    def p_ceil(self, args):
        """ ceildef ::= ceil = number """
        args[0].value = args[2]
        return args[0]
 
    def p_descr(self, args):
        """ descrdef ::= descr = string """
        args[0].value = args[2]
        return args[0]
 
    def p_rootclass(self, args):
        """ rootclass ::= class classid root """
        return args
 
    def p_ordclass(self, args):
        """ ordclass ::= class classid parent classid """
        return args

(ast.AST так само не подано, він так само практично не відрізняється від «авторського»).

Кілька зауваг:

  1. Деякі методи вертають об’єкти ast.AST, деякі інші — просто аргументи, їм передані. Це тому, що ми не хочемо мати дерево AST, яке містить усі елементи як вузли дерева. Ми хочемо мати вузлами нашого дерева лише «classes«, «root«, «classdefs» та «classdef«.
  2. Ті методи, які вертають їхні аргументи, фактично передають ці аргументи батьківським вузлам (пам’ятаємо, що SPARK переглядає дерево знизу угору).
  3. Оскільки і «classdefs«, і «classdef» можуть мати параметри, метод p_params списки списків та списки всередині списків як аргумент. Щоб передати «верхньому» вузлу лише об’єкти token.Token, необхідно «перепаковувати» такі списки.
  4. Ви можете вставити print args у кожен метод і бачити, що саме є аргументом у кожному випадку; тоді будуть зрозуміліші оті «args[0][1]«.

Отже, що ми маємо? Дивимося:

>>> parser = parser.SimpleParser()
>>> parsed = parser.parse(scanned)
>>> print "Got %s of type '%s'." % (parsed, parsed.type)
 
Got <ast.AST instance at 0xb7c5166c> of type 'classes'.
 
>>> print "It has kids:\n\t %s." % \
...        ',\n\t'.join ([ '%s of type %s' % (k, k.type) for k in parsed.kids ])
 
It has kids:
         <ast.AST instance at 0xb7c519cc> of type root,
        <ast.AST instance at 0xb7c5170c> of type classdefs.

На цьому етапі я вважаю, що ми маємо AST (Abstract Syntax Tree) для нашого конфігураційного файлу (написаного нашою власною «конфігураційною мовою»).

Робимо синтаксичні помилки

Видалимо слово «root» у 3-му рядку і перевіримо знову. маємо:

Syntax error at or near `{' token

Аналогічно, якщо замінимо «ceil» на «cello«:

Syntax error at or near `cello' token

Якщо замінимо «1024» на «1024foo«:

Syntax error at or near `foo' token

Якщо замінимо «1024» на «foo moo bar» (та помилка, яку сканер не зміг розпізнати), матимемо:

Syntax error at or near `foo' token

Знову ж, щоб довідатися, як примусити SPARK писати номери відповідних рядків, читайте документацію на сайті автора модуля.

Якщо підемо ще далі і спробуємо таке?

class 1:5 root {
        rate = 10240
        ceil = 20480
}
 
class 1:50 root {
        ceil = 2048
        rate = 1024
}

Безумовно, матимемо помилку «error at or near `root' token» — наш синтаксис дозволяє лише одне визначення кореневого класу.

Але якщо напишемо отак?

class 1:5 root {
        rate = 10240
        ceil = 20480
}
 
class 1:50 parent 1:7 {
        ceil = 2048
        rate = 1024
}

Ми вказали «parent 1:7«, але такого класу не існує; це, очевидно, є помилкою. З точки зору HTB.

Але парсер не може розпізнати таку помилку. Це задача наступного кроку — Семантичного аналізу.

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Цей сайт використовує Akismet для зменшення спаму. Дізнайтеся, як обробляються ваші дані коментарів.