dBase
xBase 系(dBASE III/IV、FoxPro、Clipper 风格)的 .prg:IF / DO WHILE / FOR / DO CASE、PROCEDURE / FUNCTION / RETURN / DO … WITH,以及一组字符串 / 数值内置函数。
大小写不敏感;任何未定义的函数名一律按库函数发 CALL,所以 ui_* / tty_* / dbf_* 直接就能写。
在手机上怎么跑
vml run examples/dbase/fizzbuzz.prg
编译要等一会儿(C 那种要一两分钟,脚本类语言几秒)。程序跑起来后屏幕底部就是手柄,方向键 + 四个动作键都在;点右上角返回可以回到命令行。
写法要点
- 输出用
?(带换行)与??(不带换行) ui_*/tty_*/dbf_*直接写就行(未定义的名字一律编译成CALL <原名>,由链接器解析)- 字符串拼接用
+,数值转字符串用STR() - 注释:整行
*(行首)、行内&& - 读写 DBF 数据文件用现成的
dbf_*库函数(dbf_open/dbf_records/dbf_close)
示例
| 文件 | 演示什么 |
|---|---|
fizzbuzz.prg | FizzBuzz(FOR + MOD + ?) |
primes.prg | 2..40 的素数(FUNCTION + 循环 + 逻辑返回) |
strings.prg | 内置字符串函数 + DO … WITH 调过程 |
实测踩过的坑
- ⚠
#是不等号,所以本前端有意不跑 C 预处理器 —— 别指望#include那一套;a # b就是「a 不等于 b」。 - ⚠ 类型是编译期推的,而且流不敏感:同一个变量在不同分支被赋成不同类型时,全程序按最后那个类型算 —— 要么避免,要么显式
INT()转换。 - ⚠ 没有数组 / 下标(
[是字符串引号);没有日期类型;&var宏替换不做。 - ⚠ 数据库命令(
USE/SELECT/INDEX/APPEND/REPLACE/LOCATE/SKIP…)、屏幕布局@ 行,列 SAY、行输入ACCEPT/INPUT/WAIT都是识别到就整行跳过(不报错、也不生成代码)—— 要判断某一行有没有真的生效,看编译产物里有没有对应指令。 - 库函数名保留原始拼写(不跟着大小写归并),要一字不差地交给链接器。
下面的内容是从 VML 源码里直接带的(third_party/vml/VMLPrepares/DBaseCompiler/): README 讲这个前端支持什么、怎么编;语言规范 讲语法本身。上游一改,这里重新生成就是最新的。
语言规范
dBase(xBase)语言子集规范
本文档描述 都来码 VML 平台的 dBase 前端支持的那一部分语言。它是"做子集"的规范:这里没写的东西不一定不支持,但这里写的都支持并已跑通。英文版见
DBASE_LANGUAGE_SPEC.en.md。
版本:v1.0 | 日期:2026-10-13 | 修订者:深圳市探索智能科技有限公司
规范标准
| 字段 | 值 |
|---|---|
| 目标标准 | dBase/xBase 子集 |
| 发布年份 | — |
| 完成度 | 子集(未逐项统计) |
| 测试 | 19 通过 / 0 失败 / 1 已知红 |
关键字
if else elseif endif endi do while enddo endd for to step next endfor case otherwise endcase exit loop return procedure proc function with store public private local
概述
支持 dBase/xBase 子集,编译为 VML 汇编代码。dBase/xBase 是事实标准 —— 它没有正式的标准编号,各家实现(dBASE III/IV、FoxPro、Clipper…)互有出入,所以本文只描述本前端实际接受的那一部分。前端大小写不敏感(词法器按大写归并;唯一例外是库函数名,它们要一字不差地交给链接器,故保留原始拼写);有意不跑 C 预处理器(dBase 里 # 是不等号,一见 # 就跑预处理会把每个不等号当成指令、静默改语义);数据库命令字(USE / SELECT / COUNT / TOTAL…)不进关键字表(它们同时是极常见的变量名,进去会让 total = 0 被当成命令整行跳掉),改在语句层按标识符文本识别并整行跳过。内置函数之外的名字一律发 CALL <原名>,由链接器解析到共享库。
支持的语言特性
本前端接受的语言特性按下方原有章节组织,每节都写明「收什么写法、是什么语义」:
- 词法:注释(行首
*、行内&&、行首NOTE)、大小写规则、字面量(整数 / 浮点 / 三种引号的字符串 / 逻辑.T.)、运算符与优先级、类型提升 —— §1 - 语句:输出、赋值、声明(= 登记变量)、条件、循环、多分支、跳出 / 继续、子程序 —— §2
- 内置函数:
LEN/UPPER/LOWER/SUBSTR/STR/VAL/INT/MOD/ABS/ROUND—— §3 - 库函数直呼:
ui_*/tty_*/dbf_*直接写,编译成CALL <原名>—— §4 - 调用约定:实参从右到左压栈、调用方清栈,返回值在
R0(Int/Str/Bool)或D0(Float) —— §5 - 明确不支持:数组与下标、日期类型、宏替换
&var、数据库命令形式、@ 行,列 SAY、行输入ACCEPT/INPUT/WAIT—— §6
详见下方各节。
1. 词法
1.1 注释
- 整行注释:
*在行首(前导空白之后)到行尾。 - 行内注释:
&&到行尾。 NOTE …在行首也是注释。
*在别处是乘号(x = 5 * 2)——判据是"是不是本行第一个非空白字符"。
1.2 大小写
dBase 大小写不敏感:关键字(IF/Do/endif)、内置函数名、用户变量名/过程名都按大写归并。唯一的例外是库函数名(ui_2d_clear / dbf_open 这种)—— 它们要一字不差地交给链接器,所以保留原始拼写。
1.3 字面量
| 形态 | 例子 | 说明 |
|---|---|---|
| 整数 | 42、-7 | 走 Int 轨(4 字节) |
| 浮点 | 3.14、1.5e3、.5 | 走 Float 轨(8 字节双精度) |
| 字符串 | "abc"、'abc'、[abc] | 三种引号都收(xBase 惯例:[ 是引号,不是下标) |
| 逻辑 | .T. / .F.(也收 .Y. / .N.) | 值 1 / 0 |
字符串里的双写引号表示一个引号字面量:"say ""hi""" → say "hi"。
1.4 运算符(按优先级从低到高)
| 优先级 | 运算符 |
|---|---|
| 1(最低) | .OR. |
| 2 | .AND. |
| 3 | = == <> # != < <= > >= |
| 4 | + - |
| 5 | * / % |
| 6 | ** ^(右结合) |
| 7(最高) | 一元 - + .NOT. |
/恒为实数除法(10 / 3=3.3333333);整除用INT(a / b)或MOD。%是整数取模(MOD(17,5)=2)。**/^是幂:两边都整数走ipow,任一侧浮点走pow。.NOT.的优先级高于比较(与 C 的!同族)。
1.5 类型提升
- 字符串参与
+⇒ 字符串拼接(任一侧是串即可)。其余算术里出现字符串按整数(指针)算。 - 算术:任一侧是 Float ⇒ 结果 Float;否则 Int。
- 比较、逻辑运算结果一律 Bool(0/1)。
2. 语句
语句以换行终结(; 也当换行)。支持的语句:
2.1 输出
? 表达式 [, 表达式 …] * 打印后换行
?? 表达式 [, 表达式 …] * 打印不换行(`:` 是 ?? 的同义写法)
? * 空 ? 只输出一个换行
表达式之间直接拼接(不加分隔符)。字符串原样打印,整数按十进制,浮点按小数。
2.2 赋值
x = 42
x := 42
STORE 42 TO x
STORE 42 TO a, b, c * 一个值赋给多个变量
m.x = 42 * `m.` 前缀(内存变量)—— 与本文件里对应
赋值按目标变量的声明类型转换右值(见 README 的"类型模型")。
2.3 声明(本子集 = 登记变量)
PUBLIC a, b
PRIVATE c
LOCAL d
2.4 条件
IF 条件
…
ELSEIF 条件
…
ELSE
…
ENDIF
ENDIF 也写作 ENDI。
2.5 循环
DO WHILE 条件 * 前置判断:条件为假则循环体一次都不跑
…
ENDDO * 也写作 ENDD
FOR i = 起 TO 止 [STEP 步长]
…
NEXT * 也写作 ENDFOR
STEP缺省为1。- 负常量步长(
STEP -1)自动按下行循环(i >= 止);否则按上行(i <= 止)。 - ⚠ 步长是变量时判不出方向(已知限制,见
bugs/vml_dbase_bugs/的 KNOWN-D01)。
2.6 多分支
DO CASE
CASE 条件
…
CASE 条件
…
OTHERWISE
…
ENDCASE
按顺序判第一个为真的 CASE;OTHERWISE 可省。
2.7 跳出 / 继续
EXIT—— 跳出最内层DO WHILE/FOR。LOOP—— 回到最内层循环的条件判断处。
2.8 子程序
PROCEDURE 名 [(形参, …)]
FUNCTION 名 [(形参, …)]
…
RETURN [表达式]
FUNCTION有返回值(无RETURN值时返回 0);PROCEDURE无返回值。- 可递归。
- 形参在栈上(
[R12+12+off]),变量在数据段(全局可见)。
调用:
DO 名 [WITH 实参, …] * 当语句调(忽略返回值)
结果 = 名(实参, …) * 当表达式调(用返回值)
实参个数与形参不一致时按传入的个数对齐(多传忽略、少传读到未定义值 —— 本子集不校验)。
3. 内置函数
| 函数 | 返回 | 说明 |
|---|---|---|
LEN(s) | Int | 字符串长度(strlen) |
UPPER(s) / LOWER(s) | Str | 转大写 / 小写(cs_to_upper / cs_to_lower) |
SUBSTR(s, 起 [, 长]) | Str | 子串,起 是 1 基;省略 长 取到末尾 |
STR(n) | Str | 数值转字符串(整数 int_to_str,浮点 float_to_str) |
VAL(s) | Int | 字符串转整数(str_to_int) |
INT(x) | Int | 截断取整(向零,浮点则 D2I) |
MOD(a, b) | Int | 取模 |
ABS(x) | 同参 | 绝对值(整数 abs,浮点 fabs) |
ROUND(x [, n]) | 同参 | 四舍五入到 n 位小数(缺省 n=0)。浮点用 floor(x*10^n+0.5)/10^n;整数原样 |
4. 库函数直呼
任何不是内置、也不是用户定义的函数名,一律编译成 CALL <原名>,由链接器解析到共享库。所以 ui_2d_clear(0)、tty_puts("x")、dbf_open("a.dbf") 都能直接写。
- 实参一律按 4 字节整数传(浮点实参会被截断)—— 共享库接口绝大多数收整数/指针。
- 返回值当整数用(字符串返回函数返回的是指针,可直接参与
+拼接或STR之外的处理)。 Lib/dbase/builtin.vml已挂上打印簇、字符串、转换、数学、dbf等模块。要用别的库,在Lib/dbase/builtin.vml里补一条.linked。
5. 与共享库的调用约定
子程序与库函数都走同一套:实参从右到左压栈,被调方从 [R12+12+4i](8 字节类型从对应偏移)读, 调用方清栈。返回值在 R0(Int/Str/Bool)或 D0(Float)。
6. 明确不支持(本子集)
- 数组 / 下标(
[是字符串引号)。 - 日期类型、宏替换
&var。 - 数据库命令形式(
USE/SELECT/INDEX/APPEND/REPLACE/LOCATE/SKIP/ …) —— 识别到就整行跳过(不报错)。要读写 DBF 用dbf_*库函数。 - 屏幕布局命令
@ 行,列 SAY …(整行跳过)。 - 行输入
ACCEPT/INPUT/WAIT(整行跳过)。
跳过类命令不报错也不生成代码 —— 这是刻意的:老程序里这类命令遍地都是,报错会把"能跑的程序"挡在门外。要判断某一行有没有真的生效,看编译产物里有没有对应指令。
编译器 README
dBase(xBase)编译器
路径:VMLPrepares/DBaseCompiler/ 语言 key:dbase | 命名空间:DBaseCompiler | 源码扩展名:.prg 语言库:Lib/dbase/(builtin.vml + 声明文件 shared.prg) 完成度:🟢 子集生产可用(「完整运算 + 控制流 + 函数调用」齐备,能写一般程序)
一句话
把 xBase 系(dBASE III/IV/FoxPro/Clipper 风格)的 .prg 源码编译成 VML,覆盖注释、输出、赋值、四则/比较/逻辑运算、IF/DO WHILE/FOR/DO CASE、 PROCEDURE/FUNCTION/RETURN/DO … WITH,以及一组字符串/数值内置函数。
别与
dbf混淆:仓库里的scripts/vml-dbf-probe/与Lib/shared/dbf.vml是 DBF 数据文件格式的库/探针,不是一门语言。本目录是 dBase 语言前端,两者互不相干。
功能
- ✅ Lexer / Parser / CodeGenerator(继承
CompilerBase的LexerBase/ParserBase<>/CodeGeneratorBase) - ✅ 注释:行首
*(整行)、行内&& - ✅ 输出:
?(带换行)、??(不带换行)、可多表达式(逗号分隔) - ✅ 赋值:
var = e、var := e、STORE e TO v1, v2 … - ✅ 声明:
PUBLIC/PRIVATE/LOCAL(本子集里等于"登记变量") - ✅ 类型:整数(i32)、浮点(f64)、字符串、逻辑(
.T./.F.) - ✅ 运算:
+ - * / % «bold»(^同«/»)、比较= == <> # != < > <= >=、逻辑.AND./.OR./.NOT. - ✅ 控制流:
IF…ELSEIF…ELSE…ENDIF、DO WHILE…ENDDO(前置判断)、FOR…TO…STEP…NEXT、DO CASE…CASE…OTHERWISE…ENDCASE、EXIT/LOOP - ✅ 子程序:
PROCEDURE/FUNCTION(可带形参、可递归)、RETURN [值]、DO 名 [WITH 实参…] - ✅ 内置函数:
LENUPPERLOWERSUBSTRSTRVALINTMODABSROUND - ✅ 库函数直呼:任何未定义的函数名 →
CALL <原名>(ui_*/tty_*/dbf_*… 直接可用) - ✅ 大小写不敏感(关键字、标识符都按大写归并;库函数名保留原拼写交链接器)
- ⚠️ 子集之外:数组、日期类型、宏替换
&var、USE/SELECT/INDEX等 DB 命令形式(整行跳过)
用法
# 开发期(本机的 csproj 是 Exe,可直接跑)
dotnet run --project VMLPrepares/DBaseCompiler -- foo.prg -o foo.vml
# 走 vmlcli(收尾接线后)
vmlcli foo.prg --lang dbase
类型模型(重要)
dBase 是动态类型语言;本前端做流不敏感的静态近似:
| 轨道 | 宽度 | 寄存器 | 谁落在这条轨 |
|---|---|---|---|
| Int / Bool / Str | 4 字节 | R0 | 整数、逻辑、字符串指针 |
| Float | 8 字节(双精度) | D0 | 带小数点/指数的数值 |
- 变量一律落在数据段(
var_<名>)—— dBase 的内存变量本来就是全局可见的,这也省掉整套栈帧局部量管理。只有子程序形参走栈([R12+12+off],与共享库的__stdcall实参区同一套约定)。 - 每个表达式的类型由字面量形态 + 变量类型 + 函数返回类型推断(
CodeGenerator.Analyze预扫描两趟)。 - 赋值会按目标变量的声明类型转换 RHS(
x = 1而x是浮点时自动I2D)。 - ⚠ 流不敏感:同一个变量在不同分支被赋成不同类型时,全程序按最后那个类型算。要么避免,要么显式
INT()/ 转换。
与 dbf 库的关系
DBF 数据文件读写是既有库的活,本前端不重复实现。 Lib/dbase/builtin.vml 已 .linked "../shared/dbf.vml",程序里直接:
h = dbf_open("data.dbf")
? "records=" + STR(dbf_records(h))
dbf_close(h)
dbf_* 是普通库函数(未定义名 → CALL dbf_xxx),与上面那条"库函数直呼"同一条路。
探针
scripts/vml-dbase-probe/run.sh # 通过 19 / 失败 0 / 已知红 1(通道:vmlcli --lang dbase)
两段都走 vmlcli(真实通道):① vmlcli <case.prg> --lang dbase --vml <tmp>.vml(编译+汇编+链接)、 ② vmlcli <tmp>.vml(装载运行)。别用 scripts/vml-dbase-probe/runner/ 那份自带工装当判据 —— 它自己拼链接集、会补上 vmlcli 缺的模块,从而掩盖「Lib/dbase/ 缺模块集」这类缺陷(首次接线时正是它漏报的)。
⚠
Lib/dbase/必须有自己的模块集(dotnet run --project third_party/vml/tools/GenLib -- -m -l dbase -r third_party/vml)。否则产物头部的裸名.linked(console.vml…)会解析到别家语言的模块上、链接期报一串「未定义的函数 'printx_int32'」。详见INTEGRATION.md第 ⓿ 步。
相关文档
- 语言规范:
DBASE_LANGUAGE_SPEC.md(英文版DBASE_LANGUAGE_SPEC.en.md) - 接进 vmlcli / 手机端的接线清单:
INTEGRATION.md - 缺陷清单:
bugs/vml_dbase_bugs/DBASE问题清单.md