1"""This regression tests abstract rule which have an alternative with string 2matches and a rule reference. 3 4See https://github.com/textX/textX/pull/128 5 6""" 7from __future__ import unicode_literals 8import sys 9from textx import metamodel_from_str 10from textx import textx_isinstance 11 12if sys.version < '3': 13 text = unicode # noqa 14else: 15 text = str 16 17# Global variable namespace 18namespace = {} 19 20 21def test_abstract_alternative_string_match(): 22 23 grammar = r''' 24 Calc: assignments*=Assignment expression=Expression; 25 Assignment: variable=ID '=' expression=Expression ';'; 26 Expression: operands=Term (operators=PlusOrMinus operands=Term)*; 27 PlusOrMinus: '+' | '-'; 28 Term: operands=Factor (operators=MulOrDiv operands=Factor)*; 29 MulOrDiv: '*' | '/' ; 30 Factor: (sign=PlusOrMinus)? op=Operand; 31 PrimitiveOperand: op_num=NUMBER | op_id=ID; 32 Operand: PrimitiveOperand | ('(' Expression ')'); 33 ''' 34 35 calc_mm = metamodel_from_str(grammar) 36 37 input_expr = ''' 38 a = 10; 39 b = 2 * a + 17; 40 -(4-1)*a+(2+4.67)+b*5.89/(.2+7) 41 ''' 42 43 model = calc_mm.model_from_str(input_expr) 44 45 def _is(x, rule): 46 return textx_isinstance(x, calc_mm[rule]) 47 48 def assertIs(x, rule): 49 assert _is(x, rule), 'Unexpected object "{}" to rule "{}"'\ 50 .format(x, rule) 51 52 def evaluate(x): 53 54 if isinstance(x, float): 55 return x 56 57 elif _is(x, 'Expression'): 58 ret = evaluate(x.operands[0]) 59 60 for operator, operand in zip(x.operators, 61 x.operands[1:]): 62 if operator == '+': 63 ret += evaluate(operand) 64 else: 65 ret -= evaluate(operand) 66 return ret 67 68 elif _is(x, 'Term'): 69 ret = evaluate(x.operands[0]) 70 71 for operator, operand in zip(x.operators, 72 x.operands[1:]): 73 if operator == '*': 74 ret *= evaluate(operand) 75 else: 76 ret /= evaluate(operand) 77 return ret 78 79 elif _is(x, 'Factor'): 80 value = evaluate(x.op) 81 return -value if x.sign == '-' else value 82 83 elif _is(x, 'Operand'): 84 if _is(x, 'PrimitiveOperand'): 85 if x.op_num is not None: 86 return x.op_num 87 elif x.op_id: 88 if x.op_id in namespace: 89 return namespace[x.op_id] 90 else: 91 raise Exception('Unknown variable "{}" at position {}' 92 .format(x.op_id, x._tx_position)) 93 else: 94 assertIs(x, 'CompoundOperand') 95 return evaluate(x.expression) 96 97 elif _is(x, 'Calc'): 98 for a in x.assignments: 99 namespace[a.variable] = evaluate(a.expression) 100 101 return evaluate(x.expression) 102 103 else: 104 assert False, 'Unexpected object "{}" of type "{}"'\ 105 .format(x, type(x)) 106 107 result = evaluate(model) 108 109 assert (result - 6.93805555) < 0.0001 110 111 112def test_abstract_alternative_multiple_rules_raises_exception(): 113 """ 114 Test that multiple rules references in a single alternative of abstract 115 rule is prohibited. 116 """ 117 118 # In this grammar A is an abstract rule and referencing C D from second 119 # alternative would yield just the first common rule object (C in this 120 # case). 121 grammar = r''' 122 Model: a+=A; 123 A: B | '(' C D ')'; 124 B: 'B' name=ID x=INT; 125 C: 'C' name=ID; 126 D: 'D' x=INT; 127 ''' 128 129 meta = metamodel_from_str(grammar) 130 model = meta.model_from_str('B somename 23 ( C othername D 67 )') 131 assert len(model.a) == 2 132 assert model.a[0].name == 'somename' 133 assert model.a[0].x == 23 134 assert type(model.a[1]).__name__ == 'C' 135 assert model.a[1].name == 'othername' 136