📝 docs(tsl): rebuild canonical syntax and routing manual

This commit is contained in:
csh
2026-04-22 16:59:20 +08:00
parent 3ed5052e61
commit 96b705b00b
83 changed files with 46446 additions and 323 deletions
+591
View File
@@ -0,0 +1,591 @@
# Basic Types And Values
文档类型:语法主线
是否可直接用于生成代码:是
是否含已验证可执行示例:是
是否含已验证反例:是
遇到不确定时跳转到:[05_variables_and_constants.md](05_variables_and_constants.md)、[07_expressions_and_operators.md](07_expressions_and_operators.md)、[20_strings_and_text.md](20_strings_and_text.md)
手册位置:第 4 篇,共 32 篇。上一篇:[03_core_model.md](03_core_model.md)。下一篇:[05_variables_and_constants.md](05_variables_and_constants.md)。
这一篇整理基本类型、字面量、数组、字符串与基础值模型,避免把值规则分散在函数或金融示例里。
## 这一篇解决什么问题
回答“基本类型怎么写、数组和字符串怎么索引、哪些值规则属于语言级事实”。
## 必须记住的规则
- 最先掌握的几类值是:整数、实数、字符串、布尔和 `array`
- 普通字符串既可以用双引号,也可以用单引号。
- 同类引号本身可以通过连续写两个同类引号放进字符串里。
- `\\``\"``\n` 这类基础转义已验证可用。
- `\t``\xNN` 这类转义也已验证可用。
- `\r``\r\n``\a``\b``\f``\v` 这些经典转义也已验证可用。
- 当前解释器接受 `%%` 原始字符串;开头的 `%%``%%tag` 后面必须先跟一个空白分隔符,正文才开始。
- `%%` 原始字符串里的反斜杠不做转义处理,并且支持多行内容。
- `array(...)` 既可以写顺序数组,也可以写字符串键表。
- 顺序数组下标从 `0` 开始。
- `Binary(...)` 创建的二进制缓冲区,当前也用 `[]` 访问,并且下标从 `0` 开始。
- 字符串下标从 `1` 开始。
- `s[0]` 在运行时会越界,不要把字符串当成 0 基下标。
- 字符串取子串用 `s[start:end]`,并且 `end` 是包含在结果里的。
- 字符串替换子串用 `s[start:end] := "..."`
- 字符串删除子串,本质上就是 `s[start:end] := ""`
- 字符串插入子串用 `s[index:0] := "..."`
- 当前解释器接受 `L""``U""` 前缀字符串,但当前手册默认仍优先普通字符串。
- 当前解释器也接受 `L%% ...%%``U%% ...%%` 前缀原始字符串。
- 当前最小验证表明:从 `IfWString(...)` 观察,混合拼接时结果是否为宽串,取决于左操作数是否为宽串;`U""` 在这里仍按非宽串处理。
- 当前最小验证表明:可以用 `String(...)` 把宽串显式转回普通串,也可以用 `WideString(...)` 把普通串显式转成宽串。
- 当前最小验证表明:`U""` 可以和 `UnicodetoUTF8(...)``UTF8ToUnicode(...)``UTF8ToAnsi(...)``AnsiToUTF8(...)` 组成一条清晰的 UTF8 转换链;`U""` 本身不等同于宽串。
- 当前最小验证表明:`\uXXXX` 在普通字符串里不要直接当成“单个字符宽串”;例如 `Length("\u0041")``2`,而 `Length(L"\u0041")``Length(U"\u0041")` 都为 `1`
- 当前最小验证表明:字符串可以包含 ASCII `0` 字符;`\0``#0` 都能构造这个字符,并且不会把字符串截断。
- `#number` 可以按字符码直接拼进字符串里;`#13#10` 这类写法可直接形成换行。
## 已验证语法
最基础的值写法:
代码块身份:已验证可执行示例
```tsl
count := 1;
price := 12.5;
name := "ABC";
flag1 := true;
flag2 := false;
items := array(1, 2, 3);
```
其中已经验证:
- `true` 可以直接写成布尔值。
- `false` 可以直接写成布尔值。
- 运行输出里,`true` / `false` 分别表现为 `1` / `0`
普通字符串字面量:
代码块身份:已验证可执行示例
```tsl
WriteLn("ABC");
WriteLn('XYZ');
```
已验证运行结果:
- 依次输出 `ABC``XYZ`
字符串内引号与基础转义:
代码块身份:已验证可执行示例
```tsl
WriteLn("A""B");
WriteLn('A''B');
WriteLn("A\\B");
WriteLn("A\"B");
WriteLn("A\nB");
```
已验证运行结果:
- 依次输出 `A"B``A'B``A\B`
- `"A\nB"` 会分成两行输出 `A``B`
更多基础转义:
代码块身份:已验证可执行示例
```tsl
WriteLn("A\tB" = "A"#9"B");
WriteLn("A\x30B" = "A0B");
```
已验证运行结果:
- 两行都输出 `1`
- 说明 `\t` 可以表示制表符,`\x30` 这类写法可以按 ANSI 字节值插入字符
经典控制字符转义:
代码块身份:已验证可执行示例
```tsl
program test;
begin
WriteLn("\r" = #13);
WriteLn("\r\n" = #13#10);
WriteLn(Length("\r\n"));
WriteLn("\a" = #7);
WriteLn("\b" = #8);
WriteLn("\f" = #12);
WriteLn("\v" = #11);
end.
```
已验证运行结果:
- 依次输出 `1``1``2``1``1``1``1`
- 说明 `\r` / `\r\n` 可以直接表示回车和回车换行
- 说明 `\a``\b``\f``\v` 也已经在当前解释器里拿到正向最小结果
`L""` / `U""` 前缀的最小已验证事实:
代码块身份:已验证可执行示例
```tsl
WriteLn(L"A" = L"\u0041");
WriteLn(U"Hello" = "Hello");
```
已验证运行结果:
- 依次输出 `1``1`
- 说明当前解释器接受 `L""``U""` 和 ASCII 范围的 `\u` 表达
- 但在当前控制台里直接 `WriteLn(L"Hello")` 会显示成带空字节的宽串输出,因此当前手册不把 `L""` 作为默认示例写法
非 ASCII 宽串的最小已验证事实:
代码块身份:已验证可执行示例
```tsl
WriteLn(L"\u5929\u8F6F" = L""#0x5929#0x8F6F);
```
已验证运行结果:
- 输出 `1`
- 说明当前解释器接受非 ASCII 宽串的 `\uXXXX``#0xXXXX` 这两种构造方式
- 但当前手册仍优先把它们当作边界写法,而不是默认入门写法
原始字符串 `%%`
代码块身份:已验证可执行示例
```tsl
s := %% ABC%%;
WriteLn(s);
```
已验证运行结果:
- 输出 `ABC`
- 说明 `%%` 开头后面的第一个空白只是分隔符,不计入最终字符串内容
原始字符串不会处理转义:
代码块身份:已验证可执行示例
```tsl
s := %% a\r\nb%%;
WriteLn(s = "a\\r\\nb");
```
已验证运行结果:
- 输出 `1`
- 说明 `%%` 原始字符串里的 `\r``\n` 只是普通字符,不会变成回车换行
带标识符的原始字符串:
代码块身份:已验证可执行示例
```tsl
s := %%tag A"B'C%%tag;
WriteLn(s);
```
已验证运行结果:
- 输出 `A"B'C`
- 说明 `%%tag ...%%tag` 的首尾标识符可以配对使用,正文里可直接放单双引号
多行原始字符串:
代码块身份:已验证可执行示例
```tsl
s := %%m
A
B%%m;
WriteLn(s);
```
已验证运行结果:
- 输出两行 `A``B`
- 说明 `%%` 原始字符串支持直接跨行
带前缀的原始字符串:
代码块身份:已验证可执行示例
```tsl
WriteLn(L%% ABC%% = L"ABC");
WriteLn(U%% ABC%% = "ABC");
```
已验证运行结果:
- 依次输出 `1``1`
- 说明 `L%% ...%%``U%% ...%%` 在当前解释器里都可用
前缀字符串与混合拼接的最小类型观察:
代码块身份:已验证可执行示例
```tsl
program test;
begin
WriteLn(IfWString("A" + L"B"));
WriteLn(IfWString(L"A" + "B"));
WriteLn(IfWString(U"A"));
WriteLn(IfWString(U"A" + L"B"));
WriteLn(IfWString(L"A" + U"B"));
end.
```
已验证运行结果:
- 依次输出 `0``1``0``0``1`
- 说明从 `IfWString(...)` 的观察角度看,混合拼接是否得到宽串,取决于左操作数是否为宽串
- 也说明 `U""` 前缀字符串在这里仍按非宽串处理,不等同于 `L""`
显式字符串类型转换:
代码块身份:已验证可执行示例
```tsl
program test;
begin
a := String(L"abcd");
b := WideString("abcd");
WriteLn(a = "abcd");
WriteLn(IfWString(a));
WriteLn(b = L"abcd");
WriteLn(IfWString(b));
end.
```
已验证运行结果:
- 依次输出 `1``0``1``1`
- 说明 `String(...)` 可以把宽串显式转回普通串
- 说明 `WideString(...)` 可以把普通串显式转成宽串
`U""` 与 UTF8 显式转换:
代码块身份:已验证可执行示例
```tsl
program test;
begin
utf8_s := U"\u5929\u8F6F";
utf8_s2 := UnicodetoUTF8(L"\u5929\u8F6F");
wide_s := UTF8ToUnicode(utf8_s);
ansi_s := UTF8ToAnsi(utf8_s);
WriteLn(IfWString(utf8_s));
WriteLn(utf8_s = utf8_s2);
WriteLn(wide_s = L"\u5929\u8F6F");
WriteLn(ansi_s = String(L"\u5929\u8F6F"));
end.
```
已验证运行结果:
- 依次输出 `0``1``1``1`
- 说明 `U""` 在当前解释器里不是宽串
- 说明 `U"\u5929\u8F6F"``UnicodetoUTF8(L"\u5929\u8F6F")` 可以对应到同一个 UTF8 串
- 说明 `UTF8ToUnicode(...)` 可以把 UTF8 串转回宽串,`UTF8ToAnsi(...)` 可以把 UTF8 串转回普通串
ANSI / UTF8 / 宽串的最小往返:
代码块身份:已验证可执行示例
```tsl
program test;
begin
ansi_s := String(L"\u5929\u8F6F");
utf8_s := AnsiToUTF8(ansi_s);
ansi_back := UTF8ToAnsi(utf8_s);
wide_s := UTF8ToUnicode(utf8_s);
WriteLn(IfWString(ansi_s));
WriteLn(IfWString(utf8_s));
WriteLn(IfWString(wide_s));
WriteLn(ansi_back = ansi_s);
WriteLn(wide_s = L"\u5929\u8F6F");
end.
```
已验证运行结果:
- 依次输出 `0``0``1``1``1`
- 说明 `AnsiToUTF8(...)` 产物在这里仍按非宽串处理
- 说明 `UTF8ToAnsi(...)``UTF8ToUnicode(...)` 都已经在当前解释器里拿到了正向最小结果
`\uXXXX` 在普通字符串、宽串和 `U` 串里的最小边界:
代码块身份:已验证可执行示例
```tsl
program test;
begin
WriteLn(Length("\u0041"));
WriteLn(Length(L"\u0041"));
WriteLn(Length(U"\u0041"));
end.
```
已验证运行结果:
- 依次输出 `2``1``1`
- 因此不要把普通字符串里的 `"\u0041"` 直接理解成和 `L"\u0041"` 一样的“单字符宽串”语义
ASCII `0` 字符不会截断字符串:
代码块身份:已验证可执行示例
```tsl
program test;
begin
s1 := "A\0B";
s2 := "A"#0"B";
WriteLn(Length(s1));
WriteLn(Length(s2));
WriteLn(s1 = s2);
WriteLn(s1[2] = #0);
end.
```
已验证运行结果:
- 依次输出 `3``3``1``1`
- 说明 `\0``#0` 都可以把 ASCII `0` 放进字符串
- 也说明当前字符串不是遇到 `#0` 就自动截断的 C 风格零结尾串
带 ASCII `0` 的字符串切片仍然保留该字符:
代码块身份:已验证可执行示例
```tsl
program test;
begin
s := "A"#0"B";
t := s[1:3];
WriteLn(Length(t));
WriteLn(t = s);
u := s[2:2];
WriteLn(Length(u));
WriteLn(u = #0);
end.
```
已验证运行结果:
- 依次输出 `3``1``1``1`
- 说明字符串切片和单字符读取对 ASCII `0` 仍然稳定,不会在中间被截断
字符码拼接:
代码块身份:已验证可执行示例
```tsl
WriteLn("A"#48"B");
WriteLn("A"#13#10"B");
```
已验证运行结果:
- 第一行输出 `A0B`
- 第二组输出会分成两行 `A``B`
- 说明 `#48` 这类写法会按字符码直接参与字符串拼接,`#13#10` 可直接表示换行
代码块身份:已验证可执行示例
```tsl
arr := array(10, 20, 30);
hash := array("Code": "0001", "Price": 12.3);
s := "ABC";
WriteLn("ARR0=", arr[0]);
WriteLn("ARR1=", arr[1]);
WriteLn("HASH=", hash["Code"]);
WriteLn("STR1=", s[1]);
WriteLn("STR2=", s[2]);
WriteLn("STR3=", s[3]);
```
已验证运行结果对应关系:
- `arr[0] = 10`
- `arr[1] = 20`
- `hash["Code"] = "0001"`
- `s[1] = "A"`
- `s[2] = "B"`
- `s[3] = "C"`
二进制缓冲区:
代码块身份:已验证可执行示例
```tsl
program test;
begin
b := Binary("30");
WriteLn(IfBinary(b));
WriteLn(b[0] = "3");
WriteLn(b[1] = "0");
b[1] := 0x31;
WriteLn(b[1] = "1");
end.
```
已验证运行结果:
- 依次输出 `1``1``1``1`
- 说明 `Binary(...)` 当前确实得到二进制缓冲区
- 说明二进制缓冲区的 `[]` 下标从 `0` 开始
- 也说明二进制缓冲区项可以直接按单字节读写
字符串子串读取:
代码块身份:已验证可执行示例
```tsl
s := "ABCDE";
WriteLn(s[2:4]);
```
已验证运行结果:
- 输出 `BCD`
- 这说明字符串区间 `2:4` 会包含结束位 `4`
字符串替换子串:
代码块身份:已验证可执行示例
```tsl
s := "ABCDE";
s[2:4] := "XYZ";
WriteLn(s);
```
已验证运行结果:
- 输出 `AXYZE`
字符串删除子串:
代码块身份:已验证可执行示例
```tsl
s := "ABCDE";
s[2:4] := "";
WriteLn(s);
```
已验证运行结果:
- 输出 `AE`
字符串插入子串:
代码块身份:已验证可执行示例
```tsl
s := "AAABBB";
s[4:0] := "111";
WriteLn(s);
```
已验证运行结果:
- 输出 `AAA111BBB`
- 这说明 `s[index:0] := ...` 是在指定位置插入,而不是删除或替换区间
## 最小可编译示例
如果你只是要抓住“基本类型 + array”的第一层,用这个最短例子:
代码块身份:已验证可执行示例
```tsl
count := 1;
price := 12.5;
name := "ABC";
flag := true;
items := array(1, 2, 3);
```
## 常见误写
-`s[0]` 访问字符串首字符。
- 误以为 `array(...)` 只能写顺序数组,不能写字符串键表。
- 误以为字符串区间和数组一样从 `0` 开始。
-`s[index:0] := ...` 误解成“替换到第 0 位”。
- 把二进制缓冲区也按字符串的 `1` 基下标去理解。
- 在普通字符串示例里默认切到 `L""`,却没有先确认下游场景真的需要宽串。
-`U""` 误解成和 `L""` 一样的宽串。
- 把普通字符串里的 `"\uXXXX"` 直接当成和 `L"\uXXXX"` 一样的单字符写法。
- 把带 `#0` / `\0` 的字符串当成会自动截断的 C 风格零结尾串。
-`%%ABC%%` 当成合法的原始字符串起手式。
代码块身份:反例 / 不可照写
```text
s := "ABC";
WriteLn(s[0]);
```
上面这类写法在运行时会报字符串下标越界。
代码块身份:反例 / 不可照写
```text
s := "ABCDE";
WriteLn(s[1:3]);
```
不要把上面这个区间访问类比成数组切片的半开区间。当前解释器里它输出的是 `ABC`,不是 `AB`;字符串区间的结束位会被包含进结果。
代码块身份:反例 / 不可照写
```text
s := %%ABC%%;
```
上面这种写法在当前解释器里会报 `missing or invalid characters``%%` 原始字符串开头后面必须先跟一个空白分隔符,或者写成 `%%tag ` 这种“标识符 + 空白”形式。
代码块身份:反例 / 不可照写
```text
WriteLn(Length("\u0041"));
WriteLn(Length(L"\u0041"));
```
不要把上面这两行想成同一件事。当前解释器里前者输出 `2`,后者输出 `1`;普通字符串里的 `\uXXXX` 不能直接按宽串单字符去理解。
代码块身份:反例 / 不可照写
```text
WriteLn(IfWString(U"\u5929\u8F6F"));
WriteLn(IfWString(L"\u5929\u8F6F"));
```
不要把 `U""``L""` 当成同一种“宽串前缀”。当前解释器里前者输出 `0`,后者输出 `1`
代码块身份:反例 / 不可照写
```text
s := "A"#0"B";
WriteLn(Length(s));
```
不要把上面这类字符串想成遇到 `#0` 就结束。当前解释器里这里输出 `3`,说明 ASCII `0` 是字符串内容的一部分。
## 跳转指引
- 值有了名字以后怎么看:见 [05_variables_and_constants.md](05_variables_and_constants.md)
- 值怎样组成表达式:见 [07_expressions_and_operators.md](07_expressions_and_operators.md)
- 看字符串与编码深水专题:见 [20_strings_and_text.md](20_strings_and_text.md)