🐛 fix(tsl_syntax): repair lookup engine and reconcile reference facts

lookup.py: exempt code-anchored ASCII identifiers from the mixed
zh/en gate so exact hits are no longer dropped; dedup parent/child
sections in results; validate write-prelude anchors in --check.

references: correct interpreter-verified facts (case-as-expression,
control-flow semicolons, __line__/__stack_frame, tslObjects order,
destroy timing, ErrDefine, truncated outputs), fix headings, scope
qualifiers and reversed quotes; strip dead preamble metadata from all
24 pages.

packaging: exclude __pycache__/*.pyc from playbook and bundle copies;
update build test file-count assertion to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
csh
2026-07-13 09:16:57 +08:00
co-authored by Claude Fable 5
parent 923bce91b0
commit b597edfc70
29 changed files with 356 additions and 256 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ DEFAULT_OUTPUT = Path("tmp") / "tsl-playbook"
def copy_tree(src: Path, dst: Path) -> None: def copy_tree(src: Path, dst: Path) -> None:
shutil.copytree(src, dst) shutil.copytree(src, dst, ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
def build_agents_text(ruleset_path: Path) -> str: def build_agents_text(ruleset_path: Path) -> str:
+12 -4
View File
@@ -3,7 +3,7 @@ import re
import sys import sys
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from shutil import copy2, copytree, rmtree, which from shutil import copy2, copytree, ignore_patterns, rmtree, which
import subprocess import subprocess
import importlib.util import importlib.util
from typing import Optional from typing import Optional
@@ -543,8 +543,16 @@ def install_snapshot(config: dict, context: dict) -> int:
if gitattributes_src.is_file(): if gitattributes_src.is_file():
copy2(gitattributes_src, dest_prefix / ".gitattributes") copy2(gitattributes_src, dest_prefix / ".gitattributes")
copytree(PLAYBOOK_ROOT / "scripts", dest_prefix / "scripts") copytree(
copytree(PLAYBOOK_ROOT / "skills", dest_prefix / "skills") PLAYBOOK_ROOT / "scripts",
dest_prefix / "scripts",
ignore=ignore_patterns("__pycache__", "*.pyc"),
)
copytree(
PLAYBOOK_ROOT / "skills",
dest_prefix / "skills",
ignore=ignore_patterns("__pycache__", "*.pyc"),
)
copy2(PLAYBOOK_ROOT / "SKILLS.md", dest_prefix / "SKILLS.md") copy2(PLAYBOOK_ROOT / "SKILLS.md", dest_prefix / "SKILLS.md")
common_docs = PLAYBOOK_ROOT / "docs/common" common_docs = PLAYBOOK_ROOT / "docs/common"
@@ -1420,7 +1428,7 @@ def install_skills_action(config: dict, context: dict) -> int:
backup = skills_dst_root / f"{name}.bak.{timestamp}" backup = skills_dst_root / f"{name}.bak.{timestamp}"
dst.rename(backup) dst.rename(backup)
log(f"Backed up existing skill: {name} -> {backup.name}") log(f"Backed up existing skill: {name} -> {backup.name}")
copytree(src, dst) copytree(src, dst, ignore=ignore_patterns("__pycache__", "*.pyc"))
rewrite_skill_docs_links(dst, resolve_docs_prefix(context)) rewrite_skill_docs_links(dst, resolve_docs_prefix(context))
tag = " [thirdparty]" if origin == "thirdparty" else "" tag = " [thirdparty]" if origin == "thirdparty" else ""
log(f"Installed: {name}{tag}") log(f"Installed: {name}{tag}")
+5 -5
View File
@@ -1,15 +1,15 @@
--- ---
name: tsl-syntax-reference name: tsl-syntax-reference
description: Use when Codex 需要编写、修改、审查或解释 TSL/TSF 代码,或排查语法错误并核对语言与运行时规则。 description: 需要编写、修改、审查或解释 TSL/TSF 代码,或排查语法错误并核对语言与运行时规则时使用
--- ---
# TSL Syntax Reference # TSL Syntax Reference
## 唯一检索入口 ## 唯一检索入口
本 Skill 只通过随附的 `scripts/lookup.py` 检索权威 TSL/TSF 语法事实,不手工选择或顺序通读 `references/` 页面。根据任务意图选择模式,并用用户的原始术语、错误文本和关键语法词组成查询: 本 Skill 只通过随附的 `scripts/lookup.py` 检索权威 TSL/TSF 语法事实,不手工选择或顺序通读 `references/` 页面。`<this-skill-dir>` 指本 `SKILL.md` 所在目录。根据任务意图选择模式,并用用户的原始术语、错误文本和关键语法词组成查询:
```powershell ```bash
python <this-skill-dir>/scripts/lookup.py --query "命名参数 默认参数" --mode write python <this-skill-dir>/scripts/lookup.py --query "命名参数 默认参数" --mode write
python <this-skill-dir>/scripts/lookup.py --query "invalid statement 声明区" --mode diagnose python <this-skill-dir>/scripts/lookup.py --query "invalid statement 声明区" --mode diagnose
python <this-skill-dir>/scripts/lookup.py --query "数组下标" --mode explain python <this-skill-dir>/scripts/lookup.py --query "数组下标" --mode explain
@@ -19,7 +19,7 @@ python <this-skill-dir>/scripts/lookup.py --query "数组下标" --mode explain
- `diagnose`:定位语法错误或错误写法。 - `diagnose`:定位语法错误或错误写法。
- `explain`:解释语言规则或代码含义。 - `explain`:解释语言规则或代码含义。
以 lookup 返回的章节作为本次任务的事实来源;需要补充时改进查询词并再次检索,不得绕过 lookup 手工路由。 以 lookup 返回的章节作为本次任务的事实来源;需要补充时改进查询词并再次检索,不得绕过 lookup 手工路由。需要复取输出中列出的某个章节时,用 `--section <Section ID>` 单独取回。
## 事实边界 ## 事实边界
@@ -53,6 +53,6 @@ lookup 无匹配或返回非零状态、结果没有支持当前结论、代码
维护本 Skill、参考页或 lookup 实现后必须运行: 维护本 Skill、参考页或 lookup 实现后必须运行:
```powershell ```bash
python <this-skill-dir>/scripts/lookup.py --check python <this-skill-dir>/scripts/lookup.py --check
``` ```
@@ -1,10 +1,5 @@
# TSL 快速落代码规则 # TSL 快速落代码规则
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
本篇只回答两个落代码前问题:用户已给出 `.tsl` / `.tsf` 后缀时,智能体如何按后缀组织代码;以及生成代码前必须先核对哪些语言硬规则。 本篇只回答两个落代码前问题:用户已给出 `.tsl` / `.tsf` 后缀时,智能体如何按后缀组织代码;以及生成代码前必须先核对哪些语言硬规则。
## 本篇职责 ## 本篇职责
@@ -44,7 +39,7 @@
- “顶层函数骨架”“顶层函数定义体”:只指 `function` 骨架;不要因为任务没有返回值就自动改成 `procedure` - “顶层函数骨架”“顶层函数定义体”:只指 `function` 骨架;不要因为任务没有返回值就自动改成 `procedure`
- `class function` / “类方法”:同一件事;前者是代码关键字写法,后者是中文描述。 - `class function` / “类方法”:同一件事;前者是代码关键字写法,后者是中文描述。
## 起手骨架选择规则 ## 任务到文件模型的选择规则
| 任务 | 起手骨架 | | 任务 | 起手骨架 |
| ------------------------------------ | ------------------------------ | | ------------------------------------ | ------------------------------ |
@@ -1,10 +1,5 @@
# TSL 文件模型规则 # TSL 文件模型规则
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
本篇说明 TSL 的文件模型判断规则:智能体如何区分 `.tsl` 可执行脚本与 `.tsf` 可复用声明文件,如何识别脚本语句区和声明区,以及为什么很多错误其实是“文件模型选错了”。 本篇说明 TSL 的文件模型判断规则:智能体如何区分 `.tsl` 可执行脚本与 `.tsf` 可复用声明文件,如何识别脚本语句区和声明区,以及为什么很多错误其实是“文件模型选错了”。
## 本篇职责 ## 本篇职责
@@ -1,10 +1,5 @@
# TSL 值与字面量 # TSL 值与字面量
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇整理基本类型、字面量、数组、字符串、字符串编码边界与基础值模型,避免把值规则分散在函数或金融示例里。 这一篇整理基本类型、字面量、数组、字符串、字符串编码边界与基础值模型,避免把值规则分散在函数或金融示例里。
## 本篇职责 ## 本篇职责
@@ -470,8 +465,7 @@ writeLn(result);
说明: 说明:
- `%d` 是整数占位符 - 示例里的 `%d` / `%s` 分别对应整数、字符串占位;只为读懂本例
- `%s` 是字符串占位符
- `format` 的完整占位符规范使用 `tsl-api-reference` skill 按名查询。 - `format` 的完整占位符规范使用 `tsl-api-reference` skill 按名查询。
### `formatdatetime` 日期格式化 ### `formatdatetime` 日期格式化
@@ -494,9 +488,7 @@ writeLn(result);
说明: 说明:
- `yyyy` 表示四位年 - 示例里的 `yyyy-mm-dd` 对应四位年、两位月、两位日;只为读懂本例
- `mm` 表示两位月份
- `dd` 表示两位日期
- `formatdatetime` 的完整格式说明使用 `tsl-api-reference` skill 按名查询。 - `formatdatetime` 的完整格式说明使用 `tsl-api-reference` skill 按名查询。
## 默认生成模板 ## 默认生成模板
@@ -1,10 +1,5 @@
# TSL 变量与常量 # TSL 变量与常量
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇收拢变量与常量初始化规则。 这一篇收拢变量与常量初始化规则。
## 本篇职责 ## 本篇职责
@@ -1,10 +1,5 @@
# TSL 函数与调用 # TSL 函数与调用
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇只负责 `function` / `procedure` 的定义、调用、参数传递和值返回。跨 `unit` 声明边界、外部系统交互和业务函数库只在本页保留最小边界。 这一篇只负责 `function` / `procedure` 的定义、调用、参数传递和值返回。跨 `unit` 声明边界、外部系统交互和业务函数库只在本页保留最小边界。
## 本篇职责 ## 本篇职责
@@ -27,13 +22,13 @@
## 核心规则 ## 核心规则
- 最稳妥的函数骨架仍然是 `function Name(...); begin ... end;` - 最稳妥的函数骨架仍然是 `function Name(...); begin ... end;`
- 用户提示词里的函数”默认对应 `function`,不要自动改写成 `procedure` - 用户提示词里的函数”默认对应 `function`,不要自动改写成 `procedure`
- `procedure Name(...); begin ... end;` 只在用户明确要求 `procedure` / 过程时生成;不要因为没有返回值就自动改用 `procedure` - `procedure Name(...); begin ... end;` 只在用户明确要求 `procedure` / 过程时生成;不要因为没有返回值就自动改用 `procedure`
-`.tsl` 文件模型层,脚本语句后可以接函数声明;语句区在前顺序执行,声明区在后提供函数/过程定义。见 [02_core_model.md](02_core_model.md)。 -`.tsl` 文件模型层,脚本语句后可以接函数声明;语句区在前顺序执行,声明区在后提供函数/过程定义。见 [02_core_model.md](02_core_model.md)。
-`.tsf` 文件模型层,顶层 `function` / `procedure` 是模块/函数扩展声明;部署到解释器 `funcext` 后可被脚本直接调用。 -`.tsf` 文件模型层,顶层 `function` / `procedure` 是模块/函数扩展声明;部署到解释器 `funcext` 后可被脚本直接调用。
- 函数头后默认保留分号;不要为了简写主动省略。 - 函数头后默认保留分号;不要为了简写主动省略。
- 函数体内部的普通语句照常用分号结尾;顶层函数/过程声明的 `end` 后必须加分号(写成 `end;`)。 - 函数体内部的普通语句照常用分号结尾;顶层函数/过程声明的 `end` 后必须加分号(写成 `end;`)。
- 函数体内的控制流块(`if`/`while`/`for` 等)的 `end` 不加分号控制流分号规则见 [07_control_flow.md](07_control_flow.md)。 - 函数体内的控制流块(`if`/`while`/`for` 等)的 `end` 后加不加分号都合法;唯一例外是 `if``then``end``else` 之间不能加分号。控制流分号规则见 [07_control_flow.md](07_control_flow.md)。
- 一个函数定义体里可以同时出现主函数和子函数。 - 一个函数定义体里可以同时出现主函数和子函数。
- 函数支持参数类型注解和返回值类型注解。 - 函数支持参数类型注解和返回值类型注解。
- 不带类型注解时,多个参数用逗号分隔。 - 不带类型注解时,多个参数用逗号分隔。
@@ -154,7 +149,7 @@ end;
2 2
``` ```
### 签名增强 ### 签名增强:参数类型与返回值类型注解
带参数类型和返回值类型: 带参数类型和返回值类型:
@@ -356,7 +351,7 @@ end;
99 99
``` ```
### 调用增强 ### 调用增强:命名参数与混用规则
命名参数调用: 命名参数调用:
@@ -977,13 +972,12 @@ writeLn(intToStr(value: 200));
代码块身份:反例 / 不可照写 代码块身份:反例 / 不可照写
```text ```text
writeLn(Pack(a: 1, 2, c: 3));
function Pack(a, b, c); function Pack(a, b, c);
begin begin
return a * 100 + b * 10 + c; return a * 100 + b * 10 + c;
end; end;
begin
writeLn(Pack(a: 1, 2, c: 3));
end.
``` ```
不要在同一次调用里“先进入命名参数模式,再退回位置参数”。这类写法会直接编译失败,错误信息包含 `paramname: not found` 不要在同一次调用里“先进入命名参数模式,再退回位置参数”。这类写法会直接编译失败,错误信息包含 `paramname: not found`
@@ -1,10 +1,5 @@
# TSL 表达式与运算符 # TSL 表达式与运算符
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇集中放语言级表达式与运算符。智能体写代码时,只使用本页和对应专题页已经明确的表达式事实,不要把其他语言或外部资料里的运算符习惯直接搬进 TSL。 这一篇集中放语言级表达式与运算符。智能体写代码时,只使用本页和对应专题页已经明确的表达式事实,不要把其他语言或外部资料里的运算符习惯直接搬进 TSL。
## 本篇职责 ## 本篇职责
@@ -1,10 +1,5 @@
# TSL 控制流 # TSL 控制流
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇只收录流程控制与异常控制,不讨论金融语义。 这一篇只收录流程控制与异常控制,不讨论金融语义。
## 本篇职责 ## 本篇职责
@@ -468,7 +463,6 @@ end
- 在 `else` 前面误加分号。 - 在 `else` 前面误加分号。
- 生成没有分号的裸分支赋值,例如 `then value := 1 else ...`;带 `else` 时用块式分支。 - 生成没有分号的裸分支赋值,例如 `then value := 1 else ...`;带 `else` 时用块式分支。
- 以为 `try ... finally` 会吞掉异常。 - 以为 `try ... finally` 会吞掉异常。
- 把 `case` 写成赋值右侧表达式。
- 在还没搞清表达式规则前,先把复杂业务函数塞进条件里。 - 在还没搞清表达式规则前,先把复杂业务函数塞进条件里。
- 把控制流问题和函数文件模型问题混在一起排查。 - 把控制流问题和函数文件模型问题混在一起排查。
@@ -1,17 +1,12 @@
# TSL 对象与类 # TSL 对象与类
文档类型:语法主线
是否可直接用于生成代码:仅部分
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇收拢面向对象语法,只保留语言层规则。 这一篇收拢面向对象语法,只保留语言层规则。
## 本篇职责 ## 本篇职责
回答“`type Name = class`、字段、`static`、方法、`property`、析构、类类型、继承和对象创建在 TSL 里怎样写”。 回答“`type Name = class`、字段、`static`、方法、`property`、析构、类类型、继承和对象创建在 TSL 里怎样写”。
本页后半段有少量依赖 `unit` 的双文件示例;如果你还没建立 `unit` / `uses` 的多文件心智模型,先看 [09_units_and_scope.md](09_units_and_scope.md)。 本页后半段有少量依赖 `unit` 的双文件示例;`unit` / `uses` 的多文件模型事实见 [09_units_and_scope.md](09_units_and_scope.md)。
## 智能体对象/类判断流程 ## 智能体对象/类判断流程
@@ -43,7 +38,7 @@
- 基础 `property` 形态:`property Name read fieldOrMethod write fieldOrMethod` - 基础 `property` 形态:`property Name read fieldOrMethod write fieldOrMethod`
- `property Name: Type ...` 这种类型注解写法可用。 - `property Name: Type ...` 这种类型注解写法可用。
- 参数化 `property` 可用;本页只写基础模式。 - 参数化 `property` 可用;本页只写基础模式。
- 参数化 `property` 的 accessor 方法记录两种常见模式:读方法接同参数个数写方法接“参数个数 + 赋值值”。 - 参数化 `property` 的 accessor 方法记录两种常见模式:读方法接同参数个数(如 `getItem(i)`),也可以写成无参形态(如 `getDateV()`);写方法接“参数个数 + 赋值值”。
- 索引型 `property` 可用;调用时用圆括号 `obj.Prop(index)` - 索引型 `property` 可用;调用时用圆括号 `obj.Prop(index)`
- 固定 `index` property 可用;它可以把某个固定索引直接映射成普通属性读写。 - 固定 `index` property 可用;它可以把某个固定索引直接映射成普通属性读写。
- `class(Name)``findClass("Name")` 都可以拿到类类型。 - `class(Name)``findClass("Name")` 都可以拿到类类型。
@@ -57,7 +52,7 @@
- 创建对象有两种方式:`new ClassName()` 最常用,`createObject(...)` 作为次选。 - 创建对象有两种方式:`new ClassName()` 最常用,`createObject(...)` 作为次选。
- 普通本地类实例化默认生成 `new ClassName()``createObject("ClassName")``createObject(ClassType)` 只在字符串类名、类类型变量或跨 `unit` 路径场景生成。 - 普通本地类实例化默认生成 `new ClassName()``createObject("ClassName")``createObject(ClassType)` 只在字符串类名、类类型变量或跨 `unit` 路径场景生成。
- 如果类里定义了 `function create(...)``new``createObject("ClassName", ...)``createObject(ClassType, ...)` 都可以透传构造参数,也都支持默认参数和命名参数。 - 如果类里定义了 `function create(...)``new``createObject("ClassName", ...)``createObject(ClassType, ...)` 都可以透传构造参数,也都支持默认参数和命名参数。
- 析构写法是无参 `function destroy();`对象引用设为 `nil` 时会触发它。 - 析构写法是无参 `function destroy();`;对象的最后一个引用被清空(如设为 `nil`时会触发它。存在别名引用时,只清空其中一个引用不会触发。
- 工厂式 `self(0)` / `self(1)` 可用;不要生成 `self()` 这种无参工厂式写法。 - 工厂式 `self(0)` / `self(1)` 可用;不要生成 `self()` 这种无参工厂式写法。
- 跨 `unit` 类路径创建和继承属于多文件边界;相关事实见 [09_units_and_scope.md](09_units_and_scope.md) 和 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md),不要从普通单文件 `new` / `createObject` 规则直接泛化。 - 跨 `unit` 类路径创建和继承属于多文件边界;相关事实见 [09_units_and_scope.md](09_units_and_scope.md) 和 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md),不要从普通单文件 `new` / `createObject` 规则直接泛化。
- `{$ifdef parentClassInUnit}` 为真,可用于探测“继承和构造单元中的类”能力是否可用。 - `{$ifdef parentClassInUnit}` 为真,可用于探测“继承和构造单元中的类”能力是否可用。
@@ -750,8 +745,8 @@ end;
输出说明: 输出说明:
- `read getItem` 这种读方法接同参数个数”的写法可以通过 - `read getItem` 这种读方法接同参数个数”的写法可以通过
- `write setItem` 这种写方法接参数个数 + 赋值值”的写法可以通过 - `write setItem` 这种写方法接参数个数 + 赋值值”的写法可以通过
- 上述例子中的 `obj.Item(2)` 输出 `x` - 上述例子中的 `obj.Item(2)` 输出 `x`
### 对象创建与类类型 ### 对象创建与类类型
@@ -904,7 +899,7 @@ end;
- `findClass("MathBox").Add(...)` 可以调用类方法 - `findClass("MathBox").Add(...)` 可以调用类方法
- 上述例子依次输出 `7``11` - 上述例子依次输出 `7``11`
### 类方法、重载、继承与析构 ### 重载、继承与析构
`overload` 方法: `overload` 方法:
@@ -1,10 +1,5 @@
# TSL unit 与作用域 # TSL unit 与作用域
文档类型:语法主线
是否可直接用于生成代码:仅部分
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇只讲“怎么写和怎么调用 `unit`”。`namespace`、查找路径、`unit` 生命周期、`tsl.conf` 与运行时入口统一放到 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。 这一篇只讲“怎么写和怎么调用 `unit`”。`namespace`、查找路径、`unit` 生命周期、`tsl.conf` 与运行时入口统一放到 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。
## 本篇职责 ## 本篇职责
@@ -43,9 +38,9 @@
- 只写在 `implementation` 里的函数,对 `unit` 外部不可见。 - 只写在 `implementation` 里的函数,对 `unit` 外部不可见。
- 出现重名接口时,未限定调用按 `uses` 从后往前解析;最后一个 `uses` 的同名接口优先。 - 出现重名接口时,未限定调用按 `uses` 从后往前解析;最后一个 `uses` 的同名接口优先。
- 要显式指定某个 `unit` 的接口,可以用 `UnitA.Ping()``unit(UnitA).Ping()``call("UnitA.Ping")` - 要显式指定某个 `unit` 的接口,可以用 `UnitA.Ping()``unit(UnitA).Ping()``call("UnitA.Ping")`
- `unit` 生命周期、接口状态、命名空间和查找路径细节,转去看 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。 - `unit` 生命周期、接口状态、命名空间和查找路径细节 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。
## 可直接照写示例 ## 示例与行为
### 最小 `unit` ### 最小 `unit`
@@ -495,8 +490,6 @@ end;
end. end.
``` ```
后面的多文件片段只在需要 `uses`、可见性或重名解析时再回来看。
## 禁止项 ## 禁止项
- 把依赖外部 `unit``uses` 例子误当成单文件最小示例。 - 把依赖外部 `unit``uses` 例子误当成单文件最小示例。
@@ -1,10 +1,5 @@
# TSL 运行时上下文、服务与全局缓存 # TSL 运行时上下文、服务与全局缓存
文档类型:语法主线
是否可直接用于生成代码:仅部分
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇只处理运行时环境参数、块环境 `with` 语句、`with` 后缀、`#` 网格调用、`timeout` 后缀、`dupvalue(...)` 和全局缓存,不处理任何金融业务语义。 这一篇只处理运行时环境参数、块环境 `with` 语句、`with` 后缀、`#` 网格调用、`timeout` 后缀、`dupvalue(...)` 和全局缓存,不处理任何金融业务语义。
## 本篇职责 ## 本篇职责
@@ -48,6 +43,8 @@
## 可直接照写示例 ## 可直接照写示例
### `setSysParam` / `getSysParam` / `sysParams[...]` 基础读写
直接设置和读取系统参数: 直接设置和读取系统参数:
代码块身份:可直接照写示例 代码块身份:可直接照写示例
@@ -163,7 +160,9 @@ writeLn(getSysParam("b"));
- 块后输出 `1``9` - 块后输出 `1``9`
- 说明 `with **` 不继承未传入的外层系统参数,并且块结束后恢复外层系统参数 - 说明 `with **` 不继承未传入的外层系统参数,并且块结束后恢复外层系统参数
沿用同一个 `TestDo.tsf`,看 `with array(...)` 的覆盖边界: ### 后缀 `with array(...)`:网格调用的临时覆盖
`#Func(...) with array(...)` 在一次网格调用里临时覆盖系统参数,调用结束后恢复外层原值。下面用一个 `TestDo.tsf` 演示覆盖边界:
代码块身份:配置片段 / 概念骨架 代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖函数文件查找路径,不是可直接复制的单文件最小示例。 代码块说明:多文件结构骨架;依赖函数文件查找路径,不是可直接复制的单文件最小示例。
@@ -193,7 +192,7 @@ writeLn(getSysParam("b"));
- 调用结束后,外层 `getSysParam("b")` 输出 `8` - 调用结束后,外层 `getSysParam("b")` 输出 `8`
- 说明 `with array(...)` 是“只在该次调用里临时覆盖,再恢复外部原值” - 说明 `with array(...)` 是“只在该次调用里临时覆盖,再恢复外部原值”
沿用上一个 `TestDo.tsf`,只把 `main.tsl` 改成下面这样: 沿用上面的 `TestDo.tsf`,只把 `main.tsl` 改成下面这样(外层未预设参数)
代码块身份:配置片段 / 概念骨架 代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖函数文件查找路径,不是可直接复制的单文件最小示例。 代码块说明:多文件结构骨架;依赖函数文件查找路径,不是可直接复制的单文件最小示例。
@@ -231,7 +230,7 @@ end;
结果说明: 结果说明:
- `#AddOne(5)` 可以执行 - `#AddOne(5)` 可以执行
- `dupvalue(r)` 返回最终结果 `6` - `dupvalue(#AddOne(5))` 返回最终结果 `6`
代码块身份:输出片段 代码块身份:输出片段
@@ -303,9 +302,7 @@ writeLn(cached_data[0], ',', cached_data[1], ',', cached_data[2]);
说明: 说明:
- TSL 标识符大小写无关,`setGlobalCache``SetGlobalCache` 等价 - TSL 标识符大小写无关,`setGlobalCache``SetGlobalCache` 等价
- 推荐使用小写 `setGlobalCache``getGlobalCache` 以保持一致性 - 示例只展示 `setGlobalCache(key, value)` / `getGlobalCache(key, out_var)` 的最小调用形态;完整参数规格(含过期时间等可选参数)用 `tsl-api-reference` skill 按名查询
- `setGlobalCache(key, value)` 写入缓存,可选第三参数指定过期时间(秒)
- `getGlobalCache(key, out_var)` 读取缓存到输出变量
### `checkGlobalCacheExpired` ### `checkGlobalCacheExpired`
@@ -324,7 +321,7 @@ writeLn(checkGlobalCacheExpired(cache_ref));
- 刚取出的缓存引用上,`checkGlobalCacheExpired(cache_ref)` 返回 `0` - 刚取出的缓存引用上,`checkGlobalCacheExpired(cache_ref)` 返回 `0`
- 同名缓存被重新设置后,旧引用上的 `checkGlobalCacheExpired(cache_ref)` 返回 `1` - 同名缓存被重新设置后,旧引用上的 `checkGlobalCacheExpired(cache_ref)` 返回 `1`
### 写入后会实例化 ### 缓存值本地写入后会实例化
代码块身份:可直接照写示例 代码块身份:可直接照写示例
@@ -398,7 +395,7 @@ begin
end; end;
``` ```
需要缓存时, `setGlobalCache` / `getGlobalCache` 那一节选择模板 需要缓存时,默认模板是 `setGlobalCache(key, value)` 写入、`getGlobalCache(key, out_var)` 读取
## 禁止项 ## 禁止项
@@ -1,10 +1,5 @@
# TSL 高频误写 # TSL 高频误写
文档类型:反例索引页
是否可直接用于生成代码:否
是否含可直接照写示例:否
是否含不可照写反例:是
这一篇不讲新知识,只做反例索引。 这一篇不讲新知识,只做反例索引。
## 本篇职责 ## 本篇职责
@@ -1,10 +1,5 @@
# TSL 数组、键表、矩阵样数据与集合运算 # TSL 数组、键表、矩阵样数据与集合运算
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:否
这一篇收拢 `array(...)` 的扩展用法:顺序数组、字符串键表、嵌套数组、本页明确的矩阵样比较,以及 `in` / `sqlin` / `union2` / `intersect` / `minus` / `outersect` 这类去重型集合关系。 这一篇收拢 `array(...)` 的扩展用法:顺序数组、字符串键表、嵌套数组、本页明确的矩阵样比较,以及 `in` / `sqlin` / `union2` / `intersect` / `minus` / `outersect` 这类去重型集合关系。
## 本篇职责 ## 本篇职责
@@ -29,7 +24,7 @@
- `in` / `not in` 处理的是元素存在关系,以及左侧为数组时的子集关系。 - `in` / `not in` 处理的是元素存在关系,以及左侧为数组时的子集关系。
- `sqlin` / `not sqlin` 处理的是行存在关系;左侧要当成一整行去匹配右侧结果集。 - `sqlin` / `not sqlin` 处理的是行存在关系;左侧要当成一整行去匹配右侧结果集。
- `union2``intersect``minus``outersect` 都按“行”运算,而不是按单元格逐个运算。 - `union2``intersect``minus``outersect` 都按“行”运算,而不是按单元格逐个运算。
- 集合运算结果会折叠重复行;如果需求是保留重复记录,改看 [13_resultset_and_filters.md](13_resultset_and_filters.md)。 - 集合运算结果会折叠重复行;保留重复记录的过滤规则见 [13_resultset_and_filters.md](13_resultset_and_filters.md)。
- 当数据本身就是一维数组时,按行集合运算和按元素集合运算是一致的。 - 当数据本身就是一维数组时,按行集合运算和按元素集合运算是一致的。
- 左侧是数组时,先判定需求语义:子集关系用 `in`,整行存在关系用 `sqlin` - 左侧是数组时,先判定需求语义:子集关系用 `in`,整行存在关系用 `sqlin`
@@ -137,6 +132,9 @@ writeLn(1 not in array(0, 2));
0 0
1 1
1 1
1
0
1
``` ```
`sqlin` 按整行判断左侧是否存在于右侧结果集中: `sqlin` 按整行判断左侧是否存在于右侧结果集中:
@@ -198,7 +196,7 @@ outersect_rows := left_rows outersect right_rows;
- 集合运算先把数据当成“行集合”来看,再做包含、并交差。 - 集合运算先把数据当成“行集合”来看,再做包含、并交差。
- 过滤运算先保留“原结果集里的每一条命中记录”;因此重复行会保留下来。 - 过滤运算先保留“原结果集里的每一条命中记录”;因此重复行会保留下来。
- 需要“集合关系”时留在本页。 - 需要“集合关系”时留在本页。
- 需要“从原表里筛出哪些行”时看 [13_resultset_and_filters.md](13_resultset_and_filters.md)。 - “从原表里筛出哪些行”的过滤规则见 [13_resultset_and_filters.md](13_resultset_and_filters.md)。
## 默认生成模板 ## 默认生成模板
@@ -211,7 +209,7 @@ matched := 1 in array(1, 2, 3);
row_matched := array(1, 2) sqlin array((1, 2), (3, 4)); row_matched := array(1, 2) sqlin array((1, 2), (3, 4));
``` ```
### 点前缀比较算符产生逻辑数组 ## 点前缀比较算符产生逻辑数组
点前缀比较(`.=``.<>``.>``.>=``.<``.<=`)对数组/矩阵逐元素比较,返回真假值数组: 点前缀比较(`.=``.<>``.>``.>=``.<``.<=`)对数组/矩阵逐元素比较,返回真假值数组:
@@ -274,7 +272,7 @@ writeLn("子集 (1,0):", subset[1][0]);
- `a[indexes]` 按下标提取对应行,等价于 `select * from a where [0] > 0.5 end` - `a[indexes]` 按下标提取对应行,等价于 `select * from a where [0] > 0.5 end`
- `mfind` 的完整用法见 [22_matrix_deep_dive.md](22_matrix_deep_dive.md) - `mfind` 的完整用法见 [22_matrix_deep_dive.md](22_matrix_deep_dive.md)
### 非完全矩阵缺位当 0 处理 ## 非完全矩阵缺位当 0 处理与标量广播
基础算符作用于非完全矩阵(行长度不一致或字符串键不对齐的数组)时,对应位置不存在或为 `nil` 时**默认当 0 处理** 基础算符作用于非完全矩阵(行长度不一致或字符串键不对齐的数组)时,对应位置不存在或为 `nil` 时**默认当 0 处理**
@@ -341,15 +339,15 @@ writeLn("(1,1):", result[1][1]);
## 禁止项 ## 禁止项
- 不要把字符串下标按数组的 `0` 起始规则来写;字符串索引事实见 [03_values_and_literals.md](03_values_and_literals.md)。 - 不要把字符串下标按数组的 `0` 起始规则来写;字符串下标从 `1` 开始,详见 [03_values_and_literals.md](03_values_and_literals.md)。
- 不要把矩阵链式比较 `::...` 和标量链式比较混写成同一种语法。 - 不要把矩阵链式比较 `::...` 和标量链式比较混写成同一种语法。
- 不要把 `in``sqlin` 当成同一个概念。 - 不要把 `in``sqlin` 当成同一个概念。
- 不要期待 `union2` 保留重复行。 - 不要期待 `union2` 保留重复行。
- 不要用集合运算去做保留原始重复记录”的过滤任务。 - 不要用集合运算去做保留原始重复记录”的过滤任务。
- 不要把二维结果集默认当成按元素逐个比较”的集合运算。 - 不要把二维结果集默认当成按元素逐个比较”的集合运算。
- 左侧数组要表达这些值是否都属于右侧集合”时,用 `in` - 左侧数组要表达这些值是否都属于右侧集合”时,用 `in`
- 左侧数组要表达这一整行是否存在于右侧结果集”时,用 `sqlin` - 左侧数组要表达这一整行是否存在于右侧结果集”时,用 `sqlin`
- `minus` 表达集合差集;如果任务要求保留左侧原始重复次数,改走 [13_resultset_and_filters.md](13_resultset_and_filters.md) 的过滤规则 - `minus` 表达集合差集;保留左侧原始重复次数的过滤规则见 [13_resultset_and_filters.md](13_resultset_and_filters.md)。
- 不要在本页发明结果集过滤、TS-SQL 查询、写回语法或更大矩阵函数族。 - 不要在本页发明结果集过滤、TS-SQL 查询、写回语法或更大矩阵函数族。
- 不要把普通 `array(...)` 自动升级成 `FMArray``FMArray` 专属事实见 [23_fmarray.md](23_fmarray.md)。 - 不要把普通 `array(...)` 自动升级成 `FMArray``FMArray` 专属事实见 [23_fmarray.md](23_fmarray.md)。
- 不要把点前缀比较 `.>` 和矩阵链式比较 `::>` 混用;`.>` 返回逻辑数组,`::>` 是链式比较。 - 不要把点前缀比较 `.>` 和矩阵链式比较 `::>` 混用;`.>` 返回逻辑数组,`::>` 是链式比较。
@@ -1,10 +1,5 @@
# TSL 结果集与过滤 # TSL 结果集与过滤
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇只讲结果集过滤语法:怎样按某一列过滤、怎样按整行过滤、怎样只返回行下标,以及怎样按字段列表过滤。它不负责 TS-SQL 查询语法,也不把去重型集合运算和业务选股逻辑混进来。 这一篇只讲结果集过滤语法:怎样按某一列过滤、怎样按整行过滤、怎样只返回行下标,以及怎样按字段列表过滤。它不负责 TS-SQL 查询语法,也不把去重型集合运算和业务选股逻辑混进来。
## 本篇职责 ## 本篇职责
@@ -168,8 +163,8 @@ sub_result := rows[keep_index, array("A", "C")];
- 过滤运算按“原结果集中的每一行是否命中”来决定保留或排除。 - 过滤运算按“原结果集中的每一行是否命中”来决定保留或排除。
- 集合运算按“行集合”来做并、交、差;结果默认会折叠重复行。 - 集合运算按“行集合”来做并、交、差;结果默认会折叠重复行。
- 需要保留重复记录时,优先看这一篇 - 保留重复记录的过滤规则由本页负责
- 需要去重后的并集、交集、差集时,优先看 [12_matrix_and_collections.md](12_matrix_and_collections.md)。 - 去重后的并集、交集、差集事实见 [12_matrix_and_collections.md](12_matrix_and_collections.md)。
## 默认生成模板 ## 默认生成模板
@@ -1,15 +1,10 @@
# TSL TS-SQL # TSL TS-SQL
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇是 TS-SQL 的唯一语法入口:内存数组查询、返回形态、字段访问、`where` / `group by` / `order by`、一维数组查询、多表 `join`(含 `left join`)、`insert` / `update` / `delete` 写回、`thisGroup``thisRowIndex``refMaxOf` / `refMinOf` 都在这里收拢。 这一篇是 TS-SQL 的唯一语法入口:内存数组查询、返回形态、字段访问、`where` / `group by` / `order by`、一维数组查询、多表 `join`(含 `left join`)、`insert` / `update` / `delete` 写回、`thisGroup``thisRowIndex``refMaxOf` / `refMinOf` 都在这里收拢。
## 本篇职责 ## 本篇职责
回答写 TS-SQL 查询和写回时,怎样从最小 `select ... from ... end` 骨架开始,逐步处理筛选、分组、排序、多表联接(含 LEFT JOIN)、组内子查询、极值引用,以及如何用 `insert`/`update`/`delete` 修改内存数组”。 回答写 TS-SQL 查询和写回时,怎样从最小 `select ... from ... end` 骨架开始,逐步处理筛选、分组、排序、多表联接(含 LEFT JOIN)、组内子查询、极值引用,以及如何用 `insert`/`update`/`delete` 修改内存数组”。
## 智能体 TS-SQL 判断流程 ## 智能体 TS-SQL 判断流程
@@ -27,7 +22,7 @@
- TS-SQL 是 TSL 自带的类 SQL 查询语法,不是金融业务函数库。 - TS-SQL 是 TSL 自带的类 SQL 查询语法,不是金融业务函数库。
- 基础查询文档骨架是:以 `select` / `sselect` / `vselect` / `mselect` 开始,以 `end` 收尾。 - 基础查询文档骨架是:以 `select` / `sselect` / `vselect` / `mselect` 开始,以 `end` 收尾。
- `from` 后面可以直接跟内存数组结果集。 - `from` 后面可以直接跟内存数组结果集。
- 在内存二维结果集上,文档字段访问写法是 `["字段名"]` - 在内存二维结果集上,文档字段访问写法是 `["字段名"]`;列没有名字(如直接来自数组)时用位置下标 `[0]``[1]` 访问
- 在一维数组上做 TS-SQL 时,优先使用 `thisRow``thisRowIndex` - 在一维数组上做 TS-SQL 时,优先使用 `thisRow``thisRowIndex`
- `select` 返回二维结果,`sselect` 返回一维结果,`vselect` 返回单值,`mselect` 返回 `Matrix` - `select` 返回二维结果,`sselect` 返回一维结果,`vselect` 返回单值,`mselect` 返回 `Matrix`
- `where``group by``order by` 可以直接接在 `from` 后面继续使用;`order by` 支持 `asc`/`desc` 与多列逗号分隔。 - `where``group by``order by` 可以直接接在 `from` 后面继续使用;`order by` 支持 `asc`/`desc` 与多列逗号分隔。
@@ -314,7 +309,7 @@ writeLn("(1,1):", result[1]["vb"]);
- `on` 子句指定联接条件,用 `[1].["k"] = [2].["k"]` 匹配键 - `on` 子句指定联接条件,用 `[1].["k"] = [2].["k"]` 匹配键
- 其他联接类型:`right join`(保留右表)、`full join`(保留双方)、`cross join`(笛卡尔积) - 其他联接类型:`right join`(保留右表)、`full join`(保留双方)、`cross join`(笛卡尔积)
### JOIN 家族其余形态 ### `right join` / `full join` / `cross join` 与逗号联接
`right join` / `full join` 的不匹配行同样用 `nil` 填充: `right join` / `full join` 的不匹配行同样用 `nil` 填充:
@@ -439,7 +434,7 @@ writeLn("(1,0):", a[1]["id"]);
- `insert into 数组变量` 直接修改原数组 - `insert into 数组变量` 直接修改原数组
- `insertfields([字段列表])` 指定要插入的字段 - `insertfields([字段列表])` 指定要插入的字段
- `values(...)` 提供对应值,可以写多组 `values``values` 实现批量插入 - `values(...)` 提供对应值,可以连续写多组 `values(...)` 实现批量插入
批量插入可以直接跟一个同结构数组,`insertfields` 也支持一次给多字段赋值: 批量插入可以直接跟一个同结构数组,`insertfields` 也支持一次给多字段赋值:
@@ -573,11 +568,13 @@ writeLn(mrows(a));
代码块身份:可直接照写示例 代码块身份:可直接照写示例
```tsl ```tsl
a := array(("cls": "A", "en": 90), ("cls": "A", "en": 80), ("cls": "B", "en": 85)); a := array(("cls": "A", "en": 90), ("cls": "A", "en": 80), ("cls": "A", "en": 90), ("cls": "B", "en": 85));
distinct_rows := select distinct ["cls"] from a end; distinct_rows := select distinct ["cls"] from a end;
distinct_sum := vselect sumof(distinct ["en"]) from a end; distinct_sum := vselect sumof(distinct ["en"]) from a end;
plain_sum := vselect sumof(["en"]) from a end;
writeLn(mrows(distinct_rows)); writeLn(mrows(distinct_rows));
writeLn(distinct_sum); writeLn(distinct_sum);
writeLn(plain_sum);
``` ```
代码块身份:输出片段 代码块身份:输出片段
@@ -585,12 +582,13 @@ writeLn(distinct_sum);
```text ```text
2 2
255 255
345
``` ```
说明: 说明:
- `select distinct [字段]` 折叠重复行 - `select distinct [字段]` 折叠重复行
- `sumof(distinct [字段])` 只对不同值求和:`90 + 80 + 85 = 255` - `sumof(distinct [字段])` 只对不同值求和:`90 + 80 + 85 = 255`;不加 `distinct` 时重复的 `90` 计两次,得 `345`
### `as` 别名、`as nil` 与字段区间 ### `as` 别名、`as nil` 与字段区间
@@ -805,10 +803,7 @@ integer
### `aggof` 自定义聚集扩展 ### `aggof` 自定义聚集扩展
> 代码块身份:仅服务端可执行示例 `aggof('名称', 表达式)` 调用一个自定义回调函数做聚集。本地 `TSL.exe` 会报 `AggOf Init Error`;下例在服务端(pyTSL)验证通过:
> 本地 `TSL.exe` 会报 `AggOf Init Error`;下例在服务端(pyTSL)验证通过。
`aggof('名称', 表达式)` 调用一个自定义回调函数做聚集:
代码块身份:仅服务端可执行示例 代码块身份:仅服务端可执行示例
@@ -855,6 +850,7 @@ TS-SQL 的最短默认骨架如下:
代码块身份:可直接照写示例 代码块身份:可直接照写示例
```tsl ```tsl
source_rows := array((1, 10), (2, 20));
query_result := select * from source_rows end; query_result := select * from source_rows end;
``` ```
@@ -894,7 +890,7 @@ query_result := select A from source_rows end;
query_result := select [0] from values end; query_result := select [0] from values end;
``` ```
这种对一维数组直接用 `[0]` 的写法虽然返回长度为 `3` 的结果,但取到的值是 `nil`,不能当成可靠入口。对一维数组应改用 `thisRow``thisRowIndex` 这种对一维数组直接用 `[0]` 的写法虽然返回与源数组等长的结果,但取到的值是 `nil`,不能当成可靠入口。对一维数组应改用 `thisRow``thisRowIndex`
代码块身份:反例 / 不可照写 代码块身份:反例 / 不可照写
@@ -1,10 +1,5 @@
# TSL 调试与性能分析器 # TSL 调试与性能分析器
文档类型:语法主线
是否可直接用于生成代码:仅部分
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇收拢本页明确的调试、计时、性能分析器和调用栈相关入口。 这一篇收拢本页明确的调试、计时、性能分析器和调用栈相关入口。
## 本篇职责 ## 本篇职责
@@ -116,7 +111,7 @@ end;
结果说明: 结果说明:
- 只输出 `before` - 只输出 `before`
- 说明 `debugReturn bb;` 不只是结束 `Inner(...)`,而是直接让整段脚本提前返回 - 说明 `debugReturn value;` 不只是结束 `Inner(...)`,而是直接让整段脚本提前返回
- 因此 `Inner(3)` 后面的 `writeLn("after")` 不会执行 - 因此 `Inner(3)` 后面的 `writeLn("after")` 不会执行
### `debugRunEnv``debugRunEnvDo` ### `debugRunEnv``debugRunEnvDo`
@@ -215,8 +210,8 @@ writeLn(line_number);
结果说明: 结果说明:
- 输出 `3` - 输出 `1`
- 说明 `__line__` 直接返回所在代码行号 - 说明 `__line__` 直接返回所在代码行号(这里 `__line__` 在脚本第 1 行)
`__stack_frame` `__stack_frame`
@@ -242,12 +237,12 @@ end;
```text ```text
array( array(
(11,"__main__"), (1,"__main__"),
(8,"Outer")) (10,"Outer"))
``` ```
- 说明 `__stack_frame` 返回的是调用栈帧数组 - 说明 `__stack_frame` 返回的是调用栈帧数组
- 在这个最小例子里,可以直接看到调用位置行号和调用者函数名 - 在这个最小例子里,可以直接看到调用位置行号和调用者函数名`__main__` 在第 1 行调用 `Outer()``Outer` 在第 10 行调用 `Inner()`
## 禁止项 ## 禁止项
@@ -1,10 +1,5 @@
# TSL 词法结构与编译选项 # TSL 词法结构与编译选项
文档类型:语法深水专题
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇吸收语法手册里“词法层”和“编译期开关”相关内容:标识符、注释、条件编译和依赖编译选项。 这一篇吸收语法手册里“词法层”和“编译期开关”相关内容:标识符、注释、条件编译和依赖编译选项。
## 本篇职责 ## 本篇职责
@@ -1,10 +1,5 @@
# TSL 类型与转换 # TSL 类型与转换
文档类型:语法深水专题
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇吸收语言基础里“数据类型、初始值、弱类型行为、类型转换、复数类型”这一层。 这一篇吸收语言基础里“数据类型、初始值、弱类型行为、类型转换、复数类型”这一层。
## 本篇职责 ## 本篇职责
@@ -256,6 +251,7 @@ writeLn(z2);
1 1
4 4
3 3
5-2j
``` ```
共轭、模与等值比较: 共轭、模与等值比较:
@@ -269,7 +265,7 @@ writeLn(abs(z));
writeLn(real(c)); writeLn(real(c));
writeLn(imag(c)); writeLn(imag(c));
if 3.15 = 3.15 + 0j then if 3.15 = 3.15 + 0j then
writeLn(1); writeLn(1)
else else
writeLn(0); writeLn(0);
``` ```
@@ -1,10 +1,5 @@
# TSL 外部调用与线程 # TSL 外部调用与线程
文档类型:语法深水专题
是否可直接用于生成代码:仅部分
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇吸收函数专题里和外部系统交互有关的部分:`external`、动态库调用、原生函数指针包装、C 回调和线程调用。 这一篇吸收函数专题里和外部系统交互有关的部分:`external`、动态库调用、原生函数指针包装、C 回调和线程调用。
## 本篇职责 ## 本篇职责
@@ -59,6 +54,7 @@ function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCou
Linux / POSIX 环境的同类最小骨架: Linux / POSIX 环境的同类最小骨架:
代码块身份:可直接照写示例 代码块身份:可直接照写示例
代码块说明:仅类 Unix 环境可执行(依赖 `libc.so.6`);Windows 下不可照抄,本块只演示 `.so` 库名写法。
```tsl ```tsl
writeLn(getpid() > 0); writeLn(getpid() > 0);
@@ -171,7 +167,7 @@ end;
结果说明: 结果说明:
- 输出 `1` - 输出 `1`
- 说明类常量字符串可以用于 `external kernel_dll` 这种 DLL 名位置 - 说明类常量字符串(如 `kKernelDll`可以用于 `external kKernelDll` 这种 DLL 名位置
不作为可写事实边界: 不作为可写事实边界:
@@ -201,8 +197,8 @@ end;
结果说明: 结果说明:
- `fp <> nil` 输出 `1` - `func_ptr <> nil` 输出 `1`
- `##f(3, 4)` 输出 `7` - `##wrapped_func(3, 4)` 输出 `7`
- 说明 `makeInstance(...)` 生成的函数指针可以再通过 `function(...); external fp;` 包装回 TSL 侧调用 - 说明 `makeInstance(...)` 生成的函数指针可以再通过 `function(...); external fp;` 包装回 TSL 侧调用
### 线程模式最小正例 ### 线程模式最小正例
@@ -1,10 +1,5 @@
# TSL 命名空间、Libpath 与 unit 运行时 # TSL 命名空间、Libpath 与 unit 运行时
文档类型:语法深水专题
是否可直接用于生成代码:仅部分
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇收拢 `unit` 的运行时行为,以及 `.tsf` / `namespace` / `libpath` 的查找规则。`unit` 的基础写法、`uses` 位置和重名解析,统一放在 [09_units_and_scope.md](09_units_and_scope.md)。 这一篇收拢 `unit` 的运行时行为,以及 `.tsf` / `namespace` / `libpath` 的查找规则。`unit` 的基础写法、`uses` 位置和重名解析,统一放在 [09_units_and_scope.md](09_units_and_scope.md)。
## 本篇职责 ## 本篇职责
@@ -1,10 +1,5 @@
# TSL 对象运行时与反射 # TSL 对象运行时与反射
文档类型:语法深水专题
是否可直接用于生成代码:仅部分
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇收拢对象模型里不适合继续堆在基础类主线里的运行时内容:类信息、函数句柄、对象状态、引用计数、运行时对象枚举、弱引用与自动弱引用。普通类声明、继承、构造和普通对象创建仍以 [08_objects_and_classes.md](08_objects_and_classes.md) 为主线;本页只在用户明确需要运行时反射、内省、对象生命周期或弱引用能力时进入。 这一篇收拢对象模型里不适合继续堆在基础类主线里的运行时内容:类信息、函数句柄、对象状态、引用计数、运行时对象枚举、弱引用与自动弱引用。普通类声明、继承、构造和普通对象创建仍以 [08_objects_and_classes.md](08_objects_and_classes.md) 为主线;本页只在用户明确需要运行时反射、内省、对象生命周期或弱引用能力时进入。
## 本篇职责 ## 本篇职责
@@ -40,7 +35,7 @@
- `functionInfo` 至少可以读出 `functionname``returntype``classname` 这几个字段。 - `functionInfo` 至少可以读出 `functionname``returntype``classname` 这几个字段。
- `tslObjects(1)` 会按类名分组返回对象信息,并且分组项里的 `"obj"` 字段可以重新拿到可调用对象。 - `tslObjects(1)` 会按类名分组返回对象信息,并且分组项里的 `"obj"` 字段可以重新拿到可调用对象。
- 对象赋值会延长对象存活;只有最后一个引用清空后才会触发 `destroy()` - 对象赋值会延长对象存活;只有最后一个引用清空后才会触发 `destroy()`
- `self` 在本页只用于需要本实例对象引用的运行时函数,例如 `objectstate(self)`普通成员访问仍按 [08_objects_and_classes.md](08_objects_and_classes.md) 的规则,不加 `self` 前缀。 - `self` 表示本实例对象引用;本页在需要该引用的场景使用它,如传给运行时函数`objectstate(self)`)或保存自身引用(`sca := self;`)。普通成员访问仍按 [08_objects_and_classes.md](08_objects_and_classes.md) 的规则,不加 `self` 前缀。
- 弱引用能力的条件编译宏是 `weakptr`;自动弱引用相关宏是 `autoWeak` - 弱引用能力的条件编译宏是 `weakptr`;自动弱引用相关宏是 `autoWeak`
- `weakRef(obj)` 可以创建弱引用;对象仍存活时,`weakref_get(w)` 可以拿回强引用。 - `weakRef(obj)` 可以创建弱引用;对象仍存活时,`weakref_get(w)` 可以拿回强引用。
- `checkWeakRef(w)` 在对象仍存活时返回 `1`,对象已释放后返回 `-1` - `checkWeakRef(w)` 在对象仍存活时返回 `1`,对象已释放后返回 `-1`
@@ -355,15 +350,15 @@ end;
代码块身份:可直接照写示例 代码块身份:可直接照写示例
```tsl ```tsl
objA := new TestClass01(100);
objB := new TestClass01(101);
objsInfo := tslObjects(1); objsInfo := tslObjects(1);
writeLn(length(objsInfo["TestClass01"])); writeLn(length(objsInfo["TestClass01"]));
newObjA := objsInfo["TestClass01"][0, "obj"]; newObjA := objsInfo["TestClass01"][0, "obj"];
writeLn(newObjA is class(TestClass01)); writeLn(newObjA is class(TestClass01));
writeLn(newObjA.add(1, 2)); writeLn(newObjA.add(1, 2));
objA := new TestClass01(100);
objB := new TestClass01(101);
type TestClass01 = class type TestClass01 = class
public public
value; value;
@@ -575,6 +570,8 @@ end;
上面这种给段落切换关键字**加分号**(`weakRef;` / `autoRef;`)的写法会编译失败,错误信息包含 `invalid class definition`。关键在分号:段落切换关键字要像 `public` / `private` 那样**不带分号**独占一行,带分号后被当成独立语句才报错。正确的段落式写法见下一段。 上面这种给段落切换关键字**加分号**(`weakRef;` / `autoRef;`)的写法会编译失败,错误信息包含 `invalid class definition`。关键在分号:段落切换关键字要像 `public` / `private` 那样**不带分号**独占一行,带分号后被当成独立语句才报错。正确的段落式写法见下一段。
### 段落式 `weakRef` / `autoRef` 的正确写法
段落式 `weakRef` / `autoRef`(不带分号)像 `public` / `private` 一样切换后续成员的弱引用属性: 段落式 `weakRef` / `autoRef`(不带分号)像 `public` / `private` 一样切换后续成员的弱引用属性:
代码块身份:可直接照写示例 代码块身份:可直接照写示例
@@ -1,10 +1,5 @@
# TSL 内置运行时对象 # TSL 内置运行时对象
文档类型:语法深水专题
是否可直接用于生成代码:仅部分
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇只收运行时内置对象的文档明确最小模型。这里讲的是语言运行时对象,不是金融业务 API。 这一篇只收运行时内置对象的文档明确最小模型。这里讲的是语言运行时对象,不是金融业务 API。
## 本篇职责 ## 本篇职责
@@ -76,7 +71,7 @@ writeLn(list["B"]);
- 依次输出 `2``bbb` - 依次输出 `2``bbb`
- 说明 `THashedStringList` 可按 `Name=Value` 的名称键读取值 - 说明 `THashedStringList` 可按 `Name=Value` 的名称键读取值
### `TStream` 家族的最小可靠入口:`TMemoryStream` ### `TStream` 家族的最小可靠入口:`TMemoryStream``TFileStream`
代码块身份:可直接照写示例 代码块身份:可直接照写示例
@@ -1,20 +1,15 @@
# TSL 矩阵深水专题 # TSL 矩阵深水专题
文档类型:语法深水专题
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇只讲矩阵专用语法主干:矩阵初始化、数列构造、矩阵逆/广义逆、矩阵乘除乘方、矩阵转置、矩阵拼接、矩阵尺寸与索引、矩阵遍历、子矩阵和 `mfind` 查找。它和 [12_matrix_and_collections.md](12_matrix_and_collections.md) 的分工是:`12` 讲普通数组与集合关系,这一篇讲矩阵专用构造、运算、遍历、子矩阵和矩阵查找接口。 这一篇只讲矩阵专用语法主干:矩阵初始化、数列构造、矩阵逆/广义逆、矩阵乘除乘方、矩阵转置、矩阵拼接、矩阵尺寸与索引、矩阵遍历、子矩阵和 `mfind` 查找。它和 [12_matrix_and_collections.md](12_matrix_and_collections.md) 的分工是:`12` 讲普通数组与集合关系,这一篇讲矩阵专用构造、运算、遍历、子矩阵和矩阵查找接口。
## 本篇职责 ## 本篇职责
回答怎样直接构造全零矩阵、全一矩阵、随机矩阵、单位矩阵、空矩阵和数列数组,怎样写矩阵逆/广义逆,怎样进行矩阵乘除乘方,怎样转置矩阵,怎样拼接矩阵,怎样拿到矩阵的行数、列数、行索引和列索引,怎样遍历矩阵、取/改子矩阵,以及怎样用 `mfind` 找到或替换符合条件的单元格”。 回答怎样直接构造全零矩阵、全一矩阵、随机矩阵、单位矩阵、空矩阵和数列数组,怎样写矩阵逆/广义逆,怎样进行矩阵乘除乘方,怎样转置矩阵,怎样拼接矩阵,怎样拿到矩阵的行数、列数、行索引和列索引,怎样遍历矩阵、取/改子矩阵,以及怎样用 `mfind` 找到或替换符合条件的单元格”。
## 智能体矩阵深水判断流程 ## 智能体矩阵深水判断流程
1. 先判断要写矩阵初始化、数列构造、矩阵逆/广义逆、矩阵尺寸与索引读取、矩阵遍历、子矩阵,还是 `mfind` 查找/替换。 1. 先判断要写矩阵初始化、数列构造、矩阵逆/广义逆、矩阵尺寸与索引读取、矩阵遍历、子矩阵,还是 `mfind` 查找/替换。
2. 基础数组矩阵样比较先回看 `12_matrix_and_collections.md` 2. 基础数组矩阵样比较的事实在 [12_matrix_and_collections.md](12_matrix_and_collections.md)
3. `mrows` / `mcols` / `msize` 等函数只照文档返回形态写。 3. `mrows` / `mcols` / `msize` 等函数只照文档返回形态写。
4. 需要逐单元执行语句块时用 `matrix::begin ... end`;需要把表达式结果写回每个单元时用 `matrix ::= expression` 4. 需要逐单元执行语句块时用 `matrix::begin ... end`;需要把表达式结果写回每个单元时用 `matrix ::= expression`
5. 需要遍历到嵌套数组最深层时用 `matrix:.begin ... end``matrix:.= expression` 5. 需要遍历到嵌套数组最深层时用 `matrix:.begin ... end``matrix:.= expression`
@@ -25,8 +20,8 @@
## 核心规则 ## 核心规则
- 矩阵初始化函数的参数规格使用 `tsl-api-reference` skill 按名查询;本页只保留矩阵行为示例返回形态边界。 - 矩阵初始化和基础函数的具体参数规格使用 `tsl-api-reference` skill 按名查询;本页只保留矩阵行为示例返回形态边界和跨函数通用的广播 / `ErrDefine` / `ErrReplace` 机制
- `zeros(...)``ones(...)``rand(...)``nils(...)``eye(...)` 都可以直接用于矩阵初始化。 - `zeros(...)``ones(...)``rand(...)``nils(...)``eye(...)` 都可以直接用于矩阵初始化;全 `nil` 单元的“空矩阵”用 `nils(...)` 构造
- `zeros(3)``ones(3)``nils(2)` 这类单参数写法可以直接生成一维结果。 - `zeros(3)``ones(3)``nils(2)` 这类单参数写法可以直接生成一维结果。
- `zeros(2, 3)``rand(2, 3)` 这类双参数写法可以直接生成二维矩阵。 - `zeros(2, 3)``rand(2, 3)` 这类双参数写法可以直接生成二维矩阵。
- `zeros(2, array("A", "B"))` 这种写法可以直接生成带列名的二维结果。 - `zeros(2, array("A", "B"))` 这种写法可以直接生成带列名的二维结果。
@@ -525,7 +520,7 @@ B
0 0
``` ```
### 矩阵乘法、除法、乘方:`:*``:/``:^` ### 矩阵乘法、除法、左除、乘方:`:*``:/``:\``:^`
`:*` 是矩阵乘法(区别于逐元素乘 `*`): `:*` 是矩阵乘法(区别于逐元素乘 `*`):
@@ -603,7 +598,7 @@ writeLn(result[1][0]);
说明: 说明:
- `:\` 左除用于解线性方程组 `A * X = B`,等价于 `X = A^(-1) * B` - `:\` 左除用于解线性方程组 AX = B(数学记法),等价于先求逆再矩阵乘:`!a :* b`
- **右侧 `b` 必须是列向量**(用 `array((4), (8))` 而非 `array(4, 8)` - **右侧 `b` 必须是列向量**(用 `array((4), (8))` 而非 `array(4, 8)`
- `a :\ b` 返回 `array((2.0), (2.0))`,即 `X` 的列向量 - `a :\ b` 返回 `array((2.0), (2.0))`,即 `X` 的列向量
- 当 A 行数 > 列数时返回最小二乘解,行数 < 列数时返回一个可行解 - 当 A 行数 > 列数时返回最小二乘解,行数 < 列数时返回一个可行解
@@ -699,10 +694,34 @@ array(2.0,NAN,3.0)
说明: 说明:
- `sqrt(data, 1, -999)` 中,第二参数 `ErrDefine=1` 表示允许 NIL 不允许错误 - `ErrDefine` 取值:`0`=不允许错误和 NIL(两者都替换为 `ErrReplace`)、`1`=允许 NIL 不允许错误NIL 保留,错误替换为 `ErrReplace`)、`2`=错误和 NIL 都保留原值(`ErrReplace` 不生效)
- `ErrReplace=-999` 是错误位置的替换值(本例中 `-1` 的平方根为错误,但 `ErrDefine=1` 保留为 NAN - 「错误」指输入无效(如 NIL、类型不符);数学定义域产生的 `NAN` 结果(如 `sqrt(-1)`)不属于错误位置,任何 `ErrDefine` 档位下都不会被 `ErrReplace` 替换,本例输出保留 `NAN` 即因如此
- `ErrDefine` 取值:`0`=不允许错误和 NIL、`1`=允许 NIL 不允许错误、`2`=错误值保留为原值 - 不带 `ErrDefine` / `ErrReplace` 时,无效输入会直接运行报错
- `ErrReplace``ErrDefine=0``1` 时生效,用于替换错误/NIL 位置
无效输入在不同 `ErrDefine` 档位下的替换差异:
代码块身份:可直接照写示例
```tsl
data := array(4, nil, "AAA", 9);
writeLn(tostn(sqrt(data, 0, -999)));
writeLn(tostn(sqrt(data, 1, -999)));
writeLn(tostn(sqrt(data, 2, -999)));
```
代码块身份:输出片段
```text
array(2.0,-999,-999,3.0)
array(2.0,NIL,-999,3.0)
array(2.0,NIL,"AAA",3.0)
```
说明:
- `ErrDefine=0` 把 NIL 和错误值 `"AAA"` 都替换成 `-999`
- `ErrDefine=1` 保留 NIL、只替换错误值
- `ErrDefine=2` 全部保留原值
另一个例子: 另一个例子:
@@ -871,7 +890,7 @@ writeLn("(1,3):", result[1][3]);
(1,3): 8 (1,3): 8
``` ```
`:|` 非完全矩阵补 `nil``|` 不补: 普通 `array` 非完全矩阵`:|` 缺位`nil``|` 直接顺接不补
代码块身份:可直接照写示例 代码块身份:可直接照写示例
@@ -887,16 +906,17 @@ writeLn("bar 结果 (1,2):", bar_result[1][2]);
代码块身份:输出片段 代码块身份:输出片段
```text ```text
colon 结果列数: 6 colon 结果列数:6
colon (1,2): nil colon (1,2):<NIL>
bar 结果 (1,2): 1 bar 结果 (1,2):2
``` ```
说明: 说明:
- `union` / `&=` 用于行方向拼接 - `union` / `&=` 用于行方向拼接
- `|` / `:|` 用于列方向拼接 - `|` / `:|` 用于列方向拼接
- 非完全矩阵(行长度不一致)`:|` 会在缺位补 `nil`,而 `|` 不补 - 普通 `array`非完全矩阵(行长度不一致)`:|` 按原列位置对齐、缺位补 `nil``|` 把两行元素直接顺接(本例第 1 行变成 `(2,3,2,3)`,所以 `[1][2]``2`
- 上述差异只适用于普通 `array``FMArray``|``:|` 行为一致且行数不齐时补 `0`,见 [23_fmarray.md](23_fmarray.md)
- 对应的复合赋值形式:`&=`(行并)、`|=`(列并)、`:|=`(列并补 nil - 对应的复合赋值形式:`&=`(行并)、`|=`(列并)、`:|=`(列并补 nil
## 默认生成模板 ## 默认生成模板
@@ -923,7 +943,7 @@ matrix_value := zeros(2, 3);
- 用 `::` 期待遍历到任意深度;深度遍历使用 `:.` - 用 `::` 期待遍历到任意深度;深度遍历使用 `:.`
- 子矩阵赋值时用形状不匹配的矩阵硬塞。 - 子矩阵赋值时用形状不匹配的矩阵硬塞。
- 把 `union` 的复合赋值写成 `union=`;正确写法是 `&=` - 把 `union` 的复合赋值写成 `union=`;正确写法是 `&=`
- 一维数组直接 `union` 期待得到二维结果;需要先双转置 ` `b `` 变成行向量。 - 一维数组直接 `union` 期待得到二维结果;需要先对它连用两次转置前缀(双转置)变成行向量。
- 在基础函数异常参数时用分号分隔;正确写法用逗号:`sqrt(data, 1, -999)` - 在基础函数异常参数时用分号分隔;正确写法用逗号:`sqrt(data, 1, -999)`
代码块身份:反例 / 不可照写 代码块身份:反例 / 不可照写
@@ -1,10 +1,5 @@
# TSL FMArray # TSL FMArray
文档类型:语法深水专题
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
这一篇只讲 `FMArray` 文档主干能力:怎样构造 `FMArray`、怎样判断类型、怎样和 `Array` 互转、怎样读取尺寸、做基础运算、做多维转置与维度交换、做矩阵连接、参与 `select/mselect`,以及 `insert/delete/update` 语法边界。 这一篇只讲 `FMArray` 文档主干能力:怎样构造 `FMArray`、怎样判断类型、怎样和 `Array` 互转、怎样读取尺寸、做基础运算、做多维转置与维度交换、做矩阵连接、参与 `select/mselect`,以及 `insert/delete/update` 语法边界。
## 本篇职责 ## 本篇职责
@@ -33,7 +28,7 @@
- `union2` 支持 `FMArray` / `Array` 混合运算;结果类型跟随左值。 - `union2` 支持 `FMArray` / `Array` 混合运算;结果类型跟随左值。
- 对超过二维的 `FMArray`,反引号转置会把全部维度倒置;`mswap` 可只交换指定维度。 - 对超过二维的 `FMArray`,反引号转置会把全部维度倒置;`mswap` 可只交换指定维度。
- `union` 可做按行连接且不去重,但列结构必须兼容。 - `union` 可做按行连接且不去重,但列结构必须兼容。
- `|``:|` 都可做矩阵并右方(按列连接);行数不一致时会用 `0` 补齐,并且两者表现一致。 - `|``:|` 都可做矩阵并右方(按列连接);`FMArray`行数不一致时会用 `0` 补齐,并且两者表现一致(普通 `array` 上两者行为不同:`:|` 缺位补 `nil``|` 直接顺接,见 [22_matrix_deep_dive.md](22_matrix_deep_dive.md)
- 对 `FMArray` 做普通 `select` 不会保留 `FMArray` 类型;`mselect` 也不会返回 `dataType=27` - 对 `FMArray` 做普通 `select` 不会保留 `FMArray` 类型;`mselect` 也不会返回 `dataType=27`
- `insert into a array(...)``delete from a where ...;``update a set ... where ... end;` 都能作用于 `FMArray`,但三者的收尾形式并不完全相同。 - `insert into a array(...)``delete from a where ...;``update a set ... where ... end;` 都能作用于 `FMArray`,但三者的收尾形式并不完全相同。
@@ -76,6 +71,11 @@ writeLn(f2[0,0], ',', f2[0,1], ',', f2[1,0], ',', f2[1,1]);
0 0
1 1
1 1
3
1,2,3
2
2
1,2,3,4
``` ```
### `mInit``mInitDiag``mRand` ### `mInit``mInitDiag``mRand`
@@ -1,10 +1,5 @@
# TSL 对象重载与迭代 # TSL 对象重载与迭代
文档类型:语法深水专题
是否可直接用于生成代码:仅部分
是否含可直接照写示例:是
是否含不可照写反例:否
这一篇只讲对象直接参与语言级操作的文档能力:基础算符重载、`[]` 重载、`for in` 重载,以及 `mrows` / `mcols` / `msize` 这类矩阵关键字重载。 这一篇只讲对象直接参与语言级操作的文档能力:基础算符重载、`[]` 重载、`for in` 重载,以及 `mrows` / `mcols` / `msize` 这类矩阵关键字重载。
## 本篇职责 ## 本篇职责
@@ -90,7 +85,7 @@ end;
- 依次输出 `20``0``1``1` - 依次输出 `20``0``1``1`
- 说明 `obj + value` 可以通过成员 `operator +` 接管 - 说明 `obj + value` 可以通过成员 `operator +` 接管
- 说明带 `is_left` 的比较算符可以同时处理 `obj < value``value < obj` - 说明带 `is_left` 的比较算符可以同时处理 `obj < value``value < obj`
- 私有成员用尾随下划线的 `real_part_` / `imaginary_part_`,对外`PascalCase` property 暴露 - 本例私有成员 `real_part_` / `imaginary_part_`,对外通过 property 暴露(命名风格本身不属于本页事实)
代码块身份:输出片段 代码块身份:输出片段
@@ -137,18 +132,32 @@ end;
### `[]` 重载:`operator[0]` / `operator[1]` ### `[]` 重载:`operator[0]` / `operator[1]`
沿用上一段的 `bb` 类与测试主体,只把读取签名从 `function operator[](index);` 改成 `function operator[0](index, s1);` 读取签名也可以写成 `function operator[0](index, s1);`(与上一段 `operator[]` 等价的另一组写法)
代码块身份:配置片段 / 概念骨架 代码块身份:可直接照写示例
```tsl ```tsl
t := array(1, 2, 3, 4, 5);
b := new IndexableBox(t);
writeLn(b[2]);
b[3] := 999;
writeLn(b.data[3]);
type IndexableBox = class type IndexableBox = class
public public
// 其余字段、create()、operator[1] 和测试主体同上一段 data;
function create(v);
begin
data := v;
end;
function operator[0](index, s1); function operator[0](index, s1);
begin begin
return data[index]; return data[index];
end; end;
function operator[1](index, v);
begin
data[index] := v;
end;
end; end;
``` ```
@@ -207,7 +216,7 @@ end;
- 说明 `operator for(flag)` 可以重载 `for in` - 说明 `operator for(flag)` 可以重载 `for in`
- 也说明同一个对象可以按返回值形态同时支持“单变量遍历”和“索引 + 值遍历” - 也说明同一个对象可以按返回值形态同时支持“单变量遍历”和“索引 + 值遍历”
### `mrows` / `mcols` / `msize` 重载 ### `mrows` / `mcols` / `msize` 重载(带参形态,支持下标列表)
代码块身份:可直接照写示例 代码块身份:可直接照写示例
@@ -263,7 +272,7 @@ end;
- 说明 `mrows(obj)``mcols(obj)``msize(obj)` 都可由对象重载接管 - 说明 `mrows(obj)``mcols(obj)``msize(obj)` 都可由对象重载接管
- 说明对象方法式调用 `obj.mcols(1)` 也可继续取得列下标列表 - 说明对象方法式调用 `obj.mcols(1)` 也可继续取得列下标列表
### `++` `+=` ### `++` / `+=` / `--` / `-=` 自增自减重载
代码块身份:可直接照写示例 代码块身份:可直接照写示例
@@ -306,8 +315,8 @@ end;
结果说明: 结果说明:
- 依次输出 `11``11``12``17` - 依次输出 `11``11``12``17`
- 说明前置 `++` 会直接修改对象状态 - 前置 `++counter``v``0` 分支,直接原地修改对象状态
- 说明这个最小样例里,后置 `counter++` 返回的是递增前快照 - 后置 `counter++` `v = 0` 分支:分支构造并返回递增后的新对象,运行时用它更新 `counter` 本身,而后置表达式的取值是递增前的原对象(所以 `c.data``11``counter.data``12`
- 说明 `operator += (v)` 可以接管 `counter += 5` - 说明 `operator += (v)` 可以接管 `counter += 5`
`--``-=` 与之对称: `--``-=` 与之对称:
@@ -349,7 +358,7 @@ end;
- 输出 `7` - 输出 `7`
- 说明 `operator -= (v)` 可以接管 `counter -= 3` - 说明 `operator -= (v)` 可以接管 `counter -= 3`
- `operator--(v)``operator++(v)` 结构对称:`v = 0` 分支返回递减前快照,否则原地递减 - `operator--(v)``operator++(v)` 结构对称:`v = 0`(后置)分支构造并返回递减后的新对象供运行时更新变量本身,后置表达式的取值是递减前的原对象;`v``0`(前置)时原地递减
### 二进制函数重载:`operator funcName` ### 二进制函数重载:`operator funcName`
@@ -435,7 +444,8 @@ end;
结果说明: 结果说明:
- 输出 `array(1,314)``ret``1`(转换成功),出参 `msg``314` - 输出 `array(1,314)``ret``1`(转换成功),出参 `msg``314`
- 说明类内需要调用被重载的同名全局函数时,用 `::` 前缀指定全局版本,否则会递归回自己 - 重载的分派按实参类型决定:本类对象走重载版本,非对象实参(如 `value` 是字符串)走全局版本
- 因此当被重载的实参已经不是本类对象(如上面 `DateToStr` 两例把对象拆成基础类型再调用)时,不加 `::` 也不会递归;只有当传给同名函数的实参仍是本类对象时才会递归回自己,这时用 `::` 前缀强制指定全局版本
- 重载函数支持通过参数传出返回值(`msg` 作为出参被赋值) - 重载函数支持通过参数传出返回值(`msg` 作为出参被赋值)
### `::` / `:.` 遍历重载与 `mcell` / `mrow` / `mcol` / `mIndexCount` / `mIndex` ### `::` / `:.` 遍历重载与 `mcell` / `mrow` / `mcol` / `mIndexCount` / `mIndex`
@@ -512,7 +522,7 @@ end;
- `:.`(深度遍历)重载方式与 `::` 相同,把内部 `data::begin ... end` 换成 `data:.begin ... end` 即可 - `:.`(深度遍历)重载方式与 `::` 相同,把内部 `data::begin ... end` 换成 `data:.begin ... end` 即可
- 遍历体里用到的 `mcell` / `mrow` / `mcol` / `mIndexCount` / `mIndex` 必须各自重载,否则报 `override function not found` - 遍历体里用到的 `mcell` / `mrow` / `mcol` / `mIndexCount` / `mIndex` 必须各自重载,否则报 `override function not found`
### 关键字函数重载:`msize` / `mrows` / `mcols` ### `mrows` / `mcols` / `msize` 重载(无参形态,只取数量且免 `::`
`msize` / `mrows` / `mcols` 这类关键字函数也能重载,形态同二进制函数重载 `[class] function operator KeyWord(...)`,但**关键字重载不需要 `::` 指定全局** `msize` / `mrows` / `mcols` 这类关键字函数也能重载,形态同二进制函数重载 `[class] function operator KeyWord(...)`,但**关键字重载不需要 `::` 指定全局**
+106 -16
View File
@@ -29,6 +29,11 @@ ALLOWED_IDENTITIES = {
} }
ROUTER_PHRASES = ("路由中心", "选择一个主专题", "候选页继续判断") ROUTER_PHRASES = ("路由中心", "选择一个主专题", "候选页继续判断")
EXCLUDED_REFERENCE_FILES = {"index.md"} EXCLUDED_REFERENCE_FILES = {"index.md"}
SUSPICIOUS_FENCE_RE = re.compile(r"^(?:\s+`{3}|`{4,})")
WRITE_PRELUDE_ANCHORS = (
("02_core_model.md", "文件模型核心规则"),
("01_quickstart.md", "语言核心事实速查"),
)
HEADING_TOKEN_SCORE = 12 HEADING_TOKEN_SCORE = 12
HEADING_EXACT_SCORE = 20 HEADING_EXACT_SCORE = 20
@@ -249,16 +254,24 @@ def _identity_problems(page: Path, lines: list[str]) -> list[ValidationProblem]:
problems.append(ValidationProblem(page, index, f"未知身份:{identity}")) problems.append(ValidationProblem(page, index, f"未知身份:{identity}"))
in_fence = False in_fence = False
for index, line in enumerate(lines): for index, line in enumerate(lines):
if not FENCE_RE.match(line): if FENCE_RE.match(line):
if in_fence:
in_fence = False
else:
if _associated_identity(lines, index) is None:
problems.append(
ValidationProblem(
page, index + 1, "每个代码围栏必须关联恰好一个代码块身份"
)
)
in_fence = True
continue continue
if in_fence: if not in_fence and SUSPICIOUS_FENCE_RE.match(line):
in_fence = False
continue
if _associated_identity(lines, index) is None:
problems.append( problems.append(
ValidationProblem(page, index + 1, "每个代码围栏必须关联恰好一个代码块身份") ValidationProblem(
page, index + 1, "不支持的代码围栏形态(缩进围栏或四个及以上反引号)"
)
) )
in_fence = True
if in_fence: if in_fence:
problems.append(ValidationProblem(page, len(lines), "代码围栏未闭合")) problems.append(ValidationProblem(page, len(lines), "代码围栏未闭合"))
return problems return problems
@@ -268,8 +281,17 @@ def _local_link_problems(
page: Path, text: str, references_dir: Path page: Path, text: str, references_dir: Path
) -> list[ValidationProblem]: ) -> list[ValidationProblem]:
problems: list[ValidationProblem] = [] problems: list[ValidationProblem] = []
without_fences = re.sub(r"```.*?```", "", text, flags=re.DOTALL) # Blank out fenced/inline code without changing line offsets, so reported
searchable_markdown = re.sub(r"`[^`\n]*`", "", without_fences) # line numbers line up with the original file.
without_fences = re.sub(
r"```.*?```",
lambda match: re.sub(r"[^\n]", " ", match.group(0)),
text,
flags=re.DOTALL,
)
searchable_markdown = re.sub(
r"`[^`\n]*`", lambda match: " " * len(match.group(0)), without_fences
)
for match in MARKDOWN_LINK_RE.finditer(searchable_markdown): for match in MARKDOWN_LINK_RE.finditer(searchable_markdown):
target = match.group(1).strip().split(maxsplit=1)[0].strip("<>") target = match.group(1).strip().split(maxsplit=1)[0].strip("<>")
if target.startswith(("#", "http://", "https://", "mailto:")): if target.startswith(("#", "http://", "https://", "mailto:")):
@@ -313,6 +335,22 @@ def validate_references(
if section.id in ids: if section.id in ids:
problems.append(ValidationProblem(section.page, 1, f"重复 section ID{section.id}")) problems.append(ValidationProblem(section.page, 1, f"重复 section ID{section.id}"))
ids[section.id] = section ids[section.id] = section
for page_name, heading in WRITE_PRELUDE_ANCHORS:
page_sections = [
section for section in sections if section.page.name == page_name
]
# Only enforce the anchor when the page is present, so validating a
# synthetic references dir (tests) does not demand the bundled pages.
if page_sections and not any(
heading in section.heading_path for section in page_sections
):
problems.append(
ValidationProblem(
references_dir / page_name,
1,
f"write 模式前置章节缺失:{page_name} 的「{heading}",
)
)
return problems return problems
@@ -394,12 +432,8 @@ def _score_section(section: Section, query: str, mode: str) -> ScoreBreakdown:
def _write_prelude(sections: list[Section]) -> list[Section]: def _write_prelude(sections: list[Section]) -> list[Section]:
preferred = [
("02_core_model.md", "文件模型核心规则"),
("01_quickstart.md", "语言核心事实速查"),
]
prelude: list[Section] = [] prelude: list[Section] = []
for page_name, heading in preferred: for page_name, heading in WRITE_PRELUDE_ANCHORS:
match = next( match = next(
( (
section section
@@ -413,6 +447,43 @@ def _write_prelude(sections: list[Section]) -> list[Section]:
return prelude return prelude
STRING_LITERAL_RE = re.compile(r'"[^"\n]*"|\'[^\'\n]*\'')
def _bare_tokens(tokens: set[str]) -> set[str]:
# ASCII_TOKEN_RE 的连续字符类会把尾缀符号吞进 token(如编译开关
# `{$varByRef-}` 产出 `varbyref-`);按剥掉尾部符号的裸形态比较。
return {token.rstrip(".$:+-") for token in tokens} - {""}
def _ascii_anchor(section: Section, ascii_tokens: set[str]) -> bool:
# 只有足够长的标识符按标识符位置命中(标题、行内代码或剥离字符串
# 字面量后的围栏代码)才豁免中文门控;短 token 与字符串样例数据
# (如 "XYZ"、"xyz*")不算点名,避免样例值驱动召回。
anchor_tokens = {
token for token in _bare_tokens(ascii_tokens) if len(token) >= 4
}
if not anchor_tokens:
return False
heading_tokens = set(
ASCII_TOKEN_RE.findall(normalize("\n".join(section.heading_path)))
)
code_text = "\n".join(
(*INLINE_CODE_RE.findall(section.body), *FENCED_CODE_RE.findall(section.body))
)
code_tokens = set(
ASCII_TOKEN_RE.findall(normalize(STRING_LITERAL_RE.sub(" ", code_text)))
)
return bool(anchor_tokens & _bare_tokens(heading_tokens | code_tokens))
def _related_sections(a: Section, b: Section) -> bool:
if a.page != b.page or len(a.heading_path) == len(b.heading_path):
return False
shorter, longer = sorted((a.heading_path, b.heading_path), key=len)
return longer[: len(shorter)] == shorter
def query_sections( def query_sections(
query: str, query: str,
mode: str, mode: str,
@@ -438,7 +509,14 @@ def query_sections(
minimum_score = MIXED_QUERY_MIN_SCORE if ascii_tokens and has_chinese else 1 minimum_score = MIXED_QUERY_MIN_SCORE if ascii_tokens and has_chinese else 1
ranked: list[QueryMatch] = [] ranked: list[QueryMatch] = []
for section in sections: for section in sections:
if ascii_tokens and has_chinese and not _has_chinese_context(section, query): # 中文上下文门控只裁剪正文级 ASCII 噪声;标识符在标题或代码里
# 精确命中的 section 不因中文措辞不同而被丢弃。
if (
ascii_tokens
and has_chinese
and not _ascii_anchor(section, ascii_tokens)
and not _has_chinese_context(section, query)
):
continue continue
score = _score_section(section, query, mode) score = _score_section(section, query, mode)
if score.lexical_total < minimum_score: if score.lexical_total < minimum_score:
@@ -451,7 +529,17 @@ def query_sections(
match.section.id, match.section.id,
) )
) )
matches = ranked[:limit] # H2 聚合 section 的正文逐字包含其 H3 子节;父子同时入选时只保留
# 排名更高的一个,避免同一内容重复返回。
matches: list[QueryMatch] = []
for match in ranked:
if any(
_related_sections(match.section, kept.section) for kept in matches
):
continue
matches.append(match)
if len(matches) == limit:
break
prelude = _write_prelude(all_sections) if mode == "write" else [] prelude = _write_prelude(all_sections) if mode == "write" else []
match_ids = {match.section.id for match in matches} match_ids = {match.section.id for match in matches}
prelude = [section for section in prelude if section.id not in match_ids] prelude = [section for section in prelude if section.id not in match_ids]
@@ -535,6 +623,8 @@ def main(argv: list[str] | None = None) -> int:
parser.error("--limit must be between 1 and 10") parser.error("--limit must be between 1 and 10")
if args.query is not None and args.mode is None: if args.query is not None and args.mode is None:
parser.error("--mode is required with --query") parser.error("--mode is required with --query")
if args.mode is not None and args.query is None:
parser.error("--mode only applies to --query")
if args.check: if args.check:
problems = validate_references(args.references_dir) problems = validate_references(args.references_dir)
for problem in problems: for problem in problems:
+86 -12
View File
@@ -6,13 +6,22 @@ import tempfile
import unittest import unittest
from pathlib import Path from pathlib import Path
from scripts.build_tsl_playbook import build_agents_text
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts" / "build_tsl_playbook.py" SCRIPT = ROOT / "scripts" / "build_tsl_playbook.py"
SYNC_WORKFLOW = ROOT / ".gitea" / "workflows" / "sync-tsl-playbook.yml" SYNC_WORKFLOW = ROOT / ".gitea" / "workflows" / "sync-tsl-playbook.yml"
README = ROOT / "README.md"
class BuildTslPlaybookTests(unittest.TestCase): class BuildTslPlaybookTests(unittest.TestCase):
def test_readme_names_tsl_syntax_skill_as_unique_fact_owner(self):
text = README.read_text(encoding="utf-8")
self.assertIn("TSL 语法事实唯一由 `tsl-syntax-reference` 管理", text)
self.assertIn("TSL 领域路由与事实边界", text)
def test_builds_minimal_tsl_playbook_tree(self): def test_builds_minimal_tsl_playbook_tree(self):
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:
output = Path(tmp_dir) / "tsl-playbook" output = Path(tmp_dir) / "tsl-playbook"
@@ -37,6 +46,9 @@ class BuildTslPlaybookTests(unittest.TestCase):
self.assertTrue((syntax_skill / "SKILL.md").is_file()) self.assertTrue((syntax_skill / "SKILL.md").is_file())
self.assertTrue((syntax_skill / "scripts" / "lookup.py").is_file()) self.assertTrue((syntax_skill / "scripts" / "lookup.py").is_file())
self.assertFalse((syntax_skill / "references" / "index.md").exists()) self.assertFalse((syntax_skill / "references" / "index.md").exists())
self.assertTrue(
(output / "skills" / "tsl-syntax-reference" / "SKILL.md").is_file()
)
self.assertTrue( self.assertTrue(
(output / "skills" / "tsl-api-reference" / "SKILL.md").is_file() (output / "skills" / "tsl-api-reference" / "SKILL.md").is_file()
) )
@@ -61,9 +73,54 @@ class BuildTslPlaybookTests(unittest.TestCase):
output_docs = count_files(output / "docs" / "tsl") output_docs = count_files(output / "docs" / "tsl")
self.assertEqual(output_docs, source_docs) self.assertEqual(output_docs, source_docs)
source_skill = count_files(ROOT / "skills" / "tsl-api-reference") for skill_name in ("tsl-syntax-reference", "tsl-api-reference"):
output_skill = count_files(output / "skills" / "tsl-api-reference") source_skill = count_files(ROOT / "skills" / skill_name)
self.assertEqual(output_skill, source_skill) output_skill = count_files(output / "skills" / skill_name)
self.assertEqual(output_skill, source_skill)
def test_missing_tsl_skill_reports_specific_source_path(self):
for skill_name in ("tsl-syntax-reference", "tsl-api-reference"):
with self.subTest(skill=skill_name), tempfile.TemporaryDirectory() as tmp_dir:
repo = Path(tmp_dir) / "repo"
repo.mkdir()
copy_required_sources(repo)
missing_path = repo / "skills" / skill_name
shutil.rmtree(missing_path)
result = subprocess.run(
[
sys.executable,
str(repo / "scripts" / "build_tsl_playbook.py"),
"--output",
str(repo / "output"),
],
capture_output=True,
text=True,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn(str(missing_path), result.stderr)
def test_build_agents_text_only_rewrites_title_and_trailing_newline(self):
marker = (
"- 需要任何 TSL 语法、文件模型、函数、模块或金融数据事实时,"
"第一跳统一进 `docs/tsl/index.md`,由它路由到具体页;"
"不在本文件内猜文件路径,也不全目录搜索。"
)
with tempfile.TemporaryDirectory() as tmp_dir:
ruleset = Path(tmp_dir) / "index.md"
ruleset.write_text(
f"# TSL 智能体规则\n\n{marker}\n\n",
encoding="utf-8",
newline="\n",
)
agents_text = build_agents_text(ruleset)
self.assertEqual(
agents_text,
f"# TSL Agent Instructions\n\n{marker}\n",
)
def test_sync_workflow_does_not_remove_entire_target_branch(self): def test_sync_workflow_does_not_remove_entire_target_branch(self):
text = SYNC_WORKFLOW.read_text(encoding="utf-8") text = SYNC_WORKFLOW.read_text(encoding="utf-8")
@@ -109,6 +166,7 @@ class BuildTslPlaybookTests(unittest.TestCase):
for path in ( for path in (
"AGENTS.md", "AGENTS.md",
"docs/tsl/index.md", "docs/tsl/index.md",
"skills/tsl-syntax-reference/SKILL.md",
"skills/tsl-api-reference/SKILL.md", "skills/tsl-api-reference/SKILL.md",
): ):
git(repo, "cat-file", "-e", f"HEAD:{path}") git(repo, "cat-file", "-e", f"HEAD:{path}")
@@ -125,6 +183,7 @@ class BuildTslPlaybookTests(unittest.TestCase):
for path in ( for path in (
"AGENTS.md", "AGENTS.md",
"docs/tsl/index.md", "docs/tsl/index.md",
"skills/tsl-syntax-reference/SKILL.md",
"skills/tsl-api-reference/SKILL.md", "skills/tsl-api-reference/SKILL.md",
): ):
git(repo, "cat-file", "-e", f"HEAD:{path}") git(repo, "cat-file", "-e", f"HEAD:{path}")
@@ -164,20 +223,18 @@ def run_sync(repo: Path) -> None:
env = os.environ.copy() env = os.environ.copy()
env.update( env.update(
{ {
"REPO_DIR": str(repo),
"TARGET_BRANCH": "tsl-playbook", "TARGET_BRANCH": "tsl-playbook",
"COMMIT_AUTHOR_NAME": "test", "COMMIT_AUTHOR_NAME": "test",
"COMMIT_AUTHOR_EMAIL": "test@example.invalid", "COMMIT_AUTHOR_EMAIL": "test@example.invalid",
} }
) )
with tempfile.NamedTemporaryFile( script_path = repo / ".sync-test.sh"
"w", suffix=".sh", encoding="utf-8", newline="\n", delete=False script_path.write_text(
) as script_file: extract_sync_workflow_script(), encoding="utf-8", newline="\n"
script_file.write(extract_sync_workflow_script()) )
script_path = script_file.name
try: try:
result = subprocess.run( result = subprocess.run(
["bash", script_path], ["bash", script_path.name],
cwd=repo, cwd=repo,
env=env, env=env,
capture_output=True, capture_output=True,
@@ -186,7 +243,7 @@ def run_sync(repo: Path) -> None:
errors="replace", errors="replace",
) )
finally: finally:
os.unlink(script_path) script_path.unlink(missing_ok=True)
if result.returncode != 0: if result.returncode != 0:
raise AssertionError(result.stderr + result.stdout) raise AssertionError(result.stderr + result.stdout)
@@ -223,6 +280,17 @@ def copy_required_sources(repo: Path) -> None:
"print('lookup')\n", encoding="utf-8", newline="\n" "print('lookup')\n", encoding="utf-8", newline="\n"
) )
syntax_skill = repo / "skills" / "tsl-syntax-reference"
(syntax_skill / "references").mkdir(parents=True)
(syntax_skill / "SKILL.md").write_text(
"---\nname: tsl-syntax-reference\n---\n",
encoding="utf-8",
newline="\n",
)
(syntax_skill / "references" / "index.md").write_text(
"# TSL Syntax Reference\n", encoding="utf-8", newline="\n"
)
(repo / "rulesets" / "tsl").mkdir(parents=True) (repo / "rulesets" / "tsl").mkdir(parents=True)
(repo / "rulesets" / "tsl" / "index.md").write_text( (repo / "rulesets" / "tsl" / "index.md").write_text(
"# TSL Agent Instructions\n", encoding="utf-8", newline="\n" "# TSL Agent Instructions\n", encoding="utf-8", newline="\n"
@@ -230,7 +298,13 @@ def copy_required_sources(repo: Path) -> None:
def count_files(path: Path) -> int: def count_files(path: Path) -> int:
return sum(1 for item in path.rglob("*") if item.is_file()) return sum(
1
for item in path.rglob("*")
if item.is_file()
and "__pycache__" not in item.parts
and item.suffix != ".pyc"
)
def extract_sync_workflow_script() -> str: def extract_sync_workflow_script() -> str: