VML:随包附带的虚拟机与 22 门语言
VML 是都来码内置的一套轻量虚拟机与编译器工具链:把 22 门语言编译成它自己的字节码, 在手机和桌面上离线运行。它不下载任何可执行代码,也不依赖系统里已有的编译器 —— 编译器本身就在包里。
它是什么
一句话:它是一台虚拟机 + 一整套编译器,随安装包一起发到你的机器上。
- 一台虚拟机:装载并执行 VML 字节码(完整 CPU 模拟、MMIO、系统调用)。
- 一套编译器:22 门语言 → VML 汇编 → VML 字节码。编译全部在本机完成。
- 离线:装完就能编译运行。示例程序与游戏随包附带,不需要联网、不需要 SDK、不需要编译器。
它与「把一门语言的官方工具链装进手机」是两回事:VML 有自己的指令集、自己的标准库、自己的 ABI, 22 门语言只是不同的写法 —— 它们最后都编到同一个后端,共用同一份运行时与同一套 UI 接口。
手机与桌面同一套
同一份源码、同一个前端编译器、同一台虚拟机。你在电脑上验证过的程序,手机上直接跑。
进程内调用,不是外挂一个 exe
手机端是把 VML 的源码(以源码复制的形式)并入安装包的,在进程内调用它 —— 不是把编译器编成可执行文件丢给 shell 去跑。
为什么必须这样:iOS 的沙箱物理拒绝 fork/exec(自己启动子进程这条路永远走不通),
Android 10+ 的 W^X 让应用私有目录里的文件不可执行。把托管代码直接链进 App,
这三条限制就都不存在了。
由什么组成
| 部件 | 做什么 |
|---|---|
| 虚拟机 | 装载并执行字节码:完整 CPU 模拟 + MMIO + 系统调用 |
| 汇编器 | VML 汇编文本 ↔ VML 字节码,两个方向都通 |
| 22 个语言前端 | 把 22 门语言的源码编译成 VML 汇编 |
| 18 个后端翻译器 | 把 VML 转译成真实硬件架构与虚拟机目标的汇编 |
| 共享库 | 跨语言通用的一套标准库,22 门语言共用同一份实现 |
| 绘图与 UI 接口 | 文字模式 tty_*、图像模式 ui_*,以及给老程序用的兼容层 |
| 示例与游戏 | examples/ 下按语言分目录,装完就能编译运行 |
22 个语言前端
每种语言一个独立的前端编译器,全部标记为生产可用:
C (C99)
.c
C++
.cpp
Python
.py
Go
.go
Rust
.rs
Java
.java
C#
.cs
JavaScript
.js
Swift
.swift
Kotlin
.kt
Dart
.dart
Ruby
.rb
Lua
.lua
Scheme
.scm
Forth
.fth / .fs
Fortran
.f90
Pascal
.pas
BASIC
.bas
Objective-C
.m
D
.d
R
.r
Ladder
.ld(梯形图)
其中两个还带方言:BASIC 10 个方言(QBasic / TurboBasic / FreeBasic / TrueBasic / PureBasic / ChipBasic / MiniBasic / GW-BASIC / PowerBASIC / VisualBasic)、Pascal 6 个方言 (Turbo Pascal / Delphi / FreePascal / ISO / UCSD / Oberon)。
共享库也是共用的:三层字符串(string / wstring / ustring)、
格式化输出与输入(printf / scanf 及宽字符变体)、表达式求值
(parserexp / parserexpf)、数学与浮点(math / float /
softdouble / softfloat / softint64),
以及 os / file / network / memory / device /
convert。一门语言写好的库,别的语言直接调。
18 个后端翻译器
VML 不止能自己跑,还能被转译成真实硬件的汇编:
| 字长 | 目标 |
|---|---|
| 8 位 | 6502(Apple II / C64 / NES)、Z80(CP/M / ZX Spectrum)、8051、AVR(Arduino / ATmega)、PIC |
| 16 位 | MSP430、PIC24 |
| 32 位 | ARM Cortex-M(STM32)、x86、MIPS、RISC-V、68000、PowerPC、SPARC |
| 虚拟机 | JVM、.NET(CLR)、Wasm(浏览器) |
绘图接口的分层:tty_* 与 ui_*
库按用途分了四层,互不越界 —— 新写的程序只该用前两层:
| 用途 | 库 | 说明 |
|---|---|---|
| 新版 · 文字模式 | tty_* | 只有字符、没有任何图形功能;坐标 1 起算、16 色索引色 |
| 新版 · 图像模式 | ui_* | 开窗、图元绘制、触摸与按键消息、定时器、音效、存档 |
| 旧程序 · 文字 | conio / crt | 兼容层 |
| 旧程序 · 图形 | graphics(BGI) | 兼容层;它转发到 ui_*,不模拟显存 |
判据很直白:新写的程序不要直接用 conio / crt / graphics ——
那是给老程序补环境的兼容层,不是新 API。手机上跑的游戏与示例,全部走 ui_*。
为什么有用
手机也能真编程
手机端自带编辑器与编译运行。examples/ 下的示例与游戏开箱即用,
vml run examples/c/tetris.c 就能跑起来。
桌面验证、手机运行
桌面上同一条编译链,秒级迭代 —— 先在电脑上把语法和逻辑验完,手机上只做最后一步。
老程序能跑
DOS / Unix 时代的文本程序大多一行不改就能编过并跑起来:经典终端时钟
tty-clock(689 行)源码零修改编译 + 运行通过,动画火车 sl(295 行)
全程动画,cmatrix(894 行)已能画出矩阵。
老程序的兼容性不按「能 / 不能」二分,而是分四档:
| 档 | 类型 | 可行性 |
|---|---|---|
| A | 纯字符界面(curses / conio / ANSI 转义) | 高 —— 大头,且已经跑通 |
| B | 用了 termios / ioctl 的终端控制 | 中 |
| C | x86 专属(DOS 内存模型、bios.h、int86、显存 0xB8000) | 低,且不该做 —— 范围外,不是"还没做" |
| D | 图形(BGI graphics.h / Allegro) | BGI 已做垫层(转发到 ui_*);Allegro / SDL 不做 |
有意不支持的边界
下面这些不是"还没做",而是有意不做(写进文档的定案):
| 形态 | 例子 |
|---|---|
| 直接操作汇编 | asm / __asm__ 块、__emit__、寄存器伪变量 _AX / _BX / _CX / _DX |
| 直接操作内存 | 显存直写(文本 0xB8000 / 图形 0xA0000)、bios.h、int86、peek / poke |
| 裸硬件 | interrupt / _interrupt 中断处理程序、端口 inp / outp、BASIC 的 DEF SEG |
理由是语义对不上:那些程序的语义是"往一块固定地址的显存写、宿主把它贴到屏幕上", 而本平台的目标形态是"程序调宿主接口画"。这是两套并行的绘图面, 而固定地址(显存、端口、中断向量)在手机上根本没有对应物。
这类东西不能被当成"透明的空白"混过去 —— 丢掉它们会静默编错,
程序看起来能跑、结果不对,比直接编不过难查得多。该改的是程序自己(把显存直写改成 ui_*、
把 asm 段改写成高级语言),不是把它们从语法里抹掉。
同样有意不做的还有:
windows.h(Win32 图形界面)—— 映射不到手机上;gfx_*/ 显存那一整套 —— 见上;system()—— 本平台没有 shell。与其假装支持(system("copy a b")会拿到"成功"却什么也没发生),不如让它编译期就找不到符号;- Allegro / SDL —— 它们自带音频、输入、定时器一整套运行时,那已经不是"补环境"而是"移植一个平台";
- 鼠标中断(INT 33h)、BGI 的驱动加载模型(
registerbgidriver/ 字体文件 /setviewport)。
口径是统一的:缺了就是编译期找不到符号,不假装支持。 "编得过、跑起来什么也没有"比"编不过"难查得多。
从哪开始
顺带一提:编辑器和 C 语言与 VML 汇编的编译运行是免费用的; 「全能版」是一次性内购(非订阅),解锁全部 22 门语言的编译器与优化器。
都来码 Dolaima