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