Знову про синтаксис
Ми визначили синтаксис таким чином:
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
так само не подано, він так само практично не відрізняється від «авторського»).
Кілька зауваг:
- Деякі методи вертають об’єкти
ast.AST
, деякі інші — просто аргументи, їм передані. Це тому, що ми не хочемо мати дерево AST, яке містить усі елементи як вузли дерева. Ми хочемо мати вузлами нашого дерева лише «classes
«, «root
«, «classdefs
» та «classdef
«. - Ті методи, які вертають їхні аргументи, фактично передають ці аргументи батьківським вузлам (пам’ятаємо, що SPARK переглядає дерево знизу угору).
- Оскільки і «
classdefs
«, і «classdef
» можуть мати параметри, методp_params
списки списків та списки всередині списків як аргумент. Щоб передати «верхньому» вузлу лише об’єктиtoken.Token
, необхідно «перепаковувати» такі списки. - Ви можете вставити
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.
Але парсер не може розпізнати таку помилку. Це задача наступного кроку — Семантичного аналізу.