pytest-dsl 0.2.0__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_dsl/cli.py +111 -12
- pytest_dsl/core/dsl_executor.py +133 -2
- pytest_dsl/core/lexer.py +23 -1
- pytest_dsl/core/parser.py +111 -12
- pytest_dsl/core/parsetab.py +82 -61
- pytest_dsl-0.3.1.dist-info/METADATA +448 -0
- {pytest_dsl-0.2.0.dist-info → pytest_dsl-0.3.1.dist-info}/RECORD +11 -11
- {pytest_dsl-0.2.0.dist-info → pytest_dsl-0.3.1.dist-info}/WHEEL +1 -1
- pytest_dsl-0.2.0.dist-info/METADATA +0 -504
- {pytest_dsl-0.2.0.dist-info → pytest_dsl-0.3.1.dist-info}/entry_points.txt +0 -0
- {pytest_dsl-0.2.0.dist-info → pytest_dsl-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.2.0.dist-info → pytest_dsl-0.3.1.dist-info}/top_level.txt +0 -0
pytest_dsl/cli.py
CHANGED
@@ -5,12 +5,16 @@ pytest-dsl命令行入口
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
import sys
|
8
|
+
import argparse
|
8
9
|
import pytest
|
10
|
+
import os
|
9
11
|
from pathlib import Path
|
10
12
|
|
11
13
|
from pytest_dsl.core.lexer import get_lexer
|
12
14
|
from pytest_dsl.core.parser import get_parser
|
13
15
|
from pytest_dsl.core.dsl_executor import DSLExecutor
|
16
|
+
from pytest_dsl.core.yaml_vars import yaml_vars
|
17
|
+
from pytest_dsl.core.auto_directory import SETUP_FILE_NAME, TEARDOWN_FILE_NAME, execute_hook_file
|
14
18
|
|
15
19
|
|
16
20
|
def read_file(filename):
|
@@ -19,26 +23,121 @@ def read_file(filename):
|
|
19
23
|
return f.read()
|
20
24
|
|
21
25
|
|
26
|
+
def parse_args():
|
27
|
+
"""解析命令行参数"""
|
28
|
+
parser = argparse.ArgumentParser(description='执行DSL测试文件')
|
29
|
+
parser.add_argument('path', help='要执行的DSL文件路径或包含DSL文件的目录')
|
30
|
+
parser.add_argument('--yaml-vars', action='append', default=[],
|
31
|
+
help='YAML变量文件路径,可以指定多个文件 (例如: --yaml-vars vars1.yaml --yaml-vars vars2.yaml)')
|
32
|
+
parser.add_argument('--yaml-vars-dir', default=None,
|
33
|
+
help='YAML变量文件目录路径,将加载该目录下所有.yaml文件')
|
34
|
+
|
35
|
+
return parser.parse_args()
|
36
|
+
|
37
|
+
|
38
|
+
def load_yaml_variables(args):
|
39
|
+
"""从命令行参数加载YAML变量"""
|
40
|
+
# 加载单个YAML文件
|
41
|
+
if args.yaml_vars:
|
42
|
+
yaml_vars.load_yaml_files(args.yaml_vars)
|
43
|
+
print(f"已加载YAML变量文件: {', '.join(args.yaml_vars)}")
|
44
|
+
|
45
|
+
# 加载目录中的YAML文件
|
46
|
+
if args.yaml_vars_dir:
|
47
|
+
yaml_vars_dir = args.yaml_vars_dir
|
48
|
+
try:
|
49
|
+
yaml_vars.load_from_directory(yaml_vars_dir)
|
50
|
+
print(f"已加载YAML变量目录: {yaml_vars_dir}")
|
51
|
+
loaded_files = yaml_vars.get_loaded_files()
|
52
|
+
if loaded_files:
|
53
|
+
dir_files = [f for f in loaded_files if Path(f).parent == Path(yaml_vars_dir)]
|
54
|
+
if dir_files:
|
55
|
+
print(f"目录中加载的文件: {', '.join(dir_files)}")
|
56
|
+
except NotADirectoryError:
|
57
|
+
print(f"YAML变量目录不存在: {yaml_vars_dir}")
|
58
|
+
sys.exit(1)
|
59
|
+
|
60
|
+
|
61
|
+
def execute_dsl_file(file_path, lexer, parser, executor):
|
62
|
+
"""执行单个DSL文件"""
|
63
|
+
try:
|
64
|
+
print(f"执行文件: {file_path}")
|
65
|
+
dsl_code = read_file(file_path)
|
66
|
+
ast = parser.parse(dsl_code, lexer=lexer)
|
67
|
+
executor.execute(ast)
|
68
|
+
return True
|
69
|
+
except Exception as e:
|
70
|
+
print(f"执行失败 {file_path}: {e}")
|
71
|
+
return False
|
72
|
+
|
73
|
+
|
74
|
+
def find_dsl_files(directory):
|
75
|
+
"""查找目录中的所有DSL文件"""
|
76
|
+
dsl_files = []
|
77
|
+
for root, _, files in os.walk(directory):
|
78
|
+
for file in files:
|
79
|
+
if file.endswith(('.dsl', '.auto')) and file not in [SETUP_FILE_NAME, TEARDOWN_FILE_NAME]:
|
80
|
+
dsl_files.append(os.path.join(root, file))
|
81
|
+
return dsl_files
|
82
|
+
|
83
|
+
|
22
84
|
def main():
|
23
85
|
"""命令行入口点"""
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
86
|
+
args = parse_args()
|
87
|
+
path = args.path
|
88
|
+
|
89
|
+
# 加载YAML变量
|
90
|
+
load_yaml_variables(args)
|
29
91
|
|
30
92
|
lexer = get_lexer()
|
31
93
|
parser = get_parser()
|
32
94
|
executor = DSLExecutor()
|
33
95
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
executor
|
38
|
-
|
39
|
-
|
96
|
+
# 检查路径是文件还是目录
|
97
|
+
if os.path.isfile(path):
|
98
|
+
# 执行单个文件
|
99
|
+
success = execute_dsl_file(path, lexer, parser, executor)
|
100
|
+
if not success:
|
101
|
+
sys.exit(1)
|
102
|
+
elif os.path.isdir(path):
|
103
|
+
# 执行目录中的所有DSL文件
|
104
|
+
print(f"执行目录: {path}")
|
105
|
+
|
106
|
+
# 先执行目录的setup文件(如果存在)
|
107
|
+
setup_file = os.path.join(path, SETUP_FILE_NAME)
|
108
|
+
if os.path.exists(setup_file):
|
109
|
+
execute_hook_file(Path(setup_file), True, path)
|
110
|
+
|
111
|
+
# 查找并执行所有DSL文件
|
112
|
+
dsl_files = find_dsl_files(path)
|
113
|
+
if not dsl_files:
|
114
|
+
print(f"目录中没有找到DSL文件: {path}")
|
115
|
+
sys.exit(1)
|
116
|
+
|
117
|
+
print(f"找到 {len(dsl_files)} 个DSL文件")
|
118
|
+
|
119
|
+
# 执行所有DSL文件
|
120
|
+
failures = 0
|
121
|
+
for file_path in dsl_files:
|
122
|
+
success = execute_dsl_file(file_path, lexer, parser, executor)
|
123
|
+
if not success:
|
124
|
+
failures += 1
|
125
|
+
|
126
|
+
# 最后执行目录的teardown文件(如果存在)
|
127
|
+
teardown_file = os.path.join(path, TEARDOWN_FILE_NAME)
|
128
|
+
if os.path.exists(teardown_file):
|
129
|
+
execute_hook_file(Path(teardown_file), False, path)
|
130
|
+
|
131
|
+
# 如果有失败的测试,返回非零退出码
|
132
|
+
if failures > 0:
|
133
|
+
print(f"总计 {failures}/{len(dsl_files)} 个测试失败")
|
134
|
+
sys.exit(1)
|
135
|
+
else:
|
136
|
+
print(f"所有 {len(dsl_files)} 个测试成功完成")
|
137
|
+
else:
|
138
|
+
print(f"路径不存在: {path}")
|
40
139
|
sys.exit(1)
|
41
140
|
|
42
141
|
|
43
142
|
if __name__ == '__main__':
|
44
|
-
main()
|
143
|
+
main()
|
pytest_dsl/core/dsl_executor.py
CHANGED
@@ -93,6 +93,12 @@ class DSLExecutor:
|
|
93
93
|
elif expr_node.type == 'BooleanExpr':
|
94
94
|
# 处理布尔值表达式
|
95
95
|
return expr_node.value
|
96
|
+
elif expr_node.type == 'ComparisonExpr':
|
97
|
+
# 处理比较表达式
|
98
|
+
return self._eval_comparison_expr(expr_node)
|
99
|
+
elif expr_node.type == 'ArithmeticExpr':
|
100
|
+
# 处理算术表达式
|
101
|
+
return self._eval_arithmetic_expr(expr_node)
|
96
102
|
else:
|
97
103
|
raise Exception(f"无法求值的表达式类型: {expr_node.type}")
|
98
104
|
|
@@ -101,6 +107,10 @@ class DSLExecutor:
|
|
101
107
|
if isinstance(value, Node):
|
102
108
|
return self.eval_expression(value)
|
103
109
|
elif isinstance(value, str):
|
110
|
+
# 如果是ID类型的变量名
|
111
|
+
if value in self.variable_replacer.local_variables:
|
112
|
+
return self.variable_replacer.local_variables[value]
|
113
|
+
|
104
114
|
# 定义变量引用模式
|
105
115
|
pattern = r'\$\{([a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*)\}'
|
106
116
|
# 检查整个字符串是否完全匹配单一变量引用模式
|
@@ -113,6 +123,86 @@ class DSLExecutor:
|
|
113
123
|
return self.variable_replacer.replace_in_string(value)
|
114
124
|
return value
|
115
125
|
|
126
|
+
def _eval_comparison_expr(self, expr_node):
|
127
|
+
"""
|
128
|
+
对比较表达式进行求值
|
129
|
+
|
130
|
+
:param expr_node: 比较表达式节点
|
131
|
+
:return: 比较结果(布尔值)
|
132
|
+
"""
|
133
|
+
left_value = self.eval_expression(expr_node.children[0])
|
134
|
+
right_value = self.eval_expression(expr_node.children[1])
|
135
|
+
operator = expr_node.value # 操作符: >, <, >=, <=, ==, !=
|
136
|
+
|
137
|
+
# 尝试类型转换
|
138
|
+
if isinstance(left_value, str) and str(left_value).isdigit():
|
139
|
+
left_value = int(left_value)
|
140
|
+
if isinstance(right_value, str) and str(right_value).isdigit():
|
141
|
+
right_value = int(right_value)
|
142
|
+
|
143
|
+
# 根据操作符执行相应的比较操作
|
144
|
+
if operator == '>':
|
145
|
+
return left_value > right_value
|
146
|
+
elif operator == '<':
|
147
|
+
return left_value < right_value
|
148
|
+
elif operator == '>=':
|
149
|
+
return left_value >= right_value
|
150
|
+
elif operator == '<=':
|
151
|
+
return left_value <= right_value
|
152
|
+
elif operator == '==':
|
153
|
+
return left_value == right_value
|
154
|
+
elif operator == '!=':
|
155
|
+
return left_value != right_value
|
156
|
+
else:
|
157
|
+
raise Exception(f"未知的比较操作符: {operator}")
|
158
|
+
|
159
|
+
def _eval_arithmetic_expr(self, expr_node):
|
160
|
+
"""
|
161
|
+
对算术表达式进行求值
|
162
|
+
|
163
|
+
:param expr_node: 算术表达式节点
|
164
|
+
:return: 计算结果
|
165
|
+
"""
|
166
|
+
left_value = self.eval_expression(expr_node.children[0])
|
167
|
+
right_value = self.eval_expression(expr_node.children[1])
|
168
|
+
operator = expr_node.value # 操作符: +, -, *, /
|
169
|
+
|
170
|
+
# 尝试类型转换 - 如果是字符串数字则转为数字
|
171
|
+
if isinstance(left_value, str) and str(left_value).replace('.', '', 1).isdigit():
|
172
|
+
left_value = float(left_value)
|
173
|
+
# 如果是整数则转为整数
|
174
|
+
if left_value.is_integer():
|
175
|
+
left_value = int(left_value)
|
176
|
+
|
177
|
+
if isinstance(right_value, str) and str(right_value).replace('.', '', 1).isdigit():
|
178
|
+
right_value = float(right_value)
|
179
|
+
# 如果是整数则转为整数
|
180
|
+
if right_value.is_integer():
|
181
|
+
right_value = int(right_value)
|
182
|
+
|
183
|
+
# 进行相应的算术运算
|
184
|
+
if operator == '+':
|
185
|
+
# 对于字符串,+是连接操作
|
186
|
+
if isinstance(left_value, str) or isinstance(right_value, str):
|
187
|
+
return str(left_value) + str(right_value)
|
188
|
+
return left_value + right_value
|
189
|
+
elif operator == '-':
|
190
|
+
return left_value - right_value
|
191
|
+
elif operator == '*':
|
192
|
+
# 如果其中一个是字符串,另一个是数字,则进行字符串重复
|
193
|
+
if isinstance(left_value, str) and isinstance(right_value, (int, float)):
|
194
|
+
return left_value * int(right_value)
|
195
|
+
elif isinstance(right_value, str) and isinstance(left_value, (int, float)):
|
196
|
+
return right_value * int(left_value)
|
197
|
+
return left_value * right_value
|
198
|
+
elif operator == '/':
|
199
|
+
# 除法时检查除数是否为0
|
200
|
+
if right_value == 0:
|
201
|
+
raise Exception("除法错误: 除数不能为0")
|
202
|
+
return left_value / right_value
|
203
|
+
else:
|
204
|
+
raise Exception(f"未知的算术操作符: {operator}")
|
205
|
+
|
116
206
|
def _get_variable(self, var_name):
|
117
207
|
"""获取变量值,优先从本地变量获取,如果不存在则尝试从全局上下文获取"""
|
118
208
|
return self.variable_replacer.get_variable(var_name)
|
@@ -120,6 +210,21 @@ class DSLExecutor:
|
|
120
210
|
def _replace_variables_in_string(self, value):
|
121
211
|
"""替换字符串中的变量引用"""
|
122
212
|
return self.variable_replacer.replace_in_string(value)
|
213
|
+
|
214
|
+
def _handle_custom_keywords_in_file(self, node):
|
215
|
+
"""处理文件中的自定义关键字定义
|
216
|
+
|
217
|
+
Args:
|
218
|
+
node: Start节点
|
219
|
+
"""
|
220
|
+
if len(node.children) > 1 and node.children[1].type == 'Statements':
|
221
|
+
statements_node = node.children[1]
|
222
|
+
for stmt in statements_node.children:
|
223
|
+
if stmt.type == 'CustomKeyword':
|
224
|
+
# 导入自定义关键字管理器
|
225
|
+
from pytest_dsl.core.custom_keyword_manager import custom_keyword_manager
|
226
|
+
# 注册自定义关键字
|
227
|
+
custom_keyword_manager._register_custom_keyword(stmt, "current_file")
|
123
228
|
|
124
229
|
def _handle_start(self, node):
|
125
230
|
"""处理开始节点"""
|
@@ -140,6 +245,8 @@ class DSLExecutor:
|
|
140
245
|
elif child.type == 'Teardown':
|
141
246
|
teardown_node = child
|
142
247
|
|
248
|
+
# 在_execute_test_iteration之前添加
|
249
|
+
self._handle_custom_keywords_in_file(node)
|
143
250
|
# 执行测试
|
144
251
|
self._execute_test_iteration(metadata, node, teardown_node)
|
145
252
|
|
@@ -341,6 +448,28 @@ class DSLExecutor:
|
|
341
448
|
expr_node = node.children[0]
|
342
449
|
return self.eval_expression(expr_node)
|
343
450
|
|
451
|
+
@allure.step("执行条件语句")
|
452
|
+
def _handle_if_statement(self, node):
|
453
|
+
"""处理if-else语句
|
454
|
+
|
455
|
+
Args:
|
456
|
+
node: IfStatement节点,包含条件表达式、if分支和可选的else分支
|
457
|
+
"""
|
458
|
+
condition = self.eval_expression(node.children[0])
|
459
|
+
|
460
|
+
# 将条件转换为布尔值进行评估
|
461
|
+
if condition:
|
462
|
+
# 执行if分支
|
463
|
+
with allure.step("执行if分支"):
|
464
|
+
return self.execute(node.children[1])
|
465
|
+
elif len(node.children) > 2:
|
466
|
+
# 如果存在else分支且条件为假,则执行else分支
|
467
|
+
with allure.step("执行else分支"):
|
468
|
+
return self.execute(node.children[2])
|
469
|
+
|
470
|
+
# 如果条件为假且没有else分支,则不执行任何操作
|
471
|
+
return None
|
472
|
+
|
344
473
|
def execute(self, node):
|
345
474
|
"""执行AST节点"""
|
346
475
|
handlers = {
|
@@ -352,7 +481,9 @@ class DSLExecutor:
|
|
352
481
|
'ForLoop': self._handle_for_loop,
|
353
482
|
'KeywordCall': self._execute_keyword_call,
|
354
483
|
'Teardown': self._handle_teardown,
|
355
|
-
'Return': self._handle_return
|
484
|
+
'Return': self._handle_return,
|
485
|
+
'IfStatement': self._handle_if_statement,
|
486
|
+
'CustomKeyword': lambda _: None # 添加对CustomKeyword节点的处理,只需注册不需执行
|
356
487
|
}
|
357
488
|
|
358
489
|
handler = handlers.get(node.type)
|
@@ -363,4 +494,4 @@ class DSLExecutor:
|
|
363
494
|
def read_file(filename):
|
364
495
|
"""读取 DSL 文件内容"""
|
365
496
|
with open(filename, 'r', encoding='utf-8') as f:
|
366
|
-
return f.read()
|
497
|
+
return f.read()
|
pytest_dsl/core/lexer.py
CHANGED
@@ -10,7 +10,9 @@ reserved = {
|
|
10
10
|
'using': 'USING', # Add new keyword for data-driven testing
|
11
11
|
'True': 'TRUE', # 添加布尔值支持
|
12
12
|
'False': 'FALSE', # 添加布尔值支持
|
13
|
-
'return': 'RETURN' # 添加return关键字支持
|
13
|
+
'return': 'RETURN', # 添加return关键字支持
|
14
|
+
'else': 'ELSE', # 添加else关键字支持
|
15
|
+
'if': 'IF' # 添加if关键字支持
|
14
16
|
}
|
15
17
|
|
16
18
|
# token 名称列表
|
@@ -36,6 +38,16 @@ tokens = [
|
|
36
38
|
'DATA_KEYWORD', # Add new token for @data keyword
|
37
39
|
'KEYWORD_KEYWORD', # 添加@keyword关键字
|
38
40
|
'IMPORT_KEYWORD', # 添加@import关键字
|
41
|
+
'GT', # 大于 >
|
42
|
+
'LT', # 小于 <
|
43
|
+
'GE', # 大于等于 >=
|
44
|
+
'LE', # 小于等于 <=
|
45
|
+
'EQ', # 等于 ==
|
46
|
+
'NE', # 不等于 !=
|
47
|
+
'PLUS', # 加法 +
|
48
|
+
'MINUS', # 减法 -
|
49
|
+
'TIMES', # 乘法 *
|
50
|
+
'DIVIDE', # 除法 /
|
39
51
|
] + list(reserved.values())
|
40
52
|
|
41
53
|
# 正则表达式定义 token
|
@@ -46,6 +58,16 @@ t_RBRACKET = r'\]'
|
|
46
58
|
t_COLON = r':'
|
47
59
|
t_COMMA = r','
|
48
60
|
t_EQUALS = r'='
|
61
|
+
t_GT = r'>'
|
62
|
+
t_LT = r'<'
|
63
|
+
t_GE = r'>='
|
64
|
+
t_LE = r'<='
|
65
|
+
t_EQ = r'=='
|
66
|
+
t_NE = r'!='
|
67
|
+
t_PLUS = r'\+'
|
68
|
+
t_MINUS = r'-'
|
69
|
+
t_TIMES = r'\*'
|
70
|
+
t_DIVIDE = r'/'
|
49
71
|
|
50
72
|
# 增加PLACEHOLDER规则,匹配 ${变量名} 格式
|
51
73
|
t_PLACEHOLDER = r'\$\{[a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*\}'
|
pytest_dsl/core/parser.py
CHANGED
@@ -12,23 +12,44 @@ class Node:
|
|
12
12
|
# 定义优先级和结合性
|
13
13
|
precedence = (
|
14
14
|
('left', 'COMMA'),
|
15
|
+
('left', 'GT', 'LT', 'GE', 'LE', 'EQ', 'NE'), # 比较运算符优先级
|
16
|
+
('left', 'PLUS', 'MINUS'), # 加减运算符优先级
|
17
|
+
('left', 'TIMES', 'DIVIDE'), # 乘除运算符优先级
|
15
18
|
('right', 'EQUALS'),
|
16
19
|
)
|
17
20
|
|
18
21
|
|
19
22
|
def p_start(p):
|
20
23
|
'''start : metadata statements teardown
|
21
|
-
| metadata statements
|
24
|
+
| metadata statements
|
25
|
+
| statements teardown
|
26
|
+
| statements'''
|
22
27
|
|
23
28
|
if len(p) == 4:
|
24
29
|
p[0] = Node('Start', [p[1], p[2], p[3]])
|
30
|
+
elif len(p) == 3:
|
31
|
+
# 判断第二个元素是teardown还是statements
|
32
|
+
if p[2].type == 'Teardown':
|
33
|
+
p[0] = Node('Start', [Node('Metadata', []), p[1], p[2]])
|
34
|
+
else:
|
35
|
+
p[0] = Node('Start', [p[1], p[2]])
|
25
36
|
else:
|
26
|
-
|
37
|
+
# 没有metadata和teardown
|
38
|
+
p[0] = Node('Start', [Node('Metadata', []), p[1]])
|
27
39
|
|
28
40
|
|
29
41
|
def p_metadata(p):
|
30
|
-
'''metadata : metadata_items
|
31
|
-
|
42
|
+
'''metadata : metadata_items
|
43
|
+
| empty'''
|
44
|
+
if p[1]:
|
45
|
+
p[0] = Node('Metadata', p[1])
|
46
|
+
else:
|
47
|
+
p[0] = Node('Metadata', [])
|
48
|
+
|
49
|
+
|
50
|
+
def p_empty(p):
|
51
|
+
'''empty :'''
|
52
|
+
p[0] = None
|
32
53
|
|
33
54
|
|
34
55
|
def p_metadata_items(p):
|
@@ -95,7 +116,8 @@ def p_statement(p):
|
|
95
116
|
| keyword_call
|
96
117
|
| loop
|
97
118
|
| custom_keyword
|
98
|
-
| return_statement
|
119
|
+
| return_statement
|
120
|
+
| if_statement'''
|
99
121
|
p[0] = p[1]
|
100
122
|
|
101
123
|
|
@@ -109,13 +131,31 @@ def p_assignment(p):
|
|
109
131
|
|
110
132
|
|
111
133
|
def p_expression(p):
|
112
|
-
'''expression :
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
134
|
+
'''expression : expr_atom
|
135
|
+
| comparison_expr
|
136
|
+
| arithmetic_expr'''
|
137
|
+
# 如果是比较表达式或其他复合表达式,则已经是一个Node对象
|
138
|
+
if isinstance(p[1], Node):
|
139
|
+
p[0] = p[1]
|
140
|
+
else:
|
141
|
+
p[0] = Node('Expression', value=p[1])
|
142
|
+
|
143
|
+
|
144
|
+
def p_expr_atom(p):
|
145
|
+
'''expr_atom : NUMBER
|
146
|
+
| STRING
|
147
|
+
| PLACEHOLDER
|
148
|
+
| ID
|
149
|
+
| boolean_expr
|
150
|
+
| list_expr
|
151
|
+
| LPAREN expression RPAREN'''
|
152
|
+
if p[1] == '(':
|
153
|
+
# 处理括号表达式,直接返回括号内的表达式节点
|
154
|
+
p[0] = p[2]
|
155
|
+
elif isinstance(p[1], Node):
|
156
|
+
p[0] = p[1]
|
157
|
+
else:
|
158
|
+
p[0] = Node('Expression', value=p[1])
|
119
159
|
|
120
160
|
|
121
161
|
def p_boolean_expr(p):
|
@@ -228,6 +268,65 @@ def p_return_statement(p):
|
|
228
268
|
p[0] = Node('Return', [p[2]])
|
229
269
|
|
230
270
|
|
271
|
+
def p_if_statement(p):
|
272
|
+
'''if_statement : IF expression DO statements END
|
273
|
+
| IF expression DO statements ELSE statements END'''
|
274
|
+
if len(p) == 6:
|
275
|
+
p[0] = Node('IfStatement', [p[2], p[4]], None)
|
276
|
+
else:
|
277
|
+
p[0] = Node('IfStatement', [p[2], p[4], p[6]], None)
|
278
|
+
|
279
|
+
|
280
|
+
def p_comparison_expr(p):
|
281
|
+
'''comparison_expr : expr_atom GT expr_atom
|
282
|
+
| expr_atom LT expr_atom
|
283
|
+
| expr_atom GE expr_atom
|
284
|
+
| expr_atom LE expr_atom
|
285
|
+
| expr_atom EQ expr_atom
|
286
|
+
| expr_atom NE expr_atom'''
|
287
|
+
|
288
|
+
# 根据规则索引判断使用的是哪个操作符
|
289
|
+
if p.slice[2].type == 'GT':
|
290
|
+
operator = '>'
|
291
|
+
elif p.slice[2].type == 'LT':
|
292
|
+
operator = '<'
|
293
|
+
elif p.slice[2].type == 'GE':
|
294
|
+
operator = '>='
|
295
|
+
elif p.slice[2].type == 'LE':
|
296
|
+
operator = '<='
|
297
|
+
elif p.slice[2].type == 'EQ':
|
298
|
+
operator = '=='
|
299
|
+
elif p.slice[2].type == 'NE':
|
300
|
+
operator = '!='
|
301
|
+
else:
|
302
|
+
print(f"警告: 无法识别的操作符类型 {p.slice[2].type}")
|
303
|
+
operator = None
|
304
|
+
|
305
|
+
p[0] = Node('ComparisonExpr', [p[1], p[3]], operator)
|
306
|
+
|
307
|
+
|
308
|
+
def p_arithmetic_expr(p):
|
309
|
+
'''arithmetic_expr : expression PLUS expression
|
310
|
+
| expression MINUS expression
|
311
|
+
| expression TIMES expression
|
312
|
+
| expression DIVIDE expression'''
|
313
|
+
|
314
|
+
# 根据规则索引判断使用的是哪个操作符
|
315
|
+
if p.slice[2].type == 'PLUS':
|
316
|
+
operator = '+'
|
317
|
+
elif p.slice[2].type == 'MINUS':
|
318
|
+
operator = '-'
|
319
|
+
elif p.slice[2].type == 'TIMES':
|
320
|
+
operator = '*'
|
321
|
+
elif p.slice[2].type == 'DIVIDE':
|
322
|
+
operator = '/'
|
323
|
+
else:
|
324
|
+
print(f"警告: 无法识别的操作符类型 {p.slice[2].type}")
|
325
|
+
operator = None
|
326
|
+
|
327
|
+
p[0] = Node('ArithmeticExpr', [p[1], p[3]], operator)
|
328
|
+
|
329
|
+
|
231
330
|
def p_error(p):
|
232
331
|
if p:
|
233
332
|
print(
|