数据类型:字符串、数组与内建操作
目标
数字之外,真实语言要有字符串和数组。本节课给解释器加上字符串字面量、[1,2,3] 数组字面量,以及几个内建操作 len / push / map。下面给出完整可运行程序。
运行环境:Node.js v18+。运行方式:保存为 data.js,执行 node data.js(在 func.js 基础上扩展字符串/数组与内建)。
// data.js —— 完整可运行:在 func.js 基础上增加 str / 数组 / 内建
const KEYWORDS = new Set(['var', 'if', 'else', 'while', 'func', 'return'])
function tokenize(src) {
const tokens = []; let i = 0
const isD = c => c >= '0' && c <= '9'
const isA = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c === '_'
while (i < src.length) {
const c = src[i]
if (c === ' ' || c === '\n' || c === '\t' || c === '\r') { i++; continue }
if (c === '"') { i++; let str = ''; while (i < src.length && src[i] !== '"') { str += src[i]; i++ } i++; tokens.push({ type: 'str', value: str }); continue }
if (isD(c)) { const s = i; while (i < src.length && isD(src[i])) i++; tokens.push({ type: 'num', value: Number(src.slice(s, i)) }); continue }
if (isA(c)) { const s = i; while (i < src.length && (isA(src[i]) || isD(src[i]))) i++; const w = src.slice(s, i); tokens.push(KEYWORDS.has(w) ? { type: 'kw', value: w } : { type: 'id', value: w }); continue }
const one = { '+': 'plus', '-': 'minus', '*': 'star', '/': 'slash', '(': 'lparen', ')': 'rparen', ';': 'semi', '{': 'lbrace', '}': 'rbrace', ',': 'comma', '[': 'lbracket', ']': 'rbracket' }[c]
if (one) { tokens.push({ type: one, value: c }); i++; continue }
throw new Error('非法字符: ' + c)
}
tokens.push({ type: 'eof', value: null }); return tokens
}
function parse(src) {
const tokens = tokenize(src); let pos = 0
const peek = () => tokens[pos], next = () => tokens[pos++]
const expect = t => { if (peek().type !== t) throw new Error('语法错误:期望 ' + t); return next() }
function parseExpr() {
let n = parseTerm()
while (peek().type === 'plus' || peek().type === 'minus') { const op = next().value; n = { type: 'binary', op, left: n, right: parseTerm() } }
return n
}
function parseTerm() {
let n = parseFactor()
while (peek().type === 'star' || peek().type === 'slash') { const op = next().value; n = { type: 'binary', op, left: n, right: parseFactor() } }
return n
}
function parseFactor() {
const t = peek()
if (t.type === 'minus') { next(); return { type: 'unary', op: '-', operand: parseFactor() } }
if (t.type === 'num') { next(); return { type: 'num', value: t.value } }
if (t.type === 'str') { next(); return { type: 'str', value: t.value } }
if (t.type === 'lbracket') {
next(); const els = []
if (peek().type !== 'rbracket') { els.push(parseExpr()); while (peek().type === 'comma') { next(); els.push(parseExpr()) } }
expect('rbracket'); return { type: 'arr', elements: els }
}
if (t.type === 'id') {
next()
if (peek().type === 'lparen') {
next(); const args = []
if (peek().type !== 'rparen') { args.push(parseExpr()); while (peek().type === 'comma') { next(); args.push(parseExpr()) } }
expect('rparen'); return { type: 'call', callee: { type: 'ident', name: t.value }, args }
}
if (peek().type === 'eq') { next(); return { type: 'assign', name: t.value, value: parseExpr() } }
return { type: 'ident', name: t.value }
}
if (t.type === 'lparen') { next(); const e = parseExpr(); expect('rparen'); return e }
throw new Error('无法解析表达式: ' + t.type)
}
function parseStmt() {
const t = peek()
if (t.type === 'kw' && t.value === 'var') {
next(); const name = expect('id').value; expect('eq'); const init = parseExpr(); expect('semi'); return { type: 'var', name, init }
}
if (t.type === 'kw' && t.value === 'func') {
next(); const name = expect('id').value; expect('lparen')
const params = []
if (peek().type !== 'rparen') { params.push(expect('id').value); while (peek().type === 'comma') { next(); params.push(expect('id').value) } }
expect('rparen'); const body = parseStmt(); return { type: 'func', name, params, body }
}
if (t.type === 'lbrace') { next(); const stmts = []; while (peek().type !== 'rbrace') stmts.push(parseStmt()); expect('rbrace'); return { type: 'block', stmts } }
const e = parseExpr(); expect('semi'); return { type: 'exprstmt', expr: e }
}
const prog = []; while (peek().type !== 'eof') prog.push(parseStmt()); return prog
}
class Environment {
constructor(parent = null) { this.vars = new Map(); this.parent = parent }
define(n, v) { this.vars.set(n, v) }
get(n) { let e = this; while (e) { if (e.vars.has(n)) return e.vars.get(n); e = e.parent } throw new Error('未定义变量: ' + n) }
assign(n, v) { let e = this; while (e) { if (e.vars.has(n)) { e.vars.set(n, v); return } e = e.parent } this.vars.set(n, v) }
}
function ev(node, env) {
switch (node.type) {
case 'num': return node.value
case 'str': return node.value
case 'arr': return node.elements.map(e => ev(e, env))
case 'ident': return env.get(node.name)
case 'unary': return -ev(node.operand, env)
case 'binary': {
const a = ev(node.left, env), b = ev(node.right, env)
if (node.op === '+' && (typeof a === 'string' || typeof b === 'string')) return String(a) + String(b)
switch (node.op) {
case '+': return a + b; case '-': return a - b; case '*': return a * b; case '/': return a / b
}
}
case 'assign': { const v = ev(node.value, env); env.assign(node.name, v); return v }
case 'var': { const v = ev(node.init, env); env.define(node.name, v); return v }
case 'exprstmt': return ev(node.expr, env)
case 'block': { const b = new Environment(env); let r; for (const s of node.stmts) r = ev(s, b); return r }
case 'func': { env.define(node.name, { type: 'UserFn', params: node.params, body: node.body, closure: env }); return node.name }
case 'call': {
const callee = ev(node.callee, env)
const args = node.args.map(a => ev(a, env))
if (typeof callee === 'function') return callee(...args)
if (callee && callee.type === 'UserFn') {
const fnEnv = new Environment(callee.closure)
callee.params.forEach((p, i) => fnEnv.define(p, args[i]))
try { ev(callee.body, fnEnv) } catch (e) { if (e && e.__ret) return e.value; throw e }
return undefined
}
throw new Error('不是可调用的对象')
}
}
throw new Error('未知节点: ' + node.type)
}
function run(src) {
const env = new Environment()
env.define('print', (...xs) => console.log(...xs))
env.define('len', x => x.length)
env.define('push', (a, x) => { a.push(x); return a })
env.define('map', (a, fn) => a.map(e => fn(e)))
for (const s of parse(src)) ev(s, env)
}
// ===== 调用示例 =====
run(`
print(len("hello"));
var a = [1, 2, 3];
print(map(a, func(x) { return x * 2; }));
print(push(a, 4));
`)map 把一个"用户函数"当参数传进去,解释器在原生 map 里通过 fn(e) 回调它——这又是一次"函数是一等公民"的实战。
名词解释
字面量(Literal):直接写进代码里的值,如 "hello"(字符串字面量)、[1,2,3](数组字面量)。它不需要计算,本身就是值。
内建函数(Builtin):语言自带的、由宿主(这里是 JS)实现的函数,如 len/push/map。它们和 UserFn 的区别是:内建函数直接是 JS 函数,不需要 AST 就能执行。
高阶函数:接收或返回函数的函数。map 是高阶函数——它接收一个"怎么变换每个元素"的函数,这正是函数式编程的基石。
课后练习
练习:用 map 和 len 计算数组 [10,20,30] 每个元素加 1 后的长度。
答案:
run(`
var a = [10, 20, 30];
var b = map(a, func(x) { return x + 1; });
print(len(b)); // 3
`)总结
数据类型是语言"能表达什么世界"的边界。加了字符串和数组,你的语言才从"计算器"变成"能处理真实数据"的工具。而 map/filter/reduce 这类高阶内建函数,是把"循环 + 副作用"升级为"声明式变换"的关键——你不再说"怎么一步步改",而是说"我想要每个元素变成什么样"。注意一个设计取舍:数组我用 JS 原生数组承载,字符串用原生字符串,这意味着我们的语言直接复用了宿主语言的数据结构。现实编译器往往不会这么做(为了跨平台/可控内存),但它极其适合教学:让你把精力放在"语言机制"而不是"内存管理"上。