import { serializeRecordKey } from '@mirascript/mirascript/subtle'; import { type MathNode, isAccessorNode, isConstantNode, isArrayNode, isObjectNode, isConditionalNode, isFunctionNode, isOperatorNode, isParenthesisNode, isRelationalNode, type OperatorNodeFn, isSymbolNode, isFunctionAssignmentNode, isRangeNode, isAssignmentNode, } from 'mathjs'; import type { State } from './state.js'; import type { Options, Result } from './interface.js'; import type { VmConst } from '@mirascript/mirascript'; import { constantValue, unsupportedNode } from './utils.js'; import { migrateAccess } from './access.js'; import { migrateAssignment } from './assignment.js'; import { migrateCall } from './call.js'; import { migrateOperator } from './operator.js'; import { migrateSymbol } from './symbol.js'; import { migrateConditional } from './conditional.js'; import { migrateFunctionAssignment } from './function.js'; import { serialize } from './serialize.js'; import { toNumber } from './to-type.js'; /** 转换 AST */ export function migrateAtomic(state: State, node: MathNode): Result { return migrateNode(state, node, { format: 'no-paren' }); } /** 转换 AST */ export function migrateParen(state: State, node: MathNode): Result { return migrateNode(state, node, { format: 'paren' }); } /** 转换 AST */ export function migrateExpr(state: State, node: MathNode): Result { return migrateNode(state, node, { format: 'expr' }); } /** 转换 AST */ export function migrateNode(state: State, node: MathNode, options: Options): Result { if (isConstantNode(node)) { const { value } = node; if (value == null) return { code: 'nil', literal: null, type: 'nil' }; return { code: serialize(value), literal: value, type: typeof value as Result['type'], }; } if (isArrayNode(node)) { const { items } = node; const literals = []; const code = []; let literal = true; for (const itemNode of items) { const item = migrateAtomic(state, itemNode); if (item.literal === undefined) { literal = false; } else { literals.push(item.literal); } code.push(item.code); } return { type: 'array', code: `[${code.join(', ')}]`, literal: literal ? literals : undefined, }; } if (isObjectNode(node)) { const { properties } = node; const literals: Record = {}; const code = []; let literal = true; for (const [key, itemNode] of Object.entries(properties)) { const item = migrateAtomic(state, itemNode); if (item.literal === undefined) { literal = false; } else { literals[key] = item.literal; } code.push(`${serializeRecordKey(key)}: ${item.code}`); } return { type: 'record', code: `(${code.join(', ')})`, literal: literal ? literals : undefined, }; } if (isSymbolNode(node)) { return migrateSymbol(state, node, true); } if (isAccessorNode(node)) { return migrateAccess(state, node); } if (isConditionalNode(node)) { return migrateConditional(state, node, options); } if (isFunctionNode(node)) { return migrateCall(state, node, options); } if (isOperatorNode(node)) { return migrateOperator(state, node, options); } if (isParenthesisNode(node)) { const { content } = node; const ret = migrateAtomic(state, content); if (options.format === 'no-paren') return ret; return { ...ret, code: `(${ret.code})` }; } if (isRelationalNode(node)) { const { conditionals, params } = node; // 优化范围判断 a <= b <= c if ( conditionals.length === 2 && params.length === 3 && conditionals[0] === 'smallerEq' && conditionals[1] === 'smallerEq' ) { // 模式匹配只支持常量 const l = constantValue(params[0]!); const r = constantValue(params[2]!); if (typeof l == 'number' && typeof r == 'number' && l < r) { const v = toNumber(state, migrateNode(state, params[1]!, options)); let code = `${v.code} is ${l}..${r}`; if (options.format !== 'no-paren') code = `(${code})`; return { type: 'boolean', code, }; } } const exprs = []; for (let i = 0; i < conditionals.length; i++) { const fn = conditionals[i]! as OperatorNodeFn; const l = params[i]!; const r = params[i + 1]!; exprs.push(migrateOperator(state, { fn, args: [l, r] }, options)); } let code = exprs.map((e) => e.code).join(' && '); if (options.format !== 'no-paren') code = `(${code})`; return { type: 'boolean', code, }; } if (isFunctionAssignmentNode(node)) { const result = migrateFunctionAssignment(state, node); state.helper(result.code); return { type: 'function', code: node.name, }; } if (isRangeNode(node)) { const { start, end, step } = node; const stepValue = step == null ? 1 : constantValue(step); // Math.js 的 : 与 MiraScript 的 .. 优先级相同,因此直接转换即可 const startNode = migrateExpr(state, start); const endNode = migrateExpr(state, end); if (stepValue === 1) { return { type: 'array', code: `[${startNode.code}..${endNode.code}]`, }; } else if (stepValue === -1) { return { type: 'array', code: `[${endNode.code}..${startNode.code}]`, }; } return unsupportedNode(state, node); } if (isAssignmentNode(node)) { return migrateAssignment(state, node, false); } // if (isIndexNode(node)) { return unsupportedNode(state, node); }