Как написать правило грамматики textx для определения стандартных типов данных без их изменения? - PullRequest
0 голосов
/ 26 апреля 2019

Я хочу написать правило грамматики textx, которое может состоять из другого определенного правила или любого типа стандартного типа данных (Int, Float, String и т. Д.).

Это для простого текстового DSL, в который должна быть включена возможность записи (и перевода в конце) условий, которые могут состоять из других правил грамматики (например, предопределенных функций) или любых типов стандартных предопределенных типов данных (String / Int / Float / Bool / ID).

Итак, я действительно хочу написать что-то вроде, например,

condition insert input data 5 equal 10 BEGIN
    ...
END

Это означает нормальный IF. insert input data 5 - это правило, которое позже преобразуется в обычный вызов функции insertOutputData(5). Я использую там грамматику:

Model: commands*=Command;
Command: Function | Branch;
Function: Func_InsertInputData | Func_InsertOutputData;
Func_InsertInputData: 'insert input data' index=INT;
Func_InsertOutputData: 'insert output data' index=INT;
Branch: 'condition' condition=Condition 'BEGIN'
    commands*=Command;
'END'
Condition: Cond_Equal | Cond_And | Cond_False;
Cond_Equal: op1=Operand 'equal' op2=Operand;
Cond_And: op1=Operand 'and' op2=Operand;
Cond_False: op1=Operand 'is false';
Operand: Function | OR_ANY_OTHER_KIND_OF_DATA;

В интерпретаторе я пытаюсь прочитать код, выполнив следующее:

def translateCommands(cmds):
    commands = []
    for cmd in cmds:
        commands.append(translateCommand(cmd))
    return commands

def translateCommand(cmd):
    print(cmd)
    print(cmd.__class__)
    if cmd.__class__.__name__ == 'int' or cmd.__class__.__name__ == 'float':
        return str(cmd)
    elif cmd.__class__.__name__ == 'str':
        return '\'' + cmd + '\''
    elif(cmd.__class__.__name__ == 'Branch'):
        s = ''
        if(cmd.condition.__class__.__name__ ==  'Cond_Equal'):
            s = 'if ' + translateCommand(cmd.condition.op1) + '==' + translateCommand(cmd.condition.op2) + ':'
        if(cmd.condition.__class__.__name__ == 'Cond_And'):
            s = 'if ' + translateCommand(cmd.condition.op1) + 'and' + translateCommand(cmd.condition.op2) + ':'
        # ...
        commandsInBlock = translateCommands(cmd.commands)
        for command in commandsInBlock:
            s += '\n    '+command
        return s

На OR ANY OTHER KIND OF DATA я пробовал это с перечислением фактических типов данных, но это не работает. Если я обработаю модель с кодом DSL, показанным выше, с Function | FLOAT | INT | BOOL | ID | STRING в качестве правила операнда, целые числа (10 после равенства в примере) преобразуются в числа с плавающей точкой

if insertInputData(5)==10.0:

Если я обработаю модель с помощью правила операнда, например Function | INT | FLOAT | BOOL | ID | STRING, я получу ошибку

textx.exceptions.TextXSyntaxError: None:13:43: error: Expected 'BEGIN' at position (13, 43) => 't equal 10*.0 BEGIN  '.

Результат, который я хотел бы видеть:

if insertInputData(5)==10:

или

if insertInputData(5)==10.0:

с

condition insert input data 5 equal 10.0 BEGIN
    ...
END

но textx, кажется, всегда пытается преобразовать значение, которое он получает в этой позиции, в предложенный тип в правиле операнда, что в данном случае плохо. Как мне изменить свое правило, чтобы оно правильно определяло каждый тип данных, ничего не изменяя?

РЕДАКТИРОВАТЬ 1

Игорь Деянович только что описал проблему, и я последовал его подходу.

грамматика (соответствующая часть):

Command: Function | Branch | MyNumber;
#...
Oparand: Function | MyNumber | BOOL | ID | STRING;
MyNumber: STRICTFLOAT | INT;
STRICTFLOAT: /[+-]?(((\d+\.(\d*)?|\.\d+)([eE][+-]?\d+)?)|((\d+)([eE][+-]?\d+)))(?<=[\w\.])(?![\w\.])/;

код:

mm = metamodel_from_str(grammar)
mm.register_obj_processors({'STRICTFLOAT': lambda x: float(x)})

dsl_code = '''
10
10.5
'''
model = mm.model_from_str(dsl_code)
commands = iterateThroughCommands(model.commands)

В результате

10
<class 'int'>

'10.5'
<class 'str'>

Итак, чего-то не хватает, чтобы заставить объектный процессор работать ...

1 Ответ

1 голос
/ 26 апреля 2019

Проблема в том, что каждое действительное целое число может интерпретироваться как FLOAT, поэтому, если вы заказываете свои правила как FLOAT | INT |..., вы получаете тип float, так как правило FLOAT будет соответствовать, но если вы заказываете правила как INT | FLOAT|... для числа с плавающей запятой парсер будет использовать часть числа до ., а затем анализ не будет продолжаться.

Эта проблема решена в версии textX для разработки (см. CHANGELOG.md ), введя правило STRICTFLOAT, которое никогда не будет совпадать с целым числом, и встроенное правило NUMBER изменяется при первой попытке соответствовать STRICTFLOAT, а затем INT.

Следующий релиз будет 2.0.0 и должен состояться в ближайшие несколько недель, я надеюсь. В то же время вы можете либо установить напрямую с github, либо изменить свою грамматику, чтобы она выглядела примерно так:

MyNumber: STRICTFLOAT | INT;
STRICTFLOAT: /[+-]?(((\d+\.(\d*)?|\.\d+)([eE][+-]?\d+)?)|((\d+)([eE][+-]?\d+)))(?<=[\w\.])(?![\w\.])/;   // or the float format you prefer

И зарегистрируйте объектный процессор для вашего STRICTFLOAT типа, который преобразуется в Python float. После обновления до textX 2.0.0 вы должны просто заменить ссылки на MyNumber на NUMBER в грамматике.

Дополнительную информацию можно найти в сообщаемом выпуске

РЕДАКТИРОВАТЬ 1:

Предлагаемое решение в настоящий момент не работает из-за сообщения об ошибке здесь

РЕДАКТИРОВАТЬ 2:

Ошибка исправлена ​​в версии для разработчиков. До выхода 2.0.0 вам нужно

pip install https://github.com/textX/textX/archive/master.zip

и тогда вам вообще не понадобится обходной путь, если вы не хотите менять типы по умолчанию.

...