import { isVmPrimitive, type VmValue } from '@mirascript/mirascript'; import { operations, serialize, serializeRecordKey } from '@mirascript/mirascript/subtle'; import { type ConstantNode, isConstantNode, isRangeNode, type AccessorNode, type MathNode, isFunctionNode, isSymbolNode, isAccessorNode, isAssignmentNode, type AssignmentNode, } from 'mathjs'; import type { Result } from './interface.js'; import { migrateAtomic, migrateExpr } from './node.js'; import type { State } from './state.js'; import { constantValue, len, unsupportedNode } from './utils.js'; /** 常量访问 */ function constIndexing(node: ConstantNode) { const { value } = node as ConstantNode; if (typeof value == 'string') { return { type: 'string', literal: value, code: serialize(value) } satisfies Result; } else if (typeof value == 'number' && Number.isInteger(value) && value > 0) { return { type: 'number', literal: value - 1, code: serialize(value - 1) } satisfies Result; } else { return { code: serialize(value) } satisfies Result; } } /** 字符串转为数组 */ function stringToArray(state: State, ret: Result): Result { if (ret.type === 'string') { return { type: 'array', code: `chars(${ret.code})`, }; } if (ret.type === 'array') { return ret; } state.helper(`fn @@maybe_chars(x) { if type(x) == 'string' { chars(x) } else { x } }`); return { type: 'array', code: `@@maybe_chars(${ret.code})`, }; } /** 访问节点 */ function accessChain( state: State, object: MathNode, index: ReadonlyArray, assignment: boolean, ): Result { const obj = migrateExpr(state, object); const chain = index.map((i) => { if (typeof i == 'number') return `[${i}]`; const s = serializeRecordKey(i); if (s !== i) return `[${s}]`; return `.${s}`; }); let type; if (!assignment && obj.global != null) { try { let v: VmValue = obj.global; for (const i of index) { if (v == null) break; v = operations.$Get(v, i); } if (v != null && isVmPrimitive(v)) { type = operations.$Type(v); } } catch (ex) { type = undefined; state.warn(`无法获取变量的类型: ${(ex as Error).message}`); } } if (!assignment && typeof index.at(-1) == 'number') { if (chain.length === 1) { return { type, code: `${stringToArray(state, obj).code}${chain[0]}`, }; } const prev = chain.slice(0, -1).join(''); return { type, code: `${ stringToArray(state, { code: `${obj.code}${prev}`, }).code }${chain.at(-1)}`, }; } return { type, code: `${obj.code}${chain.join('')}`, }; } /** 访问节点 */ function access( state: State, node: MathNode, assignment: boolean, ): Result | [object: MathNode, index: Array] { if (!isAccessorNode(node) && !isAssignmentNode(node)) { return [node, []]; } const { object, index } = node; if (!index?.dimensions[0]) { return unsupportedNode(state, node); } if (index.dimensions.length > 1) { state.warn('多维索引已拆分为多次单维索引'); const indices = index.dimensions.map((dim) => { if (isConstantNode(dim)) { const idx = constIndexing(dim); if (idx.type === 'number') { return idx; } } return { code: `${migrateAtomic(state, dim).code} - 1` }; }); const obj = migrateExpr(state, object); return { code: `${obj.code}${indices.map((idx) => `[${idx.code}]`).join('')}`, }; } const dim0 = index.dimensions[0]; if (isRangeNode(dim0)) { const obj = migrateExpr(state, object); if (obj.type === 'array') { const start = isConstantNode(dim0.start) ? constIndexing(dim0.start) : { code: `(${migrateExpr(state, dim0.start).code} - 1)` }; const end = isConstantNode(dim0.end) ? constIndexing(dim0.end) : { code: `(${migrateExpr(state, dim0.end).code} - 1)` }; const step = dim0.step == null ? 1 : constantValue(dim0.step); if ((!start.type || start.type === 'number') && (!end.type || end.type === 'number')) { if (!start.type || !end.type) { state.loose(); } if (step === 1) { return { type: 'array', code: `${obj.code}[${start.code}..${end.code}]`, }; } else if (step === -1) { return { type: 'array', code: `${obj.code}[${end.code}..${start.code}]::reverse()`, }; } } } const d = unsupportedNode(state, dim0); return { code: `${obj.code}[${d.code}]`, }; } if (index.dotNotation) { if (!isConstantNode(dim0)) return unsupportedNode(state, node); const { value } = dim0 as ConstantNode; if (typeof value != 'string') return unsupportedNode(state, node); if (!assignment && value === 'length') { const obj = migrateExpr(state, object); return len(state, obj); } const obj = access(state, object, assignment); if (Array.isArray(obj)) { return [obj[0], [...obj[1], value]]; } return { code: `${obj.code}.${value}`, }; } if (isConstantNode(dim0)) { const index = constIndexing(dim0); if (index.type === 'string') { const obj = access(state, object, assignment); if (Array.isArray(obj)) { return [obj[0], [...obj[1], index.literal]]; } return { code: `${obj.code}[${index.code}]` }; } else if (index.type === 'number') { if ( !assignment && index.literal === 0 && isFunctionNode(object) && isSymbolNode(object.fn) && !state.locals.has('size') && !state.hasGlobal('size') && object.fn.name === 'size' && object.args.length === 1 ) { return len(state, migrateAtomic(state, object.args[0]!)); } const obj = access(state, object, assignment); if (Array.isArray(obj)) { return [obj[0], [...obj[1], index.literal]]; } return { code: `${stringToArray(state, obj).code}[${index.code}]` }; } else { const obj = migrateExpr(state, object); state.err(`不支持的索引: ${node.toString()}`); return { code: `${obj.code}[${index.code}]` }; } } const d0 = migrateAtomic(state, dim0); const obj = migrateExpr(state, object); if (obj.literal && obj.type === 'record') { return { code: `${obj.code}[${d0.code}]`, }; } if (obj.literal && obj.type === 'array') { return { code: `${obj.code}[${d0.code} - 1]`, }; } state.helper(`fn @@index(index) { if type(index) == 'number' { index - 1 } else { index } }`); return { code: `${obj.code}[@@index(${d0.code})]` }; } /** 访问节点 */ export function migrateAccess(state: State, node: AccessorNode | AssignmentNode): Result { const assignment = isAssignmentNode(node); const ret = access(state, node, assignment); if (Array.isArray(ret)) { return accessChain(state, ret[0], ret[1], assignment); } return ret; }