📝 docs(tsl-syntax-reference): document verified facts from FAQ corpus
从 FAQ 语料(2018 篇镜像)与参考页做完 169 条逐条差集,补入 6 组此前未收录的语言 事实。每条先用 TSL.exe 实跑观察行为,再收敛成 39 条断言的回归门,在 TSLGen2、 AnalyseNG.NET、TSL 三个解释器上均通过后才写入。 - 16 页:0/0 得 nan、2/0 得 inf;nan 的 = > < >= <= 恒假而 <> 为真;nan 沿算术 传播;nil 在乘除减里按 0 参与 - 04 页:整体赋值免声明,但对未建立的变量写单元格赋值会报「变量类型不是Array」 - 07 页:相邻两个 if 是两条独立语句都会执行;for..in 单控制变量拿到的是键不是值; 二维数组按行推进,行本身是数组(新增 syntax-07-013) - 21 页:子矩阵赋值语句求值为 0;t[:,:] 按位置复制不按列名对齐(新增 syntax-21-019);| 列并时同名字符串列键由右操作数覆盖(新增 syntax-21-018) - 13 页:select 输出列表同名列后者覆盖前者,须用 as 改名(新增 syntax-13-032) 三条 FAQ 说法实跑不复现,故未写入:12574 的括号消歧、19857 的 -2147483648 字面量限制、31474 的 :. bugfix 记录。15305/11045 的用户/公用/共享函数同名解析 优先级需要服务端多账号环境,本地无法验证,仍为已知缺口。 lexicon.json 为 16 页补两个页级 alias,使 nan/nil 语义能被自然语言查询命中。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -111,7 +111,12 @@
|
||||
"注释怎么写",
|
||||
"嵌套注释"
|
||||
],
|
||||
"16_types_and_conversions.md": ["字符串转整数", "类型转换"],
|
||||
"16_types_and_conversions.md": [
|
||||
"字符串转整数",
|
||||
"类型转换",
|
||||
"nan 参与运算",
|
||||
"nil 参与计算"
|
||||
],
|
||||
"17_external_calls_and_threads.md": [
|
||||
"调用 dll",
|
||||
"dll",
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
- 默认变量模型是“直接赋值即得到变量”,不要求先写 `var`。
|
||||
- 例如:`a := 1; b := array(1, 2, 3);` 这种写法可直接编译。
|
||||
- 这条只覆盖整体赋值;对还没建立的变量直接写单元格赋值(`q[0][0] := 1;`)会在运行时报
|
||||
`变量类型不是Array`,要先用 `q := array();` 建立容器再写下标。
|
||||
- `var name;` 只作为显式声明写法,不能当成默认必需步骤。
|
||||
- `const name = value;` 是常量初始化的默认生成写法;`const` 不能只声明名字而不初始化。
|
||||
- 文档规则:
|
||||
@@ -73,6 +75,40 @@ var a;
|
||||
a := 1;
|
||||
```
|
||||
|
||||
整体赋值免声明,但单元格赋值不免——容器要先建立:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
whole := rand(3, 3);
|
||||
writeLn(mRows(whole));
|
||||
cell := array();
|
||||
cell[0][0] := 1;
|
||||
writeLn(cell[0][0]);
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 依次输出 `3`、`1`
|
||||
- `whole := rand(3, 3)` 说明整体接一个数组不需要先声明
|
||||
- `cell := array();` 这一行不能省;省掉后 `cell[0][0] := 1;` 会在运行时报
|
||||
`变量类型不是Array`,因为下标赋值需要一个已经是数组的容器
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
3
|
||||
1
|
||||
```
|
||||
|
||||
代码块身份:反例 / 不可照写
|
||||
|
||||
```text
|
||||
q[0][0] := 1;
|
||||
```
|
||||
|
||||
对还没建立的变量直接写单元格赋值会运行时报 `变量类型不是Array`;先写 `q := array();`。
|
||||
|
||||
`{$explicit+}` 下的显式声明:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
@@ -393,6 +429,7 @@ items := array(1, 2, 3);
|
||||
<!-- section-id: syntax-04-008 -->
|
||||
|
||||
- 以为普通变量必须先写 `var` 才能使用。
|
||||
- 把“整体赋值免声明”推广到单元格赋值,直接对未建立的变量写 `q[0][0] := 1;`。
|
||||
- 把常量初始化写成普通变量赋值风格。
|
||||
- 以为 `const =` 顶层单独写一行就一定成立。
|
||||
- 以为 `const` 可以只声明名字,不写初始化表达式。
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
<!-- section-id: syntax-07-002 -->
|
||||
|
||||
- `if ... then ... else ...` 默认写成块式分支:`then begin ... end else begin ... end`。
|
||||
- 两个相邻的 `if ... then begin ... end` 是两条**独立**语句,两个条件都会求值、都可能执行;只有写了 `else` 才是互斥分支。
|
||||
- 块式分支内部的普通语句必须用分号结尾。
|
||||
- 控制流块的 `begin ... end` 后可以加分号也可以不加(语法都允许)。
|
||||
- `for` 支持 `to`、`downto`、可选 `step`,以及 `for i, v in array` 遍历。
|
||||
- `for ... in` 也可以只写一个控制变量:`for i in array do` 里的 `i` 是行下标/键,不是行值;要拿到行值就写两个控制变量。
|
||||
- 二维数组按 `for i, v in array` 遍历时按行推进,`v` 本身是整行数组;要落到单元格需要再嵌套一层 `for ... in`。
|
||||
- 计数 `for` 的初值、终值和步长确定后,循环次数随之固定;循环体内不要给控制变量赋值。
|
||||
- `for i, v in array` 遍历期间,不要修改被遍历数组或其中元素。
|
||||
- `while` 和 `repeat ... until` 都可直接使用;`repeat` 至少会先执行一轮再判断结束条件。
|
||||
@@ -77,6 +80,47 @@ writeLn(counter);
|
||||
0
|
||||
```
|
||||
|
||||
相邻两个 `if` 与 `if ... else` 的区别:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
trace := "";
|
||||
if 1 then
|
||||
begin
|
||||
trace := trace + "A";
|
||||
end
|
||||
if 1 then
|
||||
begin
|
||||
trace := trace + "B";
|
||||
end
|
||||
writeLn(trace);
|
||||
|
||||
trace2 := "";
|
||||
if 1 then
|
||||
begin
|
||||
trace2 := trace2 + "A";
|
||||
end
|
||||
else
|
||||
begin
|
||||
trace2 := trace2 + "B";
|
||||
end
|
||||
writeLn(trace2);
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 第一段输出 `AB`:两个 `if` 是两条独立语句,条件都为真所以都执行
|
||||
- 第二段输出 `A`:`if ... else` 是一条语句,两支互斥
|
||||
- 所以“多个条件里只能命中一个”必须写 `else`,靠相邻 `if` 排列不成立
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
AB
|
||||
A
|
||||
```
|
||||
|
||||
### `for` 的几种主干写法
|
||||
|
||||
<!-- section-id: syntax-07-005 -->
|
||||
@@ -166,6 +210,73 @@ for i, value in numbers do
|
||||
230
|
||||
```
|
||||
|
||||
### `for ... in` 的控制变量个数
|
||||
|
||||
<!-- section-id: syntax-07-013 -->
|
||||
|
||||
<!-- tags: 遍历只写一个变量, 键还是值, 遍历二维数组, 每行是数组, 嵌套遍历 -->
|
||||
|
||||
一个控制变量拿到的是下标/键,两个才拿到行值:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
data := array("a": 1, "b": 5, "c": 3, "d": -2);
|
||||
total := 0;
|
||||
for i, value in data do
|
||||
total := total + value;
|
||||
writeLn(total);
|
||||
|
||||
keys := "";
|
||||
for i in data do
|
||||
keys := keys + i;
|
||||
writeLn(keys);
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 先输出 `7`:两个控制变量时,`value` 是行值
|
||||
- 再输出 `abcd`:只写一个控制变量时,它是行下标/键,不是行值
|
||||
- 所以想累加数值却只写一个控制变量,会拿到键而不是值
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
7
|
||||
abcd
|
||||
```
|
||||
|
||||
二维数组按行推进,每个 `value` 本身是一行数组:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
rows := array(("A": 1, "B": 2), ("A": 3, "B": 4));
|
||||
colA := 0;
|
||||
for i, value in rows do
|
||||
colA := colA + value["A"];
|
||||
writeLn(colA);
|
||||
|
||||
all := 0;
|
||||
for i, value in rows do
|
||||
for j, cell in value do
|
||||
all := all + cell;
|
||||
writeLn(all);
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 先输出 `4`:`value` 是整行数组,可以直接按列键取 `value["A"]`
|
||||
- 再输出 `10`:要落到每个单元格,需要再嵌套一层 `for ... in`
|
||||
- 所以二维数组不会被自动摊平成单元格序列
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
4
|
||||
10
|
||||
```
|
||||
|
||||
### `break` 与 `continue`
|
||||
|
||||
<!-- section-id: syntax-07-006 -->
|
||||
@@ -500,6 +611,9 @@ end
|
||||
- 把控制流问题和函数文件模型问题混在一起排查。
|
||||
- 在计数 `for` 循环体里给控制变量赋值。
|
||||
- 在 `for ... in` 遍历期间修改被遍历数组或其中元素。
|
||||
- 用相邻两个 `if ... then begin ... end` 表达互斥分支;它们是两条独立语句,需要互斥就写 `else`。
|
||||
- 在 `for ... in` 只写一个控制变量却当成行值使用;单变量拿到的是下标/键。
|
||||
- 以为二维数组会被 `for ... in` 摊平成单元格序列;它按行推进,行本身是数组。
|
||||
|
||||
代码块身份:反例 / 不可照写
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
- 多表联接中,`thisRow(表序号)` 和 `thisRowIndex(表序号)` 分别取得指定来源表的当前整行与原始下标。
|
||||
- 联接类型:`left join` 保留左表、`right join` 保留右表、`full join` 保留双方、`cross join` 笛卡尔积、逗号联接等价于 `cross join`;不匹配处用 `nil` 填充。
|
||||
- `select` 列表支持 `distinct` 去重、`as 别名`、`as nil`(参与计算但不返回)、`起始列 to 结束列` 字段区间、`selectopt(位选项)`、`drange(区间/M of N)`。
|
||||
- `select` 输出列表里出现同名列时,靠后的列会覆盖靠前的,结果集里只留一列;多表 `join` 两侧有同名字段时尤其容易踩。要两列都保留,用 `as` 给其中一个改名。
|
||||
- 聚集函数统一形态 `Func(Expr[, Cond[, N[, MovingFirst[, CacheId]]]])`:条件聚集、移动聚集、多字段聚集、`refof(Expr, N)` 引用相对行;`aggof('名', Expr)` 调用自定义聚集回调。
|
||||
- `thisGroup` 不是普通值,而是分组后的子结果集;要通过子 `select` / `vselect` 的 `from thisGroup` 来访问。
|
||||
- `thisRowIndex` 在 `order by` 之后仍可返回原始行位置;`thisOrder` 返回排序后的自然排名。
|
||||
@@ -699,6 +700,48 @@ writeLn(mcols(range_rows));
|
||||
- `表达式 as nil` 让该列只参与临时计算,不出现在结果集里(上例 `nil_rows` 只剩 `id` 一列)
|
||||
- `StartIndex to EndIndex` 在选择列表里取列区间;`0 to 1` 返回第 0、1 两列
|
||||
|
||||
### 输出列表里的同名列会被覆盖
|
||||
|
||||
<!-- section-id: syntax-13-032 -->
|
||||
|
||||
<!-- tags: 同名列丢失, join 后值不对, 列被覆盖, 两个表同字段, 必须起别名 -->
|
||||
|
||||
`join` 两侧有同名字段又不改名时,靠后的列覆盖靠前的:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
t1 := array(("A": 1, "B": 2, "C": 7),
|
||||
("A": 10, "B": 3, "C": 12),
|
||||
("A": 4, "B": 20, "C": 34));
|
||||
t2 := array(("A": 10, "B": 10, "D": 3),
|
||||
("A": 4, "B": 10, "D": 3));
|
||||
|
||||
dup_rows := select [1].*, [2].["B"], [2].["D"]
|
||||
from t1 join t2 on [1].["A"] = [2].["A"] end;
|
||||
writeLn("dup B:", dup_rows[0]["B"]);
|
||||
|
||||
alias_rows := select [1].*, [2].["B"] as "B2", [2].["D"]
|
||||
from t1 join t2 on [1].["A"] = [2].["A"] end;
|
||||
writeLn("kept B:", alias_rows[0]["B"]);
|
||||
writeLn("added B2:", alias_rows[0]["B2"]);
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- `dup B` 输出 `10`:`[1].*` 已带来左表的 `B`(值 `3`),后写的 `[2].["B"]` 把它覆盖成 `10`
|
||||
- 结果集里只有一列 `B`,左表原值无法再取回,且不会有任何报错
|
||||
- `kept B` 输出 `3`、`added B2` 输出 `10`:给右侧列写 `as "B2"` 后两列并存
|
||||
- 所以 `[表序号].*` 与显式列混写时,要先确认展开后有没有撞名
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
dup B:10
|
||||
kept B:3
|
||||
added B2:10
|
||||
```
|
||||
|
||||
### `drange` 取行区间
|
||||
|
||||
<!-- section-id: syntax-13-021 -->
|
||||
@@ -1005,6 +1048,7 @@ query_result := select * from source_rows end;
|
||||
- 处理一维数组时直接把 `[0]` 当成稳定列访问。
|
||||
- 把 `thisGroup` 当成普通字段或普通变量。
|
||||
- 把排序后的 `thisRowIndex` 误当成排序序号。
|
||||
- 让 `[表序号].*` 和两侧同名的显式列同时出现在选择列表里,却不给其中一个写 `as` 别名。
|
||||
- 在 `left join` 时省略 `on` 子句或不用 `[表序号].["字段"]` 形式。
|
||||
- 在 `insert` 时漏掉 `insertfields` 或字段数与值数不匹配。
|
||||
- 期望 `update`/`delete` 返回新数组;它们直接修改原数组。
|
||||
|
||||
@@ -21,12 +21,16 @@
|
||||
- 条件判断里,`0` 按假值处理,非 `0` 数值按真值处理。
|
||||
- `inf` 和 `nan` 是特殊实数常量;`ifReal(inf)`、`ifReal(nan)` 都返回 `1`。
|
||||
- `inf > 1000` 为真,`nan = nan` 为假。
|
||||
- `0 / 0` 得 `nan`,`2 / 0` 得 `inf`,`-2 / 0` 得 `-inf`。
|
||||
- `nan` 参与 `=`、`>`、`<`、`>=`、`<=` 一律为假;只有 `<>` 为真,所以 `nan <> nan` 是可用的判定形态。
|
||||
- `nan` 参与算术运算的结果仍是 `nan`:`nan + 1`、`nan * 0`、`nan - nan` 都是 `nan`。
|
||||
- 未初始化的普通变量会得到整数 `0`。
|
||||
- 新建对象的普通成员变量默认值是 `nil`。
|
||||
- 对空数组读取一个尚未赋值的下标时,结果是 `nil`。
|
||||
- `ifNil(nil)` 可用于显式空值判定。
|
||||
- 空字符串、空数组和 `nil` 都按假值处理。
|
||||
- `nil` 可以参与加法;`nil + 1` 和 `1 + nil` 都返回 `1`,`nil + nil` 仍然是 `nil`。
|
||||
- `nil` 参与其它算术运算时按 `0` 处理:`nil / 2` 得 `0.0`、`nil * 2` 得 `0`、`nil - 2` 得 `-2`、`2 - nil` 得 `2`。
|
||||
- `ifNumber(...)` 会把整数、`Int64`、实数都视为数值;普通字符串不算数值。
|
||||
- TSL 不是“完全禁止混合类型表达式”;像 `b + a` 这种“整数 + 字符串”会通过编译,但在运行时会报 `operand type error`。
|
||||
- 因此,类型转换要显式写出来,例如 `strToInt(...)`,不要把运行时失败留给解释器。
|
||||
@@ -132,6 +136,49 @@ writeLn(nan = nan);
|
||||
- 说明 `true` / `false` / `inf` / `nan` 属于文档明确常量
|
||||
- 说明数值条件判断里,`0` 为假,非 `0` 为真
|
||||
|
||||
`nan` / `inf` 的产生方式,以及 `nan` 比较与算术的完整边界:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
n := 0 / 0;
|
||||
writeLn(n);
|
||||
writeLn(2 / 0);
|
||||
writeLn(-2 / 0);
|
||||
writeLn(n > 1);
|
||||
writeLn(n < 1);
|
||||
writeLn(n >= 1);
|
||||
writeLn(n <= 1);
|
||||
writeLn(n <> n);
|
||||
writeLn(n + 1);
|
||||
writeLn(n * 0);
|
||||
writeLn(n - n);
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- `0 / 0` 输出 `NAN`,`2 / 0` 输出 `INF`,`-2 / 0` 输出 `-INF`
|
||||
- `n > 1`、`n < 1`、`n >= 1`、`n <= 1` 全部输出 `0`
|
||||
- `n <> n` 输出 `1`,是唯一对 `nan` 成立的比较,可用来判定 `nan`
|
||||
- `n + 1`、`n * 0`、`n - n` 都输出 `NAN`,说明 `nan` 会沿算术传播
|
||||
- 因此数据参与计算前要先滤掉 `nan`,否则整条链路结果都是 `nan`
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
NAN
|
||||
INF
|
||||
-INF
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
1
|
||||
NAN
|
||||
NAN
|
||||
NAN
|
||||
```
|
||||
|
||||
### 初始值与空值
|
||||
|
||||
<!-- section-id: syntax-16-005 -->
|
||||
@@ -181,6 +228,32 @@ writeLn(nil + nil = nil);
|
||||
- 其中 `nil + 1` 和 `1 + nil` 都得到 `1`
|
||||
- `nil + nil` 仍保持为 `nil`
|
||||
|
||||
`nil` 在其它算术运算里按 `0` 参与:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
writeLn(nil / 2);
|
||||
writeLn(nil * 2);
|
||||
writeLn(nil - 2);
|
||||
writeLn(2 - nil);
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 依次输出 `0.0`、`0`、`-2`、`2`
|
||||
- 说明 `nil` 在乘除减里都按 `0` 参与,不会中断计算
|
||||
- `nil / 2` 走实数除法所以显示成 `0.0`
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
0.0
|
||||
0
|
||||
-2
|
||||
2
|
||||
```
|
||||
|
||||
空值条件判断:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
@@ -376,6 +449,9 @@ writeLn(imag(z));
|
||||
- 不要把“能编译”误解成“会自动按期望类型转换”。
|
||||
- 不要把数字字符串直接当成数值参与算术;需要数值语义时先显式转换。
|
||||
- 不要从其他语言的 truthy/falsy 或隐式转换规则反推 TSL。
|
||||
- 不要用 `nan = nan` 判定 `nan`;等值和大小比较对 `nan` 恒假,只有 `<>` 为真。
|
||||
- 不要以为 `nan` 会在后续运算里被“吃掉”;它沿算术一路传播,参与计算前要先滤掉。
|
||||
- 不要以为 `nil` 参与乘除减会中断计算或报错;它按 `0` 参与。
|
||||
- 不要把复数统计函数、分解函数与更大函数族当成本页文档能力。
|
||||
|
||||
代码块身份:反例 / 不可照写
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
- 遍历时需要维度信息可用 `mIndexCount`;需要第 `n` 维下标可用 `mIndex(n)`。
|
||||
- 子矩阵提取可以写 `matrix[row_start:row_end, col_start:col_end]`、`matrix[:, col_range]`、`matrix[row_index_array, col_index_array]`。
|
||||
- 子矩阵赋值可以写成单个标量,也可以写成同结构矩阵;赋值矩阵应和目标子矩阵形状匹配。
|
||||
- 子矩阵赋值语句本身**没有返回值**:`(m[:, "a"] := v)` 求值为 `0`,而普通赋值 `(a := v)` 求值为被赋的值。不要把子矩阵赋值写在需要取值的位置。
|
||||
- `t[:, :] := source` 按**位置**逐单元复制,不按列名对齐。`source` 里若有某行的列存储顺序与其它行不同,复制结果会静默错位;先用 `source[:, array("列1", "列2")]` 统一列序再赋值。
|
||||
- 子矩阵也可以接 `::=` 做逐单元赋值。
|
||||
- `mfind(matrix)` 返回真值单元格下标;一维数组的无条件 `mfind` 返回一维下标数组。
|
||||
- `mfind(matrix, condition)` 返回符合条件的下标行;二维矩阵下每行形如 `array(row_index, col_index)`。
|
||||
@@ -424,6 +426,79 @@ writeLn(matrix_value[1][1]);
|
||||
11
|
||||
```
|
||||
|
||||
### 子矩阵赋值的返回值与对齐方式
|
||||
|
||||
<!-- section-id: syntax-21-019 -->
|
||||
|
||||
<!-- tags: 赋值返回0, 赋值没返回值, 重构数组错位, 按位置复制, 列顺序不一致 -->
|
||||
|
||||
子矩阵赋值不产生返回值,普通赋值才产生:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
source := array(1, 2, 3);
|
||||
plain := (target := source);
|
||||
writeLn("plain:", plain[0]);
|
||||
|
||||
matrix_value := zeros(3, "a");
|
||||
sub := (matrix_value[:, "a"] := source);
|
||||
writeLn("sub:", sub);
|
||||
writeLn("written:", matrix_value[0]["a"]);
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- `plain` 拿到被赋的数组,所以 `plain[0]` 输出 `1`
|
||||
- `sub` 输出 `0`:子矩阵赋值语句本身求值为 `0`,不是被赋的值
|
||||
- `written` 输出 `1`:赋值本身生效了,只是取不到返回值
|
||||
- 所以 `return m[:, "a"] := v;` 这种写法会返回 `0`,要分成两句写
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
plain:1
|
||||
sub:0
|
||||
written:1
|
||||
```
|
||||
|
||||
`t[:, :] := source` 按位置复制,不按列名对齐:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
source := array("k1": ("p": 651, "v": 637),
|
||||
"k2": ("p": 599, "v": 170),
|
||||
"k3": ("v": 200, "p": 607));
|
||||
target := zeros(mRows(source), mCols(source, 1));
|
||||
target[:, :] := source;
|
||||
writeLn("row3 p:", target[2]["p"]);
|
||||
writeLn("row3 v:", target[2]["v"]);
|
||||
|
||||
ordered := source[:, array("p", "v")];
|
||||
fixed := zeros(mRows(ordered), mCols(ordered, 1));
|
||||
fixed[:, :] := ordered;
|
||||
writeLn("fixed p:", fixed[2]["p"]);
|
||||
writeLn("fixed v:", fixed[2]["v"]);
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 第三行源数据的列存储顺序是 `v`、`p`,与前两行相反
|
||||
- `row3 p` 输出 `200`、`row3 v` 输出 `607`:两个值互换了,因为复制按位置而不按列名
|
||||
- 前两行看不出问题,所以这种错位不会报错,只会静默出现在个别行
|
||||
- `ordered := source[:, array("p", "v")]` 先统一列序后,`fixed p` 输出 `607`、`fixed v` 输出 `200`,恢复正确
|
||||
- 源数据由多个过程分别写入时,各行列序可能不一致,用 `[:, :]` 重构前先统一列序
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
row3 p:200
|
||||
row3 v:607
|
||||
fixed p:607
|
||||
fixed v:200
|
||||
```
|
||||
|
||||
### `mfind` 与 `mfindSparse`
|
||||
|
||||
<!-- section-id: syntax-21-011 -->
|
||||
@@ -963,6 +1038,44 @@ bar 结果 (1,2):2
|
||||
- 上述差异只适用于普通 `array`;`FMArray` 上 `|` 与 `:|` 行为一致且行数不齐时补 `0`,见 [22_fmarray.md](22_fmarray.md)
|
||||
- 对应的复合赋值形式:`&=`(行并)、`|=`(列并)、`:|=`(列并补 nil)
|
||||
|
||||
### 列并遇到同名字符串列键
|
||||
|
||||
<!-- section-id: syntax-21-018 -->
|
||||
|
||||
<!-- tags: 同名列合并, 列被覆盖, 合并后值变了, 相同列名, 列并覆盖 -->
|
||||
|
||||
两侧共有的字符串列键不会并成两列,而是被右操作数覆盖:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
left := ones(3, array("A", "B"));
|
||||
right := zeros(3, array("A", "C"));
|
||||
merged := left | right;
|
||||
writeLn("A:", merged[0]["A"]);
|
||||
writeLn("B:", merged[0]["B"]);
|
||||
writeLn("C:", merged[0]["C"]);
|
||||
|
||||
reversed := right | left;
|
||||
writeLn("reversed A:", reversed[0]["A"]);
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- `A` 输出 `0`:`A` 两侧都有,取右操作数 `right` 的值,`left` 的 `1` 被覆盖
|
||||
- `B` 输出 `1`、`C` 输出 `0`:只在一侧出现的列都原样保留
|
||||
- `reversed A` 输出 `1`:换序后仍是右操作数胜出,说明规则是「右侧覆盖左侧」而非「零值覆盖」
|
||||
- 默认数字列下标不会触发覆盖,只有字符串列键相同才会
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
A:0
|
||||
B:1
|
||||
C:0
|
||||
reversed A:1
|
||||
```
|
||||
|
||||
## 默认生成模板
|
||||
|
||||
<!-- section-id: syntax-21-016 -->
|
||||
@@ -990,6 +1103,9 @@ matrix_value := zeros(2, 3);
|
||||
- 把 `::=` 写成带 `begin ... end` 的语句块。
|
||||
- 用 `::` 期待遍历到任意深度;深度遍历使用 `:.`。
|
||||
- 子矩阵赋值时用形状不匹配的矩阵硬塞。
|
||||
- 把子矩阵赋值写在需要取值的位置,例如 `return m[:, "a"] := v;`;它求值为 `0`。
|
||||
- 用 `t[:, :] := source` 重构下标时,假定它会按列名对齐;它按位置复制。
|
||||
- 期待 `|` 把两侧同名字符串列键并成两列;同名键会被右操作数覆盖。
|
||||
- 把 `union` 的复合赋值写成 `union=`;正确写法是 `&=`。
|
||||
- 一维数组直接 `union` 期待得到二维结果;需要先对它连用两次转置前缀(双转置)变成行向量。
|
||||
- 在基础函数异常参数时用分号分隔;正确写法用逗号:`sqrt(data, 1, -999)`。
|
||||
|
||||
Reference in New Issue
Block a user