pytest-dsl 0.1.1__py3-none-any.whl → 0.3.0__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.
Files changed (31) hide show
  1. pytest_dsl/cli.py +42 -6
  2. pytest_dsl/core/__init__.py +7 -0
  3. pytest_dsl/core/custom_keyword_manager.py +213 -0
  4. pytest_dsl/core/dsl_executor.py +171 -3
  5. pytest_dsl/core/http_request.py +163 -54
  6. pytest_dsl/core/lexer.py +36 -1
  7. pytest_dsl/core/parser.py +155 -13
  8. pytest_dsl/core/parsetab.py +82 -49
  9. pytest_dsl/core/variable_utils.py +1 -1
  10. pytest_dsl/examples/custom/test_advanced_keywords.auto +31 -0
  11. pytest_dsl/examples/custom/test_custom_keywords.auto +37 -0
  12. pytest_dsl/examples/custom/test_default_values.auto +34 -0
  13. pytest_dsl/examples/http/http_retry_assertions.auto +2 -2
  14. pytest_dsl/examples/http/http_retry_assertions_enhanced.auto +2 -2
  15. pytest_dsl/examples/test_custom_keyword.py +9 -0
  16. pytest_dsl/examples/test_http.py +0 -139
  17. pytest_dsl/keywords/http_keywords.py +290 -102
  18. pytest_dsl/parsetab.py +69 -0
  19. pytest_dsl-0.3.0.dist-info/METADATA +448 -0
  20. {pytest_dsl-0.1.1.dist-info → pytest_dsl-0.3.0.dist-info}/RECORD +24 -24
  21. {pytest_dsl-0.1.1.dist-info → pytest_dsl-0.3.0.dist-info}/WHEEL +1 -1
  22. pytest_dsl/core/custom_auth_example.py +0 -425
  23. pytest_dsl/examples/csrf_auth_provider.py +0 -232
  24. pytest_dsl/examples/http/csrf_auth_test.auto +0 -64
  25. pytest_dsl/examples/http/custom_auth_test.auto +0 -76
  26. pytest_dsl/examples/http_clients.yaml +0 -48
  27. pytest_dsl/examples/keyword_example.py +0 -70
  28. pytest_dsl-0.1.1.dist-info/METADATA +0 -504
  29. {pytest_dsl-0.1.1.dist-info → pytest_dsl-0.3.0.dist-info}/entry_points.txt +0 -0
  30. {pytest_dsl-0.1.1.dist-info → pytest_dsl-0.3.0.dist-info}/licenses/LICENSE +0 -0
  31. {pytest_dsl-0.1.1.dist-info → pytest_dsl-0.3.0.dist-info}/top_level.txt +0 -0
@@ -149,6 +149,75 @@ def _process_request_config(config: Dict[str, Any], test_context: TestContext =
149
149
  return config
150
150
 
151
151
 
152
+ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interval=None):
153
+ """标准化断言重试配置
154
+
155
+ 将不同来源的重试配置(命令行参数、retry配置、retry_assertions配置)
156
+ 统一转换为标准化的重试配置对象。
157
+
158
+ Args:
159
+ config: 原始配置字典
160
+ assert_retry_count: 命令行级别的重试次数参数
161
+ assert_retry_interval: 命令行级别的重试间隔参数
162
+
163
+ Returns:
164
+ 标准化的重试配置字典,格式为:
165
+ {
166
+ 'enabled': 是否启用重试,
167
+ 'count': 重试次数,
168
+ 'interval': 重试间隔,
169
+ 'all': 是否重试所有断言,
170
+ 'indices': 要重试的断言索引列表,
171
+ 'specific': 特定断言的重试配置
172
+ }
173
+ """
174
+ # 初始化标准重试配置
175
+ standard_retry_config = {
176
+ 'enabled': False,
177
+ 'count': 3, # 默认重试3次
178
+ 'interval': 1.0, # 默认间隔1秒
179
+ 'all': False, # 默认不重试所有断言
180
+ 'indices': [], # 默认不指定要重试的断言索引
181
+ 'specific': {} # 默认不指定特定断言的重试配置
182
+ }
183
+
184
+ # 处理命令行参数
185
+ if assert_retry_count and int(assert_retry_count) > 0:
186
+ standard_retry_config['enabled'] = True
187
+ standard_retry_config['count'] = int(assert_retry_count)
188
+ standard_retry_config['all'] = True # 命令行参数会重试所有断言
189
+ if assert_retry_interval:
190
+ standard_retry_config['interval'] = float(assert_retry_interval)
191
+
192
+ # 处理专用retry_assertions配置
193
+ if 'retry_assertions' in config and config['retry_assertions']:
194
+ retry_assertions = config['retry_assertions']
195
+ standard_retry_config['enabled'] = True
196
+
197
+ if 'count' in retry_assertions:
198
+ standard_retry_config['count'] = retry_assertions['count']
199
+ if 'interval' in retry_assertions:
200
+ standard_retry_config['interval'] = retry_assertions['interval']
201
+ if 'all' in retry_assertions:
202
+ standard_retry_config['all'] = retry_assertions['all']
203
+ if 'indices' in retry_assertions:
204
+ standard_retry_config['indices'] = retry_assertions['indices']
205
+ if 'specific' in retry_assertions:
206
+ standard_retry_config['specific'] = retry_assertions['specific']
207
+
208
+ # 处理传统retry配置(如果专用配置不存在)
209
+ elif 'retry' in config and config['retry']:
210
+ retry_config = config['retry']
211
+ if 'count' in retry_config and retry_config['count'] > 0:
212
+ standard_retry_config['enabled'] = True
213
+ standard_retry_config['count'] = retry_config['count']
214
+ standard_retry_config['all'] = True # 传统配置会重试所有断言
215
+ if 'interval' in retry_config:
216
+ standard_retry_config['interval'] = retry_config['interval']
217
+
218
+ return standard_retry_config
219
+
220
+
152
221
  @keyword_manager.register('HTTP请求', [
153
222
  {'name': '客户端', 'mapping': 'client', 'description': '客户端名称,对应YAML变量文件中的客户端配置'},
154
223
  {'name': '配置', 'mapping': 'config', 'description': '包含请求、捕获和断言的YAML配置'},
@@ -228,24 +297,18 @@ def http_request(context, **kwargs):
228
297
  except yaml.YAMLError as e:
229
298
  raise ValueError(f"无效的YAML配置: {str(e)}")
230
299
 
231
- # 如果提供了命令行级别的断言重试参数,将其添加到新的retry_assertions配置中
232
- if assert_retry_count and int(assert_retry_count) > 0:
233
- # 检查配置中是否已经有retry_assertions配置
234
- if 'retry_assertions' not in config:
235
- config['retry_assertions'] = {}
236
-
237
- # 设置全局重试次数和间隔
238
- config['retry_assertions']['count'] = int(assert_retry_count)
239
- config['retry_assertions']['all'] = True
240
- if assert_retry_interval:
241
- config['retry_assertions']['interval'] = float(assert_retry_interval)
242
-
243
- # 向后兼容:同时设置旧格式的retry配置
244
- if 'retry' not in config:
245
- config['retry'] = {}
246
- config['retry']['count'] = int(assert_retry_count)
247
- if assert_retry_interval:
248
- config['retry']['interval'] = float(assert_retry_interval)
300
+ # 统一处理重试配置
301
+ retry_config = _normalize_retry_config(config, assert_retry_count, assert_retry_interval)
302
+
303
+ # 为了兼容性,将标准化后的重试配置写回到配置中
304
+ if retry_config['enabled']:
305
+ config['retry_assertions'] = {
306
+ 'count': retry_config['count'],
307
+ 'interval': retry_config['interval'],
308
+ 'all': retry_config['all'],
309
+ 'indices': retry_config['indices'],
310
+ 'specific': retry_config['specific']
311
+ }
249
312
 
250
313
  config = _process_request_config(config, test_context=context)
251
314
 
@@ -266,22 +329,14 @@ def http_request(context, **kwargs):
266
329
  if save_response:
267
330
  context.set(save_response, response)
268
331
 
269
- # 检查是否有配置中的断言重试设置
270
- has_retry_assertions = 'retry_assertions' in config
271
- has_legacy_retry = 'retry' in config
272
-
273
- # 处理断言(支持配置中的重试设置)
274
- if has_retry_assertions or has_legacy_retry:
275
- # 使用配置式断言重试
276
- with allure.step("执行配置式断言验证(支持选择性重试)"):
277
- _process_config_based_assertions_with_retry(http_req)
278
- elif assert_retry_count and int(assert_retry_count) > 0:
279
- # 向后兼容:使用传统的断言重试
280
- _process_assertions_with_retry(http_req, int(assert_retry_count),
281
- float(assert_retry_interval) if assert_retry_interval else 1.0)
282
- else:
283
- # 不需要重试,直接断言
284
- http_req.process_asserts()
332
+ # 统一处理断言逻辑
333
+ with allure.step("执行断言验证"):
334
+ if retry_config['enabled']:
335
+ # 使用统一的重试处理函数
336
+ _process_assertions_with_unified_retry(http_req, retry_config)
337
+ else:
338
+ # 不需要重试,直接断言
339
+ http_req.process_asserts()
285
340
 
286
341
  # 返回捕获的变量
287
342
  return captured_values
@@ -305,82 +360,129 @@ def _deep_merge(dict1, dict2):
305
360
  return dict1
306
361
 
307
362
 
308
- def _process_assertions_with_retry(http_req, retry_count, retry_interval):
309
- """处理断言并支持重试
363
+ def _process_assertions_with_unified_retry(http_req, retry_config):
364
+ """使用统一的重试配置处理断言
310
365
 
311
366
  Args:
312
367
  http_req: HTTP请求对象
313
- retry_count: 重试次数
314
- retry_interval: 重试间隔(秒)
368
+ retry_config: 标准化的重试配置
315
369
  """
316
-
317
- for attempt in range(retry_count + 1):
318
- try:
319
- # 尝试执行断言
320
- with allure.step(f"断言验证 (尝试 {attempt + 1}/{retry_count + 1})"):
321
- # 修改为获取断言结果和失败的可重试断言
322
- results, failed_retryable_assertions = http_req.process_asserts()
323
- # 断言成功,直接返回
324
- return results
325
- except AssertionError as e:
326
- # 如果还有重试机会,等待后重试
327
- if attempt < retry_count:
328
- with allure.step(f"断言失败,等待 {retry_interval} 秒后重试"):
329
- allure.attach(
330
- str(e),
331
- name="断言失败详情",
332
- attachment_type=allure.attachment_type.TEXT
333
- )
334
- time.sleep(retry_interval)
335
- # 重新发送请求
336
- http_req.execute()
337
- else:
338
- # 重试次数用完,重新抛出异常
339
- raise
340
-
341
-
342
- def _process_config_based_assertions_with_retry(http_req):
343
- """基于配置处理断言重试
344
-
345
- 支持以下重试配置格式:
346
- 1. 关键字级别参数: assert_retry_count, assert_retry_interval
347
- 2. 全局配置: retry: {count: 3, interval: 1}
348
- 3. 独立重试配置: retry_assertions: {...}
349
-
350
- Args:
351
- http_req: HTTP请求对象
352
-
353
- Returns:
354
- 断言结果列表
355
- """
356
- # 尝试执行所有断言
370
+ # 初始尝试执行所有断言
357
371
  try:
358
372
  results, failed_retryable_assertions = http_req.process_asserts()
359
- return results # 如果所有断言都通过,直接返回结果
360
- except AssertionError:
361
- # 有断言失败,需要进行重试
373
+ # 如果没有失败的断言,直接返回
374
+ return results
375
+ except AssertionError as e:
376
+ # 记录初始断言失败的详细错误信息
377
+ allure.attach(
378
+ str(e),
379
+ name="断言验证失败详情",
380
+ attachment_type=allure.attachment_type.TEXT
381
+ )
382
+
383
+ # 添加一个特殊的标记到配置中,表示我们只想收集失败的断言而不抛出异常
384
+ original_config = http_req.config.copy() if isinstance(http_req.config, dict) else {}
385
+
386
+ # 创建一个临时副本
387
+ temp_config = original_config.copy()
388
+
389
+ # 添加特殊标记,用于指示http_request.py中的process_asserts在处理fail时不抛出异常
390
+ # 注意:这需要对应修改HTTPRequest.process_asserts方法
391
+ temp_config['_collect_failed_assertions_only'] = True
392
+
393
+ try:
394
+ # 临时替换配置
395
+ http_req.config = temp_config
396
+
397
+ # 重新运行断言,这次只收集失败的断言而不抛出异常
398
+ _, failed_retryable_assertions = http_req.process_asserts()
399
+ except Exception as collect_err:
400
+ # 出现意外错误时记录
401
+ allure.attach(
402
+ f"收集失败断言时出错: {type(collect_err).__name__}: {str(collect_err)}",
403
+ name="断言收集错误",
404
+ attachment_type=allure.attachment_type.TEXT
405
+ )
406
+ failed_retryable_assertions = []
407
+ finally:
408
+ # 恢复原始配置
409
+ http_req.config = original_config
410
+
411
+ # 有断言失败,判断是否有需要重试的断言
362
412
  if not failed_retryable_assertions:
363
- # 没有可重试的断言,重新抛出异常
413
+ # 没有可重试的断言,重新抛出原始异常
364
414
  raise
365
415
 
366
- # 开始重试循环
367
- max_retry_count = 3 # 默认重试次数
416
+ # 过滤需要重试的断言
417
+ retryable_assertions = []
368
418
 
369
- # 找出所有断言中最大的重试次数
370
419
  for failed_assertion in failed_retryable_assertions:
371
- max_retry_count = max(max_retry_count, failed_assertion.get('retry_count', 3))
420
+ assertion_idx = failed_assertion['index']
372
421
 
373
- # 断言重试
422
+ # 判断该断言是否应该重试
423
+ should_retry = False
424
+ specific_retry_count = retry_config['count']
425
+ specific_retry_interval = retry_config['interval']
426
+
427
+ # 检查特定断言配置
428
+ if str(assertion_idx) in retry_config['specific']:
429
+ should_retry = True
430
+ spec_config = retry_config['specific'][str(assertion_idx)]
431
+ if isinstance(spec_config, dict):
432
+ if 'count' in spec_config:
433
+ specific_retry_count = spec_config['count']
434
+ if 'interval' in spec_config:
435
+ specific_retry_interval = spec_config['interval']
436
+ # 检查索引列表
437
+ elif assertion_idx in retry_config['indices']:
438
+ should_retry = True
439
+ # 检查是否重试所有
440
+ elif retry_config['all']:
441
+ should_retry = True
442
+
443
+ # 如果应该重试,添加到可重试断言列表
444
+ if should_retry:
445
+ # 添加重试配置到断言对象
446
+ failed_assertion['retry_count'] = specific_retry_count
447
+ failed_assertion['retry_interval'] = specific_retry_interval
448
+ retryable_assertions.append(failed_assertion)
449
+
450
+ # 如果没有可重试的断言,重新抛出异常
451
+ if not retryable_assertions:
452
+ raise
453
+
454
+ # 记录哪些断言会被重试
455
+ retry_info = "\n".join([
456
+ f"{i+1}. {a['type']} " +
457
+ (f"[{a['path']}]" if a['path'] else "") +
458
+ f": 重试 {a['retry_count']} 次,间隔 {a['retry_interval']} 秒"
459
+ for i, a in enumerate(retryable_assertions)
460
+ ])
461
+
462
+ allure.attach(
463
+ f"找到 {len(retryable_assertions)} 个可重试的断言:\n\n{retry_info}",
464
+ name="重试断言列表",
465
+ attachment_type=allure.attachment_type.TEXT
466
+ )
467
+
468
+ # 开始重试循环
469
+ max_retry_count = retry_config['count']
470
+
471
+ # 找出所有断言中最大的重试次数
472
+ for retryable_assertion in retryable_assertions:
473
+ max_retry_count = max(max_retry_count, retryable_assertion.get('retry_count', 3))
474
+
475
+ # 进行断言重试
374
476
  for attempt in range(1, max_retry_count + 1): # 从1开始,因为第0次已经尝试过了
375
477
  # 等待重试间隔
376
- with allure.step(f"断言重试 (尝试 {attempt + 1}/{max_retry_count + 1})"):
478
+ with allure.step(f"断言重试 (尝试 {attempt}/{max_retry_count})"):
377
479
  # 确定本次重试的间隔时间(使用每个断言中最长的间隔时间)
378
- retry_interval = 1.0 # 默认间隔时间
379
- for failed_assertion in failed_retryable_assertions:
380
- retry_interval = max(retry_interval, failed_assertion.get('retry_interval', 1.0))
480
+ retry_interval = retry_config['interval']
481
+ for assertion in retryable_assertions:
482
+ retry_interval = max(retry_interval, assertion.get('retry_interval', 1.0))
381
483
 
382
484
  allure.attach(
383
- f"重试 {len(failed_retryable_assertions)} 个可重试断言\n"
485
+ f"重试 {len(retryable_assertions)} 个断言\n"
384
486
  f"等待间隔: {retry_interval}秒",
385
487
  name="断言重试信息",
386
488
  attachment_type=allure.attachment_type.TEXT
@@ -393,12 +495,12 @@ def _process_config_based_assertions_with_retry(http_req):
393
495
 
394
496
  # 过滤出仍在重试范围内的断言
395
497
  still_retryable_assertions = []
396
- for failed_assertion in failed_retryable_assertions:
397
- assertion_retry_count = failed_assertion.get('retry_count', 3)
498
+ for assertion in retryable_assertions:
499
+ assertion_retry_count = assertion.get('retry_count', 3)
398
500
 
399
501
  # 如果断言的重试次数大于当前尝试次数,继续重试该断言
400
502
  if attempt < assertion_retry_count:
401
- still_retryable_assertions.append(failed_assertion)
503
+ still_retryable_assertions.append(assertion)
402
504
 
403
505
  # 如果没有可以继续重试的断言,跳出循环
404
506
  if not still_retryable_assertions:
@@ -413,18 +515,104 @@ def _process_config_based_assertions_with_retry(http_req):
413
515
  # 只处理需要重试的断言
414
516
  results, new_failed_assertions = http_req.process_asserts(specific_asserts=retry_assertions)
415
517
 
416
- # 如果所有断言都通过了,返回结果
518
+ # 如果所有断言都通过了,检查全部断言
417
519
  if not new_failed_assertions:
418
520
  # 执行一次完整的断言检查,确保所有断言都通过
419
- return http_req.process_asserts()[0]
521
+ try:
522
+ results, _ = http_req.process_asserts()
523
+ allure.attach(
524
+ "所有断言重试后验证通过",
525
+ name="重试成功",
526
+ attachment_type=allure.attachment_type.TEXT
527
+ )
528
+ return results
529
+ except AssertionError as final_err:
530
+ # 记录最终错误,然后继续重试
531
+ allure.attach(
532
+ f"重试后的完整断言验证仍有失败: {str(final_err)}",
533
+ name="完整断言仍失败",
534
+ attachment_type=allure.attachment_type.TEXT
535
+ )
536
+ continue
420
537
 
421
538
  # 更新失败的可重试断言列表
422
- failed_retryable_assertions = new_failed_assertions
539
+ retryable_assertions = new_failed_assertions
423
540
 
424
- except AssertionError:
425
- # 断言仍然失败,继续重试
541
+ except AssertionError as retry_err:
542
+ # 重试时断言失败,记录后继续重试
543
+ allure.attach(
544
+ f"第 {attempt} 次重试断言失败: {str(retry_err)}",
545
+ name=f"重试断言失败 #{attempt}",
546
+ attachment_type=allure.attachment_type.TEXT
547
+ )
426
548
  continue
427
549
 
428
550
  # 重试次数用完,执行一次完整的断言以获取最终结果和错误
429
551
  # 这会抛出异常,如果仍然有断言失败
430
- return http_req.process_asserts()[0]
552
+ allure.attach(
553
+ "所有重试次数已用完,执行最终断言验证",
554
+ name="重试完成",
555
+ attachment_type=allure.attachment_type.TEXT
556
+ )
557
+
558
+ try:
559
+ results, _ = http_req.process_asserts()
560
+ return results
561
+ except AssertionError as final_err:
562
+ # 重新格式化错误消息,添加重试信息
563
+ enhanced_error = (
564
+ f"断言验证失败 (已重试 {max_retry_count} 次):\n\n{str(final_err)}"
565
+ )
566
+ allure.attach(
567
+ enhanced_error,
568
+ name="重试后仍失败的断言",
569
+ attachment_type=allure.attachment_type.TEXT
570
+ )
571
+ raise AssertionError(enhanced_error) from final_err
572
+
573
+
574
+ # 移除旧的重试函数,使用统一的重试机制
575
+ # 以下函数保留用于向后兼容,但内部逻辑已更改为调用统一重试函数
576
+ def _process_assertions_with_retry(http_req, retry_count, retry_interval):
577
+ """处理断言并支持重试(向后兼容函数)
578
+
579
+ Args:
580
+ http_req: HTTP请求对象
581
+ retry_count: 重试次数
582
+ retry_interval: 重试间隔(秒)
583
+ """
584
+ # 创建统一的重试配置
585
+ retry_config = {
586
+ 'enabled': True,
587
+ 'count': retry_count,
588
+ 'interval': retry_interval,
589
+ 'all': True,
590
+ 'indices': [],
591
+ 'specific': {}
592
+ }
593
+
594
+ # 使用统一的重试处理函数
595
+ return _process_assertions_with_unified_retry(http_req, retry_config)
596
+
597
+
598
+ def _process_config_based_assertions_with_retry(http_req):
599
+ """基于配置处理断言重试(向后兼容函数)
600
+
601
+ Args:
602
+ http_req: HTTP请求对象
603
+ """
604
+ # 从配置中获取重试信息
605
+ retry_assertions_config = http_req.config.get('retry_assertions', {})
606
+
607
+ # 创建统一的重试配置
608
+ retry_config = {
609
+ 'enabled': True,
610
+ 'count': retry_assertions_config.get('count', 3),
611
+ 'interval': retry_assertions_config.get('interval', 1.0),
612
+ 'all': retry_assertions_config.get('all', False),
613
+ 'indices': retry_assertions_config.get('indices', []),
614
+ 'specific': retry_assertions_config.get('specific', {})
615
+ }
616
+
617
+ # 使用统一的重试处理函数
618
+ return _process_assertions_with_unified_retry(http_req, retry_config)
pytest_dsl/parsetab.py ADDED
@@ -0,0 +1,69 @@
1
+
2
+ # parsetab.py
3
+ # This file is automatically generated. Do not edit.
4
+ # pylint: disable=W,C,R
5
+ _tabversion = '3.10'
6
+
7
+ _lr_method = 'LALR'
8
+
9
+ _lr_signature = 'AUTHOR_KEYWORD COLON COMMA DATA_KEYWORD DATE DATE_KEYWORD DESCRIPTION_KEYWORD DO END EQUALS FALSE FOR ID IMPORT_KEYWORD IN KEYWORD_KEYWORD LBRACKET LPAREN NAME_KEYWORD NUMBER PLACEHOLDER RANGE RBRACKET RETURN RPAREN STRING TAGS_KEYWORD TEARDOWN_KEYWORD TRUE USINGstart : metadata keyword_definitionsmetadata : metadata_item metadata\n | metadata_itemmetadata_item : NAME_KEYWORD COLON STRING\n | NAME_KEYWORD COLON ID\n | DESCRIPTION_KEYWORD COLON STRING\n | DESCRIPTION_KEYWORD COLON ID\n | AUTHOR_KEYWORD COLON STRING\n | AUTHOR_KEYWORD COLON ID\n | DATE_KEYWORD COLON DATE\n | IMPORT_KEYWORD COLON STRINGkeyword_definitions : keyword_definition keyword_definitions\n | keyword_definitionkeyword_definition : KEYWORD_KEYWORD ID LPAREN param_list RPAREN keyword_body END\n | KEYWORD_KEYWORD ID LPAREN RPAREN keyword_body ENDkeyword_body : keyword_statementskeyword_statements : keyword_statement keyword_statements\n | keyword_statementkeyword_statement : assignment\n | keyword_call\n | return_statementassignment : ID EQUALS expression\n | ID EQUALS keyword_callexpression : NUMBER\n | STRING\n | PLACEHOLDER\n | IDparam_list : param\n | param COMMA param_listparam : ID\n | ID EQUALS STRING\n | ID EQUALS NUMBER\n | ID EQUALS IDkeyword_call : LBRACKET ID RBRACKET COMMA parameter_list\n | LBRACKET ID RBRACKETparameter_list : parameter_item\n | parameter_item COMMA parameter_listparameter_item : ID COLON expressionreturn_statement : RETURN expression'
10
+
11
+ _lr_action_items = {'NAME_KEYWORD':([0,3,20,21,22,23,24,25,26,27,],[4,4,-4,-5,-6,-7,-8,-9,-10,-11,]),'DESCRIPTION_KEYWORD':([0,3,20,21,22,23,24,25,26,27,],[5,5,-4,-5,-6,-7,-8,-9,-10,-11,]),'AUTHOR_KEYWORD':([0,3,20,21,22,23,24,25,26,27,],[6,6,-4,-5,-6,-7,-8,-9,-10,-11,]),'DATE_KEYWORD':([0,3,20,21,22,23,24,25,26,27,],[7,7,-4,-5,-6,-7,-8,-9,-10,-11,]),'IMPORT_KEYWORD':([0,3,20,21,22,23,24,25,26,27,],[8,8,-4,-5,-6,-7,-8,-9,-10,-11,]),'$end':([1,9,10,18,50,59,],[0,-1,-13,-12,-15,-14,]),'KEYWORD_KEYWORD':([2,3,10,12,20,21,22,23,24,25,26,27,50,59,],[11,-3,11,-2,-4,-5,-6,-7,-8,-9,-10,-11,-15,-14,]),'COLON':([4,5,6,7,8,64,],[13,14,15,16,17,67,]),'ID':([11,13,14,15,28,31,33,34,38,39,40,41,42,43,44,49,53,54,55,56,57,60,61,62,63,65,66,67,68,69,70,],[19,21,23,25,29,35,45,35,35,-19,-20,-21,52,57,29,57,-39,-24,-25,-26,-27,-22,-23,-35,64,-34,-36,57,64,-38,-37,]),'STRING':([13,14,15,17,33,43,49,67,],[20,22,24,27,46,55,55,55,]),'DATE':([16,],[26,]),'LPAREN':([19,],[28,]),'RPAREN':([28,29,30,32,45,46,47,58,],[31,-30,34,-28,-33,-31,-32,-29,]),'COMMA':([29,32,45,46,47,54,55,56,57,62,66,69,],[-30,44,-33,-31,-32,-24,-25,-26,-27,63,68,-38,]),'EQUALS':([29,35,],[33,49,]),'LBRACKET':([31,34,38,39,40,41,49,53,54,55,56,57,60,61,62,65,66,69,70,],[42,42,42,-19,-20,-21,42,-39,-24,-25,-26,-27,-22,-23,-35,-34,-36,-38,-37,]),'RETURN':([31,34,38,39,40,41,53,54,55,56,57,60,61,62,65,66,69,70,],[43,43,43,-19,-20,-21,-39,-24,-25,-26,-27,-22,-23,-35,-34,-36,-38,-37,]),'NUMBER':([33,43,49,67,],[47,54,54,54,]),'END':([36,37,38,39,40,41,48,51,53,54,55,56,57,60,61,62,65,66,69,70,],[50,-16,-18,-19,-20,-21,59,-17,-39,-24,-25,-26,-27,-22,-23,-35,-34,-36,-38,-37,]),'PLACEHOLDER':([43,49,67,],[56,56,56,]),'RBRACKET':([52,],[62,]),}
12
+
13
+ _lr_action = {}
14
+ for _k, _v in _lr_action_items.items():
15
+ for _x,_y in zip(_v[0],_v[1]):
16
+ if not _x in _lr_action: _lr_action[_x] = {}
17
+ _lr_action[_x][_k] = _y
18
+ del _lr_action_items
19
+
20
+ _lr_goto_items = {'start':([0,],[1,]),'metadata':([0,3,],[2,12,]),'metadata_item':([0,3,],[3,3,]),'keyword_definitions':([2,10,],[9,18,]),'keyword_definition':([2,10,],[10,10,]),'param_list':([28,44,],[30,58,]),'param':([28,44,],[32,32,]),'keyword_body':([31,34,],[36,48,]),'keyword_statements':([31,34,38,],[37,37,51,]),'keyword_statement':([31,34,38,],[38,38,38,]),'assignment':([31,34,38,],[39,39,39,]),'keyword_call':([31,34,38,49,],[40,40,40,61,]),'return_statement':([31,34,38,],[41,41,41,]),'expression':([43,49,67,],[53,60,69,]),'parameter_list':([63,68,],[65,70,]),'parameter_item':([63,68,],[66,66,]),}
21
+
22
+ _lr_goto = {}
23
+ for _k, _v in _lr_goto_items.items():
24
+ for _x, _y in zip(_v[0], _v[1]):
25
+ if not _x in _lr_goto: _lr_goto[_x] = {}
26
+ _lr_goto[_x][_k] = _y
27
+ del _lr_goto_items
28
+ _lr_productions = [
29
+ ("S' -> start","S'",1,None,None,None),
30
+ ('start -> metadata keyword_definitions','start',2,'p_start','parser.py',11),
31
+ ('metadata -> metadata_item metadata','metadata',2,'p_metadata','parser.py',15),
32
+ ('metadata -> metadata_item','metadata',1,'p_metadata','parser.py',16),
33
+ ('metadata_item -> NAME_KEYWORD COLON STRING','metadata_item',3,'p_metadata_item','parser.py',23),
34
+ ('metadata_item -> NAME_KEYWORD COLON ID','metadata_item',3,'p_metadata_item','parser.py',24),
35
+ ('metadata_item -> DESCRIPTION_KEYWORD COLON STRING','metadata_item',3,'p_metadata_item','parser.py',25),
36
+ ('metadata_item -> DESCRIPTION_KEYWORD COLON ID','metadata_item',3,'p_metadata_item','parser.py',26),
37
+ ('metadata_item -> AUTHOR_KEYWORD COLON STRING','metadata_item',3,'p_metadata_item','parser.py',27),
38
+ ('metadata_item -> AUTHOR_KEYWORD COLON ID','metadata_item',3,'p_metadata_item','parser.py',28),
39
+ ('metadata_item -> DATE_KEYWORD COLON DATE','metadata_item',3,'p_metadata_item','parser.py',29),
40
+ ('metadata_item -> IMPORT_KEYWORD COLON STRING','metadata_item',3,'p_metadata_item','parser.py',30),
41
+ ('keyword_definitions -> keyword_definition keyword_definitions','keyword_definitions',2,'p_keyword_definitions','parser.py',34),
42
+ ('keyword_definitions -> keyword_definition','keyword_definitions',1,'p_keyword_definitions','parser.py',35),
43
+ ('keyword_definition -> KEYWORD_KEYWORD ID LPAREN param_list RPAREN keyword_body END','keyword_definition',7,'p_keyword_definition','parser.py',42),
44
+ ('keyword_definition -> KEYWORD_KEYWORD ID LPAREN RPAREN keyword_body END','keyword_definition',6,'p_keyword_definition','parser.py',43),
45
+ ('keyword_body -> keyword_statements','keyword_body',1,'p_keyword_body','parser.py',50),
46
+ ('keyword_statements -> keyword_statement keyword_statements','keyword_statements',2,'p_keyword_statements','parser.py',54),
47
+ ('keyword_statements -> keyword_statement','keyword_statements',1,'p_keyword_statements','parser.py',55),
48
+ ('keyword_statement -> assignment','keyword_statement',1,'p_keyword_statement','parser.py',62),
49
+ ('keyword_statement -> keyword_call','keyword_statement',1,'p_keyword_statement','parser.py',63),
50
+ ('keyword_statement -> return_statement','keyword_statement',1,'p_keyword_statement','parser.py',64),
51
+ ('assignment -> ID EQUALS expression','assignment',3,'p_assignment','parser.py',68),
52
+ ('assignment -> ID EQUALS keyword_call','assignment',3,'p_assignment','parser.py',69),
53
+ ('expression -> NUMBER','expression',1,'p_expression','parser.py',76),
54
+ ('expression -> STRING','expression',1,'p_expression','parser.py',77),
55
+ ('expression -> PLACEHOLDER','expression',1,'p_expression','parser.py',78),
56
+ ('expression -> ID','expression',1,'p_expression','parser.py',79),
57
+ ('param_list -> param','param_list',1,'p_param_list','parser.py',83),
58
+ ('param_list -> param COMMA param_list','param_list',3,'p_param_list','parser.py',84),
59
+ ('param -> ID','param',1,'p_param','parser.py',91),
60
+ ('param -> ID EQUALS STRING','param',3,'p_param','parser.py',92),
61
+ ('param -> ID EQUALS NUMBER','param',3,'p_param','parser.py',93),
62
+ ('param -> ID EQUALS ID','param',3,'p_param','parser.py',94),
63
+ ('keyword_call -> LBRACKET ID RBRACKET COMMA parameter_list','keyword_call',5,'p_keyword_call','parser.py',103),
64
+ ('keyword_call -> LBRACKET ID RBRACKET','keyword_call',3,'p_keyword_call','parser.py',104),
65
+ ('parameter_list -> parameter_item','parameter_list',1,'p_parameter_list','parser.py',111),
66
+ ('parameter_list -> parameter_item COMMA parameter_list','parameter_list',3,'p_parameter_list','parser.py',112),
67
+ ('parameter_item -> ID COLON expression','parameter_item',3,'p_parameter_item','parser.py',119),
68
+ ('return_statement -> RETURN expression','return_statement',2,'p_return_statement','parser.py',123),
69
+ ]