import { type MathNode, isSymbolNode, isConstantNode, isOperatorNode } from 'mathjs'; import { isKeyword } from '@mirascript/mirascript/subtle'; import type { Result } from './interface.js'; import type { State } from './state.js'; import { toString } from './to-type.js'; import { migrateExpr } from './node.js'; /** 获取全局函数的名字 */ export function globalFnName(state: State, fnName: string): string { if (state.locals.has(fnName)) { return `global.${fnName}`; } return fnName; } /** 获取 symbol name */ export function symbolName(node: MathNode): string | undefined { if (!isSymbolNode(node)) return undefined; return node.name; } /** 转换为 mirascript 符号 */ export function migrateSymbolName(name: string, local: boolean): string { if (!isKeyword(name)) return name; if (local) return `__${name}__`; return `global.${name}`; } /** 获取 constant value */ export function constantValue(node: MathNode): number | string | boolean | null | undefined { if (isOperatorNode(node) && node.args.length === 1) { if (node.op === '+') { const val = constantValue(node.args[0]!); if (typeof val === 'number') return val; } else if (node.op === '-') { const val = constantValue(node.args[0]!); if (typeof val === 'number') return -val; } return undefined; } if (!isConstantNode(node)) return undefined; return node.value ?? null; } /** 添加转换错误 */ export function unsupportedNode(state: State, node: MathNode, err?: string): Result { const str = node.toString(); state.crit(err ?? `不支持的节点类型 ${node.type}: ${str}`); return { code: `(/* ${str} */)`, }; } /** 可能是矩阵时报错 */ export function scalar(op: string, state: State, re: Result): Result { if (re.type === 'array') { state.err(`'${op.trim()}' 不支持矩阵`); } else if (!re.type) { state.warn(`无法确定 '${op.trim()}' 的操作数为标量类型`); } return re; } /** 是否可能通过其他类型 toString 得到 */ function maybeToString(str: string): boolean { // 布尔值和 null/undefined if (str === 'true' || str === 'false' || str === 'null' || str === 'undefined') return true; // 数字 if (!Number.isNaN(Number(str))) return true; // Object.toString() if (str.startsWith('[') && str.endsWith(']')) return true; return false; } /** 比较字符串 */ export function equalText(state: State, op: '==' | '!=', l: MathNode, r: MathNode): Result { const lr = scalar(`${op}/equalText`, state, migrateExpr(state, l)); const rr = scalar(`${op}/equalText`, state, migrateExpr(state, r)); if ( (lr.type === 'string' && rr.type === 'string') || (typeof lr.literal == 'string' && rr.type !== 'array' && !maybeToString(lr.literal)) || (typeof rr.literal == 'string' && lr.type !== 'array' && !maybeToString(rr.literal)) ) { return { type: 'boolean', code: `${lr.code} ${op} ${rr.code}`, }; } return { type: 'boolean', code: `${toString(state, lr).code} ${op} ${toString(state, rr).code}`, }; } /** 计算长度 */ export function len(state: State, obj: Result): Result { if (obj.type === 'string') return { type: 'number', code: `len(chars(${obj.code}))`, }; if (obj.type === 'array') return { type: 'number', code: `len(${obj.code})`, }; state.helper( `fn @@length(x) { if type(x) == 'string' { len(chars(x)) } else if type(x) == 'array' { len(x) } else { x.length ?? 0 } }`, ); return { type: 'number', code: `@@length(${obj.code})`, }; }