# TSL 值与字面量 这一篇整理基本类型、字面量、数组、字符串、字符串编码边界与基础值模型,避免把值规则分散在函数或金融示例里。 ## 本篇职责 回答“基本类型怎么写、数组和字符串怎么索引、字符串编码边界怎么写、哪些值规则属于语言级事实”。本页拥有整数、实数、字符串、布尔、`array(...)` 字面量和下标 / 区间规则;矩阵与集合扩展不在本页展开。 ## 核心规则 - 最先掌握的几类值是:整数、实数、普通字符串、布尔和 `array(...)`。 - 普通字符串既可以用双引号,也可以用单引号。 - 同类引号本身可以通过连续写两个同类引号放进字符串里。 - `\\`、`\"`、`\n` 这类基础转义可用。 - `\t` 和 `\xNN` 这类转义也可用。 - `\r`、`\r\n`、`\a`、`\b`、`\f`、`\v` 这些经典转义也可用。 - `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`。 ## 可直接照写示例 使用这些示例时遵守: - 只复制任务需要的值、字面量、下标或字符串片段,不要把多个示例拼成未写入文档的新语法。 - 普通示例默认按 `.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`。 ### 字符串字面量选择与边界规则 - 普通单行文本默认使用 `"..."` 或 `'...'`;根据内容选择不冲突的引号,必要时再使用转义或连续同类引号。`%% ...%%` 仅用于多行文本、引号非常密集等原始字符串场景;不得因为内容是中文、非 ASCII 或较长就自动改用 `%%`。 - `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` 开始 - 也说明二进制缓冲区项可以直接按单字节读写 ### 字符串子串读写 字符串子串统一用 `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] := ...` 是在指定位置插入,而不是删除或替换区间 ### 字符串 `$` 连接与拼装 `$` 用于字符串连接和类型转字符串拼装: 代码块身份:可直接照写示例 ```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` 字符串 `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` 格式化占位符 `format` 用于按占位符格式化输出: 代码块身份:可直接照写示例 ```tsl result := format("%d-%s", 7, "x"); writeLn(result); ``` 代码块身份:输出片段 ```text 7-x ``` 说明: - 示例里的 `%d` / `%s` 分别对应整数、字符串占位;只为读懂本例 - `format` 的完整占位符规范使用 `tsl-api-reference` skill 按名查询。 ### `formatdatetime` 日期格式化 `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 按名查询。 ## 默认生成模板 如果你只是要抓住“基本类型 + array”的第一层,用这个最短例子: 代码块身份:可直接照写示例 ```tsl count := 1; price := 12.5; name := "ABC"; flag := true; items := array(1, 2, 3); ``` ## 禁止项 - 用 `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 ```