当前位置:   article > 正文

python使用textX构建属于自己的语言模型

textx

系列文章目录

  1. python使用textX构建属于自己的语言模型
  2. python使用textx-model包


一、textX是什么?

textX是一种元语言,用于在Python中构建领域特定语言(DSL)。它的灵感来自Xtext。

简而言之,textX将帮助您以一种简单的方式构建文本语言。您可以发明自己的语言,或者对现有的文本语言或文件格式建立支持。

textX将从单一语言描述(语法)为该语言构建一个解析器和一个元模型(也称为抽象语法)。有关详细信息,请参阅文档。

textX遵循Xtext的语法和语义,但在某些地方有所不同,并使用Arpeggio-PEG解析器在Python中100%实现——没有语法歧义、无限制的前瞻性、解释器风格。

github上的地址:https://github.com/textX/textX

二、安装

pip install textX

三、快速介绍

下面是一个完整的示例,展示了用于绘图的简单DSL的定义。我们还展示了如何定义自定义类、解释模型和搜索特定类型的实例。

代码如下(示例):

rom textx import metamodel_from_str, get_children_of_type

grammar = """
Model: commands*=DrawCommand;
DrawCommand: MoveCommand | ShapeCommand;
ShapeCommand: LineTo | Circle;
MoveCommand: MoveTo | MoveBy;
MoveTo: 'move' 'to' position=Point;
MoveBy: 'move' 'by' vector=Point;
Circle: 'circle' radius=INT;
LineTo: 'line' 'to' point=Point;
Point: x=INT ',' y=INT;
"""

# We will provide our class for Point.
# Classes for other rules will be dynamically generated.
class Point(object):
    def __init__(self, parent, x, y):
        self.parent = parent
        self.x = x
        self.y = y

    def __str__(self):
        return "{},{}".format(self.x, self.y)

    def __add__(self, other):
        return Point(self.parent, self.x + other.x, self.y + other.y)

# Create meta-model from the grammar. Provide `Point` class to be used for
# the rule `Point` from the grammar.
mm = metamodel_from_str(grammar, classes=[Point])

model_str = """
    move to 5, 10
    line to 10, 10
    line to 20, 20
    move by 5, -7
    circle 10
    line to 10, 10
"""

# Meta-model knows how to parse and instantiate models.
model = mm.model_from_str(model_str)

# At this point model is a plain Python object graph with instances of
# dynamically created classes and attributes following the grammar.

def cname(o):
    return o.__class__.__name__

# Let's interpret the model
position = Point(None, 0, 0)
for command in model.commands:
    if cname(command) == 'MoveTo':
        print('Moving to position', command.position)
        position = command.position
    elif cname(command) == 'MoveBy':
        position = position + command.vector
        print('Moving by', command.vector, 'to a new position', position)
    elif cname(command) == 'Circle':
        print('Drawing circle at', position, 'with radius', command.radius)
    else:
        print('Drawing line from', position, 'to', command.point)
        position = command.point
print('End position is', position)

# Output:
# Moving to position 5,10
# Drawing line from 5,10 to 10,10
# Drawing line from 10,10 to 20,20
# Moving by 5,-7 to a new position 25,13
# Drawing circle at 25,13 with radius 10
# Drawing line from 25,13 to 10,10

# Collect all points starting from the root of the model
points = get_children_of_type("Point", model)
for point in points:
    print('Point: {}'.format(point))

# Output:
# Point: 5,10
# Point: 10,10
# Point: 20,20
# Point: 5,-7
# Point: 10,10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

四、定义一个可执行字符串表达式的模型

过程分两步进行:

  1. 定义元模型
  2. 构建解析器

1. 定义元模型

新建一个 .tx格式的文件,文件内容如下:

Model:
         assignments*=Assignment expression=Expression
         ;
Assignment:
         variable=ID '=' expression=Expression ';'
         ;
Expression:
          op=Not (op=Logic op=Not)*
           ;
Logic:
        "and" | "or" | "in"
           ;
Not:
    _not?='not' op=Compare
        ;
Compare:
          op=AddSub (op=CompareSymbol op=AddSub)*
           ;
CompareSymbol:
        "<=" | ">=" | ">" | "<" | "=" | "!="
       ;
AddSub:
         op=Term (op=PlusOrMinus op=Term)*
           ;
PlusOrMinus:
           '+' | '-'
           ;
Term:
     op=Factor (op=MulOrDiv op=Factor)*
     ;
MulOrDiv:
       '*' | '/'
       ;
Factor:
      (sign=PlusOrMinus)?  op=Operand
      ;
Operand:
        op=NUMBER | op=BOOL | op=ID | op=STRING | ('(' op=Expression ')')
      ;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

2. 构建解析器

新建一个py文件,文件内容如下:

from __future__ import unicode_literals
from textx import metamodel_from_file


namespace = {}


def str_to_list(str_data: str):
    str_data = str_data
    return str_data


class Model(object):
    def __init__(self, **kwargs):
        self.assignments = kwargs.pop('assignments')
        self.expression = kwargs.pop('expression')

    @property
    def value(self):
        # Evaluate variables in the order of definition
        for a in self.assignments:
            namespace[a.variable] = a.expression.value
        return self.expression.value


class ExpressionElement(object):
    def __init__(self, **kwargs):
        # textX will pass in parent attribute used for parent-child
        # relationships. We can use it if we want to.
        self.parent = kwargs.get('parent', None)
        # We have 'op' attribute in all grammar rules
        self.op = kwargs['op']

        super(ExpressionElement, self).__init__()


class Expression(ExpressionElement):
    @property
    def value(self):
        ret = self.op[0].value
        for operation, operand in zip(self.op[1::2], self.op[2::2]):
            if operation == 'in':
                tag = True
                if ret[0] == '[':
                    ret = ret.lstrip('[')
                    if ret[-1] == ']':
                        ret = ret.rstrip(']').split(',')
                        for r in ret:
                            if r not in operand.value:
                                tag = False
                                break
                        ret = tag
                    else:
                        ret = ret in operand.value
                else:
                    ret = ret in operand.value
            elif operation == 'and':
                ret = ret and operand.value
            elif operation == 'or':
                ret = ret or operand.value
            elif operation == 'not':
                ret = not operand.value
        return ret


class Compare(ExpressionElement):
    @property
    def value(self):
        ret = self.op[0].value
        for operation, operand in zip(self.op[1::2], self.op[2::2]):
            if operation == '<':
                ret = ret < operand.value
            elif operation == '>':
                ret = ret > operand.value
            elif operation == '=':
                ret = ret == operand.value
            elif operation == '>=':
                ret = ret >= operand.value
            elif operation == '<=':
                ret = ret <= operand.value
            elif operation == '!=':
                ret = ret != operand.value
        return ret


class Not(ExpressionElement):
    def __init__(self, **kwargs):
        self._not = kwargs.pop('_not')
        super(Not, self).__init__(**kwargs)

    @property
    def value(self):
        ret = self.op.value
        return not ret if self._not else ret


class AddSub(ExpressionElement):
    @property
    def value(self):
        ret = self.op[0].value
        for operation, operand in zip(self.op[1::2], self.op[2::2]):
            if operation == '+':
                ret += operand.value
            else:
                ret -= operand.value
        return ret


class Term(ExpressionElement):
    @property
    def value(self):
        ret = self.op[0].value
        for operation, operand in zip(self.op[1::2], self.op[2::2]):
            if operation == '*':
                ret *= operand.value
            else:
                ret /= operand.value
        return ret


class Factor(ExpressionElement):
    def __init__(self, **kwargs):
        self.sign = kwargs.pop('sign', '+')
        super(Factor, self).__init__(**kwargs)

    @property
    def value(self):
        value = self.op.value
        return -value if self.sign == '-' else value


class Operand(ExpressionElement):
    @property
    def value(self):
        op = self.op
        if op in namespace:
            return namespace[op]
        elif type(op) in {int, float, bool, str}:
            return op
        elif isinstance(op, ExpressionElement):
            return op.value
        else:
            raise Exception('Unknown variable "{}" at position {}'
                            .format(op, self._tx_position))


# Meta.tx 为上面定义的元模型
meta = metamodel_from_file('Meta.tx',
                           classes=[Model, Expression, Not, Compare, AddSub, Term, Factor, Operand])

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150

3. 构建完成

经过上面两个步骤,生成两个文件就代表模型已构建完成

4. 使用构建的模型

在这里插入图片描述
文件结构如上图: Meta.tx 是元模型, Model.py 是构建的解析器。
代码如下(示例)

from example.Model import meta
# 该模型用于判断字符串表达式

# 字符串表达式
rule = "(current_medical_program = '白细胞计数') and (specimen_type = '全血') and " \
       "(medicine_projects in '[1,2,3]') and (time < 6)"

# 正例数据
true_example = "current_medical_program = '白细胞计数';specimen_type = '全血';medicine_projects = '2';time = 5;"

# 反例数据
false_example = "current_medical_program = '白细胞计数';specimen_type = '全血';medicine_projects = '2';time = 8;"

print(meta.model_from_str(true_example + rule).value) # 结果:True


print(meta.model_from_str(false_example + rule).value) # 结果:False
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

textx-model介绍

textx-model是以textX为基础构建出可运行条件规则语句然后输出结果的模型,文章链接:https://blog.csdn.net/weixin_44836662/article/details/124812067

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/249588
推荐阅读
相关标签
  

闽ICP备14008679号