# TSL 值与字面量

这一篇整理基本类型、字面量、数组、字符串、字符串编码边界与基础值模型，避免把值规则分散在函数或金融示例里。

## 本篇职责

<!-- section-id: syntax-03-001 -->

回答“基本类型怎么写、数组和字符串怎么索引、字符串编码边界怎么写、哪些值规则属于语言级事实”。本页拥有整数、实数、字符串、布尔、`array(...)` 字面量和下标 / 区间规则；矩阵与集合扩展不在本页展开。

## 核心规则

<!-- section-id: syntax-03-002 -->

<!-- tags: 数组下标, 数组索引, 下标从几开始, 下标起点, 字符串下标, 二进制下标 -->

- 最先掌握的几类值是：整数、实数、普通字符串、布尔和 `array(...)`。
- 普通字符串既可以用双引号，也可以用单引号。
- 同类引号本身可以通过连续写两个同类引号放进字符串里。
- `\\`、`\"`、`\n` 这类基础转义可用。
- `\t` 和 `\xNN` 这类转义也可用。
- `\r`、`\r\n`、`\a`、`\b`、`\f`、`\v` 这些经典转义也可用。

<!-- quickstart-rule: index-origins -->

- `array(...)` 既可以写顺序数组，也可以写字符串键表；顺序数组和 `binary(...)` 二进制缓冲区下标从 `0` 开始，字符串下标从 `1` 开始。
- 数组元素可以是混合类型；给不存在的下标赋值会扩张数组。显式整数键可写成 `array(0: value0, 1: value1)`。
- `s[0]` 在运行时会越界，不要把字符串当成 0 基下标。
- 字符串取子串用 `s[start:end]`，并且 `end` 是包含在结果里的。
- 字符串替换子串用 `s[start:end] := "..."`。
- 字符串删除子串，本质上就是 `s[start:end] := ""`。
- 字符串插入子串用 `s[index:0] := "..."`。
- 普通字符串、`L""` 宽串、`U""` UTF8 前缀串，以及 `%%` 原始字符串都属于文档明确写法；合法形式不等于默认选择，生成优先级见本页“字符串字面量选择与边界规则”。
- `%%` 原始字符串开头后必须先跟一个空白分隔符；可以带标识符，也支持多行。
- `L%% ...%%` 与 `U%% ...%%` 这两种带前缀的原始字符串也属于文档明确写法。
- 普通字符串里的 `\uXXXX` 不要直接按“单字符宽串”理解；`length("\u0041") = 2`，而 `length(L"\u0041") = 1`、`length(U"\u0041") = 1`。
- `U""` 不是宽串；需要在 UTF8、宽串、普通串之间显式转换时，继续用 `utf8ToUnicode(...)`、`utf8ToAnsi(...)`、`ansiToUtf8(...)`、`unicodeToUtf8(...)`、`string(...)`、`wideString(...)`。
- `#number` 可以直接把字符码拼进字符串。
- `\0` 和 `#0` 都能把 ASCII `0` 放进字符串，并且不会把字符串截断。
- 字符串 `like` 的右侧按正则表达式解释，不是 SQL `%` / `_` 通配，也不要把正则里的 `*` 当成独立的 glob 通配符；默认匹配不区分大小写。
- 数值 `like` 表示近似相等；`likeEps` 和 `likeEpsRate` 分别控制绝对误差与相对误差阈值，默认值都是 `1e-6`。

## 可直接照写示例

<!-- section-id: syntax-03-003 -->

使用这些示例时遵守：

- 只复制任务需要的值、字面量、下标或字符串片段，不要把多个示例拼成未写入文档的新语法。
- 普通示例默认按 `.tsl` 脚本语句区书写；需要函数或类型时，放在后置声明区。
- 字符串字面量选择、编码、宽串和原始字符串边界不清楚时，优先看本页“字符串字面量选择与边界规则”。

最基础的值写法：

代码块身份：可直接照写示例

```tsl
count := 1;
price := 12.5;
name := "ABC";
flag1 := true;
flag2 := false;
items := array(1, 2, 3);
writeLn(count);
writeLn(price);
writeLn(name);
writeLn(flag1);
writeLn(flag2);
writeLn(items[1]);
```

代码块身份：输出片段

```text
1
12.5
ABC
1
0
2
```

代码块说明：`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
writeLn("\r" = #13);
writeLn("\r\n" = #13#10);
writeLn(length("\r\n"));
writeLn("\a" = #7);
writeLn("\b" = #8);
writeLn("\f" = #12);
writeLn("\v" = #11);
```

结果说明：

- 依次输出 `1`、`1`、`2`、`1`、`1`、`1`、`1`
- 说明 `\r` / `\r\n` 可以直接表示回车和回车换行
- 说明 `\a`、`\b`、`\f`、`\v` 也属于可用的经典转义

代码块身份：可直接照写示例

```tsl
items := array(10, 20, 30);
row := array("Code": "0001", "Price": 12.3);
s := "ABC";
writeLn(items[0]);
writeLn(items[1]);
writeLn(row["Code"]);
writeLn(s[1]);
writeLn(s[2]);
writeLn(s[3]);
```

代码块身份：输出片段

```text
10
20
0001
A
B
C
```

代码块说明：顺序数组 `items` 从 `0` 开始，字符串键表 `row` 用字符串键访问，字符串 `s` 从 `1` 开始。

数组按写入下标扩张：

代码块身份：可直接照写示例

```tsl
items := array(1);
items[3] := 4;
keyed := array(0: 2, 1: 3);
writeLn(length(items));
writeLn(items[1] = nil);
writeLn(items[3]);
writeLn(keyed[0]);
writeLn(keyed[1]);
```

代码块身份：输出片段

```text
2
1
4
2
3
```

说明：`length(items)` 统计已有项数，不等于“最大整数键 + 1”；下标 `1` 没有写入，因此读取为 `nil`。

### 字符串字面量选择与边界规则

<!-- section-id: syntax-03-004 -->

<!-- tags: 普通字符串, 字符串字面量, 中文字符串, 普通中文内容, 非 ASCII, 原始字符串, 多行字符串, 引号怎么转义, 字符串里带引号, 长文本, U 前缀, L 前缀, 宽字符串, 宽串前缀, utf8 转换, 编码转换, 字符码拼接, 二进制缓冲区, 字节读写 -->

<!-- quickstart-rule: string-literal-default -->

- 普通单行文本默认使用 `"..."` 或 `'...'`；根据内容选择不冲突的引号，必要时再使用转义或连续同类引号。`%% ...%%` 仅用于多行文本、引号非常密集等原始字符串场景；不得因为内容是中文、非 ASCII 或较长就自动改用 `%%`。

<!-- quickstart-rule: string-prefix-by-type -->

- `U`、`L` 前缀只由目标字符串类型或已确认的 API 编码要求决定，不能因为内容是中文就自动添加；普通中文内容优先直接写成 `"中文内容"`。

需要为真实 API 选择字符串类型或编码前缀时，先用 `tsl-api-reference` 取回该 API 的精确要求；语法页不根据文本内容猜测编码。

以下 `%%` 示例只说明原始字符串的合法外形和边界，不表示普通文本应默认采用原始字符串。

原始字符串 `%%` 的空白分隔、标识符和多行：

代码块身份：可直接照写示例

```tsl
s1 := %% ABC%%;
s2 := %%tag A"B'C%%tag;
s3 := %%m
A
B%%m;
writeLn(s1);
writeLn(s2);
writeLn(s3);
```

结果说明：

- `s1` 输出 `ABC`
- `s2` 输出 `A"B'C`
- `s3` 输出两行 `A`、`B`
- 说明 `%%` 开头后的第一个空白只是分隔符，不属于正文
- 说明 `%%tag ...%%tag` 可以用标识符配对
- 也说明 `%%` 原始字符串支持直接跨行

代码块身份：输出片段

```text
ABC
A"B'C
A
B
```

带前缀的原始字符串：

代码块身份：可直接照写示例

```tsl
writeLn(L%% ABC%% = L"ABC");
writeLn(U%% ABC%% = "ABC");
```

结果说明：

- 依次输出 `1`、`1`
- 说明 `L%% ...%%` 和 `U%% ...%%` 都属于文档明确写法

`\uXXXX` 在普通串、宽串和 `U` 串里的边界：

代码块身份：可直接照写示例

```tsl
writeLn(length("\u0041"));
writeLn(length(L"\u0041"));
writeLn(length(U"\u0041"));
```

结果说明：

- 依次输出 `2`、`1`、`1`
- 因此不要把普通字符串里的 `"\u0041"` 直接理解成和 `L"\u0041"` 一样的“单字符”写法

字符串显式编码转换：

代码块身份：可直接照写示例

```tsl
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"));
```

结果说明：

- 依次输出 `0`、`1`、`1`、`1`
- 说明 `U""` 不是宽串
- 说明 UTF8、宽串、普通串之间可以按示例显式转换

ASCII `0` 字符不会截断字符串：

代码块身份：可直接照写示例

```tsl
s1 := "A\0B";
s2 := "A"#0"B";
writeLn(length(s1));
writeLn(length(s2));
writeLn(s1 = s2);
writeLn(s1[2] = #0);
```

结果说明：

- 依次输出 `3`、`3`、`1`、`1`
- 说明 `\0` 和 `#0` 都可以把 ASCII `0` 放进字符串
- 也说明 TSL 字符串不是遇到 `#0` 就自动截断的 C 风格零结尾串

字符码拼接：

代码块身份：可直接照写示例

```tsl
writeLn("A"#48"B");
writeLn("A"#13#10"B");
```

结果说明：

- 第一行输出 `A0B`
- 第二组输出会分成两行 `A`、`B`
- 说明 `#48` 这类写法会按字符码直接参与字符串拼接，`#13#10` 可以直接表示换行

二进制缓冲区：

代码块身份：可直接照写示例

```tsl
b := binary("30");
writeLn(ifBinary(b));
writeLn(b[0] = "3");
writeLn(b[1] = "0");
b[1] := 0x31;
writeLn(b[1] = "1");
```

结果说明：

- 依次输出 `1`、`1`、`1`、`1`
- 说明 `binary(...)` 会得到二进制缓冲区
- 说明二进制缓冲区的 `[]` 下标从 `0` 开始
- 也说明二进制缓冲区项可以直接按单字节读写

### 字符串子串读写

<!-- section-id: syntax-03-005 -->

<!-- tags: 取子串, 截取子串, 字符串区间, 替换子串, 删除子串, 插入子串, 插入字符串, 取字符串的一段 -->

字符串子串统一用 `s[start:end]` 区间访问，`end` 包含在结果里；同一形态既能读取，也能替换、删除和插入。

字符串子串读取：

代码块身份：可直接照写示例

```tsl
s := "ABCDE";
writeLn(s[2:4]);
```

代码块身份：输出片段

```text
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] := ...` 是在指定位置插入，而不是删除或替换区间

### 字符串 `$` 连接与拼装

<!-- section-id: syntax-03-006 -->

<!-- tags: 拼字符串, 字符串相加, 连接字符串, 数字转字符串拼接 -->

`$` 用于字符串连接和类型转字符串拼装：

代码块身份：可直接照写示例

```tsl
s1 := "abc" $ "def";
a := 1;
b := 2.34;
c := "AAA";
d := "A=" $ a $ " B=" $ b $ " C=" $ c;
writeLn(s1);
writeLn(d);
```

代码块身份：输出片段

```text
abcdef
A=1 B=2.34 C=AAA
```

说明：

- `"abc" $ "def"` 连接两个字符串得到 `"abcdef"`
- 整数、实数等类型可以直接用 `$` 拼装成字符串
- 拼接实数时，保留最高精度，表现与 `floattostr` 一致
- 纯小数可能走科学计数法：`"_" $ 0.0000005 $ ","` 返回 `"_5E-7,"`
- 带整数部分的小数保留精度：`"_" $ 1.0000005 $ ","` 返回 `"_1.0000005,"`

### 字符串与数值 `like`

<!-- section-id: syntax-03-007 -->

<!-- tags: 正则匹配, 正则表达式, 模糊匹配, 判断是否符合模式, 默认不区分大小写, 数值近似相等, like 精度 -->

字符串 `like` 的右侧是正则表达式；默认匹配不区分大小写：

代码块身份：可直接照写示例

```tsl
result1 := "a" like "A";
result2 := "HELLO" like "hello";
result3 := "2009-1-1" like "\\d+-\\d+-\\d+";
writeLn("result1:", result1);
writeLn("result2:", result2);
writeLn("result3:", result3);
```

代码块身份：输出片段

```text
result1: 1
result2: 1
result3: 1
```

说明：

- 默认情况下，`"a" like "A"` 和 `"HELLO" like "hello"` 都返回 `1`（真）
- `"\\d+-\\d+-\\d+"` 是正则表达式，匹配示例中的日期形态
- 正则量词 `*` 只修饰它前面的原子；不要把 `"he*"` 解释成“`he` 后面任意字符”的 glob 模式，需要任意字符序列时写成 `.*`
- 匹配控制标志可以改变大小写规则；控制标志的精确 API、参数与目标 scope 使用 `tsl-api-reference` skill 核对

数值 `like` 用于近似相等判断：

代码块身份：可直接照写示例

```tsl
old_abs_eps := likeEps;
old_rate_eps := likeEpsRate;
likeEps := 0.1;
likeEpsRate := 0;
writeLn(1.05 like 1);
likeEps := old_abs_eps;
likeEpsRate := old_rate_eps;
```

代码块身份：输出片段

```text
1
```

说明：

- `likeEps` 和 `likeEpsRate` 都可读写，默认值均为 `1e-6`
- 两数绝对差小于 `likeEps`，或“绝对差 / 两数绝对值的平均值”小于 `likeEpsRate` 时，数值 `like` 返回真
- 临时修改阈值后要恢复旧值，避免影响同一运行时中的后续比较

`not like` 是取反形式（TSL 2025/8 版本起支持）：

代码块身份：可直接照写示例

```tsl
result := "abc" not like "xyz.*";
writeLn(result);
```

代码块身份：输出片段

```text
1
```

### `format` 格式化占位符

<!-- section-id: syntax-03-008 -->

<!-- tags: 格式化输出, 占位符, 补零, 保留小数, 拼装带格式的串 -->

`format` 用于按占位符格式化输出：

代码块身份：可直接照写示例

```tsl
result := format("%d-%s", 7, "x");
writeLn(result);
```

代码块身份：输出片段

```text
7-x
```

说明：

- 示例里的 `%d` / `%s` 分别对应整数、字符串占位；只为读懂本例
- `format` 的完整占位符规范使用 `tsl-api-reference` skill 按名查询。

### `formatdatetime` 日期格式化

<!-- section-id: syntax-03-009 -->

<!-- tags: 日期转字符串, 时间格式化, 年月日怎么输出, 日期显示格式 -->

`formatdatetime` 用于将日期按指定格式输出：

代码块身份：可直接照写示例

```tsl
dt := strtodate("2024-01-02");
result := formatdatetime("yyyy-mm-dd", dt);
writeLn(result);
```

代码块身份：输出片段

```text
2024-01-02
```

说明：

- 示例里的 `yyyy-mm-dd` 对应四位年、两位月、两位日；只为读懂本例
- `formatdatetime` 的完整格式说明使用 `tsl-api-reference` skill 按名查询。

## 默认生成模板

<!-- section-id: syntax-03-010 -->

如果你只是要抓住“基本类型 + array”的第一层，用这个最短例子：

代码块身份：可直接照写示例

```tsl
count := 1;
price := 12.5;
name := "ABC";
flag := true;
items := array(1, 2, 3);
```

## 禁止项

<!-- section-id: syntax-03-011 -->

- 用 `s[0]` 访问字符串首字符。
- 误以为 `array(...)` 只能写顺序数组，不能写字符串键表。
- 误以为字符串区间和数组一样从 `0` 开始。
- 把字符串区间当成数组切片式的半开区间；`s[1:3]` 返回 `ABC` 而不是 `AB`，结束位会被包含进结果，正向示例见本页“字符串子串读写”。
- 把 `s[index:0] := ...` 误解成“替换到第 0 位”。
- 把二进制缓冲区也按字符串的 `1` 基下标去理解。
- 因为内容是中文、非 ASCII 或较长，就把普通单行文本自动改写成 `%%` 原始字符串。
- 因为内容是中文，就自动添加 `U` / `L` 前缀；前缀只由目标字符串类型或已确认的 API 编码要求决定。
- 在普通字符串示例里默认切到原始字符串、宽串 / UTF8 前缀、字符码或编码转换，却没有按本页字符串字面量选择与边界规则确认。
- 把普通字符串里的 `\uXXXX` 直接当成宽串单字符。
- 把 `U""` 当成宽串；需要宽串时按本页转换链处理。
- 把 `#0` / `\0` 当成 C 风格字符串终止符。
- 把字符串 `like` 误写成默认区分大小写；默认行为是不区分大小写。
- 把 `like` 只理解成字符串模式匹配，遗漏数值近似相等语义。
- 修改 `likeEps` / `likeEpsRate` 后不恢复，导致后续代码继承意外的比较阈值。
- 以为给较大整数下标赋值后，中间所有位置都会自动生成实际元素；未写入的位置仍为 `nil`。

代码块身份：反例 / 不可照写

```text
s := "ABC";
writeLn(s[0]);
```

上面这类写法在运行时会报字符串下标越界。

代码块身份：输出片段

```text
String index out of bounds
```
