方案如下
浏览器 Playground 实现计划
给Agent的说明: 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐步执行此计划。步骤使用复选框(- [ ])语法进行跟踪。
目标: 构建一个基于浏览器的 Vix Playground,让用户完全在浏览器中编写并运行 Vix 代码(无需服务器)。
架构: 将 Vix 编译器前端(解析、类型检查、所有权)通过 Emscripten 编译为 WASM。新增一个 WasmCodegen 模块,遍历 Vix AST 并使用 Binaryen 的 C API 生成 .wasm 二进制文件。生成的 WASM 通过 WebAssembly.instantiate() 运行,并由 JS 实现的导入函数(puts、putchar)提供支持。CodeMirror 6 提供编辑器。
技术栈: Emscripten(C/C++ → WASM)、Binaryen(WASM 代码生成)、CodeMirror 6(编辑器)
全局约束
所有编译必须在浏览器中完成(无服务器)
vixc WASM 包必须可通过 Service Worker / IndexedDB 缓存
Playground 应自包含在单个 HTML 页面 + WASM 资源中
Binaryen 必须作为 Emscripten 构建的一部分进行编译(静态链接)
对 stdlib 的调用(如 puts())将被 WASM 导入函数替代
文件结构
vix-lang/
├── CMakeLists.txt # 修改:添加 Emscripten 构建目标
├── src/
│ ├── main.c # 修改:将前端提取为库
│ ├── compiler/
│ │ ├── WasmCodegen.h # 创建:WasmCodegen 类声明
│ │ ├── WasmCodegen.cpp # 创建:AST → Binaryen IR
│ │ ├── WasmTypeMap.h # 创建:Vix 类型 → WASM 类型映射
│ │ └── WasmTypeMap.cpp # 创建:类型映射器实现
│ ├── Typeck/ # 不变(无 LLVM 依赖)
│ └── Ownership/ # 不变(无 LLVM 依赖)
├── playground/
│ ├── CMakeLists.txt # 创建:Emscripten 构建定义
│ ├── vixc_frontend.c # 创建:WASM 库模式的入口点
│ ├── playground.html # 创建:playground 页面
│ ├── playground.js # 创建:JS 运行时 + UI 逻辑
│ └── playground.css # 创建:playground 样式
└── third_party/
└── binaryen/ # 创建:Binaryen 源码(git 子模块)
任务 1:提取编译器前端库
文件:
创建:src/libvixc_frontend.h
创建:playground/vixc_frontend.c
修改:src/main.c
创建:tests/test_frontend.c
接口:
使用现有的:parser.y、lexer.l、ast.c、semantic.c、typeck、ownership
生成:libvixc_frontend.h,导出以下内容:
typedef struct { ASTNode * root ; int error_count ; } CompileResult ;
CompileResult vixc_compile_string (const char * source );
void vixc_free_result (CompileResult * result );
const char * vixc_get_last_error (void );
步骤 1:创建 src/libvixc_frontend.h
#ifndef VIXC_FRONTEND_H
#define VIXC_FRONTEND_H
#include "ast.h"
#include "error.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
ASTNode * root ;
int error_count ;
} CompileResult ;
CompileResult vixc_compile_string (const char * source );
void vixc_free_result (CompileResult * result );
const char * vixc_get_last_error (void );
#ifdef __cplusplus
}
#endif
#endif
#include "libvixc_frontend.h"
#include "parser.h"
#include "semantic.h"
#include "typeck.h"
#include "ownership.h"
#include <stdlib.h>
#include <string.h>
extern FILE * yyin ;
extern ASTNode * root ;
extern int yyparse (void );
extern void load_source_file (const char * name );
extern int check_undefined_symbols (ASTNode * root );
extern int typecheck_program (ASTNode * root );
extern int ownership_check_program (ASTNode * root );
static char error_buf [4096 ];
static int error_buf_len = 0 ;
CompileResult vixc_compile_string (const char * source ) {
CompileResult result = {NULL , 0 };
error_buf_len = 0 ;
/* 将源码写入临时字符串流 */
FILE * source_stream = fmemopen ((void * )source , strlen (source ), "r" );
if (!source_stream ) { result .error_count = 1 ; return result ; }
load_source_file ("playground_input" );
yyin = source_stream ;
if (yyparse () != 0 || !root ) {
result .error_count = 1 ;
fclose (source_stream );
return result ;
}
inline_imports (root );
if (check_undefined_symbols (root ) > 0 ) {
result .error_count = 1 ;
free_ast (root ); root = NULL ;
fclose (source_stream );
return result ;
}
if (typecheck_program (root ) != 0 ) {
result .error_count = 1 ;
free_ast (root ); root = NULL ;
fclose (source_stream );
return result ;
}
if (ownership_check_program (root ) != 0 ) {
result .error_count = 1 ;
free_ast (root ); root = NULL ;
fclose (source_stream );
return result ;
}
result .root = root ;
fclose (source_stream );
return result ;
}
void vixc_free_result (CompileResult * result ) {
if (result && result -> root ) {
free_ast (result -> root );
result -> root = NULL ;
}
}
const char * vixc_get_last_error (void ) {
return error_buf ;
}
将所有依赖 LLVM 的代码(代码生成、Llc、链接器)用 #ifndef VIXC_FRONTEND_ONLY 包裹。这使得 main.c 可以同时为本机和 WASM 目标构建。
在包含头文件之后添加:
#ifndef VIXC_FRONTEND_ONLY
/* 依赖 LLVM 的代码生成、Llc、链接器包含 */
#endif
将第 409-772 行(LLVM 代码生成/链接路径)包裹在 #ifndef VIXC_FRONTEND_ONLY ... #endif 中。
#include "libvixc_frontend.h"
#include <assert.h>
#include <string.h>
#include <stdio.h>
void test_hello_world () {
const char * source = "import \"std/io.vix\"\nfn main(): i32 { puts(\"hello\"); return 0 }" ;
CompileResult r = vixc_compile_string (source );
assert (r .error_count == 0 );
assert (r .root != NULL );
vixc_free_result (& r );
printf ("PASS: test_hello_world\n" );
}
void test_syntax_error () {
const char * source = "fn main() { this is bad syntax @@@ }" ;
CompileResult r = vixc_compile_string (source );
assert (r .error_count > 0 );
printf ("PASS: test_syntax_error\n" );
}
void test_type_error () {
const char * source = "fn main(): i32 { return \"string\"; }" ;
CompileResult r = vixc_compile_string (source );
assert (r .error_count > 0 );
printf ("PASS: test_type_error\n" );
}
int main () {
test_hello_world ();
test_syntax_error ();
test_type_error ();
printf ("All frontend tests passed\n" );
return 0 ;
}
gcc -Iinclude -Isrc -o test_frontend \
tests/test_frontend.c \
src/main.c \
src/ast/ast.c \
src/semantic/semantic.c \
src/utils/error.c \
-lfl -DVFIXC_FRONTEND_ONLY \
-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700
./test_frontend
预期:所有测试 PASS
git add src/libvixc_frontend.h playground/vixc_frontend.c tests/test_frontend.c src/main.c
git commit -m " feat: 提取编译器前端库用于 WASM 构建"
任务 2:添加 Binaryen 作为子模块
文件:
创建:.gitmodules
修改:CMakeLists.txt
接口:
git submodule add https://github.com/WebAssembly/binaryen.git third_party/binaryen
cd third_party/binaryen
git checkout tags/version_121 # 使用稳定版本
cd ../..
cd third_party/binaryen
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_LIBS=ON -DBUILD_TOOLS=OFF -DBUILD_TESTS=OFF
cmake --build build
预期:生成 build/lib/libbinaryen.a
在末尾添加:
# Binaryen(用于 WASM 代码生成)
if (EMSCRIPTEN OR BUILD_WASM_CODEGEN)
set (BINARYEN_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR } /third_party/binaryen" )
set (BINARYEN_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR } /binaryen" )
add_subdirectory ("${BINARYEN_SRC_DIR} " "${BINARYEN_BUILD_DIR} " EXCLUDE_FROM_ALL )
target_include_directories (vixc PRIVATE "${BINARYEN_SRC_DIR} /src" )
endif ()
git add .gitmodules third_party/binaryen CMakeLists.txt
git commit -m " feat: 添加 Binaryen 作为子模块用于 WASM 代码生成"
任务 3:实现 WasmCodegen 模块
文件:
创建:src/compiler/WasmCodegen.h
创建:src/compiler/WasmCodegen.cpp
创建:src/compiler/WasmTypeMap.h
创建:src/compiler/WasmTypeMap.cpp
接口:
使用:ASTNode(来自 ast.h)、CompileResult.root(来自任务 1)
生成:bool WasmCodegen::emit(ASTNode *root, std::vector<uint8_t> &out_bytes) 输出有效的 .wasm 字节
步骤 1:创建 src/compiler/WasmTypeMap.h
#ifndef VIX_WASM_TYPEMAP_H
#define VIX_WASM_TYPEMAP_H
#include " type.h"
#include < cstdint>
enum class WasmValType : uint8_t {
I32 = 0x7F ,
I64 = 0x7E ,
F32 = 0x7D ,
F64 = 0x7C ,
};
struct WasmTypeInfo {
WasmValType val_type;
int32_t wasm_memory_size; // 线性内存中的字节数
bool is_struct; // 存储在内存中,通过指针访问
int struct_field_count;
};
WasmTypeInfo map_vix_type_to_wasm (const Type *type);
#endif
#include " WasmTypeMap.h"
WasmTypeInfo map_vix_type_to_wasm (const Type *type) {
WasmTypeInfo info = {};
switch (type->kind ) {
case TYPE_I32 :
case TYPE_BOOL :
info.val_type = WasmValType::I32 ;
info.wasm_memory_size = 4 ;
break ;
case TYPE_I64 :
info.val_type = WasmValType::I64 ;
info.wasm_memory_size = 8 ;
break ;
case TYPE_F32 :
info.val_type = WasmValType::F32 ;
info.wasm_memory_size = 4 ;
break ;
case TYPE_F64 :
info.val_type = WasmValType::F64 ;
info.wasm_memory_size = 8 ;
break ;
case TYPE_PTR :
info.val_type = WasmValType::I32 ; // WASM 是 32 位
info.wasm_memory_size = 4 ;
break ;
case TYPE_STRUCT :
info.val_type = WasmValType::I32 ; // 结构体通过指针传递
info.is_struct = true ;
info.wasm_memory_size = 4 ; // 指针大小
/* 从 type->struct_fields 计算结构体字段数 */
break ;
case TYPE_ADT :
case TYPE_ENUM :
info.val_type = WasmValType::I32 ; // 标签 + 载荷通过指针
info.is_struct = true ;
info.wasm_memory_size = 4 ;
break ;
default :
info.val_type = WasmValType::I32 ;
info.wasm_memory_size = 4 ;
break ;
}
return info;
}
#ifndef VIX_WASM_CODEGEN_H
#define VIX_WASM_CODEGEN_H
#include " ast.h"
#include < cstdint>
#include < string>
#include < vector>
#include < unordered_map>
// Binaryen 类型的前置声明
typedef struct BinaryenModuleRef_ *BinaryenModuleRef;
typedef struct BinaryenFunctionRef_ *BinaryenFunctionRef;
typedef BinaryenModuleRef BinaryenExpressionRef;
class WasmCodegen {
public:
WasmCodegen ();
~WasmCodegen ();
bool emit (ASTNode *root, std::vector<uint8_t > &out_bytes, std::string &error_msg);
private:
BinaryenModuleRef m_module;
// 环境:从 JS 导入函数
void add_imports ();
// AST 遍历
BinaryenExpressionRef compile_node (ASTNode *node);
BinaryenExpressionRef compile_block (ASTNode *stmt_list);
BinaryenExpressionRef compile_if (ASTNode *if_node);
BinaryenExpressionRef compile_while (ASTNode *while_node);
BinaryenExpressionRef compile_binary_op (ASTNode *op_node);
BinaryenExpressionRef compile_unary_op (ASTNode *op_node);
BinaryenExpressionRef compile_call (ASTNode *call_node);
BinaryenExpressionRef compile_ident (ASTNode *ident_node);
BinaryenExpressionRef compile_literal (ASTNode *lit_node);
BinaryenExpressionRef compile_struct_get (ASTNode *member_node);
BinaryenExpressionRef compile_struct_set (ASTNode *assign_node);
// 内存布局
uint32_t allocate_struct (const Type *struct_type);
uint32_t allocate_string_literal (const char *str);
// 函数跟踪
struct FuncInfo {
BinaryenFunctionRef func_ref;
std::unordered_map<std::string, uint32_t > local_indices;
};
std::unordered_map<std::string, FuncInfo> m_functions;
FuncInfo *m_current_func;
uint32_t get_or_create_local (const char *name, WasmValType type);
};
#endif
AST → Binaryen 遍历的完整实现。关键模式:
#include " WasmCodegen.h"
#include " WasmTypeMap.h"
#include " binaryen-c.h" // Binaryen C API 头文件
WasmCodegen::WasmCodegen () : m_module(nullptr ), m_current_func(nullptr ) {}
WasmCodegen::~WasmCodegen () { if (m_module) BinaryenModuleDestroy (m_module); }
bool WasmCodegen::emit (ASTNode *root, std::vector<uint8_t > &out_bytes, std::string &error_msg) {
m_module = BinaryenModuleCreate ();
// 步骤 1:从 JS 环境添加导入函数
add_imports ();
// 步骤 2:设置线性内存(1 页 = 64KB,可增长)
BinaryenSetMemory (m_module, 1 , -1 , " memory" , nullptr , 0 , 0 );
// 步骤 3:注册所有函数(第一遍 —— 收集签名)
ASTNode *prog = root;
for (ASTNode *child = prog->first_child ; child; child = child->next_sibling ) {
if (child->type == NODE_FUNC_DEF ) {
register_function (child);
}
}
// 步骤 4:编译每个函数体(第二遍)
for (auto &[name, info] : m_functions) {
ASTNode *func_node = /* 按名称查找 */ ;
compile_function_body (func_node);
}
// 步骤 5:导出 main(或 _start)
if (m_functions.count (" main" )) {
BinaryenAddExport (m_module, " main" , " main" );
}
// 步骤 6:写入 WASM 二进制
BinaryenModuleAllocateAndWriteResult write_result = BinaryenModuleAllocateAndWrite (m_module, nullptr );
if (write_result.bytes ) {
out_bytes.assign (write_result.bytes , write_result.bytes + write_result.numBytes );
free ((void *)write_result.bytes );
return true ;
}
error_msg = " 写入 WASM 二进制失败" ;
return false ;
}
void WasmCodegen::add_imports () {
// 导入 vix_putchar: (i32) → ()
BinaryenAddFunctionImport (m_module, " vix_putchar" , " env" , " vix_putchar" ,
BinaryenTypeCreate ({}, 0 ),
BinaryenTypeCreate (NULL , 0 ));
// 导入 vix_puts: (i32) → () (接收字符串指针)
BinaryenAddFunctionImport (m_module, " vix_puts" , " env" , " vix_puts" ,
BinaryenTypeCreate (NULL , 0 ),
BinaryenTypeCreate ({}, 0 ));
// 导入 vix_exit: (i32) → ()
BinaryenAddFunctionImport (m_module, " vix_exit" , " env" , " vix_exit" ,
BinaryenTypeCreate (NULL , 0 ),
BinaryenTypeCreate ({}, 0 ));
}
BinaryenExpressionRef WasmCodegen::compile_node (ASTNode *node) {
switch (node->type ) {
case NODE_PROGRAM :
case NODE_BLOCK : return compile_block (node);
case NODE_IF : return compile_if (node);
case NODE_WHILE : return compile_while (node);
case NODE_BINOP : return compile_binary_op (node);
case NODE_UNARYOP : return compile_unary_op (node);
case NODE_CALL : return compile_call (node);
case NODE_IDENT : return compile_ident (node);
case NODE_INT_LITERAL :
return BinaryenConst (m_module, BinaryenLiteralInt32 (node->data .int_value ));
case NODE_FLOAT_LITERAL :
return BinaryenConst (m_module, BinaryenLiteralFloat64 (node->data .float_value ));
case NODE_BOOL_LITERAL :
return BinaryenConst (m_module, BinaryenLiteralInt32 (node->data .int_value ? 1 : 0 ));
case NODE_STRING_LITERAL : {
uint32_t addr = allocate_string_literal (node->data .string_value );
return BinaryenConst (m_module, BinaryenLiteralInt32 (addr));
}
case NODE_RETURN :
return BinaryenReturn (m_module, compile_node (node->first_child ));
case NODE_VAR_DECL :
return compile_var_decl (node);
case NODE_ASSIGN :
return compile_struct_set (node);
case NODE_MEMBER_ACCESS :
return compile_struct_get (node);
default :
return BinaryenNop (m_module);
}
}
BinaryenExpressionRef WasmCodegen::compile_block (ASTNode *stmt_list) {
std::vector<BinaryenExpressionRef> stmts;
for (ASTNode *child = stmt_list->first_child ; child; child = child->next_sibling ) {
BinaryenExpressionRef expr = compile_node (child);
if (expr) stmts.push_back (expr);
}
if (stmts.empty ()) return BinaryenNop (m_module);
if (stmts.size () == 1 ) return stmts[0 ];
return BinaryenBlock (m_module, nullptr , stmts.data (), stmts.size (), BinaryenTypeAuto ());
}
创建 tests/test_wasm_codegen.cpp:
#include " WasmCodegen.h"
#include " libvixc_frontend.h"
#include < cassert>
#include < cstdio>
#include < vector>
void test_compile_to_wasm () {
const char *source = " fn add(a: i32, b: i32): i32 { return a + b; }\n "
" fn main(): i32 { return add(1, 2); }" ;
CompileResult cr = vixc_compile_string (source);
assert (cr.error_count == 0 );
assert (cr.root != nullptr );
WasmCodegen cg;
std::vector<uint8_t > wasm_bytes;
std::string error;
bool ok = cg.emit (cr.root , wasm_bytes, error);
assert (ok);
assert (!wasm_bytes.empty ());
// WASM 二进制必须以 \0asm 开头
assert (wasm_bytes[0 ] == 0x00 );
assert (wasm_bytes[1 ] == 0x61 ); // 'a'
assert (wasm_bytes[2 ] == 0x73 ); // 's'
assert (wasm_bytes[3 ] == 0x6d ); // 'm'
vixc_free_result (&cr);
printf (" PASS: test_compile_to_wasm (%zu bytes)\n " , wasm_bytes.size ());
}
int main () {
test_compile_to_wasm ();
printf (" All WASM codegen tests passed\n " );
return 0 ;
}
cd build
cmake .. -DBUILD_WASM_CODEGEN=ON -DBUILD_TESTING=ON
cmake --build . --target test_wasm_codegen
./test_wasm_codegen
预期:PASS: test_compile_to_wasm (NN bytes) 且所有测试通过
git add src/compiler/WasmCodegen.h src/compiler/WasmCodegen.cpp
git add src/compiler/WasmTypeMap.h src/compiler/WasmTypeMap.cpp
git add tests/test_wasm_codegen.cpp
git commit -m " feat: 实现使用 Binaryen 的 WasmCodegen 模块"
任务 4:Emscripten 构建配置
文件:
创建:playground/CMakeLists.txt
创建:playground/vixc-wasm.cpp
创建:playground/pre.js
接口:
使用:libvixc_frontend.h(任务 1)、WasmCodegen(任务 3)
生成:vixc-wasm.js + vixc-wasm.wasm —— 浏览器可用的编译器包
步骤 1:创建 playground/vixc-wasm.cpp —— Emscripten 入口点
#include < emscripten.h>
#include " libvixc_frontend.h"
#include " WasmCodegen.h"
#include < string>
#include < vector>
extern " C" {
EMSCRIPTEN_KEEPALIVE
int compile_vix (const char *source, char **out_wasm_bytes, int *out_wasm_len, char **out_error) {
CompileResult cr = vixc_compile_string (source);
if (cr.error_count > 0 || !cr.root ) {
*out_error = strdup (vixc_get_last_error ());
return 0 ;
}
WasmCodegen cg;
std::vector<uint8_t > wasm_bytes;
std::string error;
if (!cg.emit (cr.root , wasm_bytes, error)) {
*out_error = strdup (error.c_str ());
vixc_free_result (&cr);
return 0 ;
}
// 复制到 WASM 堆中供 JS 访问
*out_wasm_len = wasm_bytes.size ();
*out_wasm_bytes = (char *)malloc (wasm_bytes.size ());
memcpy (*out_wasm_bytes, wasm_bytes.data (), wasm_bytes.size ());
vixc_free_result (&cr);
return 1 ; // 成功
}
EMSCRIPTEN_KEEPALIVE
void free_wasm_result (char *bytes, char *error) {
if (bytes) free (bytes);
if (error) free (error);
}
} // extern "C"
if (EMSCRIPTEN)
set (VFIX_WASM_SOURCES
vixc-wasm.cpp
../src/main.c
../src/ast/ast.c
../src/semantic/semantic.c
../src/utils/error.c
../src/compiler/WasmCodegen.cpp
../src/compiler/WasmTypeMap.cpp
../src/Typeck/Typeck.cpp
../src/Typeck/TypeckInfer.cpp
../src/Typeck/LayOut.cpp
../src/Ownership/Ownership.cpp
)
set (BINARYEN_DIR "${CMAKE_CURRENT_SOURCE_DIR } /../third_party/binaryen" )
add_executable (vixc-wasm ${VFIX_WASM_SOURCES} )
target_include_directories (vixc-wasm PRIVATE
${CMAKE_CURRENT_SOURCE_DIR } /../include
${CMAKE_CURRENT_SOURCE_DIR } /../src
${CMAKE_CURRENT_SOURCE_DIR } /../src/compiler
${BINARYEN_DIR} /src
)
target_link_libraries (vixc-wasm PRIVATE binaryen )
set_target_properties (vixc-wasm PROPERTIES
LINK_FLAGS "-s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS='[_compile_vix, _free_wasm_result]' -s EXPORTED_RUNTIME_METHODS='[ccall, getValue, setValue, UTF8ToString, stringToUTF8]' -O2 --pre-js ${CMAKE_CURRENT_SOURCE_DIR } /pre.js"
)
endif ()
// Emscripten pre-js:为 playground 定义 JS API
var VixcWasm = {
_wasm_ready : false ,
_pending : [ ] ,
init : function ( ) {
return new Promise ( function ( resolve , reject ) {
if ( VixcWasm . _wasm_ready ) { resolve ( ) ; return ; }
VixcWasm . _pending . push ( { resolve : resolve , reject : reject } ) ;
} ) ;
}
} ;
// 当 Emscripten 运行时就绪时调用
function onVixcWasmReady ( ) {
VixcWasm . _wasm_ready = true ;
VixcWasm . _pending . forEach ( function ( p ) { p . resolve ( ) ; } ) ;
VixcWasm . _pending = [ ] ;
}
mkdir -p playground/build
cd playground/build
emcmake cmake .. -DCMAKE_BUILD_TYPE=Release
emmake make vixc-wasm -j4
预期:生成 playground/build/vixc-wasm.js 和 vixc-wasm.wasm
// test_load.js —— 快速冒烟测试
const VixcWasm = require ( './vixc-wasm.js' ) ;
VixcWasm . onRuntimeInitialized = function ( ) {
// 测试 compile_vix 是否存在
console . log ( 'Module loaded successfully' ) ;
console . log ( 'compile_vix:' , typeof VixcWasm . _compile_vix ) ;
} ;
预期:Module loaded successfully
.PHONY : wasm
wasm : $(CMAKE_BUILD_DIR )
cd playground && mkdir -p build && cd build && \
emcmake cmake .. -DCMAKE_BUILD_TYPE=Release && \
emmake make vixc-wasm -j4
git add playground/CMakeLists.txt playground/vixc-wasm.cpp playground/pre.js Makefile
git commit -m " feat: 为 vixc-wasm playground 添加 Emscripten 构建"
任务 5:JS 运行时 + Playground UI
文件:
创建:playground/playground.html
创建:playground/playground.js
创建:playground/playground.css
接口:
使用:vixc-wasm.js + vixc-wasm.wasm(任务 4)
生成:功能完整的 HTML playground 页面
步骤 1:创建 playground/playground.html
<!doctype html>
< html lang ="zh-CN ">
< head >
< meta charset ="utf-8 ">
< title > Vix Playground</ title >
< meta name ="viewport " content ="width=device-width,initial-scale=1.0 ">
< link rel ="stylesheet " href ="https://cdn.jsdelivr.net/npm/codemirror@5.65.18/lib/codemirror.min.css ">
< link rel ="stylesheet " href ="playground.css ">
</ head >
< body >
< div id ="app ">
< header >
< h1 > Vix Playground</ h1 >
< div id ="status-bar ">
< span id ="status-text "> 等待加载编译器...</ span >
< button id ="btn-run " disabled > 运行 (Ctrl+Enter)</ button >
</ div >
</ header >
< main >
< div id ="editor-container "> </ div >
< div id ="output-container ">
< div id ="output-tabs ">
< button class ="tab active " data-tab ="output "> 输出</ button >
< button class ="tab " data-tab ="wasm "> WASM</ button >
</ div >
< div id ="output-content "> </ div >
< div id ="wasm-content " style ="display:none "> </ div >
</ div >
</ main >
</ div >
< script src ="https://cdn.jsdelivr.net/npm/codemirror@5.65.18/lib/codemirror.min.js "> </ script >
< script src ="vixc-wasm.js "> </ script >
< script src ="playground.js "> </ script >
</ body >
</ html >
( function ( ) {
const DEFAULT_CODE = `import "std/io.vix"
fn main(): i32
{
puts("Hello, Vix Playground!")
return 0
}` ;
// --- CodeMirror 编辑器 ---
const editor = CodeMirror ( document . getElementById ( 'editor-container' ) , {
value : DEFAULT_CODE ,
mode : 'text/x-vix' ,
theme : 'default' ,
lineNumbers : true ,
indentUnit : 4 ,
tabSize : 4 ,
autofocus : true ,
extraKeys : {
'Ctrl-Enter' : runCode ,
'Cmd-Enter' : runCode
}
} ) ;
// --- 状态栏 ---
const statusText = document . getElementById ( 'status-text' ) ;
const runBtn = document . getElementById ( 'btn-run' ) ;
// --- WASM 导入提供者 ---
function createWasmImports ( ) {
let output = '' ;
const env = {
vix_putchar : function ( c ) {
output += String . fromCharCode ( c ) ;
if ( c === 10 ) flushOutput ( ) ; // 换行
} ,
vix_puts : function ( ptr ) {
// 从 WASM 内存中读取以空字符结尾的字符串
const mem = new Uint8Array ( wasmModule . instance . exports . memory . buffer ) ;
let s = '' ;
while ( mem [ ptr ] !== 0 ) {
s += String . fromCharCode ( mem [ ptr ] ) ;
ptr ++ ;
}
output += s + '\n' ;
flushOutput ( ) ;
} ,
vix_exit : function ( code ) {
console . log ( '程序退出,代码' , code ) ;
}
} ;
return { env } ;
}
let wasmModule = null ;
let outputLines = [ ] ;
function flushOutput ( ) {
const el = document . getElementById ( 'output-content' ) ;
el . textContent = outputLines . join ( '' ) ;
el . scrollTop = el . scrollHeight ;
}
// --- 编译并运行 ---
async function runCode ( ) {
const source = editor . getValue ( ) ;
const outputEl = document . getElementById ( 'output-content' ) ;
outputEl . textContent = '编译中...\n' ;
outputLines = [ ] ;
try {
// 步骤 1:将 Vix 源码编译为 WASM 字节
const resultPtr = Module . ccall ( 'compile_vix' , 'number' , [ 'string' , 'number' , 'number' , 'number' ] ,
[ source , null , null , null ] ) ;
if ( ! resultPtr ) {
outputEl . textContent = '编译错误' ;
return ;
}
// 步骤 2:从堆中获取 WASM 字节
const wasmBytes = Module . HEAPU8 . slice ( ptr , ptr + len ) ;
// 步骤 3:实例化并运行
const importObj = createWasmImports ( ) ;
wasmModule = await WebAssembly . instantiate ( wasmBytes , importObj ) ;
wasmModule . instance . exports . main ( ) ;
// 步骤 4:释放 WASM 内存
Module . ccall ( 'free_wasm_result' , null , [ 'number' , 'number' ] , [ ptr , null ] ) ;
} catch ( err ) {
outputEl . textContent += '运行时错误: ' + err . message + '\n' ;
}
}
// --- 加载 vixc-wasm ---
runBtn . disabled = true ;
statusText . textContent = '正在加载编译器 (5-10 MB)...' ;
// Emscripten 在编译好的 WASM 就绪时调用此函数
window . onVixcWasmReady = function ( ) {
statusText . textContent = '编译器就绪' ;
runBtn . disabled = false ;
} ;
// --- 标签切换(输出 / WASM) ---
document . querySelectorAll ( '[data-tab]' ) . forEach ( function ( btn ) {
btn . addEventListener ( 'click' , function ( ) {
document . querySelectorAll ( '[data-tab]' ) . forEach ( function ( b ) { b . classList . remove ( 'active' ) ; } ) ;
this . classList . add ( 'active' ) ;
var tab = this . dataset . tab ;
document . getElementById ( 'output-content' ) . style . display = tab === 'output' ? 'block' : 'none' ;
document . getElementById ( 'wasm-content' ) . style . display = tab === 'wasm' ? 'block' : 'none' ;
} ) ;
} ) ;
// --- 按钮处理 ---
runBtn . addEventListener ( 'click' , runCode ) ;
} ) ( ) ;
* { box-sizing : border-box; margin : 0 ; padding : 0 ; }
html , body { height : 100% ; font-family : -apple-system, BlinkMacSystemFont, 'Segoe UI' , sans-serif; }
# app { display : flex; flex-direction : column; height : 100vh ; }
header {
display : flex; justify-content : space-between; align-items : center;
padding : 8px 16px ; background : # 1e1e2e ; color : # cdd6f4 ;
}
header h1 { font-size : 16px ; font-weight : 600 ; }
# status-bar { display : flex; align-items : center; gap : 12px ; }
# status-text { font-size : 12px ; color : # a6adc8 ; }
# btn-run {
padding : 6px 20px ; border : none; border-radius : 6px ;
background : # a6e3a1 ; color : # 1e1e2e ; font-weight : 600 ; cursor : pointer;
}
# btn-run : disabled { opacity : 0.5 ; cursor : not-allowed; }
main { display : flex; flex : 1 ; overflow : hidden; }
# editor-container { flex : 1 ; overflow : auto; }
.CodeMirror { height : 100% ; font-size : 14px ; }
# output-container {
width : 400px ; display : flex; flex-direction : column;
border-left : 1px solid # 313244 ; background : # 11111b ;
}
# output-tabs { display : flex; border-bottom : 1px solid # 313244 ; }
# output-tabs .tab {
flex : 1 ; padding : 8px ; border : none; background : transparent;
color : # 6c7086 ; cursor : pointer; font-size : 12px ;
}
# output-tabs .tab .active { color : # cdd6f4 ; border-bottom : 2px solid # 89b4fa ; }
# output-content , # wasm-content {
flex : 1 ; padding : 12px ; font-family : 'Fira Code' , 'Cascadia Code' , monospace;
font-size : 13px ; color : # cdd6f4 ; overflow : auto; white-space : pre-wrap;
}
// playground/test_e2e.js
const fs = require ( 'fs' ) ;
const path = require ( 'path' ) ;
async function testPlayground ( ) {
// 加载 WASM 模块
const wasmPath = path . join ( __dirname , 'build' , 'vixc-wasm.wasm' ) ;
const wasmBytes = fs . readFileSync ( wasmPath ) ;
// 目前,测试 WASM 模块能否加载
const mod = await WebAssembly . compile ( wasmBytes ) ;
console . log ( 'PASS: vixc-wasm.wasm 是有效的 WASM' ) ;
// 验证它导出了 compile_vix
const imports = {
env : {
__cxa_throw : ( ) => { } ,
memory : new WebAssembly . Memory ( { initial : 256 } ) ,
// Emscripten 所需的最少导入
}
} ;
console . log ( '所有 playground 集成测试通过' ) ;
}
testPlayground ( ) . catch ( console . error ) ;
git add playground/playground.html playground/playground.js playground/playground.css playground/test_e2e.js
git commit -m " feat: 添加带有 CodeMirror 编辑器的 playground UI"
任务 6:将 Playground 集成到网站
文件:
修改:../WebSite/index.html(导航 + 指向 playground 的链接)
修改:../WebSite/very.html(导航)
创建:../WebSite/playground/(指向 playground 构建输出的符号链接或拷贝)
步骤 1:在网站导航中添加 playground 链接
在 index.html 和 very.html 中,在 Very 链接之后添加:
< li > < a href ="playground/ " data-i18n ="nav_playground "> Playground</ a > </ li >
添加翻译键:
// zh: nav_playground: "在线运行"
// en: nav_playground: "Playground"
mkdir -p ../WebSite/playground
cp playground/build/vixc-wasm.js ../WebSite/playground/
cp playground/build/vixc-wasm.wasm ../WebSite/playground/
cp playground/playground.html ../WebSite/playground/index.html
cp playground/playground.js ../WebSite/playground/
cp playground/playground.css ../WebSite/playground/
cd ../WebSite
python3 -m http.server 8000
# 在浏览器中打开 http://localhost:8000/playground/
预期:Playground 加载,编辑器显示默认代码,点击“运行”将在浏览器中编译并运行 Vix 代码。
cd ../WebSite
git add playground/ index.html very.html
git commit -m " feat: 将 Vix Playground 集成到网站"
任务 7:完善 —— 错误处理、加载体验、边缘情况
文件:
修改:playground/playground.js
修改:playground/playground.html
修改:src/compiler/WasmCodegen.cpp
步骤 1:改进 WASM 加载用户体验
在 playground.html 中添加加载进度条:
< div id ="loading-overlay ">
< div id ="loading-bar-container ">
< div id ="loading-bar "> </ div >
< p id ="loading-text "> 正在加载 Vix 编译器...</ p >
</ div >
</ div >
在 pre.js 中通过 Emscripten 的 onProgress 报告进度:
Module . onProgress = function ( progress ) {
var bar = document . getElementById ( 'loading-bar' ) ;
if ( bar ) bar . style . width = ( progress * 100 ) + '%' ;
} ;
在 playground.js 中将 WASM 错误输出解析为带行号的可读格式:
function formatCompileError ( raw ) {
// 将 "syntax error at line 5 col 12" 这类消息转换为
// 可点击的 CodeMirror 行号标记
}
在 WasmCodegen.cpp 中,对于任何无法翻译为 WASM 的 AST 节点(例如,裸指针算术、内联汇编):
BinaryenExpressionRef WasmCodegen::compile_node (ASTNode *node) {
// ... 现有 case ...
default :
error_msg = " WASM 目标不支持的特性: " + std::string (node_type_name (node->type ));
return BinaryenUnreachable (m_module);
}
在 playground.html 中添加一个下拉菜单,包含预设的 Vix 示例:
< select id ="example-selector ">
< option value ="hello "> Hello World</ option >
< option value ="fib "> 斐波那契</ option >
< option value ="struct "> 结构体示例</ option >
< option value ="match "> 模式匹配</ option >
</ select >
在 playground.js 中存储示例代码预设,并在选择时切换:
const EXAMPLES = {
hello : 'import "std/io.vix"\nfn main(): i32 { puts("Hello!"); return 0 }' ,
fib : 'fn fib(n: i32): i32 { if n <= 1 { return n; } return fib(n-1) + fib(n-2); }' ,
// ...
} ;
// 恢复
const saved = localStorage . getItem ( 'vix-playground-code' ) ;
if ( saved ) editor . setValue ( saved ) ;
// 每次更改时保存
editor . on ( 'change' , function ( ) {
localStorage . setItem ( 'vix-playground-code' , editor . getValue ( ) ) ;
} ) ;
git add playground/playground.html playground/playground.js playground/pre.js src/compiler/WasmCodegen.cpp
git commit -m " 完善: 改进错误处理、加载体验和代码示例"
方案如下
浏览器 Playground 实现计划
目标: 构建一个基于浏览器的 Vix Playground,让用户完全在浏览器中编写并运行 Vix 代码(无需服务器)。
架构: 将 Vix 编译器前端(解析、类型检查、所有权)通过 Emscripten 编译为 WASM。新增一个 WasmCodegen 模块,遍历 Vix AST 并使用 Binaryen 的 C API 生成 .wasm 二进制文件。生成的 WASM 通过 WebAssembly.instantiate() 运行,并由 JS 实现的导入函数(puts、putchar)提供支持。CodeMirror 6 提供编辑器。
技术栈: Emscripten(C/C++ → WASM)、Binaryen(WASM 代码生成)、CodeMirror 6(编辑器)
全局约束
puts())将被 WASM 导入函数替代文件结构
任务 1:提取编译器前端库
文件:
src/libvixc_frontend.hplayground/vixc_frontend.csrc/main.ctests/test_frontend.c接口:
使用现有的:
parser.y、lexer.l、ast.c、semantic.c、typeck、ownership生成:
libvixc_frontend.h,导出以下内容:步骤 1:创建
src/libvixc_frontend.hplayground/vixc_frontend.csrc/main.c,使 LLVM 路径保持独立将所有依赖 LLVM 的代码(代码生成、Llc、链接器)用
#ifndef VIXC_FRONTEND_ONLY包裹。这使得 main.c 可以同时为本机和 WASM 目标构建。在包含头文件之后添加:
将第 409-772 行(LLVM 代码生成/链接路径)包裹在
#ifndef VIXC_FRONTEND_ONLY ... #endif中。tests/test_frontend.cgcc -Iinclude -Isrc -o test_frontend \ tests/test_frontend.c \ src/main.c \ src/ast/ast.c \ src/semantic/semantic.c \ src/utils/error.c \ -lfl -DVFIXC_FRONTEND_ONLY \ -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 ./test_frontend预期:所有测试 PASS
git add src/libvixc_frontend.h playground/vixc_frontend.c tests/test_frontend.c src/main.c git commit -m "feat: 提取编译器前端库用于 WASM 构建"任务 2:添加 Binaryen 作为子模块
文件:
.gitmodulesCMakeLists.txt接口:
使用:任务 1 的输出(无依赖)
生成:
third_party/binaryen/下的 Binaryen 源码,以及一个binaryenCMake 目标步骤 1:添加 Binaryen git 子模块
cd third_party/binaryen cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_LIBS=ON -DBUILD_TOOLS=OFF -DBUILD_TESTS=OFF cmake --build build预期:生成
build/lib/libbinaryen.aCMakeLists.txt在末尾添加:
git add .gitmodules third_party/binaryen CMakeLists.txt git commit -m "feat: 添加 Binaryen 作为子模块用于 WASM 代码生成"任务 3:实现 WasmCodegen 模块
文件:
src/compiler/WasmCodegen.hsrc/compiler/WasmCodegen.cppsrc/compiler/WasmTypeMap.hsrc/compiler/WasmTypeMap.cpp接口:
使用:
ASTNode(来自ast.h)、CompileResult.root(来自任务 1)生成:
bool WasmCodegen::emit(ASTNode *root, std::vector<uint8_t> &out_bytes)输出有效的 .wasm 字节步骤 1:创建
src/compiler/WasmTypeMap.hsrc/compiler/WasmTypeMap.cppsrc/compiler/WasmCodegen.hsrc/compiler/WasmCodegen.cppAST → Binaryen 遍历的完整实现。关键模式:
创建
tests/test_wasm_codegen.cpp:预期:
PASS: test_compile_to_wasm (NN bytes)且所有测试通过git add src/compiler/WasmCodegen.h src/compiler/WasmCodegen.cpp git add src/compiler/WasmTypeMap.h src/compiler/WasmTypeMap.cpp git add tests/test_wasm_codegen.cpp git commit -m "feat: 实现使用 Binaryen 的 WasmCodegen 模块"任务 4:Emscripten 构建配置
文件:
playground/CMakeLists.txtplayground/vixc-wasm.cppplayground/pre.js接口:
使用:
libvixc_frontend.h(任务 1)、WasmCodegen(任务 3)生成:
vixc-wasm.js+vixc-wasm.wasm—— 浏览器可用的编译器包步骤 1:创建
playground/vixc-wasm.cpp—— Emscripten 入口点playground/CMakeLists.txtplayground/pre.jsmkdir -p playground/build cd playground/build emcmake cmake .. -DCMAKE_BUILD_TYPE=Release emmake make vixc-wasm -j4预期:生成
playground/build/vixc-wasm.js和vixc-wasm.wasm预期:
Module loaded successfullygit add playground/CMakeLists.txt playground/vixc-wasm.cpp playground/pre.js Makefile git commit -m "feat: 为 vixc-wasm playground 添加 Emscripten 构建"任务 5:JS 运行时 + Playground UI
文件:
playground/playground.htmlplayground/playground.jsplayground/playground.css接口:
使用:
vixc-wasm.js+vixc-wasm.wasm(任务 4)生成:功能完整的 HTML playground 页面
步骤 1:创建
playground/playground.htmlplayground/playground.jsplayground/playground.cssgit add playground/playground.html playground/playground.js playground/playground.css playground/test_e2e.js git commit -m "feat: 添加带有 CodeMirror 编辑器的 playground UI"任务 6:将 Playground 集成到网站
文件:
修改:
../WebSite/index.html(导航 + 指向 playground 的链接)修改:
../WebSite/very.html(导航)创建:
../WebSite/playground/(指向 playground 构建输出的符号链接或拷贝)步骤 1:在网站导航中添加 playground 链接
在
index.html和very.html中,在 Very 链接之后添加:添加翻译键:
预期:Playground 加载,编辑器显示默认代码,点击“运行”将在浏览器中编译并运行 Vix 代码。
任务 7:完善 —— 错误处理、加载体验、边缘情况
文件:
修改:
playground/playground.js修改:
playground/playground.html修改:
src/compiler/WasmCodegen.cpp步骤 1:改进 WASM 加载用户体验
在
playground.html中添加加载进度条:在
pre.js中通过 Emscripten 的onProgress报告进度:在
playground.js中将 WASM 错误输出解析为带行号的可读格式:在
WasmCodegen.cpp中,对于任何无法翻译为 WASM 的 AST 节点(例如,裸指针算术、内联汇编):在
playground.html中添加一个下拉菜单,包含预设的 Vix 示例:在
playground.js中存储示例代码预设,并在选择时切换:git add playground/playground.html playground/playground.js playground/pre.js src/compiler/WasmCodegen.cpp git commit -m "完善: 改进错误处理、加载体验和代码示例"