VML 是什么
VML = 一台跑在你手机里的虚拟机,自带一套标准库、一台汇编器、一个字节码格式。
你写的程序(C / Python / BASIC / …共 27 种语言)会被前端编译器编成这台虚拟机认识的指令,再由它跑起来。所以:
- 同一种语言在这里跑,不需要装任何环境 —— 不用 gcc、不用解释器
- 一次写好,27 种语言都能编(同一条编译链)
- 速度快、体积小(一份 hello 世界几万条指令,字节码几百 KB)
为什么不是直接调用系统的编译器
手机上装不了、也跑不动完整的 gcc / CPython。VML 把这些工作放到了编译期—— 编译在前端完成,运行时只需要一台很小的虚拟机。
三级产物
你的源码 (.c) ──前端编译──▶ .vml(文本汇编) ──汇编──▶ .vmb(二进制字节码)
│
装载即执行
| 产物 | 是什么 | 什么时候用 |
|---|---|---|
| 源码 | 你写的 | 平时改这个 |
.vml | 文本汇编,能直接看、能 grep | 想看看编译器把你的代码编成了什么 |
.vmb | 二进制字节码 | 要跑就加载它,最快 |
vml run 三种都认,自动识别。
能画界面、能出声、能震动
VML 程序不是只能打印文字——它有一套完整的手机界面接口:
- 开一个绘图窗口,画矩形 / 圆 / 路径 / 渐变 / 文字
- 收触摸(多点)、按键、定时器消息
- 合成音效(不用带音频文件)—— 单音用
ui_audio_beep,和弦 / 旋律用ui_tone_on/off(复音,最多 32 个声部同时响)、震动、屏幕常亮 - 本地存档
已经用这套接口写了俄罗斯方块、五子棋、吃豆人、贪吃蛇、飞机大战、钢琴… 都在 examples/ 里,能直接跑。
详见「UI 开发」。
27 种语言,随便挑一门
同一套 VML 接口,27 个前端都能用。你熟悉哪门就用哪门写,编出来的东西跑在同一台虚拟机上。每种语言的详细说明(语法规范 + 这个前端支持什么)在「关于 → 使用说明 → VML 编译器 → 27 种语言」里。
27 种语言,各挑一个例子跑
| 语言 | 示例 | 跑法 |
|---|---|---|
| C | examples/c/app/tetris.c gomoku.c pacman.c mario.c starfall.c **piano.c** **audio_test.c** | vml run examples/c/app/gomoku.c |
| C++ | examples/cpp/app/snake.cpp | vml run examples/cpp/app/snake.cpp |
| C# | examples/csharp/app/snake.cs racer.cs | vml run examples/csharp/app/snake.cs |
| Objective-C | examples/objc/app/snake.m | vml run examples/objc/app/snake.m |
| Java | examples/java/test/catch.java | vml run examples/java/test/catch.java |
| Kotlin | examples/kotlin/test/catch.kt | vml run examples/kotlin/test/catch.kt |
| Swift | examples/swift/app/plane.swift snake.swift | vml run examples/swift/app/snake.swift |
| Go | examples/go/app/snake.go | vml run examples/go/app/snake.go |
| Rust | examples/rust/app/breakout.rs | vml run examples/rust/app/breakout.rs |
| D | examples/d/test/catch.d | vml run examples/d/test/catch.d |
| Dart | examples/dart/test/catch.dart | vml run examples/dart/test/catch.dart |
| Python | examples/python/app/tetris.py | vml run examples/python/app/tetris.py |
| JavaScript | examples/javascript/test/catch.js | vml run examples/javascript/test/catch.js |
| Lua | examples/lua/app/life.lua | vml run examples/lua/app/life.lua |
| Ruby | examples/ruby/test/catch.rb | vml run examples/ruby/test/catch.rb |
| R | examples/r/test/catch.r | vml run examples/r/test/catch.r |
| Pascal | examples/pascal/test/catch.pas | vml run examples/pascal/test/catch.pas |
| Fortran | examples/fortran/app/sokoban.f90 | vml run examples/fortran/app/sokoban.f90 |
| BASIC | examples/basic/app/whack.bas tetris.bas | vml run examples/basic/app/whack.bas |
| Forth | examples/forth/test/parserexp_demo.fs | vml run examples/forth/test/parserexp_demo.fs |
| Scheme | examples/scheme/test/catch.scm | vml run examples/scheme/test/catch.scm |
| Ladder | examples/ladder/test/file_io.ld | 只能编译,见下 |
每种语言目录下还有一个 sysinfo.<ext>,它调用 ui_call_json("sysinfo", "") 把设备信息打出来 —— 想知道这门语言能不能用,先跑它。
各语言怎么写
C / C++ / Objective-C —— 最完整的一条路
#include <vml_ui.h>
int main(void) {
int w = ui_scr_get_w(), h = ui_scr_get_h();
ui_win_open("演示", w, h);
ui_2d_clear(0xFF101020);
ui_2d_text(20, 40, "Hello", 0xFFFFFFFF, 20, 0);
ui_2d_present();
while (ui_win_is_closed() == 0) { int m[4]; ui_msg_wait(m, 0); }
return 0;
}
- 编译最慢(前端 + 链接标准库,手机上一两分钟)
- ⚠ Objective-C **不要
#include <vml_ui.h>**(前端解析不了那个头文件),直接调用ui_*函数即可(参考examples/objc/app/snake.m)
C# / Java / Kotlin / Dart —— 需要声明一下
这几个前端要求先声明外部函数:
// C#
class P { static void Main() { ui_win_open("演示", ui_scr_get_w(), ui_scr_get_h()); } }
// Java
static native int ui_win_open(String t, int w, int h);
// Dart
external int ui_win_open(String t, int w, int h);
Python / Lua / JavaScript / Ruby / R —— 直接调
ui_win_open("演示", ui_scr_get_w(), ui_scr_get_h())
ui_2d_clear(0xFF101020)
ui_2d_present()
⚠ Python 的列表"写不进去"(b[i] = v 之后读回来还是 0)。棋盘这类可变网格请用共享库提供的整数网格:
ui_grid_clear()
ui_grid_set(3, 1) # 第 3 格
v = ui_grid_get(3)
BASIC
NATIVE FUNCTION ui_win_open(t AS STRING, w AS INTEGER, h AS INTEGER) AS INTEGER
ui_win_open "打地鼠", w, h
⚠ 形参名不能叫 on(关键字)。
Pascal
{ 注释是 { },不是 // }
program p;
begin
ui_win_open('演示', ui_scr_get_w(), ui_scr_get_h());
ui_2d_present();
end.
⚠ 注释里只能写 ASCII —— 中文标点(破折号、逗号)会报「未知字符」。用 // 当注释的话整行会被当成代码。
Forth
S" 演示" ui_scr_get_w ui_scr_get_h ui_win_open
Scheme
(ui_win_open "演示" (ui_scr_get_w) (ui_scr_get_h))
⚠ 用户函数看不见顶层变量(两边的帧指针会撞)。游戏请写成扁平顶层程序,只把"参数全传、不碰全局"的纯函数抽出去。
Fortran
call ui_win_open('演示', ui_scr_get_w(), ui_scr_get_h())
Ladder —— 特殊
它是PLC 梯形图风格的前端,没有"带字符串参数的函数调用"这种语法,所以 UI 那套接口用不了(仓库里的 ladder/file_io.ld 本身就是一个空测试)。它能编译、能跑,但做不了手机界面程序。
共同的坑(与语言无关)
- 先问后开:
ui_scr_get_w/h→ui_win_open - **每帧
ui_2d_present()** - **重开一局前
ui_msg_clear()** - 定时器要 kill
- 数组别越界 —— 报「内存错误」十有八九是它