dBase
xBase-family .prg (dBASE III/IV, FoxPro, Clipper style): IF / DO WHILE / FOR / DO CASE, PROCEDURE / FUNCTION / RETURN / DO ... WITH, plus a set of string and numeric builtins.
It is case-insensitive, and any name that is not defined is emitted as a library CALL, so ui_* / tty_* / dbf_* can be written directly.
How to run it on your phone
vml run examples/dbase/fizzbuzz.prg
Compiling takes a while (one to two minutes for C, a few seconds for scripting languages). Once the program is running, the gamepad is at the bottom of the screen - the D-pad and the four action buttons are all there. Tap the back arrow in the top-right corner to return to the Shell.
What to know before you write
- Output goes to
?(with a newline) and??(without) ui_*/tty_*/dbf_*are written directly (an undefined name is always compiled toCALL <original name>for the linker to resolve)- Concatenate strings with
+; turn a number into a string withSTR() - Comments: a whole line with
*(at the start of the line), inline with&& - Read and write DBF data files with the existing
dbf_*library functions (dbf_open/dbf_records/dbf_close)
Examples
| File | What it shows |
|---|---|
fizzbuzz.prg | FizzBuzz (FOR plus MOD plus ?) |
primes.prg | The primes from 2 to 40 (FUNCTION plus a loop plus a logical return) |
strings.prg | Builtin string functions plus calling a procedure with DO ... WITH |
Pitfalls we hit on real devices
- ⚠
#is the not-equal operator, so this front end deliberately does not run the C preprocessor - do not expect#includeto work;a # bmeans "a is not equal to b". - ⚠ Types are inferred at compile time and the inference is flow-insensitive: when one variable is assigned different types in different branches, the whole program uses the last of those types - avoid it, or convert explicitly with
INT(). - ⚠ There are no arrays / subscripts (
[is a string quote); no date type; no&varmacro substitution. - ⚠ Database commands (
USE/SELECT/INDEX/APPEND/REPLACE/LOCATE/SKIP, ...), the screen layout@ row,col SAY, and line inputACCEPT/INPUT/WAITare all recognised and skipped whole-line (no error, and no code emitted) - to tell whether a line really took effect, look for the matching instruction in the compiled output. - Library function names keep their original spelling (they do not take part in case folding) and must reach the linker verbatim.
The rest of this page is taken straight from the VML source (third_party/vml/VMLPrepares/DBaseCompiler/): the README covers what this front end supports and how to compile it, and the language reference covers the syntax itself. When upstream changes, regenerating this page brings it up to date.
Language reference
dBase (xBase) Language Subset Specification
This describes the part of the language the dBase front end supports in the Dolaima VML platform. It is a subset spec: what is written here is supported and verified. Chinese version:
DBASE_LANGUAGE_SPEC.md.
Version: v1.0 | Date: 2026-10-13 | Revised by: Shenzhen Tanso Intelligent Technology Co., Ltd.
Specification standard
| Field | Value |
|---|---|
| Target standard | dBase/xBase subset |
| Publication year | — |
| Completeness | Subset (not itemised) |
| Tests | 19 passing / 0 failing / 1 known-red |
Keywords
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
Overview
A subset of dBase/xBase is supported, compiled to VML assembly. dBase/xBase is a de-facto standard — it has no formal standard number and implementations (dBASE III/IV, FoxPro, Clipper, …) disagree with one another — so this document describes exactly the part this frontend accepts. The frontend is case-insensitive (the lexer folds to upper case; the only exception is library function names, which must reach the linker verbatim and so keep their original spelling). It deliberately does not run the C preprocessor (in dBase # is the not-equal operator, so treating # as a directive marker would silently change meaning). Database command words (USE / SELECT / COUNT / TOTAL, …) are kept out of the keyword table (they are also very common variable names — total = 0 would be swallowed as a command), and are instead recognised at statement level by identifier text and skipped whole-line. Any name that is neither a builtin nor user-defined is emitted as CALL <original name> for the linker to resolve.
Supported language features
The features this frontend accepts are organised by the sections below; each one states what is accepted and what it means:
- Lexical: comments (
*at line start, inline&&, leadingNOTE), the case rules, literals (integers / floats / three quoting styles for strings / logical.T.), operators and precedence, type promotion — §1 - Statements: output, assignment, declaration (= registering a variable), conditionals, loops, multi-branch, exit / continue, subprograms — §2
- Builtin functions:
LEN/UPPER/LOWER/SUBSTR/STR/VAL/INT/MOD/ABS/ROUND— §3 - Calling library functions directly:
ui_*/tty_*/dbf_*are written as-is and compile toCALL <original name>— §4 - Calling convention: arguments pushed right-to-left, caller cleans the stack, return value in
R0(Int/Str/Bool) orD0(Float) — §5 - Explicitly not supported: arrays and subscripts, date types, macro substitution
&var, database command forms,@ row,col SAY, line inputACCEPT/INPUT/WAIT— §6
See the sections below for details.
1. Lexical
1.1 Comments
- Whole-line:
*at the start of a line (after leading whitespace). - Inline:
&&to end of line. NOTE …at the start of a line is also a comment.
A
*anywhere else is multiplication (x = 5 * 2).
1.2 Case
dBase is case-insensitive: keywords, built-in names, and user variable/procedure names are folded to upper case. The one exception is library function names (ui_2d_clear, dbf_open) — they keep their original spelling for the linker.
1.3 Literals
| Form | Example | Track |
|---|---|---|
| integer | 42, -7 | Int (4 bytes) |
| float | 3.14, 1.5e3, .5 | Float (8-byte double) |
| string | "abc", 'abc', [abc] | all three quote styles are strings |
| logical | .T. / .F. (also .Y. / .N.) | 1 / 0 |
Doubled quotes inside a string mean one literal quote: "say ""hi""" → say "hi".
1.4 Operators (lowest to highest precedence)
OR < AND < comparisons (= == <> # != < > <= >=) < + - < * / % < ** ^ (right-assoc) < unary - + .NOT..
/is always real division (10 / 3=3.3333333); useINT(a/b)orMODfor integer division.%is integer modulo.**/^is power (both int →ipow; either float →pow).
1.5 Type promotion
String on either side of + → string concatenation; otherwise Float wins over Int. Comparisons and logical operators always yield Bool (0/1).
2. Statements
Statements are newline-terminated (; also acts as a newline).
2.1 Output
? expr [, expr …] * print with newline
?? expr [, expr …] * print without newline (`:` is a synonym)
?
2.2 Assignment
x = 42
x := 42
STORE 42 TO x
STORE 42 TO a, b, c
m.x = 42
2.3 Declarations (variable registration)
PUBLIC a, b / PRIVATE c / LOCAL d
2.4 Condition
IF cond
…
ELSEIF cond
…
ELSE
…
ENDIF
2.5 Loops
DO WHILE cond * pre-test: the body runs zero times if cond is false
…
ENDDO
FOR i = start TO end [STEP step]
…
NEXT
STEPdefaults to1.- A negative constant step (
STEP -1) loops downward (i >= end); otherwise upward (i <= end). - A variable step cannot have its direction determined (known limitation, KNOWN-D01).
2.6 Multi-branch
DO CASE
CASE cond
…
OTHERWISE
…
ENDCASE
2.7 Loop control
EXIT breaks the innermost loop; LOOP jumps back to its condition.
2.8 Subroutines
PROCEDURE name [(p1, …)]
FUNCTION name [(p1, …)]
…
RETURN [expr]
FUNCTION returns a value (0 if none); PROCEDURE returns nothing. Recursion works. Parameters live on the stack; variables live in the data section (globally visible).
Call: DO name [WITH a, …] (statement) or result = name(a, …) (expression).
3. Built-in functions
| Function | Returns | Note |
|---|---|---|
LEN(s) | Int | string length (strlen) |
UPPER(s) / LOWER(s) | Str | case conversion |
SUBSTR(s, start [, len]) | Str | start is 1-based |
STR(n) | Str | number → string |
VAL(s) | Int | string → integer |
INT(x) | Int | truncate toward zero |
MOD(a, b) | Int | modulo |
ABS(x) | same | absolute value |
ROUND(x [, n]) | same | round to n decimals (n defaults to 0) |
4. Direct library calls
Any name that is neither a built-in nor user-defined compiles to CALL <name>, resolved by the linker against the shared libraries — ui_2d_clear(0), tty_puts("x"), dbf_open("a.dbf") all work. Arguments are passed as 4-byte integers. Lib/dbase/builtin.vml already links the print/string/ convert/math/dbf modules; add a .linked line there for more.
5. Calling convention
Arguments are pushed right-to-left; the callee reads them from [R12+12+4i] (or the matching offset for 8-byte types); the caller cleans the stack. Return value is in R0 (Int/Str/Bool) or D0 (Float).
6. Explicitly unsupported (this subset)
- Arrays / subscripts (
[is a string quote here). - Date type, macro substitution
&var. - DB command forms (
USE/SELECT/INDEX/APPEND/REPLACE/LOCATE/SKIP/ …) — recognised and skipped line by line (no error). Usedbf_*functions to read/write DBF. - Screen layout
@ row,col SAY …(skipped). ACCEPT/INPUT/WAIT(skipped).
Compiler README
dBase (xBase) Compiler
Path: VMLPrepares/DBaseCompiler/ Language key: dbase | Namespace: DBaseCompiler | Source extension: .prg Language library: Lib/dbase/ (builtin.vml + the declaration file shared.prg) Status: production-ready subset ("full operators + control flow + function calls"; you can write general programs)
In one line
Compiles xBase-family (dBASE III/IV, FoxPro, Clipper style) .prg sources into VML: comments, output, assignment, arithmetic/comparison/logical operators, IF / DO WHILE / FOR / DO CASE, PROCEDURE / FUNCTION / RETURN / DO … WITH, plus a set of string/numeric built-ins.
Not to be confused with
dbf:scripts/vml-dbf-probe/andLib/shared/dbf.vmlin this repository are the DBF data-file format library/probe, not a language. This directory is a dBase language front end; the two are unrelated.
Features
- Lexer / Parser / CodeGenerator built on
CompilerBase(LexerBase/ParserBase<>/CodeGeneratorBase) - Comments: leading-line
*and inline&& - Output:
?(newline) and??(no newline), multiple expressions separated by commas - Assignment:
var = e,var := e,STORE e TO v1, v2 … - Declarations:
PUBLIC/PRIVATE/LOCAL(treated as variable registration) - Types: integer (i32), float (f64), string, logical (
.T./.F.) - Operators:
+ - * / % «bold»(^=«/»), comparisons= == <> # != < > <= >=, logic.AND./.OR./.NOT. - Control flow:
IF…ELSEIF…ELSE…ENDIF,DO WHILE…ENDDO(pre-test),FOR…TO…STEP…NEXT,DO CASE…CASE…OTHERWISE…ENDCASE,EXIT/LOOP - Subroutines:
PROCEDURE/FUNCTION(parameters, recursion),RETURN [expr],DO name [WITH args…] - Built-ins:
LENUPPERLOWERSUBSTRSTRVALINTMODABSROUND - Direct library calls: any unknown name →
CALL <name>(ui_*/tty_*/dbf_*… usable directly) - Case-insensitive keywords/identifiers; library names keep their original spelling for the linker
- Outside the subset: arrays, date type, macro substitution
&var, and DB command forms (USE/SELECT/INDEX…) — those lines are skipped
Usage
# during development (this project's csproj is an Exe)
dotnet run --project VMLPrepares/DBaseCompiler -- foo.prg -o foo.vml
# via vmlcli (after the integration step)
vmlcli foo.prg --lang dbase
Type model
dBase is dynamically typed; this front end does a flow-insensitive static approximation:
| Track | Width | Register | What falls here |
|---|---|---|---|
| Int / Bool / Str | 4 bytes | R0 | integers, logicals, string pointers |
| Float | 8 bytes (double) | D0 | numbers with a decimal point / exponent |
Variables live in the data section (var_<NAME>); only subroutine parameters use the stack ([R12+12+off], the same __stdcall argument area the shared libraries use).
Relation to the dbf library
DBF read/write is the existing library's job and is not reimplemented here. Lib/dbase/builtin.vml already links ../shared/dbf.vml, so you can call dbf_open(...), dbf_records(h), dbf_close(h) directly as ordinary library functions.
Probe
scripts/vml-dbase-probe/run.sh # 19 pass / 0 fail / 1 known-red (via vmlcli --lang dbase)
Both stages go through vmlcli (the real channel): (1) vmlcli <case.prg> --lang dbase --vml <tmp>.vml (compile + assemble + link), (2) vmlcli <tmp>.vml (load + run). Do not use the bundled scripts/vml-dbase-probe/runner/ as the criterion — it assembles its own link set, which hides defects like a missing Lib/dbase/ module set (that is exactly what it missed on first integration).
⚠
Lib/dbase/needs its own module set (dotnet run --project third_party/vml/tools/GenLib -- -m -l dbase -r third_party/vml). Without it, bare.linkednames in the product header (console.vml…) resolve to another language's modules and linking fails with a string ofprintx_int32undefined errors.
See also
- Language spec:
DBASE_LANGUAGE_SPEC.en.md - Integration checklist:
INTEGRATION.md - Defect ledger:
bugs/vml_dbase_bugs/DBASE问题清单.md