Сканер
Ми «читатимемо» запропонований конфігураційний файл таким чином:
- Конфігураційний файл містить класи («classes»)
- Кожен клас починається «елементом» типу «class»…
- … та містить ідентифікатор класу (типу «classid») та «блок» («block»)
- Блок містить інші параметри класу.
Використовуючи SPARK ми створимо клас, який наслідує we will spark.GenericScanner
клас та визначимо регулярні вирази для розпізнавання елементів у рядках документації Python (docstrings) відповідних методів. Таким чином для кожного «елемента», що відповідає регулярному виразу „[a-zA-Z_]+[0-9]*
‘, буде викликано t_string
і т.д, і т.п:
#!/usr/bin/env python import spark import token class SimpleScanner(spark.GenericScanner): def __init__(self): spark.GenericScanner.__init__(self) self.keywords = [ '<strong>class</strong>', 'rate', 'ceil', 'descr', 'root', 'parent', ] def tokenize(self, input): self.rv = [] spark.GenericScanner.tokenize(self, input) return self.rv def t_whitespace(self, s): r'\s+' pass def t_comment(self, s): r'\<em>#.*'</em> pass def t_semicol(self, s): r';' self.rv.append(token.Token(type=s, attr=s)) def t_openblock(self, s): r'{' self.rv.append(token.Token(type=s, attr=s)) def t_closeblock(self, s): r'}' self.rv.append(token.Token(type=s, attr=s)) def t_equal(self, s): r'=' self.rv.append(token.Token(type=s, attr=s)) def t_number(self, s): r'[0-9]+' self.rv.append(token.Token(type='number', attr=s)) def t_classid(self,s): r'[0-9]+:[0-9]+' self.rv.append(token.Token(type='classid', attr=s)) def t_keyword(self, s): # r' class | irate | iceil | descr ' self.rv.append(token.Token(type=s, attr=s)) def t_string(self, s): r'[a-zA-Z_]+[0-9]*' if s in self.keywords: self.t_keyword(s) else: self.rv.append(token.Token(type='string', attr=s)) |
Що ж нам дає для розв’язання нашої задачі такий відносно простий клас? Спробуємо таке:
#!/usr/bin/env python import spark import scanner def scan(f): input = f.read() scnr = scanner.SimpleScanner() return scnr.tokenize(input) f = open('test.confg') scanned = scan(f) print scanned |
`print scanned
‘ надрукує послідовність елементів (я розбив рядок на коротші):
[class, 1:5, root, {, rate, =, 10240, ceil, =, 20480, }, class, 1:50, parent, 1:5, {, ceil, =, 2048, rate, =, 1024, descr, =, My_favorite_client, }, class, 1:53, parent, 1:50, {, descr, =, My_other_client, ceil, =, 2048, rate, =, 1024, } ] |
Не так і погано: наш конфігураційний файл розібрано на елементи!
Чудово, далі:
>>> print '\n'.join(['Token "%s" of type "%s"' % (x.attr, x.type) for x in scanned]) Token "class" of type "class" Token "1:5" of type "classid" Token "root" of type "root" Token "{" of type "{" Token "rate" of type "rate" Token "=" of type "=" Token "10240" of type "number" Token "ceil" of type "ceil" <... etc-etc ...> Token "1024" of type "number" Token "}" of type "}" |
Отже, ми «зісканували» нашу конфігурацію успішно — ми маємо список елементів (list of tokens) з іхніми типами (клас token.Token
визначає типи елементів — я його не подав, але він практично не відрізняється від поданого автором SPARK, John Aycock, у його документації).
Робимо лексичні помилки
Напишемо «roo*» замість «root» у 3-му рядку і спробуємо ще раз. Матимемо:
Specification error: unmatched input |
Бачимо, що SPARK не пише номери рядків, де було «зіскановано» помилку. Читайте підручник SPARK, там йдестья про те, як це «виправити».
Але що станеться, якщо напишемо таке?
class 1:50 parent 1:5 { ceil = foo moo bar rate = 1024 } |
Ми маємо «успішно» зіскановний список елементів:
. . . Token "ceil" of type "ceil" Token "=" of type "=" Token "foo" of type "string" Token "moo" of type "string" Token "bar" of type "string" . . . |
Отже, з лексичної точки зору — все нормально. Але, безумовно, це помилка :-)
Але це задача для наступного кроку — Парсингу.