mirror of
https://github.com/motajs/template.git
synced 2026-09-26 21:28:14 +08:00
feat: 将 anon-tokyo 内嵌到项目中
This commit is contained in:
parent
46a38fa0c6
commit
ef58f79518
3
packages/anon-tokyo/package.json
Normal file
3
packages/anon-tokyo/package.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "@motajs/anon-tokyo"
|
||||
}
|
||||
644
packages/anon-tokyo/src/compile.ts
Normal file
644
packages/anon-tokyo/src/compile.ts
Normal file
@ -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 = <T extends FlowNode>(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<string, FlowNode>();
|
||||
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<any, any>
|
||||
): 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<string, CallableFunction> = {};
|
||||
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<any, any>
|
||||
) => {
|
||||
const flowRoot = flowAnalyzePass(script);
|
||||
console.log(flowRoot);
|
||||
const program = nodeGenPass(flowRoot, getBuiltInFunction);
|
||||
console.log(program);
|
||||
return program;
|
||||
};
|
||||
2
packages/anon-tokyo/src/index.ts
Normal file
2
packages/anon-tokyo/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './interpreter';
|
||||
export * from './type';
|
||||
219
packages/anon-tokyo/src/interpreter.ts
Normal file
219
packages/anon-tokyo/src/interpreter.ts
Normal file
@ -0,0 +1,219 @@
|
||||
import { compile } from './compile';
|
||||
import {
|
||||
Block,
|
||||
OPType,
|
||||
ExecutionNode,
|
||||
Scope,
|
||||
Expression,
|
||||
Statement
|
||||
} from './type';
|
||||
|
||||
export interface BuiltInFunction<
|
||||
P extends Record<string, any>,
|
||||
E extends Record<string, any>
|
||||
> {
|
||||
name: string;
|
||||
save?: boolean;
|
||||
func: (parameters: P, env: E) => any;
|
||||
}
|
||||
|
||||
export interface LanguageFeature {
|
||||
builtInFunctions: BuiltInFunction<any, any>[];
|
||||
globalFunctions: [name: string, Block][];
|
||||
}
|
||||
|
||||
const EXIT_SIGNAL = Symbol('EXIT');
|
||||
|
||||
export class AnonTokyoIterator {
|
||||
constructor(
|
||||
private readonly executable: AnonTokyoExecutable,
|
||||
private readonly parameters: Record<string, any>,
|
||||
private readonly executionContext: AnonTokyoExecutionContext
|
||||
) {}
|
||||
|
||||
private context: Record<string, any> = {};
|
||||
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<string, any>,
|
||||
private readonly interpreter: AnonTokyoInterpreter
|
||||
) {}
|
||||
|
||||
callByName(name: string, parameters: Record<string, any>) {
|
||||
const executable = this.interpreter.getGlobalFunction(name);
|
||||
return this.call(executable, parameters);
|
||||
}
|
||||
|
||||
async call(
|
||||
executable: AnonTokyoExecutable,
|
||||
parameters: Record<string, any>
|
||||
) {
|
||||
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<string, any>,
|
||||
env: Record<string, any>
|
||||
): Promise<unknown> {
|
||||
const context = new AnonTokyoExecutionContext(env, this.interpreter);
|
||||
return context.call(this, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解释器,可以将事件编译为 `Executable` 或者立即执行
|
||||
*/
|
||||
export class AnonTokyoInterpreter {
|
||||
private readonly builtInFunctionMap: Map<string, BuiltInFunction<any, any>>;
|
||||
private readonly globalFunctionMap: Map<string, AnonTokyoExecutable>;
|
||||
|
||||
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<string, AnonTokyoExecutable>(
|
||||
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<string, Expression>,
|
||||
env: Record<string, any>
|
||||
) {
|
||||
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<string, any>,
|
||||
env: Record<string, any>
|
||||
) {
|
||||
const executable = this.compile(script);
|
||||
return executable.exec(parameters, env);
|
||||
}
|
||||
}
|
||||
216
packages/anon-tokyo/src/type.ts
Normal file
216
packages/anon-tokyo/src/type.ts
Normal file
@ -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<string, Value>;
|
||||
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<string, any>, next: number]
|
||||
| [OPType.Return, returnValue?: any]
|
||||
| [OPType.Exit];
|
||||
|
||||
export interface Scope {
|
||||
args: Record<string, any>;
|
||||
local: Record<string, any>;
|
||||
env: Record<string, any>;
|
||||
}
|
||||
|
||||
export type ExecutionNode = (scope: Scope) => Promise<OP> | OP;
|
||||
12
packages/anon-tokyo/src/utils.ts
Normal file
12
packages/anon-tokyo/src/utils.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export const splitArray = <T>(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;
|
||||
};
|
||||
@ -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':
|
||||
|
||||
Loading…
Reference in New Issue
Block a user