Почему доклад такой странный ― Он для тех, кто умеет писать регулярные выражения; ― У меня 40 минут, а в Книге дракона 1184 страницы; ― Книгу дракона я не дочитал; ― И тем не менее, я хочу рассказать полную реализацию шаблонизатора; ― Если вы знаете, в чем разница между LR(0) и LR(2), то вам тут будет скучно; ― 40 минут — у меня нет времени, буду лихо, хамски, срезать кучу углов.
Hello {{ first_name }}, {% if last_name %} {{ last_name }} {% elif title %} {{ title }} {% else %} Doe {% /if %}! Here is the list of stuff I like: {% loop stuff %} - {{ item.key }} (because of {{ item.value }}) {% /loop %} And that is all!
Пара слов о парсерах 1. Будем писать парсер по старинке; 2. Парсер — почти классический shift-reduce (восходящий, rightmost); 3. Мы не будем писать грамматику; 4. Мы будем сразу строить AST на стеке.
Генерируем шаблон 1. Делаем in-order обход дерева; 2. На основе контекста генерируем кусочек текста из ноды; 3. Собираем кусочки в том порядке, в котором они были сгенерированы; 4. Конкатенируем эти кусочки.
Генерируем шаблон class Node: ... def process(self, context): return "".join(self.emit(context)) def emit(self, context): for node in self: yield from node.emit(context)
Генерируем шаблон class IfNode(BlockTagNode): ... def emit(self, context): if self.evaluate_expression(context): yield from super().emit(context) elif self.elsenode: yield from self.elsenode.emit(context)
Генерируем шаблон class LoopNode(BlockTagNode): ... def emit(self, context): resolved = self.evaluate_expression(context) context_copy = context.copy() if isinstance(resolved, dict): for key, value in sorted(resolved.items()): context_copy["item"] = {"key": key, "value": value} yield from super().emit(context_copy) else: for item in resolved: context_copy["item"] = item yield from super().emit(context_copy)