Как ограничить повторы в грамматике textx?

Я пытаюсь создать грамматику в textx.

Синтаксис должен выглядеть так:

name_a
name_a, name_b
name_a, name_b: name_c, *
name_a, name_b: name_c, *, name_d
*, name_d
*

Где звездочка (*) означает "все". Я хочу, чтобы это не повторялось. Текущая грамматика такова:

Subsets: ColumnsSet*;
ColumnsSet: SetItem (',' ColumnsSet)*;
SetItem: ColumnName | Star;
Star: '*';
ColumnName: name=ID (':' rename=ID)*;

Позволяет повторять звездочку. Я хочу предотвратить такой случай, чтобы эти строки были недействительными:

name_a, *, *
name_a, *, name_b, *
*, name_a, *

Как мне переписать грамматику?

Есть ли способ сгладить вывод вложенного правила: ColumnsSet: SetItem (',' ColumnsSet)*;?

1 ответ

Решение

Есть несколько проблем с вашей грамматикой. Сначала в textX вам нужно использовать назначения для сбора соответствующих данных. Использовать *=, +=стиль присвоения для обозначения множества ноль / один или более. Используйте модификаторы повторения разделителя, чтобы избежать Something (',' Something)* шаблон.

Предотвращать *происходит многократно, вы можете зарегистрировать объектный процессор, который может проверять семантические ошибки.

Кроме того, чтобы убедиться, что язык ориентирован на строки, вам может потребоваться изучить модификатор правила noskipws.

textX - это не просто парсер, он из грамматики выводит метамодель языка, которую вы можете визуализировать

Вот одно (возможно, неполное) решение, которое может стать хорошим началом.

from textx import metamodel_from_str, TextXSemanticError
from textx.scoping.tools import get_location

grammar = r'''
    Subsets: col_sets+=ColumnsSet;
    ColumnsSet: set_items+=SetItem[','];
    SetItem: ColumnName | Star;
    Star: '*';
    ColumnName: name=ID (':' rename=ID)?;
'''

def column_set_proc(cs):
    if len([x for x in cs.set_items if x == '*']) > 1:
        raise TextXSemanticError('Cannot use multiple * in a single line', **get_location(cs))

mm = metamodel_from_str(grammar)
mm.register_obj_processors({'ColumnsSet': column_set_proc})

# This will pass
model = mm.model_from_str(r'''name_a
name_a, name_b
name_a, name_b: name_c, *
name_a, name_b: name_c, *, name_d
*, name_d
*
''')

# Each of these raise TextXSemanticError
count = 0
for invalid in ['name_a, *, *', ' name_a, *, name_b, *', ' *, name_a, *']:
    try:
        mm.model_from_str(invalid)
    except TextXSemanticError:
        count += 1

assert count == 3

Другие вопросы по тегам