1597 lines
78 KiB
Python
1597 lines
78 KiB
Python
import re
|
||
import unittest
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
RULESET_TSL = ROOT / "rulesets" / "tsl" / "index.md"
|
||
TSL_QUICKSTART = ROOT / "docs" / "tsl" / "syntax" / "01_quickstart.md"
|
||
TSL_CORE_MODEL = ROOT / "docs" / "tsl" / "syntax" / "02_core_model.md"
|
||
TSL_VALUES = ROOT / "docs" / "tsl" / "syntax" / "03_values_and_literals.md"
|
||
TSL_VARIABLES = ROOT / "docs" / "tsl" / "syntax" / "04_variables_and_constants.md"
|
||
TSL_FUNCTIONS = ROOT / "docs" / "tsl" / "syntax" / "05_functions_and_calls.md"
|
||
TSL_EXPRESSIONS = ROOT / "docs" / "tsl" / "syntax" / "06_expressions_and_operators.md"
|
||
TSL_CONTROL_FLOW = ROOT / "docs" / "tsl" / "syntax" / "07_control_flow.md"
|
||
TSL_TS_SQL = ROOT / "docs" / "tsl" / "syntax" / "14_ts_sql.md"
|
||
TSL_TYPES = ROOT / "docs" / "tsl" / "syntax" / "17_types_and_conversions.md"
|
||
TSL_EXTERNAL = ROOT / "docs" / "tsl" / "syntax" / "18_external_calls_and_threads.md"
|
||
TSL_MATRIX_DEEP_DIVE = ROOT / "docs" / "tsl" / "syntax" / "22_matrix_deep_dive.md"
|
||
TSL_FMARRAY = ROOT / "docs" / "tsl" / "syntax" / "23_fmarray.md"
|
||
TSL_OBJECT_RUNTIME = ROOT / "docs" / "tsl" / "syntax" / "20_object_runtime_and_introspection.md"
|
||
TSL_BUILTIN_RUNTIME_OBJECTS = ROOT / "docs" / "tsl" / "syntax" / "21_builtin_runtime_objects.md"
|
||
README = ROOT / "README.md"
|
||
PLAYBOOK_EXAMPLE = ROOT / "playbook.toml.example"
|
||
SKILLS_DOC = ROOT / "SKILLS.md"
|
||
TEMPLATES_CI_README = ROOT / "templates" / "ci" / "README.md"
|
||
TEMPLATES_README = ROOT / "templates" / "README.md"
|
||
REMOVED_TSL_GUIDE = ROOT / "skills" / "tsl-guide"
|
||
TSL_REFERENCE_INDEX = ROOT / "docs" / "tsl" / "reference" / "index.md"
|
||
TSL_REFERENCE_UNAVAILABLE = ROOT / "docs" / "tsl" / "reference" / "unavailable_methods.md"
|
||
TSL_MODULES_INDEX = ROOT / "docs" / "tsl" / "modules" / "index.md"
|
||
TSL_TOOLCHAIN = ROOT / "docs" / "tsl" / "toolchain.md"
|
||
|
||
TSL_REMAINING_SYNTAX_AGENT_SECTIONS = {
|
||
"07_control_flow.md": "智能体控制流判断流程",
|
||
"08_objects_and_classes.md": "智能体对象/类判断流程",
|
||
"09_units_and_scope.md": "智能体 unit/作用域判断流程",
|
||
"10_runtime_context_and_with.md": "智能体运行时上下文判断流程",
|
||
"11_pitfalls.md": "智能体常见误写判断流程",
|
||
"12_matrix_and_collections.md": "智能体数组/矩阵样数据判断流程",
|
||
"13_resultset_and_filters.md": "智能体结果集/过滤判断流程",
|
||
"14_ts_sql.md": "智能体 TS-SQL 判断流程",
|
||
"15_debug_and_profiler.md": "智能体调试/性能分析器判断流程",
|
||
"16_lexical_structure_and_compile_options.md": "智能体词法/编译选项判断流程",
|
||
"17_types_and_conversions.md": "智能体类型/转换判断流程",
|
||
"18_external_calls_and_threads.md": "智能体外部调用/线程判断流程",
|
||
"19_namespace_libpath_and_unit_runtime.md": "智能体命名空间/Libpath 判断流程",
|
||
"20_object_runtime_and_introspection.md": "智能体对象运行时/反射判断流程",
|
||
"21_builtin_runtime_objects.md": "智能体内置运行时对象判断流程",
|
||
"22_matrix_deep_dive.md": "智能体矩阵深水判断流程",
|
||
"23_fmarray.md": "智能体 FMArray 判断流程",
|
||
"24_object_overloads_and_iteration.md": "智能体对象重载/迭代判断流程",
|
||
}
|
||
|
||
TSL_SYNTAX_MAINLINE_FILES = [
|
||
"01_quickstart.md",
|
||
"02_core_model.md",
|
||
"03_values_and_literals.md",
|
||
"04_variables_and_constants.md",
|
||
"05_functions_and_calls.md",
|
||
"06_expressions_and_operators.md",
|
||
"07_control_flow.md",
|
||
"08_objects_and_classes.md",
|
||
"09_units_and_scope.md",
|
||
"10_runtime_context_and_with.md",
|
||
"12_matrix_and_collections.md",
|
||
"13_resultset_and_filters.md",
|
||
"14_ts_sql.md",
|
||
"15_debug_and_profiler.md",
|
||
]
|
||
|
||
TSL_SYNTAX_DEEP_DIVE_FILES = [
|
||
"16_lexical_structure_and_compile_options.md",
|
||
"17_types_and_conversions.md",
|
||
"18_external_calls_and_threads.md",
|
||
"19_namespace_libpath_and_unit_runtime.md",
|
||
"20_object_runtime_and_introspection.md",
|
||
"21_builtin_runtime_objects.md",
|
||
"22_matrix_deep_dive.md",
|
||
"23_fmarray.md",
|
||
"24_object_overloads_and_iteration.md",
|
||
]
|
||
|
||
|
||
class TslEntrypointsConsistencyTests(unittest.TestCase):
|
||
def test_ruleset_lists_canonical_tsl_layers(self):
|
||
text = RULESET_TSL.read_text(encoding="utf-8")
|
||
self.assertIn("docs/tsl/index.md", text)
|
||
self.assertIn("docs/tsl/reference/catalog/datawarehouse.md", text)
|
||
self.assertIn("docs/tsl/modules/pytsl_api.md", text)
|
||
self.assertIn("docs/tsl/modules/tsbacktesting.md", text)
|
||
self.assertIn("docs/tsl/modules/index.md", text)
|
||
self.assertIn("docs/tsl/reference/index.md", text)
|
||
|
||
tsl_index = (ROOT / "docs/tsl/index.md").read_text(encoding="utf-8")
|
||
self.assertIn("syntax/index.md", tsl_index)
|
||
|
||
def test_readme_only_lists_two_official_deployment_routes(self):
|
||
text = README.read_text(encoding="utf-8")
|
||
self.assertIn("方式一:git subtree", text)
|
||
self.assertIn("方式二:外部 clone 后执行部署", text)
|
||
self.assertIn("- `rulesets/typescript/index.md`:TypeScript 核心约定", text)
|
||
self.assertIn("`project_root`:目标项目根目录", text)
|
||
self.assertIn("`playbook_root`:相对于 `project_root` 的项目内 Playbook 根目录", text)
|
||
self.assertIn("不是外部 clone 出来的 Playbook 仓库路径", text)
|
||
self.assertIn("外部 clone 场景下必须显式填写 `playbook_root`", text)
|
||
self.assertNotIn("方式二:手动复制快照", text)
|
||
self.assertNotIn("方式三:CLI 裁剪复制", text)
|
||
self.assertNotIn("如果省略 `playbook_root`,默认仍部署到 `docs/standards/playbook`", text)
|
||
|
||
def test_playbook_example_defines_playbook_root_as_target_path(self):
|
||
text = PLAYBOOK_EXAMPLE.read_text(encoding="utf-8")
|
||
self.assertIn('playbook_root = "docs/standards/playbook"', text)
|
||
self.assertIn('install_mode = "subtree"', text)
|
||
self.assertIn("相对于 project_root", text)
|
||
self.assertIn("不是外部 clone 的 playbook 路径", text)
|
||
self.assertIn("snapshot 表示从外部 clone 安装快照", text)
|
||
self.assertNotIn("target_dir", text)
|
||
|
||
def test_deployment_docs_do_not_reference_legacy_terms(self):
|
||
self.assertNotIn("vendoring", README.read_text(encoding="utf-8"))
|
||
self.assertNotIn("`.tmp`", README.read_text(encoding="utf-8"))
|
||
self.assertNotIn("vendoring", SKILLS_DOC.read_text(encoding="utf-8"))
|
||
self.assertNotIn("vendoring", TEMPLATES_CI_README.read_text(encoding="utf-8"))
|
||
|
||
def test_repo_docs_use_platform_neutral_skills_source_dir(self):
|
||
readme_text = README.read_text(encoding="utf-8")
|
||
skills_text = SKILLS_DOC.read_text(encoding="utf-8")
|
||
templates_text = TEMPLATES_README.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("`skills/`", readme_text)
|
||
self.assertIn("`skills/`", skills_text)
|
||
self.assertIn("├── skills/", templates_text)
|
||
|
||
self.assertNotIn("`codex/skills/`", readme_text)
|
||
self.assertNotIn("`codex/skills/`", skills_text)
|
||
self.assertNotIn("├── codex/skills/", templates_text)
|
||
|
||
def test_repo_no_longer_ships_tsl_guide_skill(self):
|
||
self.assertFalse(REMOVED_TSL_GUIDE.exists())
|
||
self.assertNotIn("tsl-guide", README.read_text(encoding="utf-8"))
|
||
self.assertNotIn("tsl-guide", SKILLS_DOC.read_text(encoding="utf-8"))
|
||
self.assertNotIn("$tsl-guide", RULESET_TSL.read_text(encoding="utf-8"))
|
||
|
||
def test_tsl_ruleset_describes_agent_decision_router(self):
|
||
text = RULESET_TSL.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("# TSL 智能体规则", text)
|
||
self.assertEqual(len(text.splitlines()), 45)
|
||
self.assertIn("作用:控制智能体在阅读、修改、生成 TSL 代码前的判断顺序", text)
|
||
self.assertIn("本文件不是 TSL 语法手册", text)
|
||
self.assertIn("禁止凭 Pascal、Python、JavaScript、TypeScript", text)
|
||
self.assertIn("代码生成协议", text)
|
||
self.assertIn("TSL 核心事实", text)
|
||
self.assertIn("任务路由", text)
|
||
self.assertIn("文档事实使用策略", text)
|
||
self.assertRegex(
|
||
text,
|
||
r"\| 语法、文件模型、最短骨架、反例\s+\| `docs/tsl/index\.md`\s+\|",
|
||
)
|
||
self.assertNotIn("最终 TSL 自检", text)
|
||
self.assertNotIn("修改纪律", text)
|
||
self.assertNotIn("文档入口", text)
|
||
self.assertNotIn("## 安全规则", text)
|
||
self.assertNotIn("`docs/tsl/index.md` 或 `docs/tsl/syntax/index.md`", text)
|
||
self.assertNotIn("代码块身份:可直接照写示例", text)
|
||
self.assertNotIn("funcext", text)
|
||
|
||
def test_tsl_syntax_docs_keep_script_then_declarations_model(self):
|
||
quickstart = (ROOT / "docs/tsl/syntax/01_quickstart.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
core_model = (ROOT / "docs/tsl/syntax/02_core_model.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
functions = (ROOT / "docs/tsl/syntax/05_functions_and_calls.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
pitfalls = (ROOT / "docs/tsl/syntax/11_pitfalls.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
|
||
for text in (quickstart, core_model, functions, pitfalls):
|
||
self.assertIn("语句区", text)
|
||
self.assertIn("声明区", text)
|
||
|
||
self.assertIn("test();", quickstart)
|
||
self.assertIn("function test();", quickstart)
|
||
self.assertIn("脚本语句后可以接函数声明", functions)
|
||
self.assertIn("不要在声明区后面继续写脚本语句", pitfalls)
|
||
self.assertNotIn("不要把顶层函数定义和松散语句混写", quickstart)
|
||
self.assertNotIn("不要把顶层函数定义和松散语句混写", core_model)
|
||
self.assertNotIn("不要把顶层函数定义和松散语句混在同一个文件模型里", functions)
|
||
self.assertIn("后缀就是判断依据", quickstart)
|
||
self.assertIn("未给后缀", quickstart)
|
||
|
||
def test_tsl_agent_entrypoints_do_not_require_runtime_verification(self):
|
||
entrypoints = [
|
||
RULESET_TSL,
|
||
ROOT / "docs/tsl/index.md",
|
||
ROOT / "docs/tsl/syntax/index.md",
|
||
]
|
||
|
||
forbidden_phrases = [
|
||
"用当前解释器验证",
|
||
"本地用 `tsl` 验证",
|
||
"用 `tsl` 实测",
|
||
"最小实测",
|
||
"才写最小 `.tsl` / `.tsf` 例子",
|
||
]
|
||
|
||
for path in entrypoints:
|
||
text = path.read_text(encoding="utf-8")
|
||
for phrase in forbidden_phrases:
|
||
self.assertNotIn(phrase, text, msg=f"{phrase!r} found in {path}")
|
||
|
||
def test_tsl_reader_entrypoints_are_decision_routes_not_agent_process_docs(self):
|
||
entrypoints = [
|
||
ROOT / "docs/tsl/index.md",
|
||
ROOT / "docs/tsl/syntax/index.md",
|
||
]
|
||
|
||
for path in entrypoints:
|
||
with self.subTest(path=path.relative_to(ROOT)):
|
||
text = path.read_text(encoding="utf-8")
|
||
self.assertIn("任务信号", text)
|
||
self.assertIn("入口", text)
|
||
self.assertIn("阻断条件", text)
|
||
self.assertNotIn("智能体语法判断流程", text)
|
||
self.assertNotIn("智能体提交前最低自检", text)
|
||
self.assertNotIn("本套 TSL 文档面向智能体决策", text)
|
||
self.assertNotIn("## 决策顺序", text)
|
||
|
||
def test_tsl_docs_use_compact_uncertain_header(self):
|
||
docs_root = ROOT / "docs" / "tsl"
|
||
|
||
for path in docs_root.rglob("*.md"):
|
||
with self.subTest(path=path.relative_to(ROOT)):
|
||
text = path.read_text(encoding="utf-8")
|
||
self.assertNotIn("遇到不确定时跳转到", text)
|
||
|
||
def test_tsl_index_uses_documented_generation_facts(self):
|
||
text = (ROOT / "docs/tsl/index.md").read_text(encoding="utf-8")
|
||
|
||
self.assertIn("证据规则", text)
|
||
self.assertIn("停止条件", text)
|
||
self.assertIn("代码外形缺少 `代码块身份:可直接照写示例`", text)
|
||
self.assertIn("无文档事实、文件模型不明或项目执行事实缺失", text)
|
||
self.assertIn("语法页不承载执行环境、账户体系、真实接口或验证命令", text)
|
||
self.assertIn("项目执行信息会被写成通用 TSL 事实", text)
|
||
self.assertNotIn("## 路由冲突", text)
|
||
self.assertNotIn("## 生成前阻断条件", text)
|
||
|
||
def test_tsl_toolchain_is_template_not_execution_authority(self):
|
||
text = TSL_TOOLCHAIN.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("# TSL 工具链与验证命令模板", text)
|
||
self.assertIn("文档类型:项目工具链模板页", text)
|
||
self.assertIn("不是通用执行手册", text)
|
||
self.assertIn("不是 TSL 语法事实", text)
|
||
self.assertIn("目标项目已真实填写", text)
|
||
self.assertIn("仍有 `<...>` 占位符时", text)
|
||
self.assertIn("不能作为执行依据", text)
|
||
self.assertIn("不提供真实可执行命令", text)
|
||
self.assertNotIn("真实 CLI 示例", text)
|
||
|
||
def test_tsl_execution_routes_do_not_treat_toolchain_template_as_facts(self):
|
||
tsl_index = (ROOT / "docs" / "tsl" / "index.md").read_text(encoding="utf-8")
|
||
code_style = (ROOT / "docs" / "tsl" / "code_style.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
readme = README.read_text(encoding="utf-8")
|
||
|
||
self.assertIn(
|
||
"目标项目已真实填写且没有 `<...>` 占位符的 [toolchain.md](toolchain.md)",
|
||
tsl_index,
|
||
)
|
||
self.assertIn("验证命令或自动化入口", code_style)
|
||
self.assertIn("目标项目已真实填写且没有 `<...>` 占位符的 [toolchain.md](toolchain.md)", code_style)
|
||
self.assertNotIn(
|
||
"遇到不确定时:[naming.md](naming.md)、[syntax/index.md](syntax/index.md)、[toolchain.md](toolchain.md)",
|
||
code_style,
|
||
)
|
||
# README 中的 Layer 3 表格描述是合理的,不需要额外断言检查
|
||
|
||
def test_tsl_naming_and_code_style_delegate_file_model_facts_to_core_model(self):
|
||
naming = (ROOT / "docs" / "tsl" / "naming.md").read_text(encoding="utf-8")
|
||
code_style = (ROOT / "docs" / "tsl" / "code_style.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
|
||
self.assertIn("本页只规定命名后的实体如何取名", naming)
|
||
self.assertIn("syntax/02_core_model.md", naming)
|
||
self.assertNotIn("文件只能有一个顶层声明", naming)
|
||
self.assertNotIn("文件基名必须与该顶层声明名字一致", naming)
|
||
self.assertNotIn("部署到解释器 `funcext`", naming)
|
||
self.assertNotIn("语句区按顺序执行", naming)
|
||
self.assertNotIn("语句区在前,声明区在后", naming)
|
||
self.assertNotIn("语句区在前,并按顺序执行", naming)
|
||
|
||
self.assertIn("文件模型和顶层声明规则以", code_style)
|
||
self.assertIn("syntax/02_core_model.md", code_style)
|
||
self.assertNotIn("语句区在前并按顺序执行", code_style)
|
||
self.assertNotIn("部署到解释器 `funcext`", code_style)
|
||
self.assertNotIn("每个 `.tsf` 文件只能有一个顶层声明", code_style)
|
||
self.assertNotIn("文件基名必须与顶层声明同名", code_style)
|
||
|
||
def test_tsl_syntax_docs_do_not_keep_stale_manual_sequence_metadata(self):
|
||
self.assertFalse((ROOT / "docs/tsl/syntax/01_introduction.md").exists())
|
||
|
||
forbidden_phrases = [
|
||
"01_introduction.md",
|
||
"手册位置:",
|
||
"共 32 篇",
|
||
"上一篇:",
|
||
"下一篇:",
|
||
"完整吸收原 8 个",
|
||
"原 8 章",
|
||
"编号说明",
|
||
]
|
||
|
||
syntax_paths = sorted((ROOT / "docs" / "tsl" / "syntax").glob("*.md"))
|
||
numbered_paths = [path for path in syntax_paths if path.name != "index.md"]
|
||
numbers = []
|
||
for path in numbered_paths:
|
||
match = re.match(r"^(\d{2})_", path.name)
|
||
self.assertIsNotNone(match, msg=f"missing two-digit prefix: {path.name}")
|
||
numbers.append(int(match.group(1)))
|
||
|
||
self.assertEqual(list(range(1, len(numbered_paths) + 1)), numbers)
|
||
self.assertEqual("01_quickstart.md", numbered_paths[0].name)
|
||
|
||
for path in syntax_paths:
|
||
text = path.read_text(encoding="utf-8")
|
||
for phrase in forbidden_phrases:
|
||
self.assertNotIn(phrase, text, msg=f"{phrase!r} found in {path}")
|
||
|
||
def test_tsl_quickstart_is_agent_coding_gate(self):
|
||
text = TSL_QUICKSTART.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("智能体快速落代码流程", text)
|
||
self.assertIn("先看用户有没有指定 `.tsl` / `.tsf` 后缀", text)
|
||
self.assertIn("再根据交付目标判断 `.tsl` 或 `.tsf`", text)
|
||
self.assertIn("入口流程、脚本任务或一次性执行逻辑用 `.tsl`", text)
|
||
self.assertIn("可复用交付物(函数、过程、类、模块或扩展文件)用 `.tsf`", text)
|
||
self.assertIn("如果只是脚本内部封装函数或类,仍按 `.tsl` 处理", text)
|
||
self.assertIn("只从 `代码块身份:可直接照写示例` 的骨架起手", text)
|
||
self.assertIn("不要发明语法", text)
|
||
self.assertIn("`procedure` 头后不允许写返回类型", text)
|
||
self.assertIn("不要在声明区后面继续追加脚本语句", text)
|
||
self.assertIn("普通对象创建默认优先 `new ClassName()`", text)
|
||
self.assertIn("`createObject(...)` 也是对象创建方式", text)
|
||
self.assertIn("obj := new MyClass();", text)
|
||
self.assertNotIn('obj := createObject("MyClass");', text)
|
||
|
||
def test_tsl_quickstart_labels_observable_outputs(self):
|
||
text = TSL_QUICKSTART.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("代码块身份:输出片段", text)
|
||
self.assertIn("```text\ntest\n```", text)
|
||
self.assertIn("```text\n5\n```", text)
|
||
self.assertIn("```text\ntest1\n```", text)
|
||
self.assertIn("```text\nhello\n```", text)
|
||
|
||
def test_tsl_quickstart_omits_environment_verification_details(self):
|
||
text = TSL_QUICKSTART.read_text(encoding="utf-8")
|
||
|
||
for phrase in [
|
||
"Docker",
|
||
"docker exec",
|
||
"LD_LIBRARY_PATH",
|
||
"/data/workspace",
|
||
"U22Cli",
|
||
"gitea-runner",
|
||
]:
|
||
self.assertNotIn(phrase, text)
|
||
|
||
def test_tsl_core_model_is_agent_file_model_decision_page(self):
|
||
text = TSL_CORE_MODEL.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("智能体文件模型判断流程", text)
|
||
self.assertIn("后缀是第一证据", text)
|
||
self.assertIn("入口流程、脚本任务或一次性执行逻辑", text)
|
||
self.assertIn("可复用交付物(函数、过程、类、模块或扩展文件)", text)
|
||
self.assertIn("只是脚本内部封装函数或类时,仍按 `.tsl` 处理", text)
|
||
self.assertIn("`.tsf` 顶层类声明", text)
|
||
self.assertIn("因为 `.tsl` 脚本内部需要函数或类,就自动改成 `.tsf`", text)
|
||
self.assertIn("类声明必须使用 `type Name = class ... end;`", text)
|
||
self.assertIn("普通对象创建默认优先 `new ClassName()`", text)
|
||
self.assertIn("`createObject(...)` 也是对象创建方式", text)
|
||
self.assertIn("obj := new MyClass();", text)
|
||
self.assertNotIn('obj := createObject("MyClass");', text)
|
||
self.assertIn("不要把 `.tsl` 写成只有顶层函数的模块", text)
|
||
self.assertIn("不要把 `.tsf` 写成会直接执行脚本语句的入口", text)
|
||
self.assertIn("没有文档证据时不要发明文件模型", text)
|
||
self.assertNotIn("误以为扩展名本身就完全决定能否写函数、类或 `unit`", text)
|
||
|
||
def test_tsl_core_model_examples_have_verified_outputs(self):
|
||
text = TSL_CORE_MODEL.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("代码块身份:输出片段", text)
|
||
self.assertIn("```text\ntest\n```", text)
|
||
self.assertIn("```text\n5\n```", text)
|
||
self.assertIn("```text\n1\n```", text)
|
||
self.assertIn("```text\ninvalid statement\n```", text)
|
||
|
||
def test_tsl_core_model_omits_environment_verification_details(self):
|
||
text = TSL_CORE_MODEL.read_text(encoding="utf-8")
|
||
|
||
for phrase in [
|
||
"Docker",
|
||
"docker exec",
|
||
"LD_LIBRARY_PATH",
|
||
"/data/workspace",
|
||
"U22Cli",
|
||
"gitea-runner",
|
||
]:
|
||
self.assertNotIn(phrase, text)
|
||
|
||
def test_tsl_object_docs_prefer_new_for_plain_construction(self):
|
||
text = (ROOT / "docs/tsl/syntax/08_objects_and_classes.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
|
||
self.assertIn("创建对象有两种方式:`new ClassName()` 最常用", text)
|
||
self.assertIn("[02_core_model.md](02_core_model.md)(优先)", text)
|
||
self.assertNotIn("[09_units_and_scope.md](09_units_and_scope.md)(优先)", text)
|
||
self.assertIn("命中对象反射 / 运行时状态、内置运行时对象、对象重载 / 迭代时", text)
|
||
self.assertIn("21_builtin_runtime_objects.md", text)
|
||
self.assertIn("24_object_overloads_and_iteration.md", text)
|
||
self.assertIn("`createObject(...)` 作为次选", text)
|
||
self.assertIn("普通本地类创建默认用 `new ClassName()`", text)
|
||
self.assertIn("普通本地类实例化默认生成 `new ClassName()`", text)
|
||
self.assertIn("普通类成员默认显式写 `public` 段", text)
|
||
self.assertIn("生成代码默认显式写 `public`", text)
|
||
self.assertIn("跨 `unit` 类路径创建和继承属于多文件边界", text)
|
||
self.assertIn("跨 `unit` 类路径骨架", text)
|
||
self.assertIn("跨 `unit` 父类路径骨架", text)
|
||
self.assertIn("type Class1 = class\npublic\n type Class2 = class", text)
|
||
self.assertNotIn("多文件文档事实", text)
|
||
self.assertNotIn("已在多文件环境下验证", text)
|
||
self.assertNotIn("`createObject(\"Unit1.Class1.Class2\")` 可以创建对象", text)
|
||
self.assertNotIn("`type MyNewClass = class(Unit1.Class1.Class2)` 可以通过", text)
|
||
self.assertIn("字段可以写类型注解", text)
|
||
self.assertIn("类方法参数和返回值可以写类型注解", text)
|
||
self.assertIn("类的声明和实现可以分离", text)
|
||
self.assertIn("类内可以只声明方法签名", text)
|
||
self.assertIn("类外实现属于声明区", text)
|
||
self.assertIn("字段和类方法类型注解", text)
|
||
self.assertIn("name_: string;", text)
|
||
self.assertIn("value_: any;", text)
|
||
self.assertIn("function create(_name: string; _value: any);", text)
|
||
self.assertIn("function ReadName(): string;", text)
|
||
self.assertIn("property Value: any read value_ write value_;", text)
|
||
self.assertIn("```text\nabc\n7\nabc\n```", text)
|
||
self.assertIn("类内声明、类外实现的带类型重载方法", text)
|
||
self.assertIn("function PairBox.create(_left: string); overload;", text)
|
||
self.assertIn(
|
||
"function PairBox.create(_left: string; _right: any); overload;", text
|
||
)
|
||
self.assertIn("function PairBox.ReadLeft(): string;", text)
|
||
self.assertIn("create(_left, nil);", text)
|
||
self.assertIn("```text\nleft\n<NIL>\nleft\n2\nleft\n```", text)
|
||
self.assertIn("成员读写为了性能不加 `self` 前缀", text)
|
||
self.assertIn("普通成员访问仍直接写成员名,不加 `self` 前缀", text)
|
||
self.assertIn("字段、方法和 `new`", text)
|
||
self.assertIn("return new Person();", text)
|
||
self.assertIn("obj := new MyClass();", text)
|
||
self.assertIn("static mCount;", text)
|
||
self.assertIn("单个静态字段默认写成 `static mCount;`", text)
|
||
self.assertIn("上述声明顺序可以编译通过", text)
|
||
self.assertLess(
|
||
text.index("a := new Person(22, c:99);"),
|
||
text.index('b := createObject("Person", 11);'),
|
||
)
|
||
self.assertNotIn("score_value", text)
|
||
self.assertNotIn("slot_type", text)
|
||
self.assertNotIn("裸 `class Person`", text)
|
||
self.assertNotIn("没有任何类声明证据", text)
|
||
self.assertNotIn("把普通当前实例成员访问写成带 `self` 前缀", text)
|
||
self.assertIn("不把裸类名成员访问当成文档事实", text)
|
||
self.assertIn("MathBox.Add(1, 2);\nTHuman.mCount := 7;", text)
|
||
self.assertIn("上面这种裸类名成员访问不作为可写事实", text)
|
||
self.assertNotIn("上面这种裸类名静态字段访问不作为可写事实", text)
|
||
self.assertNotIn("字段、方法和 `createObject`", text)
|
||
self.assertNotIn('obj := createObject("MyClass");', text)
|
||
self.assertNotIn("static\n mCount;", text)
|
||
self.assertNotIn("self.", text)
|
||
self.assertNotIn("上述 `.tsf` 例子", text)
|
||
self.assertNotIn("当前解释器", text)
|
||
self.assertNotIn("文档明确运行结果", text)
|
||
self.assertNotIn("文档结果", text)
|
||
self.assertNotIn("当前已记录验证", text)
|
||
self.assertNotIn("已做双文件运行验证", text)
|
||
|
||
def test_tsl_syntax_docs_do_not_use_self_member_access(self):
|
||
for path in (ROOT / "docs" / "tsl" / "syntax").glob("*.md"):
|
||
text = path.read_text(encoding="utf-8")
|
||
self.assertNotIn("self.", text, msg=f"self. found in {path}")
|
||
|
||
def test_tsl_values_page_is_agent_value_decision_page(self):
|
||
text = TSL_VALUES.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("智能体值写法判断流程", text)
|
||
self.assertIn("普通值优先从整数、实数、普通字符串、布尔和 `array(...)` 起手", text)
|
||
self.assertIn("字符串默认用普通字符串", text)
|
||
self.assertIn("只有编码、宽串、UTF8、原始字符串、字符码或 ASCII `0` 需求明确时", text)
|
||
self.assertIn("先判断要顺序数组还是字符串键表", text)
|
||
self.assertIn("顺序数组和二进制缓冲区下标从 `0` 开始", text)
|
||
self.assertIn("字符串下标从 `1` 开始", text)
|
||
self.assertIn("矩阵、集合扩展", text)
|
||
self.assertIn("12_matrix_and_collections.md", text)
|
||
self.assertIn("普通示例默认按 `.tsl` 脚本语句区书写", text)
|
||
self.assertIn("没有对应代码块时不要发明值写法", text)
|
||
self.assertNotIn("20_strings_and_text.md", text)
|
||
|
||
def test_tsl_values_key_examples_have_verified_output_snippets(self):
|
||
text = TSL_VALUES.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("代码块身份:输出片段", text)
|
||
self.assertIn("```text\n1\n12.5\nABC\n1\n0\n2\n```", text)
|
||
self.assertIn("```text\n10\n20\n0001\nA\nB\nC\n```", text)
|
||
self.assertIn("```text\nBCD\n```", text)
|
||
self.assertIn("```text\nABC\n```", text)
|
||
self.assertIn("```text\nString index out of bounds\n```", text)
|
||
|
||
def test_tsl_values_page_owns_string_boundaries(self):
|
||
text = TSL_VALUES.read_text(encoding="utf-8")
|
||
|
||
self.assertFalse((ROOT / "docs/tsl/syntax/20_strings_and_text.md").exists())
|
||
self.assertIn("字符串边界规则", text)
|
||
self.assertIn("宽串 / UTF8 前缀", text)
|
||
self.assertIn("原始字符串", text)
|
||
self.assertIn("字符码拼接", text)
|
||
self.assertIn("ASCII `0` 字符不会截断字符串", text)
|
||
self.assertIn("普通字符串里的 `\\uXXXX` 不要直接按“单字符宽串”理解", text)
|
||
self.assertIn("`U\"\"` 不是宽串", text)
|
||
self.assertIn("`#number` 可以直接把字符码拼进字符串", text)
|
||
self.assertIn("`\\0` 和 `#0` 都能把 ASCII `0` 放进字符串", text)
|
||
|
||
def test_tsl_values_string_boundary_examples_have_verified_output_snippets(self):
|
||
text = TSL_VALUES.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("代码块身份:输出片段", text)
|
||
self.assertIn("```text\nABC\nA\"B'C\nA\nB\n```", text)
|
||
self.assertIn("writeLn(length(\"\\u0041\"));", text)
|
||
self.assertIn("writeLn(length(L\"\\u0041\"));", text)
|
||
self.assertIn("writeLn(length(U\"\\u0041\"));", text)
|
||
self.assertIn("依次输出 `2`、`1`、`1`", text)
|
||
self.assertIn("writeLn(ifWString(utf8_s));", text)
|
||
self.assertIn("依次输出 `0`、`1`、`1`、`1`", text)
|
||
self.assertIn('s2 := "A"#0"B";', text)
|
||
self.assertIn("依次输出 `3`、`3`、`1`、`1`", text)
|
||
|
||
def test_tsl_values_omits_environment_verification_details(self):
|
||
text = TSL_VALUES.read_text(encoding="utf-8")
|
||
|
||
for phrase in [
|
||
"Docker",
|
||
"docker exec",
|
||
"LD_LIBRARY_PATH",
|
||
"/data/workspace",
|
||
"U22Cli",
|
||
"gitea-runner",
|
||
]:
|
||
self.assertNotIn(phrase, text)
|
||
|
||
def test_tsl_variables_page_is_agent_binding_decision_page(self):
|
||
text = TSL_VARIABLES.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("智能体变量/常量判断流程", text)
|
||
self.assertIn("普通变量默认直接用 `:=` 首次赋值", text)
|
||
self.assertIn("只有用户要求显式声明或遇到 `{$explicit+}` 时才优先写 `var`", text)
|
||
self.assertIn("常量声明必须同时初始化", text)
|
||
self.assertIn("默认只生成 `const name = value;`", text)
|
||
self.assertIn("单变量拆包必须写成 `[name, ] := array(...)`", text)
|
||
self.assertIn("06_expressions_and_operators.md", text)
|
||
self.assertIn("16_lexical_structure_and_compile_options.md", text)
|
||
self.assertIn("普通示例默认按 `.tsl` 脚本语句区书写", text)
|
||
self.assertIn("复制常量示例时只使用 `const name = value;`", text)
|
||
self.assertIn("没有对应代码块时不要发明变量/常量写法", text)
|
||
self.assertIn("运行时类型 / 转换", text)
|
||
self.assertIn("17_types_and_conversions.md", text)
|
||
self.assertNotIn("const name :=", text)
|
||
self.assertNotIn("语法设计兼容边界", text)
|
||
self.assertNotIn("兼容通过", text)
|
||
self.assertNotRegex(text, r"const\s+[A-Za-z_][A-Za-z0-9_]*\s*:=")
|
||
|
||
def test_tsl_types_page_does_not_own_declaration_rules(self):
|
||
text = TSL_TYPES.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("智能体类型/转换判断流程", text)
|
||
self.assertIn("运行时值会变成什么类型", text)
|
||
self.assertIn("默认值是什么", text)
|
||
self.assertIn("怎样显式转换", text)
|
||
self.assertIn("`var` / `const` 声明选择回看", text)
|
||
self.assertIn("16_lexical_structure_and_compile_options.md", text)
|
||
self.assertNotIn("显式声明边界", text)
|
||
self.assertNotIn("怎样受编译选项影响", text)
|
||
|
||
def test_tsl_variables_key_examples_have_verified_output_snippets(self):
|
||
text = TSL_VARIABLES.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("代码块身份:输出片段", text)
|
||
self.assertIn("```text\n1\n2\n```", text)
|
||
self.assertIn("```text\n7\n```", text)
|
||
self.assertIn("```text\n1\n3\n```", text)
|
||
self.assertIn("```text\n1\n1\n```", text)
|
||
self.assertIn("```text\n1\n2\n3\n4\n```", text)
|
||
self.assertIn("```text\n0\n7\n```", text)
|
||
self.assertIn("```text\n6\n```", text)
|
||
self.assertIn("```text\nvariable not defined\n```", text)
|
||
|
||
def test_tsl_variables_omits_environment_verification_details(self):
|
||
text = TSL_VARIABLES.read_text(encoding="utf-8")
|
||
|
||
for phrase in [
|
||
"Docker",
|
||
"docker exec",
|
||
"LD_LIBRARY_PATH",
|
||
"/data/workspace",
|
||
"U22Cli",
|
||
"gitea-runner",
|
||
]:
|
||
self.assertNotIn(phrase, text)
|
||
|
||
def test_tsl_functions_page_is_agent_function_decision_page(self):
|
||
text = TSL_FUNCTIONS.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("智能体函数/调用判断流程", text)
|
||
self.assertIn("先判断目标文件是 `.tsl` 还是 `.tsf`", text)
|
||
self.assertIn("`.tsl` 中先写语句区,再把 `function` / `procedure` 声明放在后面", text)
|
||
self.assertIn("用户只说“写一个函数”且没有指定 `procedure` 时,默认用 `function`", text)
|
||
self.assertIn("即使任务没有返回值,也默认用 `function`", text)
|
||
self.assertIn("不要因为没有返回值就自动改用 `procedure`", text)
|
||
self.assertIn("用户提示词里的“函数”默认对应 `function`", text)
|
||
self.assertIn("命名参数只写 `name: value`", text)
|
||
self.assertIn("不要把 `name = value` 当成命名参数", text)
|
||
self.assertIn("函数头后默认保留分号", text)
|
||
self.assertIn("16_lexical_structure_and_compile_options.md", text)
|
||
self.assertIn("普通运行示例默认按 `.tsl` 脚本语句区书写", text)
|
||
self.assertIn("`.tsf` 函数 / 过程示例只按可复用顶层声明理解", text)
|
||
self.assertIn("`procedure` 示例只在用户明确要求 `procedure` / 过程时复制", text)
|
||
self.assertIn("默认从 `function` 骨架起步", text)
|
||
self.assertIn("跨 `unit` 声明边界", text)
|
||
self.assertIn("默认参数先按普通函数规则处理", text)
|
||
self.assertIn("匿名函数和 TSL 函数值", text)
|
||
self.assertIn("原生函数指针包装", text)
|
||
self.assertIn("C 回调", text)
|
||
self.assertIn("只按本页明确的 `call(f, ...)` / `##f(...)` 生成", text)
|
||
self.assertIn("没有对应代码块时不要发明函数/调用写法", text)
|
||
self.assertNotIn("在我于", text)
|
||
self.assertNotIn("LIBPATH", text)
|
||
self.assertNotIn("tsl .\\main.tsl", text)
|
||
self.assertNotIn("command", text)
|
||
self.assertNotIn("function MissingSemi()", text)
|
||
self.assertNotIn("input_value", text)
|
||
self.assertNotIn("handler): result_type", text)
|
||
self.assertNotIn("命名参数可以调换顺序", text)
|
||
self.assertNotIn("命名参数也可以和位置参数混用", text)
|
||
|
||
def test_tsl_functions_key_examples_have_verified_output_snippets(self):
|
||
text = TSL_FUNCTIONS.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("代码块身份:输出片段", text)
|
||
self.assertIn("```text\ntest\n```", text)
|
||
self.assertIn("```text\n2\n```", text)
|
||
self.assertIn("```text\n9\n1\n7\n```", text)
|
||
self.assertIn("```text\n12\n```", text)
|
||
self.assertIn("```text\n888\n123\n```", text)
|
||
self.assertIn("```text\ninvalid statement\n```", text)
|
||
|
||
def test_tsl_functions_omits_environment_verification_details(self):
|
||
text = TSL_FUNCTIONS.read_text(encoding="utf-8")
|
||
|
||
for phrase in [
|
||
"Docker",
|
||
"docker exec",
|
||
"LD_LIBRARY_PATH",
|
||
"/data/workspace",
|
||
"U22Cli",
|
||
"gitea-runner",
|
||
]:
|
||
self.assertNotIn(phrase, text)
|
||
|
||
def test_tsl_expressions_page_is_agent_expression_decision_page(self):
|
||
text = TSL_EXPRESSIONS.read_text(encoding="utf-8")
|
||
text = text.replace("|", "|")
|
||
|
||
self.assertIn("智能体表达式/运算符判断流程", text)
|
||
self.assertIn("先判断要写基础表达式运算符,还是集合、矩阵、对象重载、运行时调用或 TS-SQL 这类专题运算符", text)
|
||
self.assertIn("本页直接生成的运算符", text)
|
||
self.assertIn("专题运算符入口", text)
|
||
self.assertIn("默认生成规则", text)
|
||
self.assertIn("按需生成规则", text)
|
||
self.assertIn("边界规则", text)
|
||
self.assertIn("赋值只能用 `:=`", text)
|
||
self.assertIn("比较才用 `=`", text)
|
||
self.assertIn("`const name = value;` 是常量初始化规则", text)
|
||
self.assertIn("常量初始化不按普通赋值判断", text)
|
||
self.assertIn("函数签名里的默认参数 `name = value` 不是比较表达式", text)
|
||
self.assertIn("不要把函数签名里的 `name = value` 当成比较表达式", text)
|
||
self.assertIn("04_variables_and_constants.md", text)
|
||
self.assertIn("05_functions_and_calls.md", text)
|
||
self.assertIn("03_values_and_literals.md", text)
|
||
self.assertIn("12_matrix_and_collections.md", text)
|
||
self.assertIn("16_lexical_structure_and_compile_options.md", text)
|
||
self.assertNotIn("20_strings_and_text.md", text)
|
||
self.assertIn("`{$ifdef ...}` 只作为能力探测", text)
|
||
self.assertIn("条件求值优先用 `flag ? true_value : false_value`", text)
|
||
self.assertIn("`if condition then true_value else false_value` 必须带 `else`", text)
|
||
self.assertIn("需要延迟求值或动态表达式对象时,才用 `@expr` 或 `&\"...\"`", text)
|
||
self.assertIn("数组逐元素链式比较才用 `::>`", text)
|
||
self.assertIn("本页是 TSL 表达式与运算符的生成规则页", text)
|
||
self.assertIn("只使用本页或对应专题页明确记录的运算符", text)
|
||
self.assertIn("`div`、`mod`、`^`", text)
|
||
self.assertIn("一元倒数", text)
|
||
self.assertIn("`!x`", text)
|
||
self.assertIn("整型、实型输入返回实型倒数", text)
|
||
self.assertIn("`+`、`$`", text)
|
||
self.assertIn("`and`、`or`、`not`、`&&`、||", text)
|
||
self.assertIn("`.&`、`.|`、`.^`", text)
|
||
self.assertIn("`^=`、`.&=`、`.|=`、`.^=`", text)
|
||
self.assertIn("`a++`、`a--`、`++a`、`--a`", text)
|
||
self.assertIn("`union2`、`intersect`、`minus`、`outersect`", text)
|
||
self.assertIn("`union`、|、`:|`", text)
|
||
self.assertIn("矩阵并右方", text)
|
||
self.assertIn("`->`、`!matrix`", text)
|
||
self.assertIn("矩阵逆/广义逆", text)
|
||
self.assertIn("反引号转置", text)
|
||
self.assertIn("`operator +`", text)
|
||
self.assertIn("`select` / `sselect` / `vselect` / `mselect`", text)
|
||
self.assertIn("没有文档事实的运算符", text)
|
||
self.assertIn("不要生成猜测写法", text)
|
||
self.assertNotIn("`. ^`", text)
|
||
self.assertNotIn("补证", text)
|
||
self.assertNotIn("待确认", text)
|
||
self.assertNotIn("不是完整运算符支持清单", text)
|
||
self.assertNotIn("未列运算符不等于语法不支持", text)
|
||
self.assertNotIn("未列出的运算符不要直接判为不支持", text)
|
||
self.assertNotIn("直接写成该运算符不支持", text)
|
||
self.assertNotIn("当前环境里", text)
|
||
|
||
def test_tsl_expressions_key_examples_have_verified_output_snippets(self):
|
||
text = TSL_EXPRESSIONS.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("代码块身份:输出片段", text)
|
||
self.assertIn("```text\n3\n```", text)
|
||
self.assertIn("```text\n3\n3\n12\n4\n1\n2\n1\n8\n2\n3\n```", text)
|
||
self.assertIn("```text\n1\n1\n1\n1\n1\n1\n```", text)
|
||
self.assertIn("```text\n0.5\n0.25\n1\n1\n```", text)
|
||
self.assertIn("```text\n1\n1\n1\n1\n1\n```", text)
|
||
self.assertIn("```text\n2\n5\n5\n```", text)
|
||
self.assertIn("```text\n8\n2\n5\n5\n```", text)
|
||
self.assertIn("```text\nAB\n```", text)
|
||
self.assertIn("```text\n222888\n1\n1\n1\n1\n```", text)
|
||
self.assertIn("```text\n1\n0\n```", text)
|
||
self.assertIn("```text\n2\n```", text)
|
||
self.assertIn("++a;", text)
|
||
self.assertIn("--a;", text)
|
||
self.assertIn("```text\n6\n```", text)
|
||
self.assertIn("```text\n1\n7\n1\n```", text)
|
||
self.assertIn("```text\ninvalid statement\n```", text)
|
||
|
||
def test_tsl_expressions_omits_environment_verification_details(self):
|
||
text = TSL_EXPRESSIONS.read_text(encoding="utf-8")
|
||
|
||
for phrase in [
|
||
"Docker",
|
||
"docker exec",
|
||
"LD_LIBRARY_PATH",
|
||
"/data/workspace",
|
||
"U22Cli",
|
||
"gitea-runner",
|
||
]:
|
||
self.assertNotIn(phrase, text)
|
||
|
||
def test_tsl_control_flow_page_routes_function_and_debug_boundaries(self):
|
||
text = TSL_CONTROL_FLOW.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("智能体控制流判断流程", text)
|
||
self.assertIn("05_functions_and_calls.md", text)
|
||
self.assertIn("函数里的控制流只按控制流语法处理", text)
|
||
self.assertIn("15_debug_and_profiler.md", text)
|
||
self.assertIn("默认写成块式分支", text)
|
||
self.assertIn("控制流块的 `end` 默认不加分号", text)
|
||
self.assertIn("只按语句形态生成", text)
|
||
self.assertIn("begin\n value := 1;\nend\nelse\nbegin\n value := 0;\nend", text)
|
||
self.assertNotIn("begin\n value := 1;\nend\nelse\nbegin\n value := 0;\nend;", text)
|
||
self.assertIn("不要在 `else` 前提前加分号", text)
|
||
self.assertNotIn("then\n value := 1\nelse", text)
|
||
self.assertNotIn("b := case", text)
|
||
self.assertNotIn("b := @case", text)
|
||
self.assertNotIn("@case", text)
|
||
self.assertNotIn("case 表达式形态", text)
|
||
self.assertNotIn("当前解释器", text)
|
||
self.assertNotIn("文档明确运行结果", text)
|
||
self.assertNotIn("文档明确支持", text)
|
||
|
||
def test_tsl_control_flow_key_examples_have_verified_output_snippets(self):
|
||
text = TSL_CONTROL_FLOW.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("代码块身份:输出片段", text)
|
||
self.assertIn("```text\n1\n0\n```", text)
|
||
self.assertIn("```text\n3\n```", text)
|
||
self.assertIn("```text\n10\n120\n230\n```", text)
|
||
self.assertIn("```text\n6\n4\n```", text)
|
||
self.assertIn("```text\ntwo\n```", text)
|
||
self.assertIn("```text\nbefore\ncaught\nraise: boom\nafter\n```", text)
|
||
self.assertIn("```text\nbefore\nbody\nfinally\n```", text)
|
||
|
||
def test_tsl_units_page_omits_environment_and_orders_uses_before_statements(self):
|
||
text = (ROOT / "docs/tsl/syntax/09_units_and_scope.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
|
||
self.assertIn("多文件结构骨架", text)
|
||
self.assertIn("[02_core_model.md](02_core_model.md)(优先)", text)
|
||
self.assertNotIn("[19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)(优先)", text)
|
||
self.assertIn("命中 `unit` 生命周期、命名空间、查找路径、`tsl.conf` 或命令行查找路径参数", text)
|
||
self.assertIn("没有对应代码块时不要发明 unit/作用域写法", text)
|
||
self.assertIn(
|
||
"interface\n\nfunction Ping();\n\nimplementation\n\nfunction Ping();",
|
||
text,
|
||
)
|
||
self.assertIn("`implementation` 前保留空行", text)
|
||
self.assertIn("`unit` 允许接口声明与实现段分离", text)
|
||
self.assertIn("类方法放在 `implementation` 段实现", text)
|
||
self.assertIn("### 接口声明与实现段分离", text)
|
||
self.assertIn("function UnitBox.create(_value);", text)
|
||
self.assertIn("function UnitBox.ReadValue();", text)
|
||
self.assertIn("只写在 `implementation` 段,对 `unit` 外部不可见", text)
|
||
self.assertIn("uses PrivateDemo;\nwriteLn(PublicFunc());\n```", text)
|
||
self.assertIn("代码块身份:反例 / 不可照写\n\n```text\nuses PrivateDemo;\nwriteLn(PrivateFunc());", text)
|
||
self.assertNotIn("interface\n function Ping();", text)
|
||
self.assertNotIn("implementation\n function Ping();", text)
|
||
self.assertNotIn("## 生成代码排版规则", text)
|
||
self.assertIn("type Worker = class\n uses DemoUnit;\npublic", text)
|
||
self.assertIn("// main.tsl\n\nuses UnitA;", text)
|
||
self.assertIn("// main.tsl\n\nuses PrivateDemo;", text)
|
||
self.assertIn("type TBox = class\npublic", text)
|
||
self.assertIn("type Box = class\npublic", text)
|
||
self.assertIn("function Box.create();", text)
|
||
self.assertIn("合并成单条 `uses UnitA, UnitB;`", text)
|
||
self.assertIn("失败点是“第二条 `uses`”", text)
|
||
self.assertIn("只生成单条逗号合并写法", text)
|
||
self.assertIn("连续多条顶层 `uses` 不再作为文档明确生成形态", text)
|
||
self.assertIn("uses UnitA, UnitB;", text)
|
||
self.assertIn("多个 `unit` 默认写成 `uses UnitA, UnitB;`", text)
|
||
self.assertNotIn("当前解释器", text)
|
||
self.assertNotIn("文档结果", text)
|
||
self.assertNotIn("已在多文件环境下验证", text)
|
||
self.assertNotIn("// command", text)
|
||
self.assertNotIn("LIBPATH", text)
|
||
self.assertNotIn("tsl .", text)
|
||
|
||
def test_tsl_runtime_context_page_omits_environment_process_details(self):
|
||
text = (ROOT / "docs/tsl/syntax/10_runtime_context_and_with.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
|
||
self.assertIn("系统参数优先用本页明确的", text)
|
||
self.assertIn("运行时上下文、服务与全局缓存", text)
|
||
self.assertIn("本地函数后缀 `with` 属于反例", text)
|
||
self.assertIn("网格调用返回的不是最终值;需要最终结果时继续写 `dupvalue(...)`", text)
|
||
self.assertIn("全局缓存读写要成对出现", text)
|
||
self.assertIn("多文件结构骨架", text)
|
||
self.assertIn("依赖函数文件查找路径", text)
|
||
self.assertIn("结果说明:", text)
|
||
self.assertIn('r := #TestDo() with array("a": 101, "b": 202);', text)
|
||
self.assertIn("writeLn(dupvalue(#AddOne(5)));", text)
|
||
self.assertIn("writeLn(dupvalue(#AddOne(5) timeout 3000));", text)
|
||
self.assertIn('writeLn(setGlobalCache("PB_TEST_GC_BASIC", v1));', text)
|
||
self.assertIn("writeLn(checkGlobalCacheExpired(v));", text)
|
||
self.assertIn("对缓存值做 `select` 可以正常执行", text)
|
||
self.assertIn("这种“本地函数后缀 `with`”写法不作为可写事实", text)
|
||
self.assertFalse((ROOT / "docs/tsl/syntax/30_runtime_services_and_global_cache.md").exists())
|
||
self.assertNotIn("30_runtime_services_and_global_cache.md", text)
|
||
self.assertNotIn("当前解释器", text)
|
||
self.assertNotIn("文档明确运行结果", text)
|
||
self.assertNotIn("当前已做双文件运行验证", text)
|
||
|
||
def test_tsl_external_page_owns_native_pointer_and_callback_boundaries(self):
|
||
text = TSL_EXTERNAL.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("智能体外部调用/线程判断流程", text)
|
||
self.assertIn("原生函数指针包装", text)
|
||
self.assertIn("C 回调", text)
|
||
self.assertIn("包装原生函数指针", text)
|
||
self.assertIn("生成 C 回调", text)
|
||
self.assertIn("makeInstance(thisFunction(Func), \"cdecl\", 0)", text)
|
||
self.assertIn("makeInstance(..., \"cdecl\", 1)", text)
|
||
self.assertIn("函数指针包装", text)
|
||
self.assertNotIn("普通函数怎么写已经清楚后,外部 DLL、函数指针、多线程", text)
|
||
self.assertNotIn("// command", text)
|
||
self.assertNotIn("LIBPATH", text)
|
||
self.assertNotIn("tsl .", text)
|
||
|
||
def test_tsl_pitfalls_page_uses_stable_boundary_language(self):
|
||
text = (ROOT / "docs/tsl/syntax/11_pitfalls.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
|
||
self.assertIn("## 反例索引", text)
|
||
self.assertIn("必须跳回对应语法页找可直接照写示例", text)
|
||
self.assertIn("只有已经有明确反例边界的误写", text)
|
||
self.assertIn("细节型边界放回对应专题页", text)
|
||
self.assertIn("最容易诱导智能体写错的高频误写", text)
|
||
self.assertIn("只针对函数体和类定义体里的 `uses`", text)
|
||
self.assertIn("默认回到顶层写成单条 `uses UnitA, UnitB;`", text)
|
||
self.assertIn("类定义体里出现了第二条 `uses`", text)
|
||
self.assertIn("可用写法是先拿到类类型", text)
|
||
self.assertIn("不要把反例边界写成可用语法", text)
|
||
self.assertNotIn("当前解释器", text)
|
||
self.assertNotIn("当前命令行解释器", text)
|
||
self.assertNotIn("实测", text)
|
||
self.assertNotIn("当前文档明确", text)
|
||
self.assertNotIn("对应代码块", text)
|
||
self.assertNotIn("## 不可照写反例索引", text)
|
||
self.assertNotIn("LIBPATH", text)
|
||
self.assertNotIn("在同一作用域里重复写第二个 `uses`", text)
|
||
self.assertNotIn('writeLn(length("\\u0041"));', text)
|
||
self.assertNotIn('writeLn(ifWString(U"\\u5929\\u8F6F"));', text)
|
||
self.assertNotIn('s := "A"#0"B";', text)
|
||
self.assertNotIn("const value = 1;", text)
|
||
self.assertNotIn("unit UnitConst;", text)
|
||
self.assertNotIn("TickFromExpr", text)
|
||
self.assertNotIn("文件就在当前目录", text)
|
||
self.assertNotIn("DemoUnit.UnitCounter", text)
|
||
|
||
def test_tsl_matrix_collections_page_uses_entry_language(self):
|
||
text = (
|
||
ROOT / "docs/tsl/syntax/12_matrix_and_collections.md"
|
||
).read_text(encoding="utf-8")
|
||
|
||
self.assertIn("本页明确的矩阵样比较", text)
|
||
self.assertIn("本页文档明确形态", text)
|
||
self.assertIn("14_ts_sql.md", text)
|
||
self.assertIn("[14_ts_sql.md](14_ts_sql.md)", text)
|
||
self.assertIn("没有对应代码块时不要发明数组/矩阵样数据/集合关系写法", text)
|
||
self.assertIn("结果说明:", text)
|
||
self.assertIn("矩阵链式比较 `::>`、`::<`、`::<>`、`::==`、`::>=`、`::<=`", text)
|
||
self.assertIn("`in` / `not in` 处理的是元素存在关系", text)
|
||
self.assertIn("`sqlin` / `not sqlin` 处理的是行存在关系", text)
|
||
self.assertIn("`union2`、`intersect`、`minus`、`outersect` 都按“行”运算", text)
|
||
self.assertIn("集合运算结果会折叠重复行", text)
|
||
self.assertIn("writeLn(1 not in array(0, 2));", text)
|
||
self.assertIn("writeLn(array(5, 6) not sqlin array((1, 2), (3, 4)));", text)
|
||
self.assertIn("union_rows := left_rows union2 right_rows;", text)
|
||
self.assertIn("outersect_rows := left_rows outersect right_rows;", text)
|
||
self.assertNotIn("25_set_operations.md", text)
|
||
self.assertNotIn("当前解释器", text)
|
||
self.assertNotIn("文档明确运行结果", text)
|
||
self.assertNotIn("文档明确运行结果对应关系", text)
|
||
self.assertNotIn("稳定验证", text)
|
||
self.assertNotIn("稳定通过", text)
|
||
|
||
def test_tsl_resultset_filters_page_uses_complete_examples(self):
|
||
text = (
|
||
ROOT / "docs/tsl/syntax/13_resultset_and_filters.md"
|
||
).read_text(encoding="utf-8")
|
||
|
||
self.assertIn("字段访问优先照本页明确的", text)
|
||
self.assertIn("复杂查询需求优先跳转到 [14_ts_sql.md](14_ts_sql.md)", text)
|
||
self.assertIn("金融数据筛选要先确认数据来源", text)
|
||
self.assertIn("[../reference/catalog/datawarehouse.md](../reference/catalog/datawarehouse.md)", text)
|
||
self.assertIn("没有对应代码块时不要发明结果集/过滤写法", text)
|
||
self.assertIn("结果说明:", text)
|
||
self.assertIn('writeLn(keep_rows[0]["Code"]);', text)
|
||
self.assertIn('writeLn(drop_rows[1]["Code"]);', text)
|
||
self.assertIn('rows := array(\n ("Code": "0001"),\n ("Code": "0002")\n);', text)
|
||
self.assertIn('code_arr := array("0001");\nkeep_rows := filterIn(rows, code_arr, "Code");', text)
|
||
self.assertIn("本页只把显式写出 `nil` 作为可靠规则", text)
|
||
self.assertNotIn("文档明确运行结果", text)
|
||
self.assertNotIn("当前手册", text)
|
||
self.assertNotIn("当前解释器", text)
|
||
|
||
def test_tsl_ts_sql_page_uses_entry_and_route_language(self):
|
||
text = (ROOT / "docs/tsl/syntax/14_ts_sql.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
|
||
self.assertIn("没有对应代码块时不要发明 TS-SQL 写法", text)
|
||
self.assertIn("基础查询文档骨架", text)
|
||
self.assertIn("文档字段访问写法", text)
|
||
self.assertIn("在一维数组上做 TS-SQL 时,优先使用", text)
|
||
self.assertIn("只想按已有结果集保留/排除行时跳到", text)
|
||
self.assertIn("13_resultset_and_filters.md", text)
|
||
self.assertIn("12_matrix_and_collections.md", text)
|
||
self.assertIn("23_fmarray.md", text)
|
||
self.assertIn("这一篇是 TS-SQL 的唯一语法入口", text)
|
||
self.assertIn("多表 `join` 时,字段访问应写成", text)
|
||
self.assertIn("结果说明:", text)
|
||
self.assertNotIn("当前的稳定骨架", text)
|
||
self.assertNotIn("当前稳定字段访问", text)
|
||
self.assertNotIn("当前应优先", text)
|
||
self.assertNotIn("文档明确运行结果", text)
|
||
|
||
def test_tsl_debug_profiler_page_uses_entry_language(self):
|
||
text = (ROOT / "docs/tsl/syntax/15_debug_and_profiler.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
|
||
self.assertIn("本页明确的调试、计时、性能分析器", text)
|
||
self.assertIn("没有对应代码块时不要发明调试/性能分析器写法", text)
|
||
self.assertIn("只照文档最小调用写", text)
|
||
self.assertIn("普通控制流优先回到 [07_control_flow.md](07_control_flow.md)", text)
|
||
self.assertIn("属于文档明确写法", text)
|
||
self.assertIn("本页正向边界只覆盖", text)
|
||
self.assertIn("跨函数跳转不作为可写事实", text)
|
||
self.assertIn("目标 `label` 单独成行不作为可写事实", text)
|
||
self.assertIn("不作为本页输出事实", text)
|
||
self.assertIn("结果说明:", text)
|
||
self.assertIn("不要把调试客户端副作用写成普通输出事实", text)
|
||
self.assertNotIn("当前", text)
|
||
self.assertNotIn("当前解释器", text)
|
||
self.assertNotIn("文档明确运行结果", text)
|
||
self.assertNotIn("文档结果", text)
|
||
self.assertNotIn("未验证参数", text)
|
||
self.assertNotIn("当前命令行环境", text)
|
||
self.assertNotIn("命令行环境不可直接观察", text)
|
||
|
||
def test_tsl_runtime_context_routes_function_query_and_runtime_boundaries(self):
|
||
text = (ROOT / "docs/tsl/syntax/10_runtime_context_and_with.md").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
|
||
self.assertIn("智能体运行时上下文判断流程", text)
|
||
self.assertIn("05_functions_and_calls.md", text)
|
||
self.assertIn("14_ts_sql.md", text)
|
||
self.assertIn("普通函数调用回到 [05_functions_and_calls.md](05_functions_and_calls.md)", text)
|
||
self.assertIn("缓存值参与 `select` 时,查询语法仍回到 [14_ts_sql.md](14_ts_sql.md)", text)
|
||
self.assertIn("内置运行时对象", text)
|
||
|
||
def test_tsl_complex_and_weakref_content_is_split_by_owner_page(self):
|
||
self.assertFalse((ROOT / "docs/tsl/syntax/31_complex_and_weakref.md").exists())
|
||
|
||
types_text = TSL_TYPES.read_text(encoding="utf-8")
|
||
object_text = TSL_OBJECT_RUNTIME.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("复数类型", types_text)
|
||
self.assertIn("复数字面量可以直接写成 `a + bj`", types_text)
|
||
self.assertIn("z1 := 4 + 3j;", types_text)
|
||
self.assertIn("z2 := complex(5, -2);", types_text)
|
||
self.assertIn("`dataType(z)` 对复数返回 `41`", types_text)
|
||
self.assertIn("complex(array(1, 2, 3), 5.5)", types_text)
|
||
self.assertIn("complex(fmarray[1, 2, 3], 5.5)", types_text)
|
||
|
||
self.assertIn("弱引用与自动弱引用", object_text)
|
||
self.assertIn("访问弱引用前先做 `checkWeakRef(...)` 判定", object_text)
|
||
self.assertIn("弱引用能力的条件编译宏是 `weakptr`", object_text)
|
||
self.assertIn("自动弱引用相关宏是 `autoWeak`", object_text)
|
||
self.assertIn("w := weakRef(a);", object_text)
|
||
self.assertIn("s := weakref_get(w);", object_text)
|
||
self.assertIn("[weakRef] owner1;", object_text)
|
||
self.assertIn("[autoRef] owner2;", object_text)
|
||
self.assertIn("invalid class definition", object_text)
|
||
|
||
self.assertNotIn("31_complex_and_weakref.md", types_text)
|
||
self.assertNotIn("31_complex_and_weakref.md", object_text)
|
||
|
||
def test_tsl_syntax_examples_do_not_use_unterminated_if_else_branches(self):
|
||
pattern = re.compile(r"if[^\n]*then\s*\n\s*[^\n;]+\n\s*else")
|
||
|
||
for path in (ROOT / "docs" / "tsl" / "syntax").glob("*.md"):
|
||
text = path.read_text(encoding="utf-8")
|
||
self.assertIsNone(pattern.search(text), msg=f"裸 if/else 分支缺分号: {path}")
|
||
|
||
def test_tsl_matrix_deep_dive_documents_inverse_operator(self):
|
||
text = TSL_MATRIX_DEEP_DIVE.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("矩阵逆/广义逆", text)
|
||
self.assertIn("`!A` 是一元倒数运算符作用于矩阵的形态", text)
|
||
self.assertIn("inverse_value := !matrix_value;", text)
|
||
self.assertIn("不要把它改写成 `1 / matrix_value`", text)
|
||
self.assertIn("`!` 不表示逻辑非", text)
|
||
self.assertIn("```text\n2\n2\n-2\n1\n1.5\n-0.5\n```", text)
|
||
self.assertIn(
|
||
"```text\n"
|
||
"3\n"
|
||
"2\n"
|
||
"-0.944444444444444\n"
|
||
"0.444444444444444\n"
|
||
"-0.111111111111111\n"
|
||
"0.111111111111111\n"
|
||
"0.722222222222222\n"
|
||
"-0.222222222222222\n"
|
||
"```",
|
||
text,
|
||
)
|
||
|
||
def test_tsl_fmarray_documents_right_matrix_merge_operator(self):
|
||
text = TSL_FMARRAY.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("矩阵连接 / 矩阵并右方:`union`、`|`、`:|`", text)
|
||
self.assertIn("`|` 和 `:|` 都可做矩阵并右方(按列连接)", text)
|
||
self.assertIn("`|` 和 `:|` 会执行矩阵并右方,也就是按列拼接", text)
|
||
self.assertIn(
|
||
"```text\n"
|
||
"27\n"
|
||
"3\n"
|
||
"4\n"
|
||
"1,2,3,4\n"
|
||
"3,4,7,8\n"
|
||
"5,5,6,9\n"
|
||
"1,2,3,4\n"
|
||
"3,4,7,8\n"
|
||
"5,5,6,9\n"
|
||
"```",
|
||
text,
|
||
)
|
||
self.assertIn(
|
||
"```text\n"
|
||
"1,2,3,4\n"
|
||
"3,4,0,0\n"
|
||
"5,5,0,0\n"
|
||
"3,4,1,2\n"
|
||
"0,0,3,4\n"
|
||
"0,0,5,5\n"
|
||
"```",
|
||
text,
|
||
)
|
||
|
||
def test_tsl_deep_dive_pages_avoid_process_verification_language(self):
|
||
syntax_root = ROOT / "docs" / "tsl" / "syntax"
|
||
forbidden_phrases = [
|
||
"当前解释器",
|
||
"当前命令行",
|
||
"当前版本",
|
||
"文档明确运行结果",
|
||
"文档结果",
|
||
"文档明确支持",
|
||
"实测",
|
||
"补证",
|
||
"待确认",
|
||
"没有文档明确",
|
||
"未验证",
|
||
"// command",
|
||
]
|
||
|
||
for filename in TSL_SYNTAX_DEEP_DIVE_FILES:
|
||
with self.subTest(filename=filename):
|
||
text = (syntax_root / filename).read_text(encoding="utf-8")
|
||
for phrase in forbidden_phrases:
|
||
self.assertNotIn(phrase, text)
|
||
|
||
def test_tsl_object_runtime_prefers_new_for_normal_object_creation(self):
|
||
text = TSL_OBJECT_RUNTIME.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("obj := new RuntimeBox();", text)
|
||
self.assertIn("type RuntimeBox = class", text)
|
||
self.assertIn('t := findOverLoad(2, "fun", new TestClass());', text)
|
||
self.assertNotIn("new TStringList()", text)
|
||
self.assertNotIn('obj := createObject("TStringList");', text)
|
||
self.assertNotIn('findOverLoad(2, "fun", createObject("TestClass"))', text)
|
||
|
||
def test_tsl_builtin_runtime_objects_default_to_new_syntax(self):
|
||
text = TSL_BUILTIN_RUNTIME_OBJECTS.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("`TStringList` 可以直接用 `new TStringList()` 创建", text)
|
||
self.assertIn("obj := new TStringList();", text)
|
||
self.assertIn("mem := new TMemoryStream();", text)
|
||
self.assertIn("cipher := new TCipher(2);", text)
|
||
self.assertIn("bad := new TCipher();", text)
|
||
self.assertIn("`new TStream()`", text)
|
||
self.assertIn("New Class TCipher Error.", text)
|
||
self.assertNotIn('createObject("TStringList")', text)
|
||
self.assertNotIn('createObject("TMemoryStream")', text)
|
||
self.assertNotIn('createObject("TCipher"', text)
|
||
|
||
def test_tsl_ts_sql_page_documents_thisrow_field_order(self):
|
||
text = TSL_TS_SQL.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("第一行 `Value=20, Idx=1`,第二行 `Value=30, Idx=2`", text)
|
||
self.assertNotIn("`(1,20)`、`(2,30)`", text)
|
||
|
||
def test_tsl_ts_sql_has_single_authoritative_syntax_page(self):
|
||
syntax_root = ROOT / "docs" / "tsl" / "syntax"
|
||
text = TSL_TS_SQL.read_text(encoding="utf-8")
|
||
|
||
self.assertFalse((syntax_root / "28_ts_sql_core.md").exists())
|
||
self.assertFalse((syntax_root / "29_ts_sql_advanced.md").exists())
|
||
self.assertIn("这一篇是 TS-SQL 的唯一语法入口", text)
|
||
self.assertIn("多表 `join` 时,字段访问应写成 `[表序号].[\"字段名\"]`", text)
|
||
self.assertIn("`thisGroup` 不是普通值,而是分组后的子结果集", text)
|
||
self.assertIn("`refMaxOf(...)` 和 `refMinOf(...)` 可与", text)
|
||
|
||
def test_remaining_tsl_syntax_pages_are_agent_decision_pages(self):
|
||
syntax_root = ROOT / "docs" / "tsl" / "syntax"
|
||
|
||
for filename, section in TSL_REMAINING_SYNTAX_AGENT_SECTIONS.items():
|
||
with self.subTest(filename=filename):
|
||
text = (syntax_root / filename).read_text(encoding="utf-8")
|
||
self.assertIn(section, text)
|
||
self.assertIn("智能体", text)
|
||
self.assertIn("不要发明", text)
|
||
|
||
def test_tsl_syntax_pages_label_mainline_and_deep_dive_roles(self):
|
||
syntax_root = ROOT / "docs" / "tsl" / "syntax"
|
||
|
||
for filename in TSL_SYNTAX_MAINLINE_FILES:
|
||
with self.subTest(filename=filename):
|
||
text = (syntax_root / filename).read_text(encoding="utf-8")
|
||
self.assertIn("文档类型:语法主线", text)
|
||
|
||
text = (syntax_root / "11_pitfalls.md").read_text(encoding="utf-8")
|
||
self.assertIn("文档类型:反例索引页", text)
|
||
|
||
for filename in TSL_SYNTAX_DEEP_DIVE_FILES:
|
||
with self.subTest(filename=filename):
|
||
text = (syntax_root / filename).read_text(encoding="utf-8")
|
||
self.assertIn("文档类型:语法深水专题", text)
|
||
|
||
def test_tsl_syntax_uncertain_routes_have_center_fallbacks(self):
|
||
syntax_root = ROOT / "docs" / "tsl" / "syntax"
|
||
|
||
for path in syntax_root.glob("*.md"):
|
||
with self.subTest(filename=path.name):
|
||
text = path.read_text(encoding="utf-8")
|
||
self.assertNotIn("遇到不确定时跳转到", text)
|
||
self.assertNotIn("补证", text)
|
||
self.assertNotIn("待确认", text)
|
||
self.assertIn("遇到不确定时:", text)
|
||
self.assertIn("TSL 总入口 [../index.md](../index.md)", text)
|
||
|
||
if path.name == "index.md":
|
||
self.assertIn("按任务跳转", text)
|
||
continue
|
||
|
||
self.assertIn("继续判断", text)
|
||
self.assertIn("语法路由中心 [index.md](index.md)", text)
|
||
|
||
def test_tsl_syntax_examples_do_not_use_program_test_shell(self):
|
||
syntax_root = ROOT / "docs" / "tsl" / "syntax"
|
||
|
||
for path in syntax_root.glob("*.md"):
|
||
with self.subTest(filename=path.name):
|
||
text = path.read_text(encoding="utf-8")
|
||
self.assertNotIn("program test;", text)
|
||
self.assertNotIn("`program test; begin ... end.` 只是自包含示例外壳", text)
|
||
|
||
def test_tsl_syntax_code_block_identity_labels_are_fixed(self):
|
||
syntax_root = ROOT / "docs" / "tsl" / "syntax"
|
||
allowed_labels = {
|
||
"可直接照写示例",
|
||
"输出片段",
|
||
"反例 / 不可照写",
|
||
"配置片段 / 概念骨架",
|
||
}
|
||
|
||
for path in syntax_root.glob("*.md"):
|
||
text = path.read_text(encoding="utf-8")
|
||
for match in re.finditer(r"^代码块身份:(.+)$", text, re.MULTILINE):
|
||
with self.subTest(filename=path.name, label=match.group(1)):
|
||
self.assertIn(match.group(1), allowed_labels)
|
||
|
||
def test_remaining_tsl_syntax_pages_have_verified_output_snippets(self):
|
||
syntax_root = ROOT / "docs" / "tsl" / "syntax"
|
||
|
||
for filename in TSL_REMAINING_SYNTAX_AGENT_SECTIONS:
|
||
with self.subTest(filename=filename):
|
||
text = (syntax_root / filename).read_text(encoding="utf-8")
|
||
if (
|
||
"代码块身份:可直接照写示例" in text
|
||
or "代码块身份:反例 / 不可照写" in text
|
||
):
|
||
self.assertIn("代码块身份:输出片段", text)
|
||
|
||
def test_remaining_tsl_syntax_pages_omit_personal_and_environment_details(self):
|
||
syntax_root = ROOT / "docs" / "tsl" / "syntax"
|
||
forbidden_phrases = [
|
||
"我在",
|
||
"当前环境里",
|
||
"Docker",
|
||
"docker exec",
|
||
"LD_LIBRARY_PATH",
|
||
"/data/workspace",
|
||
"U22Cli",
|
||
"gitea-runner",
|
||
]
|
||
|
||
for filename in TSL_REMAINING_SYNTAX_AGENT_SECTIONS:
|
||
with self.subTest(filename=filename):
|
||
text = (syntax_root / filename).read_text(encoding="utf-8")
|
||
for phrase in forbidden_phrases:
|
||
self.assertNotIn(phrase, text)
|
||
|
||
def test_tsl_reference_index_requires_function_fact_workflow(self):
|
||
text = TSL_REFERENCE_INDEX.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("智能体函数使用规则", text)
|
||
self.assertIn("进入 catalog 分类页后按函数事实条目生成调用", text)
|
||
self.assertIn("只从带完整参数表的函数事实条目读取签名", text)
|
||
self.assertIn("catalog 使用推荐大小写展示函数名", text)
|
||
self.assertIn("以函数事实条目的函数名拼写为准", text)
|
||
self.assertIn("没在 catalog 正式条目里的函数,不要根据函数名猜调用", text)
|
||
self.assertIn("函数名属于哪个模块", text)
|
||
self.assertIn("模块目录", text)
|
||
self.assertIn("catalog/base.md", text)
|
||
self.assertIn("catalog/math.md", text)
|
||
self.assertIn("catalog/resource.md", text)
|
||
self.assertNotIn("函数参数事实", text)
|
||
self.assertNotIn("catalog 里的裸函数名只是候选名", text)
|
||
self.assertNotIn("裸函数名仍只表示候选名", text)
|
||
self.assertNotIn("catalog/index.md", text)
|
||
self.assertNotIn("core.md", text)
|
||
self.assertNotIn("functions/", text)
|
||
self.assertNotIn("verified", text)
|
||
self.assertNotIn("unavailable_methods.md", text)
|
||
self.assertNotIn("不可用方法", text)
|
||
self.assertNotIn("verification_failures", text)
|
||
self.assertNotIn("函数入库验证流程", text)
|
||
self.assertNotIn("验证过程", text)
|
||
self.assertNotIn("代码块身份", text)
|
||
self.assertNotIn("输出片段", text)
|
||
self.assertNotIn("验证失败", text)
|
||
|
||
def test_tsl_reference_index_contains_function_fact_catalog(self):
|
||
catalog_index = TSL_REFERENCE_INDEX.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("函数模块目录", catalog_index)
|
||
self.assertIn("进入分类页后,按函数事实条目的参数表生成调用", catalog_index)
|
||
self.assertIn("函数事实数", catalog_index)
|
||
self.assertIn("catalog/base.md", catalog_index)
|
||
self.assertIn("catalog/math.md", catalog_index)
|
||
self.assertIn("catalog/system.md", catalog_index)
|
||
self.assertIn("catalog/third_party.md", catalog_index)
|
||
self.assertIn("catalog 使用推荐大小写展示函数名", catalog_index)
|
||
self.assertIn("以函数事实条目的函数名拼写为准", catalog_index)
|
||
self.assertNotIn("catalog/index.md", catalog_index)
|
||
self.assertNotIn("core.md", catalog_index)
|
||
self.assertNotIn("functions/", catalog_index)
|
||
self.assertNotIn("verified", catalog_index)
|
||
self.assertNotIn("错误参数组合", catalog_index)
|
||
self.assertNotIn("参数验证", catalog_index)
|
||
|
||
catalog_root = ROOT / "docs" / "tsl" / "reference" / "catalog"
|
||
route_pages = {
|
||
"base.md",
|
||
"math.md",
|
||
"datawarehouse.md",
|
||
"system.md",
|
||
"resource.md",
|
||
"graphics.md",
|
||
}
|
||
for path in catalog_root.rglob("*.md"):
|
||
text = path.read_text(encoding="utf-8")
|
||
if path.parent == catalog_root and path.name in route_pages:
|
||
self.assertIn("文档类型:函数事实路由页", text)
|
||
self.assertIn("分类目录", text)
|
||
self.assertNotIn("### `", text)
|
||
continue
|
||
self.assertIn("文档类型:函数事实页", text)
|
||
self.assertIn("参数位置", text, msg=f"{path.name} missing parameter facts")
|
||
self.assertNotIn("候选函数索引", text)
|
||
self.assertIn("../index.md", text)
|
||
|
||
def test_tsl_reference_catalog_function_names_start_lowercase(self):
|
||
catalog_root = ROOT / "docs" / "tsl" / "reference" / "catalog"
|
||
|
||
for path in catalog_root.rglob("*.md"):
|
||
if path.name == "index.md":
|
||
continue
|
||
text = path.read_text(encoding="utf-8")
|
||
for match in re.finditer(r"^(?:- |#{3,6} )`([^`(]+)", text, re.MULTILINE):
|
||
with self.subTest(path=path.relative_to(ROOT), name=match.group(1)):
|
||
name = match.group(1)
|
||
self.assertFalse(name[0].isupper(), msg=f"{name} starts uppercase")
|
||
|
||
def test_tsl_reference_completed_catalog_pages_record_parameter_facts(self):
|
||
catalog_root = ROOT / "docs" / "tsl" / "reference" / "catalog"
|
||
completed_pages = {
|
||
"base.md": [
|
||
"dateToStr",
|
||
"dateTimeToStr",
|
||
"formatDateTime",
|
||
"time",
|
||
"now",
|
||
"date",
|
||
"currentYear",
|
||
"dayOfWeek",
|
||
"encodeDate",
|
||
"encodeTime",
|
||
"strToDate",
|
||
"strToTime",
|
||
"strToDateTime",
|
||
"strToDateDef",
|
||
"strToTimeDef",
|
||
"timeToStr",
|
||
"dateToInt",
|
||
"intToDate",
|
||
"today",
|
||
"yesterday",
|
||
"tomorrow",
|
||
"format",
|
||
"setLength",
|
||
"copy",
|
||
"upperCase",
|
||
"lowerCase",
|
||
"trim",
|
||
"trimLeft",
|
||
"trimRight",
|
||
"pos",
|
||
"strToIntDef",
|
||
"floatToStr",
|
||
"strToInt",
|
||
"intToStr",
|
||
"length",
|
||
],
|
||
"compression.md": [
|
||
"zipCompress",
|
||
"zipExtract",
|
||
"rarExtract",
|
||
"unicompress",
|
||
"uniuncompress",
|
||
],
|
||
"digest_encoding.md": [
|
||
"getMsgDigest",
|
||
"strToBase64",
|
||
"base64ToStr",
|
||
"encodeRadixstr",
|
||
"decodeRadixstr",
|
||
"encoderadixwstr",
|
||
"decoderadixwstr",
|
||
],
|
||
"system.md": [
|
||
"dataType",
|
||
"ifInt",
|
||
"ifInt64",
|
||
"ifReal",
|
||
"ifNumber",
|
||
"ifString",
|
||
"ifArray",
|
||
"ifNil",
|
||
"ifExp",
|
||
"ifGraph",
|
||
"ifGraphGroup",
|
||
"ifBinary",
|
||
"ifMatrix",
|
||
"ifObj",
|
||
"createMatrix",
|
||
"matrixToArray",
|
||
"getMatrixFields",
|
||
"createBinary",
|
||
"convertDataToBuf",
|
||
"convertBufToData",
|
||
"integer",
|
||
"int64",
|
||
"real",
|
||
"string",
|
||
"wideString",
|
||
"binary",
|
||
"setPrecision",
|
||
"toSTM",
|
||
"toSTN",
|
||
"exportCsv",
|
||
"importCsv",
|
||
"dupValue",
|
||
"randomize",
|
||
"random",
|
||
"randomFrom",
|
||
"ifThen",
|
||
"eval",
|
||
"call",
|
||
"callInArray",
|
||
"invoke",
|
||
"invokeinarray",
|
||
"setProfiler",
|
||
"getProfilerInfo",
|
||
"sysgettsllibpath",
|
||
"syssettsllibpath",
|
||
],
|
||
}
|
||
|
||
for filename, signatures in completed_pages.items():
|
||
path = catalog_root / filename
|
||
text = path.read_text(encoding="utf-8")
|
||
if "文档类型:函数事实路由页" in text:
|
||
text = "\n".join(
|
||
detail.read_text(encoding="utf-8")
|
||
for detail in sorted((catalog_root / path.stem).glob("*.md"))
|
||
)
|
||
with self.subTest(filename=filename):
|
||
self.assertIn("文档类型:函数事实页", text)
|
||
self.assertIn("参数个数:", text)
|
||
self.assertIn("返回值:", text)
|
||
self.assertRegex(
|
||
text,
|
||
r"\|\s*参数位置\s*\|\s*参数名\s*\|\s*必填\s*\|\s*接收类型\s*\|\s*说明\s*\|",
|
||
)
|
||
for name in signatures:
|
||
self.assertRegex(text, rf"(?m)^### `{re.escape(name)}\(", msg=name)
|
||
|
||
def test_tsl_reference_index_records_parameter_facts(self):
|
||
text = TSL_REFERENCE_INDEX.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("函数事实路由页", text)
|
||
self.assertIn("查可调用函数参数类型", text)
|
||
self.assertIn("带完整参数表的函数事实条目", text)
|
||
self.assertIn("函数事实数", text)
|
||
self.assertIn("catalog/base.md", text)
|
||
self.assertIn("catalog/system.md", text)
|
||
self.assertNotIn("## 函数参数事实", text)
|
||
self.assertNotIn("`abs(value)`", text)
|
||
self.assertNotIn("verified", text)
|
||
self.assertNotIn("代码块身份", text)
|
||
self.assertNotIn("输出片段", text)
|
||
self.assertNotIn("Function Abs execution error", text)
|
||
self.assertNotIn("错误参数", text)
|
||
|
||
def test_tsl_reference_all_markdown_avoid_verification_logs(self):
|
||
for path in (ROOT / "docs" / "tsl" / "reference").rglob("*.md"):
|
||
text = path.read_text(encoding="utf-8")
|
||
with self.subTest(path=path.relative_to(ROOT)):
|
||
self.assertNotIn("验证过程", text)
|
||
self.assertNotIn("函数入库验证流程", text)
|
||
self.assertNotIn("代码块身份", text)
|
||
self.assertNotIn("输出片段", text)
|
||
self.assertNotIn("Function Abs execution error", text)
|
||
|
||
def test_tsl_reference_does_not_ship_verified_or_unavailable_layers(self):
|
||
reference_root = ROOT / "docs" / "tsl" / "reference"
|
||
|
||
self.assertFalse((reference_root / "verified").exists())
|
||
self.assertFalse((reference_root / "functions").exists())
|
||
self.assertFalse((reference_root / "core.md").exists())
|
||
self.assertFalse((reference_root / "catalog" / "index.md").exists())
|
||
self.assertFalse(TSL_REFERENCE_UNAVAILABLE.exists())
|
||
|
||
for path in reference_root.rglob("*.md"):
|
||
text = path.read_text(encoding="utf-8")
|
||
with self.subTest(path=path.relative_to(ROOT)):
|
||
self.assertNotIn("verified", text)
|
||
self.assertNotIn("unavailable_methods", text)
|
||
self.assertNotIn("不可用方法", text)
|
||
self.assertNotIn("已写入文档", text)
|
||
|
||
def test_tsl_reference_docs_do_not_name_valid_parameter_mismatches_as_failures(self):
|
||
reference_root = ROOT / "docs" / "tsl" / "reference"
|
||
|
||
for path in reference_root.rglob("*.md"):
|
||
text = path.read_text(encoding="utf-8")
|
||
with self.subTest(path=path.relative_to(ROOT)):
|
||
self.assertNotIn("verification_failures", text)
|
||
self.assertNotIn("错误参数", text)
|
||
self.assertNotIn("验证失败", text)
|
||
self.assertNotIn("Abs(\"-3\")", text)
|
||
self.assertNotIn("DateToStr(\"2011-12-31\")", text)
|
||
self.assertNotIn("Length(123)", text)
|
||
self.assertNotIn("abs(\"-3\")", text)
|
||
self.assertNotIn("dateToStr(\"2011-12-31\")", text)
|
||
self.assertNotIn("length(123)", text)
|
||
|
||
def test_tsl_finance_directory_is_removed(self):
|
||
finance_root = ROOT / "docs" / "tsl" / "finance"
|
||
|
||
self.assertFalse(finance_root.exists())
|
||
|
||
def test_tsl_modules_docs_are_agent_integration_boundary_pages(self):
|
||
modules_root = ROOT / "docs" / "tsl" / "modules"
|
||
|
||
for path in modules_root.glob("*.md"):
|
||
text = path.read_text(encoding="utf-8")
|
||
with self.subTest(path=path.name):
|
||
self.assertIn("智能体", text)
|
||
self.assertIn("不要发明", text)
|
||
self.assertNotIn("是否含文档明确", text)
|
||
self.assertNotIn("代码块身份", text)
|
||
self.assertNotIn("验证过程", text)
|
||
|
||
def test_tsl_modules_index_routes_all_module_pages(self):
|
||
text = TSL_MODULES_INDEX.read_text(encoding="utf-8")
|
||
|
||
self.assertIn("智能体模块路由规则", text)
|
||
self.assertIn("tsbacktesting.md", text)
|
||
self.assertIn("wechat_message.md", text)
|
||
self.assertIn("pytsl_api.md", text)
|
||
|
||
def test_tsl_modules_omit_environment_verification_details(self):
|
||
forbidden_phrases = [
|
||
"Docker",
|
||
"docker exec",
|
||
"LD_LIBRARY_PATH",
|
||
"/data/workspace",
|
||
"U22Cli",
|
||
"gitea-runner",
|
||
"当前环境里",
|
||
"实测",
|
||
]
|
||
|
||
for path in (ROOT / "docs" / "tsl" / "modules").glob("*.md"):
|
||
text = path.read_text(encoding="utf-8")
|
||
for phrase in forbidden_phrases:
|
||
self.assertNotIn(phrase, text, msg=f"{phrase!r} found in {path}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|