import type { VmContext, VmValue } from '@mirascript/mirascript'; import { parse } from './parser.js'; import { isAssignmentNode, isBlockNode, isConstantNode, isFunctionAssignmentNode, isSymbolNode, type MathNode, } from 'mathjs'; import type { Result } from './interface.js'; import { migrateAtomic } from './node.js'; import { toBoolean } from './to-type.js'; import { constantValue } from './utils.js'; import { specialMigrate } from './special.js'; import { migrateFunctionAssignment } from './function.js'; import { migrateAssignment } from './assignment.js'; /** 转换 AST */ function migrateLast(state: State, node: MathNode): Result { if (isAssignmentNode(node)) { if (node.index == null && isSymbolNode(node.object)) { state.loose(); node = node.value; } else { // 需要返回赋值结果,isTopLevel = false return migrateAssignment(state, node, false); } } if (state.condition) { if (constantValue(node) === null) return migrateAtomic(state, node); return toBoolean(state, node); } else { return migrateAtomic(state, node); } } /** 转换 AST */ function migrateNormal(state: State, node: MathNode): Result { if (isAssignmentNode(node)) { return migrateAssignment(state, node, true); } else { return migrateAtomic(state, node); } } /** 转换状态 */ export class BaseState { constructor( private readonly expr: string, readonly condition: boolean, /** 可识别的全局环境 */ protected readonly globals: VmContext, ) {} private readonly globalsCache = new Map(); /** 获取全局变量 */ hasGlobal(name: string): boolean { this.global(name); return this.globalsCache.has(name); } /** 获取全局变量 */ global(name: string): VmValue | undefined { if (this.globalsCache.has(name)) { return this.globalsCache.get(name); } try { if (!this.globals.has(name)) return undefined; const value = this.globals.get(name); this.globalsCache.set(name, value); return value; } catch (ex) { this.warn(`全局变量 ${name} 求值失败: ${(ex as Error).message || String(ex)}`); this.globalsCache.set(name, undefined); return undefined; } } /** 帮助函数 */ private readonly helpers = new Set(); /** 添加帮助函数 */ helper(code: string): void { this.helpers.add(code); } /** 产生的警告 */ private readonly warnings = new Set(); /** 产生的错误 */ private readonly errors = new Set(); /** 产生的失败 */ private readonly critical = new Set(); /** 声明的本地变量 / 函数 */ readonly locals = new Map(); /** 标记为非精确转换 */ loose(): void { this.warnings.add(`使用了非精确转换的语法或函数,结果可能不准确`); } /** 标记为非精确转换 */ warn(message: string): void { this.warnings.add(message); } /** 标记为非精确转换 */ err(message: string): void { this.errors.add(message); } /** 标记为转换失败 */ crit(message: string): void { this.critical.add(message); } private readonly ret: string[] = []; /** 转换一个语句 */ private migrateStmt(migrator: typeof migrateLast, stmt: boolean, node: MathNode): void { const isFunc = isFunctionAssignmentNode(node); const r = isFunc ? migrateFunctionAssignment(this, node) : migrator(this, node); let { code } = r; if (stmt && !isFunc && !code.endsWith(';')) code += ';'; if (node.comment) code += ' //' + node.comment.slice(1); this.ret.push(code); } /** 转换 */ migrate(): void { let ast; try { const e = this.expr.startsWith('=') ? this.expr.slice(1) : this.expr; ast = parse(e); } catch (ex) { this.crit(`解析 math.js 失败: ${(ex as Error).message || String(ex)}`); return; } try { const sp = specialMigrate(this.expr, ast); if (sp != null) { this.ret.push(sp); return; } if (isBlockNode(ast)) { const { blocks } = ast; for (const [index, block] of blocks.entries()) { if (index !== blocks.length - 1 || !block.visible) { this.migrateStmt(migrateNormal, true, block.node); } else { this.migrateStmt(migrateLast, false, block.node); } } return; } else if (isConstantNode(ast) && ast.value === undefined && ast.comment) { this.ret.push(`//${ast.comment.slice(1)}`); } else { this.migrateStmt(migrateLast, false, ast); return; } } catch (ex) { this.crit(`转换失败: ${String(ex)}`.replaceAll('\n', ' ')); return; } } /** 获取结果 */ result(): string { // 精确转换:产生了结果,没有帮助方法,且未产生错误和警告 if (this.ret.length && !this.helpers.size && !this.errors.size && !this.warnings.size && !this.critical.size) { return this.ret.join('\n'); } // 写入原始 math.js 作为注释 const lines = this.expr.split('\n').map((line) => `// ${line}`); lines.unshift(`// # 原始 math.js 表达式`); const addComment = (prefix: string, item: string) => { if (!item.includes('\n')) { lines.unshift(`// ${prefix}: ${item}`); return; } const l = item.split('\n'); const indent = ' '.repeat(prefix.length + 2); for (let i = l.length - 1; i >= 1; i--) { lines.unshift(`// ${indent}${l[i]}`); } lines.unshift(`// ${prefix}: ${l[0]}`); }; if (this.errors.size || this.warnings.size || this.critical.size) { const warnings = [...this.warnings]; for (let i = warnings.length - 1; i >= 0; i--) { const warn = warnings[i]!; addComment('- W', warn); } const errors = [...this.errors]; for (let i = errors.length - 1; i >= 0; i--) { const err = errors[i]!; addComment('- E', err); } const critical = [...this.critical]; for (let i = critical.length - 1; i >= 0; i--) { const crit = critical[i]!; addComment('- C', crit); } lines.unshift(`// # 转换日志`); } // 写入转换结果 if (this.ret.length) { for (const value of this.helpers) { lines.unshift(value); } if (this.helpers.size) { lines.unshift(`// # 帮助函数`); } for (let i = this.ret.length - 1; i >= 0; i--) { const value = this.ret[i]!; const explicitRet = this.helpers.size && i === this.ret.length - 1 && !value.endsWith(';'); const retExpr = explicitRet ? `return ${value};` : value; lines.unshift(retExpr); } } // 添加异常 if (!this.ret.length || this.critical.size) { lines.unshift(`panic("无法从 math.js 转换为 MiraScript");`); } return lines.join('\n'); } } /** 转换状态 */ export type State = Omit<{ [K in keyof BaseState]: BaseState[K] }, 'migrate' | 'result'>; /** 函数状态 */ export class FunctionState implements State { constructor( readonly baseState: State, params: readonly string[], ) { this.locals = new Map(baseState.locals); for (const p of params) { this.locals.set(p, { code: p }); } } /** @inheritdoc */ readonly condition = false; /** @inheritdoc */ hasGlobal(name: string): boolean { return this.baseState.hasGlobal(name); } /** @inheritdoc */ global(name: string): VmValue | undefined { return this.baseState.global(name); } /** @inheritdoc */ helper(code: string): void { this.baseState.helper(code); } /** @inheritdoc */ readonly locals: Map; /** @inheritdoc */ loose(): void { this.baseState.loose(); } /** @inheritdoc */ warn(message: string): void { this.baseState.warn(message); } /** @inheritdoc */ err(message: string): void { this.baseState.err(message); } /** @inheritdoc */ crit(message: string): void { this.baseState.crit(message); } }