← 都来码 / Dolaima← 例程索引basic/app/stock.bas原文

basic/app/stock.bas · 70465 字节 · 下载原文

' stock.bas —— 常用小程序③:**股票行情** —— 由 `Examples/c/app/stock.c` 移植

' stock.bas -- Everyday utility #3: **stock quotes**, ported from `Examples/c/app/stock.c`

'

' 干什么:一屏卡片,每只股票一张 —— 代码、名称、现价、涨跌幅(中国习惯:涨红跌绿),

' What it does: one screen of cards, one per stock -- code, name, last price, change percent

' 外加一条按涨跌幅画的小条(中间那根竖线是 0,往右红 = 涨,往左绿 = 跌)。

' (red for up, green for down, the Chinese convention), plus a bar drawn from the change percent

' (the tick in the middle is zero: right is red/up, left is green/down).

'

' 数据从哪来:`qt.gtimg.cn` 的**明文 HTTP**(80 端口,免密钥)——

' Where the data comes from: plain HTTP (port 80, no API key) against `qt.gtimg.cn` --

' VM 里没有 TLS,https 走不通,所以只挑了这个能明文说话的行情源。

' there is no TLS in the VM so https is out of reach, and this is a quote source that speaks plaintext.

'      GET /q=sh600000,sz000001,... HTTP/1.0

' 响应是一行 `v_<代码>="0~1~2~…";`,**记录之间用 `;` 分、字段之间用 `~` 分**。

' The reply is a line of `v_<code>="0~1~2~...";`, **records separated by `;` and fields by `~`**.

'

' 离线怎么回落:DNS / 建套接字 / 连接 / 发送 / HTTP 状态 / 正文分界,**任一环节失败**就去读

' Offline fallback: if DNS, socket, connect, send, the HTTP status or the header/body boundary fails,

' 同目录的 `stocks.txt`(每行 `代码|名称|现价|涨跌幅`,`#` 开头是注释,UTF-8),

' it reads `stocks.txt` from the program's directory (one `code|name|price|change%` per line, `#` comments,

' 标题上标一行"离线:读本地 stocks.txt",界面照常出。

' UTF-8) and marks the title "offline: reading local stocks.txt"; the screen renders just the same.

' 候选路径依次试 `stocks.txt`、`examples/basic/app/stocks.txt`、`Examples/basic/app/stocks.txt`。

' The candidate paths are tried in order: `stocks.txt`, `examples/basic/app/stocks.txt`, `Examples/basic/app/stocks.txt`.

'

' 跑法(桌面脚手架,秒级,别为改一行去打 APK):

' How to run (desktop scaffolding, seconds -- do not build an APK just to change one line):

'     dotnet scripts/vmlcli/bin/Release/net10.0/vmlcli.dll third_party/vml/Examples/basic/app/stock.bas '         --vml-home third_party/vml --timeout 60 --frame /tmp/stock.png

' 手机上:命令行页敲 `vml run examples/basic/app/stock.bas`。

' On the phone: type `vml run examples/basic/app/stock.bas` in the command-line page.

'

' ⚠ 五个**实测**出来的坑(C 版的文件头也记着同一批,这里补上 BASIC 侧的处置):

' ⚠ Five pitfalls **measured** rather than guessed (the C header records the same batch; the BASIC side is added here):

'

' ① **`encoding_gbk_decode` 是桩函数,不是真 GBK 表**(2026-10-07 实测)。它返回

' ① **`encoding_gbk_decode` is a stub, not a real GBK table** (measured 2026-10-07). It returns

'    `0x554A + (b0 - 0xB0) * 94 + (b1 - 0xA1)`,实测 `浦`(C6 D6) 给 23955 而不是 U+6D66。

'    `0x554A + (b0 - 0xB0) * 94 + (b1 - 0xA1)`; measured, the GBK bytes C6 D6 should be U+6D66 but it

'    returns 23955 (U+5D93) -- a different hanzi that looks plausible.

'    症状**不是乱码**,而是"画出一个长得像字、但不是那个字"的汉字 ⇒ 所以本文件自带一张

'    The symptom is **not mojibake** but "a wrong hanzi that looks plausible" => this file carries its own

'    **GB2312 全表**(6768 项)。BASIC 侧只借它两半里**确实正确**的那一半:`MID$` 按字节取。

'    **complete GB2312 table** (6768 entries). The BASIC side borrows the half that **is** correct: `MID$` by byte.

'

' ② **表在 BASIC 里怎么放**:整张表存成一个 **20304 字节的字符串字面量**(`gtab`)。

' ② **How the table is stored in BASIC**: the whole table is one **20304-byte string literal** (`gtab`).

'    ⚠ **每一项都必须是 3 字节**,所以表里那 5 个占位 `?` 换成了 3 字节的 `□`(U+25A1)——

'    ⚠ **Every entry must be three bytes**, so the five `?` placeholders became a three-byte `□` (U+25A1):

'    这样"第 n 个字"的字节偏移就是 `n*3`,可以直接 `MID$(gtab, n * 3 + 1, 3)` 取出来。

'    that makes the byte offset of "entry n" simply `n*3`, so `MID$(gtab, n * 3 + 1, 3)` picks it out.

'    ⚠ 别把它拆成几行去拼:**字符串拼接在 255 字节处截断**(库里那块轮转缓冲),拼不出 20KB。

'    ⚠ Do not split it across lines and join: **concatenation truncates at 255 bytes** (the library's rotating buffer).

'    ⚠ 也**别把它赋给一个字符串数组** —— `DIM a(8) AS STRING` 实测是坏的(读出来是地址)。

'    ⚠ Nor assign it into a string array -- `DIM a(8) AS STRING` is measurably broken (addresses come back).

'

' ③ **字段下标是数出来的,不是抄来的**。拿实测响应逐个数(0 起):

' ③ **The field indices are counted, not copied.** Counting the live response from 0:

'    1=名称、2=代码、3=现价、4=昨收、5=今开,中间 6~28 是买卖五档,29 空、

'    1=name, 2=code, 3=last price, 4=previous close, 5=open; 6-28 are the five bid/ask levels, 29 is empty,

'    30=时间戳,**31=涨跌额、32=涨跌幅%**。

'    30 is the timestamp, and **31=change amount, 32=change percent**.

'

' ④ **名称里可以带空格**(实测 `sz000858` 回的字段 1 是 `五 粮 液`)⇒ 解析时**不能"遇到空格就断"**,

' ④ **The name may contain spaces** (measured: field 1 for `sz000858` has a space between each pair of hanzi)

'   字段的边界只有 `~` 和收尾的 `"`。

'   => the parser **must not break on a space**: the only boundaries are `~` and the closing `"`.

'

' ⑤ **宿主不解析域名**(同 weather 的坑):`#334 SocketConnect(fd, ip, port)` 内部是 `IPAddress.Parse`,

' ⑤ **The host does not resolve names** (same as weather): `#334 SocketConnect(fd, ip, port)` is `IPAddress.Parse`,

'   喂域名直接回 -1,所以先走 `vml_dns_resolve`,失败时它回的是 **-1**(不是 0)。

'   so a domain name returns -1; DNS comes first, and a failed lookup returns **-1** (not 0).

'

' ── BASIC 侧另外三条(都实测过)────────────────────────────────

' -- Three more on the BASIC side (all measured) --

' · **循环终结符是 `LOOP`**(`DO WHILE … LOOP`),写 `END DO` 不报错、控制流静默歪掉;本文件用 `WHILE … WEND`,

' · **The loop terminator is `LOOP`**; `END DO` reports nothing and skews control flow. This file uses `WHILE ... WEND`

'   退出循环一律用标志位,不用 `EXIT WHILE`。

'   and always leaves loops through a flag, never through `EXIT WHILE`.

' · **单行 `IF … THEN …` 嵌在块 IF 的 THEN 里、后面再跟 `ELSE`,会让外层循环当场退出**(实测只跑了 1 轮),

' · **An inline `IF ... THEN ...` inside a block IF's THEN followed by `ELSE` exits the outer loop at once** (measured:

'   所以本文件**所有分支都是多行块**。

'   one iteration instead of thirteen), so **every branch here is a multi-line block**.

' · **响应缓冲必须是 `vml_alloc` 的裸内存**(BASIC 的字符串临时量在 256 字节处封顶,而响应实测 3414 字节);

' · **The response buffer must be raw `vml_alloc` memory** (BASIC string temporaries cap at 256 bytes; the response

'   收数据的偏移要用**地址整数**相加,读的时候再把地址**绑成字符串**(`rbuf = base`)。

'   measures 3414). Offsets for `recv` are **address arithmetic**; for reading, the address is **bound to a string**.

'

' 版面:所有尺寸都从宿主问(`ui_scr_get_w()/_h()`),颜色一律真彩 `0xAARRGGBB`,

' Layout: every dimension is asked of the host (`ui_scr_get_w()/_h()`), colours are true colour `0xAARRGGBB`,

' 画完必须 `ui_2d_present()`(漏了就黑屏)。循环**必须有界**:空闲 8 秒自己退出,`Esc`/`q` 也退出;

' and the frame must end with `ui_2d_present()`. The loop **must be bounded**: 8 s of idling quits, `Esc`/`q` quit,

' 其余任意键 = 重新抓一次并刷新。写真正的程序时把 `IDLE_EXIT_MS` 去掉即可。

' any other key re-fetches and redraws. Drop `IDLE_EXIT_MS` in a real program.


' ── UI 接口:引用**库侧**的声明文件 ────────────────────────────────

' ── UI interface: include the **library-side** declaration file ────────────────────────────────

'$INCLUDE: 'vml_ui.bas'


' ── 共享库里的网络 / 文件 / 堆(都在 `Lib/shared/vmlsys.vml`,随 builtins 自动链上)──

' ── Networking / files / heap from the shared library (all in `Lib/shared/vmlsys.vml`, auto-linked with builtins) ──

' ⚠ 形参写 `AS STRING` 就是"把这个串的地址传过去";写 `AS INTEGER` 就是"这个整数就是地址"。

' ⚠ A parameter written `AS STRING` passes the string's address; `AS INTEGER` means the integer *is* the address.

NATIVE FUNCTION vml_dns_resolve(hostname AS STRING) AS INTEGER
NATIVE FUNCTION vml_socket_create(domain AS INTEGER, stype AS INTEGER) AS INTEGER
NATIVE FUNCTION vml_socket_connect(fd AS INTEGER, ip AS STRING, port AS INTEGER) AS INTEGER
NATIVE FUNCTION vml_socket_send(fd AS INTEGER, buf AS STRING, ln AS INTEGER) AS INTEGER
NATIVE FUNCTION vml_socket_recv(fd AS INTEGER, addr AS INTEGER, ln AS INTEGER) AS INTEGER
NATIVE FUNCTION vml_socket_close(fd AS INTEGER) AS INTEGER
' `http_get_to`:连 IP(宿主不解析域名)、`Host:` 头写域名 —— 虚拟主机按 Host 路由,

' 而 `http_get` 把这两件事绑在一个参数上 ⇒ 传 IP 只会拿到 404。

' `http_get_to`: connect to the IP while `Host:` carries the hostname.

NATIVE FUNCTION http_get_to(ip AS STRING, port AS INTEGER, hostname AS STRING, path AS STRING, out AS INTEGER, cap AS INTEGER) AS INTEGER
' BASIC 里没有「按地址取长度」(`LEN` 只吃字符串)⇒ 正文长度问库要这一个。

' BASIC has no "length at an address", so the body length comes from the library.

NATIVE FUNCTION http_last_body_len() AS INTEGER
' `strcpy` 只借它"返回 dst"这一条:自拷一次就把一块字面量的地址拿到手。

' `strcpy` is borrowed purely for "it returns dst": one self-copy yields a literal's address.

NATIVE FUNCTION strcpy(dst AS STRING, src AS STRING) AS INTEGER
NATIVE FUNCTION vml_file_open(path AS STRING, mode AS INTEGER) AS INTEGER
NATIVE FUNCTION vml_file_read(handle AS INTEGER, addr AS INTEGER, ln AS INTEGER) AS INTEGER
NATIVE FUNCTION vml_file_close(handle AS INTEGER) AS INTEGER

' ══ 常量 ══════════════════════════════════════════════════════

' ══ Constants ══════════════════════════════════════════════════════

DIM HOST AS STRING
DIM QUERY AS STRING
DIM OUTFILE AS STRING
DIM PORT AS INTEGER
DIM WANT AS INTEGER
DIM MAXSTK AS INTEGER
DIM BUFCAP AS INTEGER
DIM MAXROUNDS AS INTEGER
DIM IDLE_EXIT_MS AS INTEGER

' 配色(真彩 AA RRGGBB)

' Colours (true colour AARRGGBB)

DIM C_BG AS INTEGER
DIM C_CARD AS INTEGER
DIM C_TRACK AS INTEGER
DIM C_TITLE AS INTEGER
DIM C_DIM AS INTEGER
DIM C_UP AS INTEGER
DIM C_DOWN AS INTEGER
DIM C_WARN AS INTEGER

' ══ 全局状态 ══════════════════════════════════════════════════

' ══ Global state ══════════════════════════════════════════════════

DIM SW AS INTEGER
DIM SH AS INTEGER
DIM g_lang AS INTEGER
DIM base AS INTEGER
DIM rbuf AS STRING
DIM total AS INTEGER
DIM head AS INTEGER
DIM blen AS INTEGER
DIM lim AS INTEGER
DIM offline AS INTEGER
DIM g_first AS INTEGER
DIM g_pad AS INTEGER
DIM g_top AS INTEGER
DIM g_cardh AS INTEGER

' 一组**扁平定长**缓冲:每只股票一格。**不存名字的字符串副本** —— 只存它在响应缓冲里的

' One set of **flat fixed-size** buffers, a cell per stock. **No string copy of the name is kept** -- only its

' 位置(起点 + 长度),画的时候现取(见 `name_text`)。

' position (start + length) inside the response buffer; it is read out at draw time (see `name_text`).

DIM s_n AS INTEGER
DIM s_doff(8) AS INTEGER
DIM s_dlen(8) AS INTEGER
DIM s_noff(8) AS INTEGER
DIM s_nlen(8) AS INTEGER
DIM s_proff(8) AS INTEGER
DIM s_prlen(8) AS INTEGER
DIM s_pcoff(8) AS INTEGER
DIM s_pclen(8) AS INTEGER
DIM s_pct100(8) AS INTEGER

' 切分结果(4 段的起点/长度),由 `split_fields` 写、两个解析器读。

' The split result (start/length of four fields), written by `split_fields` and read by both parsers.

DIM f_off(5) AS INTEGER
DIM f_len(5) AS INTEGER

' GB2312 全表(**数据,不是逻辑**):6768 项 × 3 字节 = 20304 字节,由 `load_gtab` 装进来。

' The complete GB2312 table (**data, not logic**): 6768 entries x 3 bytes = 20304 bytes, loaded by `load_gtab`.

DIM gtab AS STRING

' 文案(开局按宿主语言算一次)

' Strings (computed once at startup from the host language)

DIM sWin AS STRING
DIM sTitle AS STRING
DIM sOff AS STRING
DIM sSrc AS STRING
DIM sNoData AS STRING
DIM sHint AS STRING

SUB initLang()
    g_lang = ui_scr_get_language()
    IF g_lang = 0 THEN
        sWin = "股票行情(BASIC)"
        sTitle = "股票行情"
        sOff = "离线:读本地 stocks.txt"
        sSrc = "数据源 qt.gtimg.cn"
        sNoData = "没有数据"
        sHint = "任意键刷新(重新抓一次)   Esc / q 退出"
    ELSE
        sWin = "Stock Quotes (BASIC)"
        sTitle = "Stock Quotes"
        sOff = "offline: reading local stocks.txt"
        sSrc = "source qt.gtimg.cn"
        sNoData = "no data"
        sHint = "any key refreshes   Esc / q quits"
    END IF
END SUB

' ── 缓冲里的第 i 个字节(0 起)。`ASC` 给的是**有符号**字节,负的先加回 256。──

' ── Byte i of the buffer (0-based). `ASC` hands back a **signed** byte, so negatives get 256 added back. ──

FUNCTION gbyte(i AS INTEGER) AS INTEGER
    DIM v AS INTEGER
    v = ASC(MID$(rbuf, i + 1, 1))
    IF v < 0 THEN
        v = v + 256
    END IF
    gbyte = v
END FUNCTION

' ── GBK → UTF-8(**只做数据,不做近似**)────────────────────────────

' ── GBK to UTF-8 (**data, never an approximation**) ────────────────────────────

' ASCII 直通;落在 GB2312 那 72 个区里就查表;其余(GBK 扩展区)回 `?`。

' ASCII passes straight through, the 72 GB2312 zones are looked up, and anything else (the GBK extensions) is `?`.

' ⚠ 表里每一项**都是 3 字节**(占位符也换成了 3 字节的 `□`),所以第 n 项就是 `MID$(gtab, n*3+1, 3)`。

' ⚠ **Every entry in the table is three bytes** (the placeholder is a three-byte `□` too), so entry n is `MID$(gtab, n*3+1, 3)`.

FUNCTION gbk_to_utf8(off AS INTEGER, ln AS INTEGER) AS STRING
    DIM p AS INTEGER
    DIM e AS INTEGER
    DIM out AS STRING
    DIM b0 AS INTEGER
    DIM b1 AS INTEGER
    DIM idx AS INTEGER
    DIM ch AS STRING
    p = off
    e = off + ln
    out = ""
    WHILE p < e
        b0 = gbyte(p)
        IF b0 < 128 THEN
            ch = CHR$(b0)
            p = p + 1
        ELSE
            ' 认不出的槽位回 `?`,所以下标越界也不会画出空字。

            ' An unrecognized slot yields `?`, so even a bogus index draws something.

            ch = "?"
            b1 = 32
            IF p + 1 < e THEN
                b1 = gbyte(p + 1)
            END IF
            IF b0 >= 176 AND b0 <= 247 AND b1 >= 161 AND b1 <= 254 THEN
                idx = (b0 - 176) * 94 + (b1 - 161)
                ch = MID$(gtab, idx * 3 + 1, 3)
            END IF
            p = p + 2
        END IF
        out = out + ch
    WEND
    gbk_to_utf8 = out
END FUNCTION

' 第 i 只股票要显示的**名称**:在线是 GBK(查表转 UTF-8),离线是文件里的 UTF-8(原样抄)。

' The **name** to display for stock i: GBK when online (table lookup to UTF-8), already UTF-8 from the file when offline.

FUNCTION name_text(i AS INTEGER) AS STRING
    DIM s AS STRING
    IF offline = 0 THEN
        s = gbk_to_utf8(s_noff(i), s_nlen(i))
    ELSE
        s = MID$(rbuf, s_noff(i) + 1, s_nlen(i))
    END IF
    name_text = s
END FUNCTION

' "3.27" / "-1.50" / "+0.00" → ×100 的整数(327 / -150 / 0),认不出来回 0。

' "3.27" / "-1.50" / "+0.00" to an integer x100 (327 / -150 / 0); 0 when it cannot be read.

' ⚠ 只收**两位小数**:行情接口给的正好是两位(实测 "3.27"、"1.94")。

' ⚠ **Two decimals only**: the quote feed hands back exactly two (measured "3.27", "1.94").

FUNCTION parse_signed_100(off AS INTEGER, ln AS INTEGER) AS INTEGER
    DIM i AS INTEGER
    DIM neg AS INTEGER
    DIM v AS INTEGER
    DIM c AS INTEGER
    DIM go AS INTEGER
    neg = 0
    i = 0
    v = 0
    IF ln > 0 THEN
        IF gbyte(off) = 45 THEN
            neg = 1
            i = 1
        END IF
        IF gbyte(off) = 43 THEN
            i = 1
        END IF
    END IF
    go = 1
    WHILE i < ln AND go = 1
        c = gbyte(off + i)
        IF c >= 48 AND c <= 57 THEN
            v = v * 10 + (c - 48)
            i = i + 1
        ELSE
            go = 0
        END IF
    WEND
    v = v * 100
    IF i < ln THEN
        IF gbyte(off + i) = 46 THEN
            i = i + 1
            IF i < ln THEN
                c = gbyte(off + i)
                IF c >= 48 AND c <= 57 THEN
                    v = v + (c - 48) * 10
                    i = i + 1
                END IF
            END IF
            IF i < ln THEN
                c = gbyte(off + i)
                IF c >= 48 AND c <= 57 THEN
                    v = v + (c - 48)
                    i = i + 1
                END IF
            END IF
        END IF
    END IF
    IF neg = 1 THEN
        v = 0 - v
    END IF
    parse_signed_100 = v
END FUNCTION

' 已经攒了几条记录(行首的 `v_`)—— 给读循环加一道"够了就收"的保险。

' How many records have arrived so far (a `v_` at the start of a line) -- lets the read loop stop early.

FUNCTION count_records() AS INTEGER
    DIM i AS INTEGER
    DIM c AS INTEGER
    c = 0
    i = 0
    WHILE i < total
        IF gbyte(i) = 118 THEN
            IF i = 0 THEN
                c = c + 1
            ELSE
                IF gbyte(i - 1) = 10 THEN
                    c = c + 1
                END IF
            END IF
        END IF
        i = i + 1
    WEND
    count_records = c
END FUNCTION

' ── 切分:把 [ls, le) 这段按 `sep` 切成若干段,起点/长度写进 f_off/f_len ──

' ── Split: cut the [ls, le) range on `sep` into fields, writing start/length into f_off/f_len ──

SUB split_fields(ls AS INTEGER, le AS INTEGER, sep AS INTEGER)
    DIM i AS INTEGER
    DIM fi AS INTEGER
    DIM fs AS INTEGER
    i = ls
    fi = 0
    fs = ls
    WHILE i <= le
        IF i = le OR gbyte(i) = sep THEN
            IF fi < 4 THEN
                f_off(fi) = fs
                f_len(fi) = i - fs
            END IF
            fi = fi + 1
            fs = i + 1
        END IF
        IF fi > 4 THEN
            i = le + 1
        ELSE
            i = i + 1
        END IF
    WEND
    IF fi < 4 THEN
        f_len(0) = 0
    END IF
END SUB

' ── 解析(在线的行情行)───────────────────────────────────────

' ── Parsing (the live quote line) ───────────────────────────────────────

' 记录形状:`v_sh600000="0~名称~代码~现价~昨收~今开~…~31 涨跌额~32 涨跌幅~…";`

' Record shape: `v_sh600000="0~name~code~price~prevClose~open~...~31 change~32 change%~...";`

' ⚠ 字段边界只有 `~` 和收尾的 `"` —— **不要拿空格当分界**(名称里就有空格,见文件头 ④)。

' ⚠ The only field boundaries are `~` and the closing `"` -- **never treat a space as one** (pitfall 4).

SUB parse_record(start AS INTEGER, slot AS INTEGER)
    DIM i AS INTEGER
    DIM e AS INTEGER
    DIM fi AS INTEGER
    DIM fs AS INTEGER
    DIM state AS INTEGER
    i = start
    e = head + blen
    fi = 0
    fs = 0
    state = 0
    WHILE i + 1 < e
        IF state = 2 THEN
            ' 记录读完了:把游标推出去,让循环条件结束它。

            ' The record is done: push the cursor past the end so the loop condition stops it.

            i = e
        ELSE
            IF state = 0 THEN
                ' 扫到 `="` 为止,然后站到字段 0 上。

                ' Scan up to `="`, then stand on field 0.

                IF gbyte(i) = 61 AND gbyte(i + 1) = 34 THEN
                    state = 1
                    i = i + 2
                    fs = i
                    fi = 0
                ELSE
                    i = i + 1
                END IF
            ELSE
                IF gbyte(i) = 126 OR gbyte(i) = 34 THEN
                    IF fi = 1 THEN
                        s_noff(slot) = fs
                        s_nlen(slot) = i - fs
                    END IF
                    IF fi = 2 THEN
                        s_doff(slot) = fs
                        s_dlen(slot) = i - fs
                    END IF
                    IF fi = 3 THEN
                        s_proff(slot) = fs
                        s_prlen(slot) = i - fs
                    END IF
                    IF fi = 32 THEN
                        s_pcoff(slot) = fs
                        s_pclen(slot) = i - fs
                    END IF
                    IF gbyte(i) = 34 THEN
                        state = 2
                        i = i + 1
                    ELSE
                        fi = fi + 1
                        i = i + 1
                        fs = i
                    END IF
                ELSE
                    i = i + 1
                END IF
            END IF
        END IF
    WEND
    ' 走到字段 32 才收(半条记录宁可不要)。

    ' Only a record that reached field 32 is kept (half a record is worse than none).

    IF fi >= 32 THEN
        s_pct100(slot) = parse_signed_100(s_pcoff(slot), s_pclen(slot))
    ELSE
        s_nlen(slot) = 0
        s_dlen(slot) = 0
    END IF
END SUB

SUB parse_quote()
    DIM i AS INTEGER
    DIM e AS INTEGER
    DIM n AS INTEGER
    i = head
    e = head + blen
    n = 0
    ' 第一个记录可能正好在正文开头,所以从 `head - 1` 这个"虚拟行首"起步。

    ' The first record may sit right at the start of the body, so the scan starts from the virtual line head `head - 1`.

    i = head - 1
    WHILE i < e
        IF i + 1 < e THEN
            IF gbyte(i) = 10 OR i = head - 1 THEN
                IF gbyte(i + 1) = 118 THEN
                    IF n < MAXSTK THEN
                        parse_record(i + 1, n)
                        IF s_dlen(n) > 0 THEN
                            n = n + 1
                        END IF
                    END IF
                END IF
            END IF
        END IF
        i = i + 1
    WEND
    s_n = n
END SUB

' ── 解析(离线的 stocks.txt)────────────────────────────────────

' ── Parsing (the offline stocks.txt) ────────────────────────────────────

' `代码|名称|现价|涨跌幅`,`#` 开头是注释。名称本来就是 UTF-8,原样抄。

' `code|name|price|change%`; a leading `#` is a comment. The name is already UTF-8, so it is copied as is.

' ⚠ 一行写完不代表字段够 4 段:**凑不满 4 段的整行丢掉**(宁可少一张卡,不要一张半空的卡)。

' ⚠ A line being present does not mean it has four fields: **a line with fewer than four is dropped**.

SUB parse_local()
    DIM p AS INTEGER
    DIM ls AS INTEGER
    DIM le AS INTEGER
    DIM n AS INTEGER
    p = 0
    ls = 0
    n = 0
    WHILE p <= blen
        IF p = blen OR gbyte(p) = 10 THEN
            le = p
            IF le > ls THEN
                IF gbyte(le - 1) = 13 THEN
                    le = le - 1
                END IF
            END IF
            IF le > ls AND n < MAXSTK THEN
                IF gbyte(ls) <> 35 THEN
                    split_fields(ls, le, 124)
                    IF f_len(0) > 0 THEN
                        IF f_len(1) > 0 THEN
                            s_doff(n) = f_off(0)
                            s_dlen(n) = f_len(0)
                            s_noff(n) = f_off(1)
                            s_nlen(n) = f_len(1)
                            s_proff(n) = f_off(2)
                            s_prlen(n) = f_len(2)
                            s_pcoff(n) = f_off(3)
                            s_pclen(n) = f_len(3)
                            s_pct100(n) = parse_signed_100(f_off(3), f_len(3))
                            n = n + 1
                        END IF
                    END IF
                END IF
            END IF
            ls = p + 1
        END IF
        p = p + 1
    WEND
    s_n = n
END SUB

' ── 取数据(在线优先,失败回落本地)──────────────────────────────

' ── Acquire the data (network first, local fallback) ──────────────────────────────

SUB acquire()
    DIM ipaddr AS INTEGER
    DIM ip AS STRING
    DIM fd AS INTEGER
    DIM n AS INTEGER
    DIM want AS INTEGER
    DIM code AS INTEGER
    DIM rounds AS INTEGER
    DIM fh AS INTEGER
    DIM src AS INTEGER
    DIM k AS INTEGER
    DIM reading AS INTEGER
    DIM i AS INTEGER
    DIM stopearly AS INTEGER

    s_n = 0
    offline = 0
    total = 0
    head = 0
    blen = 0
    lim = 0

    IF g_first = 1 THEN
        IF g_lang = 0 THEN
            PRINT "=== VML 股票行情 ==="
        ELSE
            PRINT "=== VML Stock Quotes ==="
        END IF
        PRINT "主机 "; HOST; "  请求 "; QUERY
    END IF

    ' ① 域名 → IP(宿主不解析域名,见文件头 ⑤)。失败回 -1,所以先按整数收。

    ' (1) Hostname to IP (the host does not resolve names, see pitfall 5). A failure returns -1, so collect an integer first.

    ipaddr = vml_dns_resolve(HOST)
    IF ipaddr <= 0 THEN
        IF g_first = 1 THEN
            PRINT "[警告] DNS 解析失败(转为离线)"
        END IF
        offline = 1
    ELSE
        ip = ipaddr
        IF g_first = 1 THEN
            PRINT "[1/3] DNS  "; ip
        END IF
    END IF

    ' ②③ 抓数据 —— 建套接字、连接、发请求、收完整响应、剥头、状态码**全在库里**。

    '   ⚠ 用 `http_get_to`(不是 `http_get`):连 IP、`Host:` 头写域名 —— 虚拟主机按 Host 路由,

    '     而 `http_get` 把这两件事绑在一个参数上 ⇒ 传 IP 只会拿到 404(见声明处那段)。

    ' The fetch lives in the library's `http_get_to`; the body length comes from `http_last_body_len`.

    IF offline = 0 THEN
        code = http_get_to(ip, PORT, HOST, "/q=" + QUERY, base, BUFCAP)
        IF code <> 200 THEN
            IF g_first = 1 THEN
                PRINT "[警告] 抓取失败(转为离线)"
            END IF
            offline = 1
        ELSE
            blen = http_last_body_len()      ' 库里记着上一次的正文长度

            head = 0
            lim = head + blen
            IF g_first = 1 THEN
                PRINT "[3/3] HTTP "; code; "  正文 "; blen; " 字节"
            END IF
            parse_quote()
            IF s_n <= 0 THEN
                ' 连上了、HTTP 也对,却一条都没解出来 ⇒ 接口改版了,退回本地。

                ' Connected with a valid HTTP status yet nothing parsed -- the feed changed shape, so fall back.

                IF g_first = 1 THEN
                    PRINT "[警告] 一条也没解出来(接口改版?转离线)"
                END IF
                offline = 1
            END IF
        END IF
    END IF

    ' ④ 离线:读本地的 stocks.txt。⚠ `mode = 0` 是**只读**,写成 1 会把文件清空再读。

    ' (4) Offline: read the local stocks.txt. ⚠ `mode = 0` is **read-only**; passing 1 truncates it first.

    IF offline <> 0 THEN
        src = -1
        k = 0
        WHILE k < 3 AND src < 0
            fh = -1
            IF k = 0 THEN
                fh = vml_file_open("stocks.txt", 0)
            END IF
            IF k = 1 THEN
                fh = vml_file_open("examples/basic/app/stocks.txt", 0)
            END IF
            IF k = 2 THEN
                fh = vml_file_open("Examples/basic/app/stocks.txt", 0)
            END IF
            IF fh >= 0 THEN
                src = fh
            END IF
            k = k + 1
        WEND
        IF src < 0 THEN
            PRINT "[失败] 离线且找不到 stocks.txt —— 把它放到本程序同目录。"
            s_n = 0
        ELSE
            blen = vml_file_read(src, base, BUFCAP - 1)
            n = vml_file_close(src)
            IF blen <= 0 THEN
                PRINT "[失败] stocks.txt 是空的"
                s_n = 0
            ELSE
                lim = blen
                parse_local()
                IF g_first = 1 THEN
                    PRINT "[离线] stocks.txt 解析出 "; s_n; " 只"
                END IF
            END IF
        END IF
    END IF

    IF g_first = 1 THEN
        IF s_n > 0 THEN
            PRINT "解析 "; s_n; " 只:"
            i = 0
            WHILE i < s_n
                PRINT "  "; MID$(rbuf, s_doff(i) + 1, s_dlen(i)); " "; name_text(i); " "; MID$(rbuf, s_proff(i) + 1, s_prlen(i)); " "; MID$(rbuf, s_pcoff(i) + 1, s_pclen(i)); "%"
                i = i + 1
            WEND
        END IF
    END IF
END SUB

' ── 版面与绘制(绘图全在 SUB 里;模块级只留调用)──────────────────

' ── Layout and drawing (all drawing lives in SUBs; module level only calls them) ──────────────────

SUB layout()
    DIM avail AS INTEGER
    g_pad = SW \ 18
    IF g_pad < 6 THEN
        g_pad = 6
    END IF
    g_top = g_pad + 62
    avail = SH - g_top - 34
    IF s_n > 0 THEN
        g_cardh = avail \ s_n
    ELSE
        g_cardh = avail
    END IF
    IF g_cardh > 96 THEN
        g_cardh = 96
    END IF
    IF g_cardh < 52 THEN
        g_cardh = 52
    END IF
    ' 卡片块在可用高度里**居中**:只有三四只时,顶着标题下面会留一大片空(实测 4 只时上面空 0、

    ' **Centre** the block in the available height: with only three or four cards, hugging the heading leaves a big

    ' 下面空 132 像素,看着像"下面还有内容没画出来")。六只时这一项是 0,行为不变。

    ' gap (measured: 0 px above and 132 px below with four). With six the term is 0 and nothing changes.

    g_top = g_top + (avail - s_n * g_cardh) \ 2
END SUB

SUB render()
    DIM cx AS INTEGER
    DIM i AS INTEGER
    DIM y AS INTEGER
    DIM h AS INTEGER
    DIM by AS INTEGER
    DIM ix AS INTEGER
    DIM iw AS INTEGER
    DIM mid AS INTEGER
    DIM amt AS INTEGER
    DIM bw AS INTEGER
    DIM col AS INTEGER
    DIM ds AS STRING
    DIM ps AS STRING

    cx = SW \ 2
    layout()
    ui_2d_clear(C_BG)
    ui_2d_text_styled(cx, g_pad, sTitle, C_TITLE, 17, VML_ANCHOR_CENTER, VML_FONT_BOLD)
    IF offline <> 0 THEN
        ui_2d_text(cx, g_pad + 26, sOff, C_WARN, 11, VML_ANCHOR_CENTER)
    ELSE
        ui_2d_text(cx, g_pad + 26, sSrc, C_DIM, 11, VML_ANCHOR_CENTER)
    END IF

    IF s_n <= 0 THEN
        ui_2d_text(cx, SH \ 2 - 8, sNoData, C_DIM, 14, VML_ANCHOR_CENTER)
        ui_2d_present()
    ELSE
        i = 0
        WHILE i < s_n
            y = g_top + i * g_cardh
            h = g_cardh - 6
            ui_2d_rect(g_pad, y, SW - g_pad * 2, h, C_CARD, 1, 0, 10)

            ' 代码(左上,暗)

            ' The code (top left, dim)

            ds = MID$(rbuf, s_doff(i) + 1, s_dlen(i))
            ui_2d_text(g_pad + 12, y + 9, ds, C_DIM, 11, VML_ANCHOR_LEFT)

            ' 涨跌幅(右上,带符号;涨红跌绿)

            ' The change percent (top right, signed; red up, green down)

            col = C_DIM
            IF s_pct100(i) > 0 THEN
                col = C_UP
            END IF
            IF s_pct100(i) < 0 THEN
                col = C_DOWN
            END IF
            ps = MID$(rbuf, s_pcoff(i) + 1, s_pclen(i))
            IF s_pct100(i) >= 0 THEN
                IF LEFT$(ps, 1) <> "+" THEN
                    IF LEFT$(ps, 1) <> "-" THEN
                        ps = "+" + ps
                    END IF
                END IF
            END IF
            ps = ps + "%"
            ui_2d_text(SW - g_pad - 12, y + 8, ps, col, 13, VML_ANCHOR_RIGHT)

            ' 名称(左,粗)与现价(右,大)

            ' The name (left, bold) and the price (right, large)

            ds = name_text(i)
            ui_2d_text_styled(g_pad + 12, y + 28, ds, C_TITLE, 16, VML_ANCHOR_LEFT, VML_FONT_BOLD)
            ds = MID$(rbuf, s_proff(i) + 1, s_prlen(i))
            ui_2d_text(SW - g_pad - 12, y + 26, ds, C_TITLE, 18, VML_ANCHOR_RIGHT)

            ' 涨跌幅条:中间那根竖线是 0,满格 = ±10%。纯装饰,但一眼看得出谁在动。

            ' The change bar: the tick in the middle is zero and a full half is +/-10%. Decorative, but it shows who is moving.

            ix = g_pad + 12
            iw = SW - g_pad * 2 - 24
            IF iw < 40 THEN
                iw = 40
            END IF
            by = y + h - 18
            ui_2d_rect(ix, by, iw, 8, C_TRACK, 1, 0, 4)
            mid = ix + iw \ 2
            ui_2d_rect(mid - 1, by - 1, 2, 10, C_DIM, 1, 0, 1)
            amt = s_pct100(i)
            IF amt < 0 THEN
                amt = 0 - amt
            END IF
            IF amt > 1000 THEN
                amt = 1000
            END IF
            bw = amt * (iw \ 2) \ 1000
            IF bw < 2 THEN
                bw = 2
            END IF
            IF s_pct100(i) > 0 THEN
                ui_2d_rect(mid, by, bw, 8, C_UP, 1, 0, 2)
            END IF
            IF s_pct100(i) < 0 THEN
                ui_2d_rect(mid - bw, by, bw, 8, C_DOWN, 1, 0, 2)
            END IF

            i = i + 1
        WEND
        ui_2d_text(cx, SH - 26, sHint, C_DIM, 11, VML_ANCHOR_CENTER)
        ui_2d_present()
    END IF
END SUB

' ── 重新问一次尺寸(开窗前后各一次:画布尺寸与开窗前的估算**可能不同**)──

' ── Ask for the size again (before and after opening: the canvas may differ from the pre-open estimate) ──

SUB measure()
    SW = ui_scr_get_w()
    SH = ui_scr_get_h()
    IF SW <= 0 THEN
        SW = 360
    END IF
    IF SH <= 0 THEN
        SH = 620
    END IF
END SUB

' ── 开窗 + 消息循环(整段搬进 SUB:模块级的 IF/ELSE 代码流会读坏实参)──

' ── Open the window and run the message loop (all inside a SUB: module-level IF/ELSE code misreads arguments) ──

SUB run_ui()
    DIM t AS INTEGER
    DIM t0 AS INTEGER
    DIM done AS INTEGER
    DIM k AS INTEGER

    measure()
    ui_win_open_ex(sWin, 0, 0, VML_WIN_ROTATABLE, VML_WIN_NO_GAMEPAD)
    measure()
    render()

    t0 = ui_time_get_ms()
    done = 0
    WHILE done = 0
        IF ui_win_is_closed() <> 0 THEN
            done = 1
        END IF
        IF done = 0 THEN
            t = ui_msg_fetch_wait(200)
            IF t = VML_MSG_NONE THEN
                ' 空闲超时(自动验收用)

                ' Idle timeout (for automated checks)

                IF ui_time_get_ms() - t0 > IDLE_EXIT_MS THEN
                    done = 1
                END IF
            END IF
            IF t <> VML_MSG_NONE THEN
                t0 = ui_time_get_ms()
                IF t = VML_MSG_KEYDOWN THEN
                    k = ui_msg_get_a()
                    IF k = VML_KEY_ESCAPE OR k = 113 OR k = 81 THEN
                        done = 1
                    ELSE
                        ' 任意键 = 重新抓一次再刷新

                        ' Any other key re-fetches and redraws

                        acquire()
                        render()
                    END IF
                END IF
                IF t = VML_MSG_WINDOWORIENT OR t = VML_MSG_WINDOWRESIZE THEN
                    measure()
                    render()
                END IF
                IF t = VML_MSG_WINDOWCLOSE THEN
                    done = 1
                END IF
            END IF
        END IF
    WEND
END SUB

' ══ 主程序 ════════════════════════════════════════════════════

' ══ Main program ════════════════════════════════════════════════════

HOST = "qt.gtimg.cn"
' 请求的 6 只(沪 `sh` + 代码、深 `sz` + 代码)。特意**涨跌都有**,好一眼看出红绿两种条。

' The six requested stocks (`sh`/`sz` plus the code). Deliberately **a mix of risers and fallers**, so both bars show up.

QUERY = "sh600000,sz000001,sh601318,sh600519,sz000333,sh688981"
OUTFILE = "stocks.txt"
PORT = 80
WANT = 6
MAXSTK = 8
BUFCAP = 8192
MAXROUNDS = 64
IDLE_EXIT_MS = 8000

C_BG = &HFF0E1116
C_CARD = &HFF1A1F28
C_TRACK = &HFF2A3038
C_TITLE = &HFFE8E8F0
C_DIM = &HFF9AA0B0
C_UP = &HFFE05A4A
C_DOWN = &HFF3FAE6A
C_WARN = &HFFE0A040

initLang
load_gtab

' 响应缓冲:**一整块 8192 字节的字符串字面量**。下面那一行**故意很长** ——

' The response buffer: **one 8192-byte string literal**. The line below is **deliberately long** --

' 它必须是字面量,不能拆行、也不能用 `+` 拼(拼接在 256 字节处截断)。

' it must be a literal, not split or joined (concatenation truncates at 256 bytes).

' `recv` 会从第一个字节开始覆盖它;长度一律看 `total`/`blen`,**别看 `LEN(rbuf)`**。

' `recv` overwrites it from the first byte; lengths come from `total`/`blen`, **never `LEN(rbuf)`**.

' ⚠ **别用 `vml_alloc`** —— 那个堆的起点(实测 ~102400)**落在本程序自己的数据段里**,往里写就把

' ⚠ **`vml_alloc` is not used** -- its heap base (~102400 measured) **lands inside this program's data segment**,

' BASIC 的全局变量覆盖掉(实测:读 81 字节后 `base` 变成文件里的 4 个字节)。

' so writing into it overwrites BASIC globals (measured: after an 81-byte read, `base` became four bytes of the file).

rbuf = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
base = strcpy(rbuf, rbuf)

g_first = 1
acquire()
g_first = 0

IF s_n > 0 THEN
    run_ui()
END IF

' ⚠ **不调 `ui_win_close()`**:桌面脚手架的 `--frame` 是"退出后合成一张图",关窗会把场景置空。

' ⚠ **`ui_win_close()` is not called**: the scaffold's `--frame` composites after exit, and closing nulls the scene.

PRINT "stock done (BASIC)"

' ── GB2312 全表 ────────────────────────────────────────────────

' ── The complete GB2312 table ────────────────────────────────────────────────

' 一行 = GB2312 的一个"区"(94 个字),区号从 0xB0 排到 0xF7,共 72 行 6768 项;

' One line is one GB2312 "zone" (94 characters), zones running from 0xB0 to 0xF7 -- 72 lines, 6768 entries.

' 下标 = (b0 - 0xB0) * 94 + (b1 - 0xA1),查出来是 **Unicode 码点**。

' The index is (b0 - 0xB0) * 94 + (b1 - 0xA1) and the value is a **Unicode codepoint**.

' 表里查不到的槽位填的是 `□`(U+25A1,同样三字节),所以下标越界也不会画出空字。

' Slots the standard leaves undefined hold `□` (U+25A1, also three bytes), so even a bogus index draws something.

' ⚠ 整张表**一个字面量装完**(20304 字节),不拆行、不用 `+` 拼 —— 拼接在 255 字节处截断。

' ⚠ The whole table fits in **one literal** (20304 bytes); it is not split or joined -- concatenation truncates at 255.

SUB load_gtab()
    gtab = "啊阿埃挨哎唉哀皑癌蔼矮艾碍爱隘鞍氨安俺按暗岸胺案肮昂盎凹敖熬翱袄傲奥懊澳芭捌扒叭吧笆八疤巴拔跋靶把耙坝霸罢爸白柏百摆佰败拜稗斑班搬扳般颁板版扮拌伴瓣半办绊邦帮梆榜膀绑棒磅蚌镑傍谤苞胞包褒剥薄雹保堡饱宝抱报暴豹鲍爆杯碑悲卑北辈背贝钡倍狈备惫焙被奔苯本笨崩绷甭泵蹦迸逼鼻比鄙笔彼碧蓖蔽毕毙毖币庇痹闭敝弊必辟壁臂避陛鞭边编贬扁便变卞辨辩辫遍标彪膘表鳖憋别瘪彬斌濒滨宾摈兵冰柄丙秉饼炳病并玻菠播拨钵波博勃搏铂箔伯帛舶脖膊渤泊驳捕卜哺补埠不布步簿部怖擦猜裁材才财睬踩采彩菜蔡餐参蚕残惭惨灿苍舱仓沧藏操糙槽曹草厕策侧册测层蹭插叉茬茶查碴搽察岔差诧拆柴豺搀掺蝉馋谗缠铲产阐颤昌猖场尝常长偿肠厂敞畅唱倡超抄钞朝嘲潮巢吵炒车扯撤掣彻澈郴臣辰尘晨忱沉陈趁衬撑称城橙成呈乘程惩澄诚承逞骋秤吃痴持匙池迟弛驰耻齿侈尺赤翅斥炽充冲虫崇宠抽酬畴踌稠愁筹仇绸瞅丑臭初出橱厨躇锄雏滁除楚础储矗搐触处揣川穿椽传船喘串疮窗幢床闯创吹炊捶锤垂春椿醇唇淳纯蠢戳绰疵茨磁雌辞慈瓷词此刺赐次聪葱囱匆从丛凑粗醋簇促蹿篡窜摧崔催脆瘁粹淬翠村存寸磋撮搓措挫错搭达答瘩打大呆歹傣戴带殆代贷袋待逮怠耽担丹单郸掸胆旦氮但惮淡诞弹蛋当挡党荡档刀捣蹈倒岛祷导到稻悼道盗德得的蹬灯登等瞪凳邓堤低滴迪敌笛狄涤翟嫡抵底地蒂第帝弟递缔颠掂滇碘点典靛垫电佃甸店惦奠淀殿碉叼雕凋刁掉吊钓调跌爹碟蝶迭谍叠丁盯叮钉顶鼎锭定订丢东冬董懂动栋侗恫冻洞兜抖斗陡豆逗痘都督毒犊独读堵睹赌杜镀肚度渡妒端短锻段断缎堆兑队对墩吨蹲敦顿囤钝盾遁掇哆多夺垛躲朵跺舵剁惰堕蛾峨鹅俄额讹娥恶厄扼遏鄂饿恩而儿耳尔饵洱二贰发罚筏伐乏阀法珐藩帆番翻樊矾钒繁凡烦反返范贩犯饭泛坊芳方肪房防妨仿访纺放菲非啡飞肥匪诽吠肺废沸费芬酚吩氛分纷坟焚汾粉奋份忿愤粪丰封枫蜂峰锋风疯烽逢冯缝讽奉凤佛否夫敷肤孵扶拂辐幅氟符伏俘服浮涪福袱弗甫抚辅俯釜斧脯腑府腐赴副覆赋复傅付阜父腹负富讣附妇缚咐噶嘎该改概钙盖溉干甘杆柑竿肝赶感秆敢赣冈刚钢缸肛纲岗港杠篙皋高膏羔糕搞镐稿告哥歌搁戈鸽胳疙割革葛格蛤阁隔铬个各给根跟耕更庚羹埂耿梗工攻功恭龚供躬公宫弓巩汞拱贡共钩勾沟苟狗垢构购够辜菇咕箍估沽孤姑鼓古蛊骨谷股故顾固雇刮瓜剐寡挂褂乖拐怪棺关官冠观管馆罐惯灌贯光广逛瑰规圭硅归龟闺轨鬼诡癸桂柜跪贵刽辊滚棍锅郭国果裹过哈骸孩海氦亥害骇酣憨邯韩含涵寒函喊罕翰撼捍旱憾悍焊汗汉夯杭航壕嚎豪毫郝好耗号浩呵喝荷菏核禾和何合盒貉阂河涸赫褐鹤贺嘿黑痕很狠恨哼亨横衡恒轰哄烘虹鸿洪宏弘红喉侯猴吼厚候后呼乎忽瑚壶葫胡蝴狐糊湖弧虎唬护互沪户花哗华猾滑画划化话槐徊怀淮坏欢环桓还缓换患唤痪豢焕涣宦幻荒慌黄磺蝗簧皇凰惶煌晃幌恍谎灰挥辉徽恢蛔回毁悔慧卉惠晦贿秽会烩汇讳诲绘荤昏婚魂浑混豁活伙火获或惑霍货祸击圾基机畸稽积箕肌饥迹激讥鸡姬绩缉吉极棘辑籍集及急疾汲即嫉级挤几脊己蓟技冀季伎祭剂悸济寄寂计记既忌际妓继纪嘉枷夹佳家加荚颊贾甲钾假稼价架驾嫁歼监坚尖笺间煎兼肩艰奸缄茧检柬碱硷拣捡简俭剪减荐槛鉴践贱见键箭件健舰剑饯渐溅涧建僵姜将浆江疆蒋桨奖讲匠酱降蕉椒礁焦胶交郊浇骄娇嚼搅铰矫侥脚狡角饺缴绞剿教酵轿较叫窖揭接皆秸街阶截劫节桔杰捷睫竭洁结解姐戒藉芥界借介疥诫届巾筋斤金今津襟紧锦仅谨进靳晋禁近烬浸尽劲荆兢茎睛晶鲸京惊精粳经井警景颈静境敬镜径痉靖竟竞净炯窘揪究纠玖韭久灸九酒厩救旧臼舅咎就疚鞠拘狙疽居驹菊局咀矩举沮聚拒据巨具距踞锯俱句惧炬剧捐鹃娟倦眷卷绢撅攫抉掘倔爵觉决诀绝均菌钧军君峻俊竣浚郡骏喀咖卡咯开揩楷凯慨刊堪勘坎砍看康慷糠扛抗亢炕考拷烤靠坷苛柯棵磕颗科壳咳可渴克刻客课肯啃垦恳坑吭空恐孔控抠口扣寇枯哭窟苦酷库裤夸垮挎跨胯块筷侩快宽款匡筐狂框矿眶旷况亏盔岿窥葵奎魁傀馈愧溃坤昆捆困括扩廓阔垃拉喇蜡腊辣啦莱来赖蓝婪栏拦篮阑兰澜谰揽览懒缆烂滥琅榔狼廊郎朗浪捞劳牢老佬姥酪烙涝勒乐雷镭蕾磊累儡垒擂肋类泪棱楞冷厘梨犁黎篱狸离漓理李里鲤礼莉荔吏栗丽厉励砾历利傈例俐痢立粒沥隶力璃哩俩联莲连镰廉怜涟帘敛脸链恋炼练粮凉梁粱良两辆量晾亮谅撩聊僚疗燎寥辽潦了撂镣廖料列裂烈劣猎琳林磷霖临邻鳞淋凛赁吝拎玲菱零龄铃伶羚凌灵陵岭领另令溜琉榴硫馏留刘瘤流柳六龙聋咙笼窿隆垄拢陇楼娄搂篓漏陋芦卢颅庐炉掳卤虏鲁麓碌露路赂鹿潞禄录陆戮驴吕铝侣旅履屡缕虑氯律率滤绿峦挛孪滦卵乱掠略抡轮伦仑沦纶论萝螺罗逻锣箩骡裸落洛骆络妈麻玛码蚂马骂嘛吗埋买麦卖迈脉瞒馒蛮满蔓曼慢漫谩芒茫盲氓忙莽猫茅锚毛矛铆卯茂冒帽貌贸么玫枚梅酶霉煤没眉媒镁每美昧寐妹媚门闷们萌蒙檬盟锰猛梦孟眯醚靡糜迷谜弥米秘觅泌蜜密幂棉眠绵冕免勉娩缅面苗描瞄藐秒渺庙妙蔑灭民抿皿敏悯闽明螟鸣铭名命谬摸摹蘑模膜磨摩魔抹末莫墨默沫漠寞陌谋牟某拇牡亩姆母墓暮幕募慕木目睦牧穆拿哪呐钠那娜纳氖乃奶耐奈南男难囊挠脑恼闹淖呢馁内嫩能妮霓倪泥尼拟你匿腻逆溺蔫拈年碾撵捻念娘酿鸟尿捏聂孽啮镊镍涅您柠狞凝宁拧泞牛扭钮纽脓浓农弄奴努怒女暖虐疟挪懦糯诺哦欧鸥殴藕呕偶沤啪趴爬帕怕琶拍排牌徘湃派攀潘盘磐盼畔判叛乓庞旁耪胖抛咆刨炮袍跑泡呸胚培裴赔陪配佩沛喷盆砰抨烹澎彭蓬棚硼篷膨朋鹏捧碰坯砒霹批披劈琵毗啤脾疲皮匹痞僻屁譬篇偏片骗飘漂瓢票撇瞥拼频贫品聘乒坪苹萍平凭瓶评屏坡泼颇婆破魄迫粕剖扑铺仆莆葡菩蒲埔朴圃普浦谱曝瀑期欺栖戚妻七凄漆柒沏其棋奇歧畦崎脐齐旗祈祁骑起岂乞企启契砌器气迄弃汽泣讫掐恰洽牵扦钎铅千迁签仟谦乾黔钱钳前潜遣浅谴堑嵌欠歉枪呛腔羌墙蔷强抢橇锹敲悄桥瞧乔侨巧鞘撬翘峭俏窍切茄且怯窃钦侵亲秦琴勤芹擒禽寝沁青轻氢倾卿清擎晴氰情顷请庆琼穷秋丘邱球求囚酋泅趋区蛆曲躯屈驱渠取娶龋趣去圈颧权醛泉全痊拳犬券劝缺炔瘸却鹊榷确雀裙群然燃冉染瓤壤攘嚷让饶扰绕惹热壬仁人忍韧任认刃妊纫扔仍日戎茸蓉荣融熔溶容绒冗揉柔肉茹蠕儒孺如辱乳汝入褥软阮蕊瑞锐闰润若弱撒洒萨腮鳃塞赛三叁伞散桑嗓丧搔骚扫嫂瑟色涩森僧莎砂杀刹沙纱傻啥煞筛晒珊苫杉山删煽衫闪陕擅赡膳善汕扇缮墒伤商赏晌上尚裳梢捎稍烧芍勺韶少哨邵绍奢赊蛇舌舍赦摄射慑涉社设砷申呻伸身深娠绅神沈审婶甚肾慎渗声生甥牲升绳省盛剩胜圣师失狮施湿诗尸虱十石拾时什食蚀实识史矢使屎驶始式示士世柿事拭誓逝势是嗜噬适仕侍释饰氏市恃室视试收手首守寿授售受瘦兽蔬枢梳殊抒输叔舒淑疏书赎孰熟薯暑曙署蜀黍鼠属术述树束戍竖墅庶数漱恕刷耍摔衰甩帅栓拴霜双爽谁水睡税吮瞬顺舜说硕朔烁斯撕嘶思私司丝死肆寺嗣四伺似饲巳松耸怂颂送宋讼诵搜艘擞嗽苏酥俗素速粟僳塑溯宿诉肃酸蒜算虽隋随绥髓碎岁穗遂隧祟孙损笋蓑梭唆缩琐索锁所塌他它她塔獭挞蹋踏胎苔抬台泰酞太态汰坍摊贪瘫滩坛檀痰潭谭谈坦毯袒碳探叹炭汤塘搪堂棠膛唐糖倘躺淌趟烫掏涛滔绦萄桃逃淘陶讨套特藤腾疼誊梯剔踢锑提题蹄啼体替嚏惕涕剃屉天添填田甜恬舔腆挑条迢眺跳贴铁帖厅听烃汀廷停亭庭挺艇通桐酮瞳同铜彤童桶捅筒统痛偷投头透凸秃突图徒途涂屠土吐兔湍团推颓腿蜕褪退吞屯臀拖托脱鸵陀驮驼椭妥拓唾挖哇蛙洼娃瓦袜歪外豌弯湾玩顽丸烷完碗挽晚皖惋宛婉万腕汪王亡枉网往旺望忘妄威巍微危韦违桅围唯惟为潍维苇萎委伟伪尾纬未蔚味畏胃喂魏位渭谓尉慰卫瘟温蚊文闻纹吻稳紊问嗡翁瓮挝蜗涡窝我斡卧握沃巫呜钨乌污诬屋无芜梧吾吴毋武五捂午舞伍侮坞戊雾晤物勿务悟误昔熙析西硒矽晰嘻吸锡牺稀息希悉膝夕惜熄烯溪汐犀檄袭席习媳喜铣洗系隙戏细瞎虾匣霞辖暇峡侠狭下厦夏吓掀锨先仙鲜纤咸贤衔舷闲涎弦嫌显险现献县腺馅羡宪陷限线相厢镶香箱襄湘乡翔祥详想响享项巷橡像向象萧硝霄削哮嚣销消宵淆晓小孝校肖啸笑效楔些歇蝎鞋协挟携邪斜胁谐写械卸蟹懈泄泻谢屑薪芯锌欣辛新忻心信衅星腥猩惺兴刑型形邢行醒幸杏性姓兄凶胸匈汹雄熊休修羞朽嗅锈秀袖绣墟戌需虚嘘须徐许蓄酗叙旭序畜恤絮婿绪续轩喧宣悬旋玄选癣眩绚靴薛学穴雪血勋熏循旬询寻驯巡殉汛训讯逊迅压押鸦鸭呀丫芽牙蚜崖衙涯雅哑亚讶焉咽阉烟淹盐严研蜒岩延言颜阎炎沿奄掩眼衍演艳堰燕厌砚雁唁彦焰宴谚验殃央鸯秧杨扬佯疡羊洋阳氧仰痒养样漾邀腰妖瑶摇尧遥窑谣姚咬舀药要耀椰噎耶爷野冶也页掖业叶曳腋夜液一壹医揖铱依伊衣颐夷遗移仪胰疑沂宜姨彝椅蚁倚已乙矣以艺抑易邑屹亿役臆逸肄疫亦裔意毅忆义益溢诣议谊译异翼翌绎茵荫因殷音阴姻吟银淫寅饮尹引隐印英樱婴鹰应缨莹萤营荧蝇迎赢盈影颖硬映哟拥佣臃痈庸雍踊蛹咏泳涌永恿勇用幽优悠忧尤由邮铀犹油游酉有友右佑釉诱又幼迂淤于盂榆虞愚舆余俞逾鱼愉渝渔隅予娱雨与屿禹宇语羽玉域芋郁吁遇喻峪御愈欲狱育誉浴寓裕预豫驭鸳渊冤元垣袁原援辕园员圆猿源缘远苑愿怨院曰约越跃钥岳粤月悦阅耘云郧匀陨允运蕴酝晕韵孕匝砸杂栽哉灾宰载再在咱攒暂赞赃脏葬遭糟凿藻枣早澡蚤躁噪造皂灶燥责择则泽贼怎增憎曾赠扎喳渣札轧铡闸眨栅榨咋乍炸诈摘斋宅窄债寨瞻毡詹粘沾盏斩辗崭展蘸栈占战站湛绽樟章彰漳张掌涨杖丈帐账仗胀瘴障招昭找沼赵照罩兆肇召遮折哲蛰辙者锗蔗这浙珍斟真甄砧臻贞针侦枕疹诊震振镇阵蒸挣睁征狰争怔整拯正政帧症郑证芝枝支吱蜘知肢脂汁之织职直植殖执值侄址指止趾只旨纸志挚掷至致置帜峙制智秩稚质炙痔滞治窒中盅忠钟衷终种肿重仲众舟周州洲诌粥轴肘帚咒皱宙昼骤珠株蛛朱猪诸诛逐竹烛煮拄瞩嘱主著柱助蛀贮铸筑住注祝驻抓爪拽专砖转撰赚篆桩庄装妆撞壮状椎锥追赘坠缀谆准捉拙卓桌琢茁酌啄着灼浊兹咨资姿滋淄孜紫仔籽滓子自渍字鬃棕踪宗综总纵邹走奏揍租足卒族祖诅阻组钻纂嘴醉最罪尊遵昨左佐柞做作坐座□□□□□亍丌兀丐廿卅丕亘丞鬲孬噩丨禺丿匕乇夭爻卮氐囟胤馗毓睾鼗丶亟鼐乜乩亓芈孛啬嘏仄厍厝厣厥厮靥赝匚叵匦匮匾赜卦卣刂刈刎刭刳刿剀剌剞剡剜蒯剽劂劁劐劓冂罔亻仃仉仂仨仡仫仞伛仳伢佤仵伥伧伉伫佞佧攸佚佝佟佗伲伽佶佴侑侉侃侏佾佻侪佼侬侔俦俨俪俅俚俣俜俑俟俸倩偌俳倬倏倮倭俾倜倌倥倨偾偃偕偈偎偬偻傥傧傩傺僖儆僭僬僦僮儇儋仝氽佘佥俎龠汆籴兮巽黉馘冁夔勹匍訇匐凫夙兕亠兖亳衮袤亵脔裒禀嬴蠃羸冫冱冽冼凇冖冢冥讠讦讧讪讴讵讷诂诃诋诏诎诒诓诔诖诘诙诜诟诠诤诨诩诮诰诳诶诹诼诿谀谂谄谇谌谏谑谒谔谕谖谙谛谘谝谟谠谡谥谧谪谫谮谯谲谳谵谶卩卺阝阢阡阱阪阽阼陂陉陔陟陧陬陲陴隈隍隗隰邗邛邝邙邬邡邴邳邶邺邸邰郏郅邾郐郄郇郓郦郢郜郗郛郫郯郾鄄鄢鄞鄣鄱鄯鄹酃酆刍奂劢劬劭劾哿勐勖勰叟燮矍廴凵凼鬯厶弁畚巯坌垩垡塾墼壅壑圩圬圪圳圹圮圯坜圻坂坩垅坫垆坼坻坨坭坶坳垭垤垌垲埏垧垴垓垠埕埘埚埙埒垸埴埯埸埤埝堋堍埽埭堀堞堙塄堠塥塬墁墉墚墀馨鼙懿艹艽艿芏芊芨芄芎芑芗芙芫芸芾芰苈苊苣芘芷芮苋苌苁芩芴芡芪芟苄苎芤苡茉苷苤茏茇苜苴苒苘茌苻苓茑茚茆茔茕苠苕茜荑荛荜茈莒茼茴茱莛荞茯荏荇荃荟荀茗荠茭茺茳荦荥荨茛荩荬荪荭荮莰荸莳莴莠莪莓莜莅荼莶莩荽莸荻莘莞莨莺莼菁萁菥菘堇萘萋菝菽菖萜萸萑萆菔菟萏萃菸菹菪菅菀萦菰菡葜葑葚葙葳蒇蒈葺蒉葸萼葆葩葶蒌蒎萱葭蓁蓍蓐蓦蒽蓓蓊蒿蒺蓠蒡蒹蒴蒗蓥蓣蔌甍蔸蓰蔹蔟蔺蕖蔻蓿蓼蕙蕈蕨蕤蕞蕺瞢蕃蕲蕻薤薨薇薏蕹薮薜薅薹薷薰藓藁藜藿蘧蘅蘩蘖蘼廾弈夼奁耷奕奚奘匏尢尥尬尴扌扪抟抻拊拚拗拮挢拶挹捋捃掭揶捱捺掎掴捭掬掊捩掮掼揲揸揠揿揄揞揎摒揆掾摅摁搋搛搠搌搦搡摞撄摭撖摺撷撸撙撺擀擐擗擤擢攉攥攮弋忒甙弑卟叱叽叩叨叻吒吖吆呋呒呓呔呖呃吡呗呙吣吲咂咔呷呱呤咚咛咄呶呦咝哐咭哂咴哒咧咦哓哔呲咣哕咻咿哌哙哚哜咩咪咤哝哏哞唛哧唠哽唔哳唢唣唏唑唧唪啧喏喵啉啭啁啕唿啐唼唷啖啵啶啷唳唰啜喋嗒喃喱喹喈喁喟啾嗖喑啻嗟喽喾喔喙嗪嗷嗉嘟嗑嗫嗬嗔嗦嗝嗄嗯嗥嗲嗳嗌嗍嗨嗵嗤辔嘞嘈嘌嘁嘤嘣嗾嘀嘧嘭噘嘹噗嘬噍噢噙噜噌噔嚆噤噱噫噻噼嚅嚓嚯囔囗囝囡囵囫囹囿圄圊圉圜帏帙帔帑帱帻帼帷幄幔幛幞幡岌屺岍岐岖岈岘岙岑岚岜岵岢岽岬岫岱岣峁岷峄峒峤峋峥崂崃崧崦崮崤崞崆崛嵘崾崴崽嵬嵛嵯嵝嵫嵋嵊嵩嵴嶂嶙嶝豳嶷巅彳彷徂徇徉後徕徙徜徨徭徵徼衢彡犭犰犴犷犸狃狁狎狍狒狨狯狩狲狴狷猁狳猃狺狻猗猓猡猊猞猝猕猢猹猥猬猸猱獐獍獗獠獬獯獾舛夥飧夤夂饣饧饨饩饪饫饬饴饷饽馀馄馇馊馍馐馑馓馔馕庀庑庋庖庥庠庹庵庾庳赓廒廑廛廨廪膺忄忉忖忏怃忮怄忡忤忾怅怆忪忭忸怙怵怦怛怏怍怩怫怊怿怡恸恹恻恺恂恪恽悖悚悭悝悃悒悌悛惬悻悱惝惘惆惚悴愠愦愕愣惴愀愎愫慊慵憬憔憧憷懔懵忝隳闩闫闱闳闵闶闼闾阃阄阆阈阊阋阌阍阏阒阕阖阗阙阚丬爿戕氵汔汜汊沣沅沐沔沌汨汩汴汶沆沩泐泔沭泷泸泱泗沲泠泖泺泫泮沱泓泯泾洹洧洌浃浈洇洄洙洎洫浍洮洵洚浏浒浔洳涑浯涞涠浞涓涔浜浠浼浣渚淇淅淞渎涿淠渑淦淝淙渖涫渌涮渫湮湎湫溲湟溆湓湔渲渥湄滟溱溘滠漭滢溥溧溽溻溷滗溴滏溏滂溟潢潆潇漤漕滹漯漶潋潴漪漉漩澉澍澌潸潲潼潺濑濉澧澹澶濂濡濮濞濠濯瀚瀣瀛瀹瀵灏灞宀宄宕宓宥宸甯骞搴寤寮褰寰蹇謇辶迓迕迥迮迤迩迦迳迨逅逄逋逦逑逍逖逡逵逶逭逯遄遑遒遐遨遘遢遛暹遴遽邂邈邃邋彐彗彖彘尻咫屐屙孱屣屦羼弪弩弭艴弼鬻屮妁妃妍妩妪妣妗姊妫妞妤姒妲妯姗妾娅娆姝娈姣姘姹娌娉娲娴娑娣娓婀婧婊婕娼婢婵胬媪媛婷婺媾嫫媲嫒嫔媸嫠嫣嫱嫖嫦嫘嫜嬉嬗嬖嬲嬷孀尕尜孚孥孳孑孓孢驵驷驸驺驿驽骀骁骅骈骊骐骒骓骖骘骛骜骝骟骠骢骣骥骧纟纡纣纥纨纩纭纰纾绀绁绂绉绋绌绐绔绗绛绠绡绨绫绮绯绱绲缍绶绺绻绾缁缂缃缇缈缋缌缏缑缒缗缙缜缛缟缡缢缣缤缥缦缧缪缫缬缭缯缰缱缲缳缵幺畿巛甾邕玎玑玮玢玟珏珂珑玷玳珀珉珈珥珙顼琊珩珧珞玺珲琏琪瑛琦琥琨琰琮琬琛琚瑁瑜瑗瑕瑙瑷瑭瑾璜璎璀璁璇璋璞璨璩璐璧瓒璺韪韫韬杌杓杞杈杩枥枇杪杳枘枧杵枨枞枭枋杷杼柰栉柘栊柩枰栌柙枵柚枳柝栀柃枸柢栎柁柽栲栳桠桡桎桢桄桤梃栝桕桦桁桧桀栾桊桉栩梵梏桴桷梓桫棂楮棼椟椠棹椤棰椋椁楗棣椐楱椹楠楂楝榄楫榀榘楸椴槌榇榈槎榉楦楣楹榛榧榻榫榭槔榱槁槊槟榕槠榍槿樯槭樗樘橥槲橄樾檠橐橛樵檎橹樽樨橘橼檑檐檩檗檫猷獒殁殂殇殄殒殓殍殚殛殡殪轫轭轱轲轳轵轶轸轷轹轺轼轾辁辂辄辇辋辍辎辏辘辚軎戋戗戛戟戢戡戥戤戬臧瓯瓴瓿甏甑甓攴旮旯旰昊昙杲昃昕昀炅曷昝昴昱昶昵耆晟晔晁晏晖晡晗晷暄暌暧暝暾曛曜曦曩贲贳贶贻贽赀赅赆赈赉赇赍赕赙觇觊觋觌觎觏觐觑牮犟牝牦牯牾牿犄犋犍犏犒挈挲掰搿擘耄毪毳毽毵毹氅氇氆氍氕氘氙氚氡氩氤氪氲攵敕敫牍牒牖爰虢刖肟肜肓肼朊肽肱肫肭肴肷胧胨胩胪胛胂胄胙胍胗朐胝胫胱胴胭脍脎胲胼朕脒豚脶脞脬脘脲腈腌腓腴腙腚腱腠腩腼腽腭腧塍媵膈膂膑滕膣膪臌朦臊膻臁膦欤欷欹歃歆歙飑飒飓飕飙飚殳彀毂觳斐齑斓於旆旄旃旌旎旒旖炀炜炖炝炻烀炷炫炱烨烊焐焓焖焯焱煳煜煨煅煲煊煸煺熘熳熵熨熠燠燔燧燹爝爨灬焘煦熹戾戽扃扈扉礻祀祆祉祛祜祓祚祢祗祠祯祧祺禅禊禚禧禳忑忐怼恝恚恧恁恙恣悫愆愍慝憩憝懋懑戆肀聿沓泶淼矶矸砀砉砗砘砑斫砭砜砝砹砺砻砟砼砥砬砣砩硎硭硖硗砦硐硇硌硪碛碓碚碇碜碡碣碲碹碥磔磙磉磬磲礅磴礓礤礞礴龛黹黻黼盱眄眍盹眇眈眚眢眙眭眦眵眸睐睑睇睃睚睨睢睥睿瞍睽瞀瞌瞑瞟瞠瞰瞵瞽町畀畎畋畈畛畲畹疃罘罡罟詈罨罴罱罹羁罾盍盥蠲钅钆钇钋钊钌钍钏钐钔钗钕钚钛钜钣钤钫钪钭钬钯钰钲钴钶钷钸钹钺钼钽钿铄铈铉铊铋铌铍铎铐铑铒铕铖铗铙铘铛铞铟铠铢铤铥铧铨铪铩铫铮铯铳铴铵铷铹铼铽铿锃锂锆锇锉锊锍锎锏锒锓锔锕锖锘锛锝锞锟锢锪锫锩锬锱锲锴锶锷锸锼锾锿镂锵镄镅镆镉镌镎镏镒镓镔镖镗镘镙镛镞镟镝镡镢镤镥镦镧镨镩镪镫镬镯镱镲镳锺矧矬雉秕秭秣秫稆嵇稃稂稞稔稹稷穑黏馥穰皈皎皓皙皤瓞瓠甬鸠鸢鸨鸩鸪鸫鸬鸲鸱鸶鸸鸷鸹鸺鸾鹁鹂鹄鹆鹇鹈鹉鹋鹌鹎鹑鹕鹗鹚鹛鹜鹞鹣鹦鹧鹨鹩鹪鹫鹬鹱鹭鹳疒疔疖疠疝疬疣疳疴疸痄疱疰痃痂痖痍痣痨痦痤痫痧瘃痱痼痿瘐瘀瘅瘌瘗瘊瘥瘘瘕瘙瘛瘼瘢瘠癀瘭瘰瘿瘵癃瘾瘳癍癞癔癜癖癫癯翊竦穸穹窀窆窈窕窦窠窬窨窭窳衤衩衲衽衿袂袢裆袷袼裉裢裎裣裥裱褚裼裨裾裰褡褙褓褛褊褴褫褶襁襦襻疋胥皲皴矜耒耔耖耜耠耢耥耦耧耩耨耱耋耵聃聆聍聒聩聱覃顸颀颃颉颌颍颏颔颚颛颞颟颡颢颥颦虍虔虬虮虿虺虼虻蚨蚍蚋蚬蚝蚧蚣蚪蚓蚩蚶蛄蚵蛎蚰蚺蚱蚯蛉蛏蚴蛩蛱蛲蛭蛳蛐蜓蛞蛴蛟蛘蛑蜃蜇蛸蜈蜊蜍蜉蜣蜻蜞蜥蜮蜚蜾蝈蜴蜱蜩蜷蜿螂蜢蝽蝾蝻蝠蝰蝌蝮螋蝓蝣蝼蝤蝙蝥螓螯螨蟒蟆螈螅螭螗螃螫蟥螬螵螳蟋蟓螽蟑蟀蟊蟛蟪蟠蟮蠖蠓蟾蠊蠛蠡蠹蠼缶罂罄罅舐竺竽笈笃笄笕笊笫笏筇笸笪笙笮笱笠笥笤笳笾笞筘筚筅筵筌筝筠筮筻筢筲筱箐箦箧箸箬箝箨箅箪箜箢箫箴篑篁篌篝篚篥篦篪簌篾篼簏簖簋簟簪簦簸籁籀臾舁舂舄臬衄舡舢舣舭舯舨舫舸舻舳舴舾艄艉艋艏艚艟艨衾袅袈裘裟襞羝羟羧羯羰羲籼敉粑粝粜粞粢粲粼粽糁糇糌糍糈糅糗糨艮暨羿翎翕翥翡翦翩翮翳糸絷綦綮繇纛麸麴赳趄趔趑趱赧赭豇豉酊酐酎酏酤酢酡酰酩酯酽酾酲酴酹醌醅醐醍醑醢醣醪醭醮醯醵醴醺豕鹾趸跫踅蹙蹩趵趿趼趺跄跖跗跚跞跎跏跛跆跬跷跸跣跹跻跤踉跽踔踝踟踬踮踣踯踺蹀踹踵踽踱蹉蹁蹂蹑蹒蹊蹰蹶蹼蹯蹴躅躏躔躐躜躞豸貂貊貅貘貔斛觖觞觚觜觥觫觯訾謦靓雩雳雯霆霁霈霏霎霪霭霰霾龀龃龅龆龇龈龉龊龌黾鼋鼍隹隼隽雎雒瞿雠銎銮鋈錾鍪鏊鎏鐾鑫鱿鲂鲅鲆鲇鲈稣鲋鲎鲐鲑鲒鲔鲕鲚鲛鲞鲟鲠鲡鲢鲣鲥鲦鲧鲨鲩鲫鲭鲮鲰鲱鲲鲳鲴鲵鲶鲷鲺鲻鲼鲽鳄鳅鳆鳇鳊鳋鳌鳍鳎鳏鳐鳓鳔鳕鳗鳘鳙鳜鳝鳟鳢靼鞅鞑鞒鞔鞯鞫鞣鞲鞴骱骰骷鹘骶骺骼髁髀髅髂髋髌髑魅魃魇魉魈魍魑飨餍餮饕饔髟髡髦髯髫髻髭髹鬈鬏鬓鬟鬣麽麾縻麂麇麈麋麒鏖麝麟黛黜黝黠黟黢黩黧黥黪黯鼢鼬鼯鼹鼷鼽鼾齄"
END SUB