feat: 将 anon-tokyo 内嵌到项目中

This commit is contained in:
unanmed 2026-09-13 18:49:29 +08:00
parent 46a38fa0c6
commit ef58f79518
7 changed files with 1109 additions and 17 deletions

View File

@ -0,0 +1,3 @@
{
"name": "@motajs/anon-tokyo"
}

View 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;
};

View File

@ -0,0 +1,2 @@
export * from './interpreter';
export * from './type';

View 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);
}
}

View 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;

View 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;
};

View File

@ -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':