playbook/docs/tsl/syntax/04_values_and_literals.md

592 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)