diff --git a/packages/anon-tokyo/package.json b/packages/anon-tokyo/package.json new file mode 100644 index 0000000..2cf6597 --- /dev/null +++ b/packages/anon-tokyo/package.json @@ -0,0 +1,3 @@ +{ + "name": "@motajs/anon-tokyo" +} diff --git a/packages/anon-tokyo/src/compile.ts b/packages/anon-tokyo/src/compile.ts new file mode 100644 index 0000000..6a8ad96 --- /dev/null +++ b/packages/anon-tokyo/src/compile.ts @@ -0,0 +1,644 @@ +import { isFunction, mapValues } from 'lodash-es'; +import { BuiltInFunction } from './interpreter'; +import { splitArray } from './utils'; +import { + Block, + BlockFlowNode, + FlowNode, + Statement, + FlowNodeType, + StatementType, + LoopFlowNode, + LoopInitializerFlowNode, + ReturnFlowNode, + Expression, + ExecutionNode, + OPType, + Scope +} from './type'; + +const AsyncFunction = async function () {}.constructor; + +export const flowAnalyzePass = (program: Block): BlockFlowNode => { + const flowNodeMap: FlowNode[] = []; + const addNode = (node: T) => { + node.id = flowNodeMap.length; + flowNodeMap.push(node); + }; + const toBlockFlowNode = ( + statements: Statement[], + successor: FlowNode + ): BlockFlowNode => { + let last = successor; + const nodes = statements + .toReversed() + .map(statement => { + last = toFlowNode(statement, last); + return last; + }) + .toReversed(); + return { + id: -1, + type: FlowNodeType.Block, + nodes, + next: successor, + mergable: nodes.every(node => node.mergable) + }; + }; + const labelMap = new Map(); + const breakableNodeStack: FlowNode[] = []; + const toFlowNode = ( + statement: Statement, + successor: FlowNode + ): FlowNode => { + switch (statement.type) { + case StatementType.Expression: { + return { + id: -1, + type: FlowNodeType.Normal, + statement, + next: successor, + mergable: true + }; + } + case StatementType.Call: { + if (statement.builtIn) { + return { + id: -1, + type: FlowNodeType.Normal, + statement, + next: successor, + mergable: true + }; + } else { + return { + id: -1, + type: FlowNodeType.ExternCall, + statement, + next: successor, + mergable: false + }; + } + } + case StatementType.Return: { + return { + id: -1, + type: FlowNodeType.Return, + statement, + mergable: true + }; + } + case StatementType.If: { + const branches = statement.branches.map( + ({ condition, body }) => { + return { + condition, + node: toBlockFlowNode(body, successor) + }; + } + ); + const otherwise = + statement.otherwise && + toBlockFlowNode(statement.otherwise, successor); + return { + id: -1, + type: FlowNodeType.If, + statement, + branches, + otherwise, + mergable: + branches.every(branch => branch.node.mergable) && + (otherwise?.mergable ?? true), + next: successor + }; + } + case StatementType.Switch: { + const otherwise = + statement.otherwise && + toBlockFlowNode(statement.otherwise, successor); + let last = otherwise ?? successor; + const branches = statement.branches + .toReversed() + .map(({ condition, body }) => { + last = toBlockFlowNode(body, last); + return { + condition, + node: toBlockFlowNode(body, last) + }; + }) + .toReversed(); + return { + id: -1, + type: FlowNodeType.Switch, + statement, + branches, + otherwise, + mergable: + branches.every(branch => branch.node.mergable) && + (otherwise?.mergable ?? true), + next: successor + }; + } + case StatementType.Loop: { + const loopNode: LoopFlowNode = { + id: -1, + type: FlowNodeType.Loop, + statement, + body: toBlockFlowNode([], successor), + next: successor, + mergable: false + }; + if (statement.label) { + if (labelMap.has(statement.label)) { + throw new Error(`duplicate label ${statement.label}`); + } + labelMap.set(statement.label, loopNode); + } + breakableNodeStack.push(loopNode); + loopNode.body = toBlockFlowNode(statement.body, loopNode); + loopNode.mergable = loopNode.body.mergable; + if (statement.label) { + labelMap.delete(statement.label); + } + breakableNodeStack.pop(); + if (statement.initializer) { + const initializerNode: LoopInitializerFlowNode = { + id: -1, + type: FlowNodeType.LoopInitializer, + main: loopNode, + mergable: true + }; + return initializerNode; + } else { + if (statement.skipInitialCheck) return loopNode.body; + else return loopNode; + } + } + case StatementType.Break: { + if (statement.label) { + const to = labelMap.get(statement.label); + if (!to) { + throw new Error(`undef label ${to}`); + } + return { + id: -1, + type: FlowNodeType.Jump, + statement, + next: to, + mergable: true + }; + } else { + const to = breakableNodeStack.at(-1); + if (!to) { + throw new Error(`unexcepted break`); + } + return { + id: -1, + type: FlowNodeType.Jump, + statement, + next: to, + mergable: true + }; + } + } + case StatementType.Continue: { + const to = breakableNodeStack.at(-1); + if (!to) { + throw new Error(`unexcepted continue`); + } + return { + id: -1, + type: FlowNodeType.Jump, + statement, + next: to, + mergable: true + }; + } + case StatementType.Exit: { + return { + id: -1, + type: FlowNodeType.Exit, + statement, + mergable: true + }; + } + default: { + throw 'unknown statement type'; + } + } + }; + const implicitReturnNode: ReturnFlowNode = { + id: -1, + type: FlowNodeType.Return, + statement: { + type: StatementType.Return + }, + mergable: true + }; + const root = toBlockFlowNode(program, implicitReturnNode); + root.nodes.push(implicitReturnNode); + const labelingFlowNode = (node: FlowNode) => { + switch (node.type) { + case FlowNodeType.Normal: + case FlowNodeType.Return: + case FlowNodeType.Jump: + case FlowNodeType.Exit: { + addNode(node); + break; + } + case FlowNodeType.ExternCall: { + addNode(node); + break; + } + case FlowNodeType.If: + case FlowNodeType.Switch: { + addNode(node); + node.branches.forEach(branch => labelingFlowNode(branch.node)); + if (node.otherwise) labelingFlowNode(node.otherwise); + break; + } + case FlowNodeType.LoopInitializer: { + addNode(node); + labelingFlowNode(node.main); + break; + } + case FlowNodeType.Loop: { + addNode(node); + labelingFlowNode(node.body); + break; + } + case FlowNodeType.Block: { + node.nodes.forEach(node => { + labelingFlowNode(node); + }); + node.id = node.nodes[0].id; + break; + } + } + }; + labelingFlowNode(root); + return root; +}; + +const emitJITExpression = (expression: Expression) => { + const code = expression.toString(); + // const [parameterList, rawBody] = code.split("=>"); + // const body = rawBody.trim(); + return `(${code})(scope)`; +}; + +export const nodeGenPass = ( + program: BlockFlowNode, + getBuiltInFunction: (name: string) => BuiltInFunction +): ExecutionNode[] => { + const nodeMap: ExecutionNode[] = Array(program.next.id).fill(null); + const addNode = (id: number, node: ExecutionNode) => { + nodeMap[id] = node; + }; + const emitJITNode = (nodes: FlowNode[]) => { + console.log('emitJITNode', nodes); + const entry = nodes[0]; + const builtInFunctions: Record = {}; + const emitJITCode = (node: FlowNode): string => { + switch (node.type) { + case FlowNodeType.Normal: { + const { statement } = node; + if (statement.type === StatementType.Expression) { + const prefix = statement.async ? 'await ' : ''; + return `${prefix}${emitJITExpression(statement.expression)};`; + } else { + const { builtIn, async, functionName } = statement; + if (!builtIn) { + throw new Error('unexcept node'); + } + const prefix = async ? 'await ' : ''; + builtInFunctions[functionName] = + getBuiltInFunction(functionName).func; + const parameterListCode = Object.entries( + statement.parameters + ) + .map(([k, v]) => { + const value = `${isFunction(v) ? emitJITExpression(v) : v}`; + return `"${k}": ${value}`; + }) + .join(','); + return `${prefix}helper.builtIn.${statement.functionName}({${parameterListCode}}, scope.env);`; + } + } + case FlowNodeType.ExternCall: { + throw new Error( + "unexcept jit node, extern call can't emit jit" + ); + } + case FlowNodeType.Return: { + const { statement } = node; + if (isFunction(statement.value)) { + return `return [${OPType.Return}, ${emitJITExpression(statement.value)}];`; + } else { + return `return [${OPType.Return}, ${statement.value}];`; + } + } + case FlowNodeType.If: { + const branchListCode = node.branches + .map(branch => { + return `if (${emitJITExpression(branch.condition)})${emitJITCode(branch.node)}`; + }) + .join('else'); + const otherwiseCode = node.otherwise + ? `else ${emitJITCode(node.otherwise)}` + : ''; + return `${branchListCode}${otherwiseCode}`; + } + case FlowNodeType.Switch: { + const branchListCode = node.branches + .map(branch => { + return `case (${emitJITExpression(branch.condition)}): ${emitJITCode(branch.node)}`; + }) + .join('\n'); + const otherwiseCode = node.otherwise + ? `default: ${emitJITCode(node.otherwise)}` + : ''; + return `switch (${emitJITExpression(node.statement.pattern)}){${branchListCode}${otherwiseCode}}`; + } + case FlowNodeType.LoopInitializer: { + return emitJITCode(node.main); + } + case FlowNodeType.Loop: { + const { statement } = node; + const initializerCode = statement.initializer + ? emitJITExpression(statement.initializer) + : ''; + const iteratorCode = statement.iterator + ? emitJITExpression(statement.iterator) + : ''; + const conditionCode = statement.condition + ? emitJITExpression(statement.condition) + : ''; + const bodyCode = emitJITCode(node.body); + if (statement.skipInitialCheck) { + return `${initializerCode}; do { ${bodyCode} ${iteratorCode}; } while (${conditionCode});`; + } + return `for (${initializerCode}; ${conditionCode}; ${iteratorCode}) ${bodyCode}`; + } + case FlowNodeType.Jump: { + const { statement } = node; + // 一个 trick: 当跳出地址 < jitBlock 根地址时才是跳出 jitBlock + if (node.next.id < entry.id) { + return `return [${OPType.Move}, ${node.next.id}];`; + } + if (statement.type === StatementType.Break) { + if (statement.label) { + return `break ${statement.label};`; + } + return `break;`; + } else { + return `continue;`; + } + } + case FlowNodeType.Block: { + return `{\n${node.nodes.map(node => emitJITCode(node)).join('\n')}\n}`; + } + case FlowNodeType.Exit: { + return `return [${OPType.Exit}];`; + } + default: { + throw 'unknown statement type'; + } + } + }; + const code = nodes.map(node => emitJITCode(node)).join('\n'); + // @ts-expect-error 无法推导 + const jitFunction = new AsyncFunction('scope', 'helper', code); + addNode(nodes[0].id, scope => + jitFunction(scope, { + builtIn: builtInFunctions + }) + ); + }; + const emitNode = (node: FlowNode) => { + switch (node.type) { + case FlowNodeType.Normal: { + const { statement } = node; + if (statement.type === StatementType.Expression) { + if (statement.async) { + addNode(node.id, async (scope: Scope) => { + await statement.expression(scope); + return [OPType.Move, node.next.id]; + }); + } else { + addNode(node.id, (scope: Scope) => { + statement.expression(scope); + return [OPType.Move, node.next.id]; + }); + } + } else { + if (statement.builtIn) { + const builtFunc = getBuiltInFunction( + statement.functionName + ); + if (statement.async) { + addNode(node.id, async scope => { + const statementParameters = mapValues( + statement.parameters, + value => { + if (isFunction(value)) + return value(scope); + return value; + } + ); + await builtFunc.func( + statementParameters, + scope.env + ); + return [OPType.Move, node.next.id]; + }); + } else { + addNode(node.id, scope => { + const statementParameters = mapValues( + statement.parameters, + value => { + if (isFunction(value)) + return value(scope); + return value; + } + ); + builtFunc.func(statementParameters, scope.env); + return [OPType.Move, node.next.id]; + }); + } + } else { + throw new Error('not impelement'); + } + } + break; + } + case FlowNodeType.ExternCall: { + const { statement } = node; + if (statement.async) { + addNode(node.id, scope => { + const statementParameters = mapValues( + statement.parameters, + value => { + if (isFunction(value)) return value(scope); + return value; + } + ); + return [ + OPType.Call, + statement.functionName, + statementParameters, + node.next.id + ]; + }); + } else { + addNode(node.id, scope => { + const statementParameters = mapValues( + statement.parameters, + value => { + if (isFunction(value)) return value(scope); + return value; + } + ); + return [ + OPType.Call, + statement.functionName, + statementParameters, + node.next.id + ]; + }); + } + return; + } + case FlowNodeType.Return: { + const { statement } = node; + if (isFunction(statement.value)) { + addNode(node.id, async (scope: Scope) => [ + OPType.Return, + await (statement.value as Expression)(scope) + ]); + } else { + addNode(node.id, () => [OPType.Return, statement.value]); + } + break; + } + case FlowNodeType.If: { + node.branches.forEach(({ node }) => { + emitNode(node); + }); + if (node.otherwise) emitNode(node.otherwise); + addNode(node.id, async (scope: Scope) => { + for (const branch of node.branches) { + if (await branch.condition(scope)) { + return [OPType.Move, branch.node.id]; + } + } + if (node.otherwise !== void 0) { + return [OPType.Move, node.otherwise.id]; + } + return [OPType.Move, node.next.id]; + }); + break; + } + case FlowNodeType.Switch: { + node.branches.forEach(({ node }) => { + emitNode(node); + }); + if (node.otherwise) emitNode(node.otherwise); + const pattern = node.statement.pattern; + addNode(node.id, async (scope: Scope) => { + for (const branch of node.branches) { + if ( + pattern(scope) === (await branch.condition(scope)) + ) { + return [OPType.Move, branch.node.id]; + } + } + if (node.otherwise !== void 0) { + return [OPType.Move, node.otherwise.id]; + } + return [OPType.Move, node.next.id]; + }); + break; + } + case FlowNodeType.LoopInitializer: { + const { main } = node; + if (main.statement.skipInitialCheck) { + addNode(node.id, async scope => { + await main.statement.initializer!(scope); + return [OPType.Move, main.body.id]; + }); + } else { + addNode(node.id, async scope => { + await main.statement.initializer!(scope); + return [OPType.Move, main.id]; + }); + } + emitNode(node.main); + break; + } + case FlowNodeType.Loop: { + const { statement } = node; + addNode(node.id, async scope => { + if (statement.iterator) { + statement.iterator(scope); + } + if ( + statement.condition && + (await statement.condition(scope)) + ) { + return [OPType.Move, node.body.id]; + } else { + return [OPType.Move, node.next.id]; + } + }); + emitNode(node.body); + break; + } + case FlowNodeType.Jump: { + return addNode(node.id, () => [OPType.Move, node.next.id]); + } + case FlowNodeType.Block: { + const chunks = splitArray(node.nodes, node => !node.mergable); + for (const chunk of chunks) { + if (chunk.length === 0) continue; + if ( + chunk.length === 1 && + (![ + FlowNodeType.If, + FlowNodeType.Switch, + FlowNodeType.Loop + ].includes(chunk[0].type) || + !chunk[0].mergable) + ) { + emitNode(chunk[0]); + } else { + emitJITNode(chunk); + } + } + // for (const xnode of node.nodes) { + // emitNode(xnode); + // } + break; + } + case FlowNodeType.Exit: { + return addNode(node.id, () => [OPType.Exit]); + } + default: { + throw 'unknown statement type'; + } + } + }; + emitNode(program); + return nodeMap; +}; + +export const compile = ( + script: Block, + getBuiltInFunction: (name: string) => BuiltInFunction +) => { + const flowRoot = flowAnalyzePass(script); + console.log(flowRoot); + const program = nodeGenPass(flowRoot, getBuiltInFunction); + console.log(program); + return program; +}; diff --git a/packages/anon-tokyo/src/index.ts b/packages/anon-tokyo/src/index.ts new file mode 100644 index 0000000..bdb6bf2 --- /dev/null +++ b/packages/anon-tokyo/src/index.ts @@ -0,0 +1,2 @@ +export * from './interpreter'; +export * from './type'; diff --git a/packages/anon-tokyo/src/interpreter.ts b/packages/anon-tokyo/src/interpreter.ts new file mode 100644 index 0000000..b675420 --- /dev/null +++ b/packages/anon-tokyo/src/interpreter.ts @@ -0,0 +1,219 @@ +import { compile } from './compile'; +import { + Block, + OPType, + ExecutionNode, + Scope, + Expression, + Statement +} from './type'; + +export interface BuiltInFunction< + P extends Record, + E extends Record +> { + name: string; + save?: boolean; + func: (parameters: P, env: E) => any; +} + +export interface LanguageFeature { + builtInFunctions: BuiltInFunction[]; + globalFunctions: [name: string, Block][]; +} + +const EXIT_SIGNAL = Symbol('EXIT'); + +export class AnonTokyoIterator { + constructor( + private readonly executable: AnonTokyoExecutable, + private readonly parameters: Record, + private readonly executionContext: AnonTokyoExecutionContext + ) {} + + private context: Record = {}; + private current = 0; + private returnValue: any = undefined; + + async next() { + const op = await this.executable.execNode(this.current, { + local: this.context, + args: this.parameters, + env: this.executionContext.env + }); + const [type] = op; + switch (type) { + case OPType.Move: { + const [, next] = op; + this.current = next; + break; + } + case OPType.Call: { + const [, name, parameters, next] = op; + this.current = next; + await this.executionContext.callByName(name, parameters); + break; + } + case OPType.Return: { + const [, returnValue] = op; + this.current = -1; + this.returnValue = returnValue; + break; + } + case OPType.Exit: { + this.current = -2; + } + } + } + + async run() { + while (this.current >= 0) { + await this.next(); + } + if (this.current === -1) { + return this.returnValue; + } else if (this.current === -2) { + return EXIT_SIGNAL; + } + } +} + +/** + * 执行上下文,每次执行 Executable 都会生成一个上下文,上下文被设计为可序列化的。 + */ +export class AnonTokyoExecutionContext { + private callStack: AnonTokyoIterator[] = []; + + constructor( + public readonly env: Record, + private readonly interpreter: AnonTokyoInterpreter + ) {} + + callByName(name: string, parameters: Record) { + const executable = this.interpreter.getGlobalFunction(name); + return this.call(executable, parameters); + } + + async call( + executable: AnonTokyoExecutable, + parameters: Record + ) { + const iterator = new AnonTokyoIterator(executable, parameters, this); + this.callStack.push(iterator); + const res = await iterator.run(); + this.callStack.pop(); + return res; + } + + dump() {} +} + +/** + * 可执行的函数 + */ +export class AnonTokyoExecutable { + constructor( + private program: ExecutionNode[], + private readonly interpreter: AnonTokyoInterpreter + ) {} + + execNode(id: number, scope: Scope) { + if (id >= this.program.length) throw id; + return this.program[id](scope); + } + + /** + * 执行函数 + * @param parameters 参数 + * @param env 环境变量,调用链中的所有函数均可见 + * @returns + */ + async exec( + parameters: Record, + env: Record + ): Promise { + const context = new AnonTokyoExecutionContext(env, this.interpreter); + return context.call(this, parameters); + } +} + +/** + * 解释器,可以将事件编译为 `Executable` 或者立即执行 + */ +export class AnonTokyoInterpreter { + private readonly builtInFunctionMap: Map>; + private readonly globalFunctionMap: Map; + + constructor(lang: LanguageFeature) { + const { builtInFunctions, globalFunctions } = lang; + this.builtInFunctionMap = new Map( + builtInFunctions.map(e => [e.name, e]) + ); + this.globalFunctionMap = this.loadGlobalFunctions(globalFunctions); + } + + loadGlobalFunctions(globalFunctions: [name: string, Block][]) { + return new Map( + globalFunctions.map(([name, script]) => [ + name, + this.compile(script) + ]) + ); + } + + getGlobalFunction(name: string) { + const executable = this.globalFunctionMap.get(name); + if (!executable) { + throw `missing global function "${name}"`; + } + return executable; + } + + getBuiltInFunction(name: string) { + const func = this.builtInFunctionMap.get(name); + if (!func) { + throw `missing built-in function "${name}"`; + } + return func; + } + + execBuiltInFunction( + name: string, + parameters: Record, + env: Record + ) { + const func = this.builtInFunctionMap.get(name); + if (!func) { + throw `missing built-in function "${name}"`; + } + return func.func(parameters, env); + } + + /** + * 将指令编译为可执行的类 + * @param script + * @returns + */ + compile(script: Statement[]): AnonTokyoExecutable { + const program = compile(script, name => { + return this.getBuiltInFunction(name); + }); + return new AnonTokyoExecutable(program, this); + } + + /** + * 直接执行一组指令 + * @param script 指令数组 + * @param parameters 参数 + * @param env 环境 + * @returns + */ + exec( + script: Statement[], + parameters: Record, + env: Record + ) { + const executable = this.compile(script); + return executable.exec(parameters, env); + } +} diff --git a/packages/anon-tokyo/src/type.ts b/packages/anon-tokyo/src/type.ts new file mode 100644 index 0000000..361570e --- /dev/null +++ b/packages/anon-tokyo/src/type.ts @@ -0,0 +1,216 @@ +export enum StatementType { + Expression, + + Call, + Return, + + If, + Switch, + Loop, + Break, + Continue, + Block, + + Exit, + + FunctionDeclare +} + +export type Literal = string | number | boolean | null; +export type Expression = (env: Scope) => any; +export type Value = Literal | Expression; + +export type Block = Statement[]; + +export interface ExpressionStatement { + type: StatementType.Expression; + expression: Expression; + async?: boolean; +} + +export interface CallStatement { + type: StatementType.Call; + functionName: string; + parameters: Record; + builtIn?: boolean; + async?: boolean; + save?: boolean; +} + +export interface ReturnStatement { + type: StatementType.Return; + value?: Value; +} + +export interface Branch { + condition: Expression; + body: Block; +} + +export interface IfStatement { + type: StatementType.If; + branches: Branch[]; + otherwise?: Block; +} + +export interface SwitchStatement { + type: StatementType.Switch; + pattern: Expression; + branches: Branch[]; + otherwise?: Block; +} + +export interface LoopStatement { + type: StatementType.Loop; + skipInitialCheck?: boolean; + initializer?: Expression; + condition?: Expression; + iterator?: Expression; + label?: string; + body: Block; +} + +export interface BreakStatement { + type: StatementType.Break; + label?: string; +} + +export interface ContinueStatement { + type: StatementType.Continue; +} + +export interface ExitStatement { + type: StatementType.Exit; +} + +export type Statement = + | ExpressionStatement + | CallStatement + | ReturnStatement + | IfStatement + | SwitchStatement + | LoopStatement + | BreakStatement + | ContinueStatement + | ExitStatement; + +export enum FlowNodeType { + Normal, + ExternCall, + Return, + If, + Switch, + Loop, + LoopInitializer, + Jump, + Exit, + Block, + Idle +} + +export interface FlowNodeBase { + id: number; + type: FlowNodeType; + mergable: boolean; +} + +export interface NormalFlowNode extends FlowNodeBase { + type: FlowNodeType.Normal; + statement: CallStatement | ExpressionStatement; + next: FlowNode; +} + +export interface ExternCallFlowNode extends FlowNodeBase { + type: FlowNodeType.ExternCall; + statement: CallStatement; + receiver?: number; + next: FlowNode; +} + +export interface ReturnFlowNode extends FlowNodeBase { + type: FlowNodeType.Return; + statement: ReturnStatement; +} + +export interface FlowBranch { + condition: Expression; + node: BlockFlowNode; +} + +export interface IfFlowNode extends FlowNodeBase { + type: FlowNodeType.If; + statement: IfStatement; + branches: FlowBranch[]; + otherwise?: BlockFlowNode; + next: FlowNode; +} + +export interface SwitchFlowNode extends FlowNodeBase { + type: FlowNodeType.Switch; + statement: SwitchStatement; + branches: FlowBranch[]; + otherwise?: BlockFlowNode; + next: FlowNode; +} + +export interface LoopFlowNode extends FlowNodeBase { + type: FlowNodeType.Loop; + statement: LoopStatement; + body: BlockFlowNode; + next: FlowNode; +} + +export interface LoopInitializerFlowNode extends FlowNodeBase { + type: FlowNodeType.LoopInitializer; + main: LoopFlowNode; +} + +export interface JumpFlowNode extends FlowNodeBase { + type: FlowNodeType.Jump; + statement: BreakStatement | ContinueStatement; + next: FlowNode; +} + +export interface ExitFlowNode extends FlowNodeBase { + type: FlowNodeType.Exit; + statement: ExitStatement; +} + +export interface BlockFlowNode extends FlowNodeBase { + type: FlowNodeType.Block; + nodes: FlowNode[]; + next: FlowNode; +} + +export type FlowNode = + | NormalFlowNode + | ExternCallFlowNode + | ReturnFlowNode + | IfFlowNode + | SwitchFlowNode + | LoopFlowNode + | LoopInitializerFlowNode + | JumpFlowNode + | ExitFlowNode + | BlockFlowNode; + +export enum OPType { + Move, + Call, + Return, + Exit +} + +export type OP = + | [OPType.Move, next: number] + | [OPType.Call, name: string, parameters: Record, next: number] + | [OPType.Return, returnValue?: any] + | [OPType.Exit]; + +export interface Scope { + args: Record; + local: Record; + env: Record; +} + +export type ExecutionNode = (scope: Scope) => Promise | OP; diff --git a/packages/anon-tokyo/src/utils.ts b/packages/anon-tokyo/src/utils.ts new file mode 100644 index 0000000..45eba39 --- /dev/null +++ b/packages/anon-tokyo/src/utils.ts @@ -0,0 +1,12 @@ +export const splitArray = (array: T[], predictor: (val: T) => boolean) => { + const chunks: T[][] = [[]]; + for (const item of array) { + if (predictor(item)) { + chunks.push([item]); + chunks.push([]); + } else { + chunks.at(-1)!.push(item); + } + } + return chunks; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8ea3a0..68db4ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -309,12 +309,12 @@ importers: '@user/data-state': specifier: workspace:* version: link:../data-state - '@user/data-utils': - specifier: workspace:* - version: link:../data-utils packages-user/data-state: dependencies: + '@motajs/anon-tokyo': + specifier: workspace:* + version: link:../../packages/anon-tokyo '@motajs/common': specifier: workspace:* version: link:../../packages/common @@ -324,12 +324,12 @@ importers: '@user/data-base': specifier: workspace:* version: link:../data-base + '@user/data-common': + specifier: workspace:* + version: link:../data-common '@user/data-system': specifier: workspace:* version: link:../data-system - '@user/data-utils': - specifier: workspace:* - version: link:../data-utils packages-user/data-system: dependencies: @@ -340,12 +340,6 @@ importers: specifier: workspace:* version: link:../data-base - packages-user/data-utils: - dependencies: - '@user/data-base': - specifier: workspace:* - version: link:../data-base - packages-user/entry-client: dependencies: '@motajs/client': @@ -396,15 +390,18 @@ importers: '@user/data-base': specifier: workspace:* version: link:../data-base + '@user/data-common': + specifier: workspace:* + version: link:../data-common '@user/data-fallback': specifier: workspace:* version: link:../data-fallback '@user/data-state': specifier: workspace:* version: link:../data-state - '@user/data-utils': + '@user/data-system': specifier: workspace:* - version: link:../data-utils + version: link:../data-system '@user/legacy-plugin-data': specifier: workspace:* version: link:../legacy-plugin-data @@ -426,12 +423,11 @@ importers: '@user/data-state': specifier: workspace:* version: link:../data-state - '@user/data-utils': - specifier: workspace:* - version: link:../data-utils packages/animate: {} + packages/anon-tokyo: {} + packages/audio: dependencies: '@motajs/common':