REPL:把前面所有拼成可交互解释器
目标
把前 10 节串起来:做一个 REPL(Read-Eval-Print Loop)——读一行、执行、打印结果、再读下一行。这是你亲手写的"迷你 Node 终端"。下面给出完整可运行程序。
运行环境:Node.js v18+。运行方式:保存为 repl.js,执行 node repl.js。真实 REPL 用 readline 从终端逐行读;为便于教学,这里用一个"行数组"模拟用户输入,逻辑完全一致。
// repl.js —— 完整可运行:复用前几节的 parse/ev/Environment/标准库
function createGlobalEnv() {
const env = new Environment()
env.define('print', (...xs) => console.log('»', ...xs))
env.define('len', x => x.length)
env.define('map', (a, fn) => a.map(e => fn(e)))
return env
}
// 执行一段源码(可包含多条语句),返回最后一条表达式的值
function run(src, env) {
const ast = parse(src)
let last
for (const s of ast) last = ev(s, env)
return last
}
// REPL 主循环:读一行 -> 求值 -> 打印
function repl(lines) {
const env = createGlobalEnv()
for (const line of lines) {
if (line.trim() === '') continue
if (line.trim() === ':q') { console.log('bye'); return }
try {
const v = run(line, env)
if (v !== undefined) console.log('=>', v)
} catch (e) {
console.log('错误:', e.message)
}
}
}
// ===== 调用示例(模拟用户在终端逐行输入)=====
repl([
'var x = 10;',
'x = x * x;',
'print(x);',
'print(len("hi"));',
':q',
])真实场景里,把 repl(['...']) 换成 readline 的逐行回调即可——run 和 ev 完全不变。这就是"语言内核"与"交互外壳"解耦的好处。
名词解释
REPL:Read(读)– Eval(求值)– Print(打印)– Loop(循环)的缩写。它是很多动态语言(Python、Node、Lua)的交互式入口,边写边看结果。
交互式 vs 脚本式:REPL 是交互式(一行一行来,保留状态);把代码写进文件执行是脚本式。二者共用同一套 parse/ev,只差"输入从哪来"。
状态保持:REPL 的 env 在循环外创建,所以上一行定义的 x,下一行还能用——这正是"会话"的感觉。
课后练习
练习:改造 repl,让输入 :q 时退出循环(打印 "bye")。
答案:已在上方完整代码中实现——循环内判断 line.trim() === ':q' 时打印 bye 并 return。
总结
REPL 是这门小语言的高光时刻:它证明前 10 节不是零散练习,而是一套完整语言的零件。最值得学的设计是内核与外壳解耦——parse/ev 是语言本身,REPL 只是"怎么把输入喂给它"的一层皮。同一套内核,套上文件执行器就是脚本语言,套上浏览器就是网页脚本语言,套上 REPL 就是交互终端。你此刻拥有的,已经是一个能跑、能交互、能报错的迷你语言内核了。下一步往哪走,取决于你想让它更"快"(编译,见第12节)还是更"大"(更多语法)。