@awayfl/avm2
Version:
Virtual machine for executing AS3 code
452 lines (353 loc) • 10.6 kB
text/typescript
import { Multiname } from './../abc/lazy/Multiname';
import { getExtClassField, extClasses, LONG_NAMES } from '../ext/external';
import { IGenerator } from './IGenerator';
import { NamespaceType } from '../abc/lazy/NamespaceType';
export interface IImportDefinition {
name: Multiname;
alias: string;
options: IImportGenOptions | undefined;
}
export interface IImportGenOptions {
findProp?: boolean;
scope?: string;
mnIndex: number;
}
export interface ILexGenerator extends IGenerator {
/**
* Test Multiname to support import generation
*/
test(mn: Multiname, isLexCall: boolean): boolean;
findAliases(mn: Multiname, findProp: boolean): IImportDefinition[];
/**
* Generate import alias if can be generated
* @throws lex import can't be generated
*/
getLexAlias(mn: Multiname, options?: IImportGenOptions): string;
/**
* Generate import alias if can be generated
* @throws lex import can't be generated
*/
getPropStrictAlias(mn: Multiname, options?: IImportGenOptions): string;
}
export abstract class LexImportsGenerator implements ILexGenerator {
imports: Array<IImportDefinition> = [];
private _lexMode = false;
public test(_mn: Multiname, _isLexCall: boolean) {
return false;
}
protected _genEntry(def: IImportDefinition, ...args: any[]): string {
return '//' + def.name;
}
protected _genAlias(mn: Multiname, _options?: IImportGenOptions): string {
return mn.namespace.uri.replace(/\./g, '_') + '__' + mn.name;
}
findAliases(mn: Multiname, findProp: boolean): IImportDefinition[] {
return this.imports.filter((e) => {
return e.name === mn && (findProp ? findProp === e.options.findProp : true);
});
}
public getLexAlias(mn: Multiname, options?: IImportGenOptions): string {
this._lexMode = true;
const res = this.getPropStrictAlias(mn, Object.assign({ findProp: true, mnIndex: -1 }, options || {}));
this._lexMode = false;
return res;
}
public getPropStrictAlias(
mn: Multiname, options: IImportGenOptions = { findProp: false, mnIndex: -1 }): string {
if (!this.test(mn, this._lexMode)) {
throw `Can't generate static alias for ${mn.name} of ${mn.namespace?.uri}`;
}
const def: IImportDefinition = this.imports.find((e) => {
return e.name === mn && options.findProp === e.options.findProp;
});
if (def) {
return def.alias;
}
const alias = this._genAlias(mn, options);
if (!this.imports.find((e) => e.alias === alias)) {
this.imports.push({
name: mn,
alias,
options
});
}
return alias;
}
public genHeader(ident: string = '', ...args: any[]): string {
if (!this.imports.length) {
return '';
}
const header = [`\n${ident} /* ${this.constructor.name} */`];
for (const def of this.imports) {
header.push(ident + ' ' + this._genEntry(def));
}
header.push('\n');
return header.join('\n');
}
public genBody(_ident: string = '', ..._args: any[]): string {
return '';
}
public genPost(input: string[]): string[] {
return input;
}
reset(): void {
this.imports.length = 0;
}
}
/**
* Generate imports for all lex generators
*/
export class ComplexGenerator implements ILexGenerator {
/**
* Allowed collsion for alias of generator, return first alias;
*/
public allowColissions: Boolean = false;
constructor(public generators: ILexGenerator[]) {
if (!generators) {
throw 'Generators array can\'t be null';
}
}
/**
* Return generator that will used for lex generation
*/
public getGenerator(mn: Multiname, isLexCall: boolean): ILexGenerator | null {
for (const g of this.generators) {
if (g.test(mn, isLexCall)) {
return g;
}
}
return null;
}
public test(mn: Multiname, isLexCall: boolean): boolean {
if (!this.generators.length) {
return false;
}
return !!this.getGenerator(mn, isLexCall);
}
findAliases(mn: Multiname, findProp: boolean): IImportDefinition[] {
let res = [];
for (const g of this.generators) {
res = res.concat(g.findAliases(mn, findProp));
}
return res;
}
/**
* Return generator that will used for propstrict generation
*/
public getLexAlias(mn: Multiname, options?: IImportGenOptions): string {
const gen = this.getGenerator(mn, true);
if (!gen) {
throw `Can't generate static alias for ${mn.name} of ${mn.namespace?.uri}`;
}
return gen.getLexAlias(mn, options);
}
public getPropStrictAlias(mn: Multiname, options?: IImportGenOptions): string {
const gen = this.getGenerator(mn, false);
if (!gen) {
throw `Can't generate static alias for ${mn.name} of ${mn.namespace?.uri}`;
}
return gen.getPropStrictAlias(mn, options);
}
public genHeader(ident: string): string {
let header = '';
for (const g of this.generators) {
header += g.genHeader(ident);
}
return header;
}
public genBody(ident: string): string {
let body = '';
for (const g of this.generators) {
body += g.genBody(ident);
}
return body;
}
public genPost(arr: string[]): string[] {
for (const g of this.generators) {
arr = g.genPost(arr);
}
return arr;
}
reset(): void {
this.generators.forEach((e) => e.reset());
}
}
/* -------------------- GENERATORS ------------------- */
/**
* Import generator for Box2D and Nape external libs
*/
export class PhysicsLex extends LexImportsGenerator {
constructor(public allows: { box2D?: boolean; nape?: boolean } = null) {
super();
this.allows = Object.assign({ box2D: true, nape: true }, allows);
}
protected _genEntry(def: IImportDefinition): string {
const uri = def.name.namespace.uri;
const name = def.name.name;
return `const ${def.alias} = context.getStaticImportExt('${uri}', '${name}');`;
}
protected _genAlias(mn: Multiname, _options?: IImportGenOptions): string {
return mn.namespace.uri.replace(/\./g, '_') + '__' + mn.name;
}
public test(mn: Multiname) {
const uri = mn.namespace?.uri;
if (!uri || !extClasses.lib) {
return false;
}
// generate static for box2D
if (uri.startsWith('Box2D') && !this.allows.box2D) {
return false;
}
if (uri.startsWith('nape.') && !this.allows.nape) {
return false;
}
if (mn.name.includes('Debug')) {
return false;
}
const ns = mn.namespace?.uri;
const isLong = ns && LONG_NAMES.test(ns);
return !!getExtClassField(mn.name, isLong ? ns : undefined);
}
}
const ALLOWED_TOP_LEVEL_NAMES: String[] = [
'flash.geom',
'flash.utils',
'Math',
'trace',
'parseInt',
'RegExp'
];
const NOT_ALLOWED: String[] = [
'getDefinitionByName',
'SetIntervalTimer',
'setTimeout',
':trace'
];
interface ITopGenOptions extends IImportGenOptions {
nameAlias: string;
}
/**
* @description Generete single constant reference on top level API props: trace, pareseInt etc
*/
export class TopLevelLex extends LexImportsGenerator {
public test(mn: Multiname) {
const uri = mn.namespace?.uri || '';
const name = mn.name;
if (typeof uri === 'undefined') {
return false;
}
if (
NOT_ALLOWED.indexOf(name) > -1 ||
NOT_ALLOWED.indexOf(uri) > -1) {
return false;
}
if (
(ALLOWED_TOP_LEVEL_NAMES.indexOf(name) > -1 && !uri) || // trace, Math
ALLOWED_TOP_LEVEL_NAMES.indexOf(uri) > -1) {
return true;
}
return false;
}
protected _genEntry(def: IImportDefinition): string {
const uri = def.name.namespace.uri;
const name = def.name.name;
const { mnIndex, findProp } = <ITopGenOptions> def.options || {};
if (typeof mnIndex !== 'number' || mnIndex < 0) {
throw 'Name alias required for generatin Toplevel exports!';
}
const id = mnIndex;
const mnname = `['${Multiname.getPublicMangledName(name)}']`;
return `const ${def.alias} = context.getTopLevel(${id})${ findProp ? mnname : ''}; // ${uri}:${name}`;
}
protected _genAlias(mn: Multiname, options: IImportGenOptions): string {
const uri = mn.namespace.uri.split(/[.:]/g);
if (typeof options.mnIndex !== 'number' || options.mnIndex < 0) {
throw 'Name alias required for generatin Toplevel exports!';
}
return `${uri.join('_')}__${mn.name}${options.findProp ? '' : '_def'}`;
}
}
interface StaticHoistLexGenOptions extends IImportGenOptions {
nameAlias: string;
scope: string;
}
/**
* @description Generate single reference on class namespace/class with static field
*/
export class StaticHoistLex extends LexImportsGenerator {
private _mn: Set<Multiname> = new Set<Multiname>();
private _loc: Record<string, number> = {};
markScope(name: string, pos: number = 0) {
if (!pos) {
throw 'Scope location should be marked!';
}
this._loc[name] = pos;
}
test (mn: Multiname, isLexCall: boolean) {
if (!isLexCall) {
return false;
}
// when there are not URI and no public - skip
if (!mn.uri || mn.namespace.type !== NamespaceType.Public) {
return false;
}
if (!this._mn.has(mn)) {
this._mn.add(mn);
return false;
}
return true;
}
protected _genAlias(mn: Multiname, options: IImportGenOptions): string {
const uri = mn.namespace.uri.split(/[.|:]/g);
const opt = <StaticHoistLexGenOptions> options;
if (!opt.nameAlias) {
throw 'Name alias required for generatin FirstUsageScopeLex exports!';
}
if (!opt.scope) {
throw 'Scope alias required for generatin FirstUsageScopeLex exports!';
}
return `${uri.join('_')}__${mn.name}`;
}
findAliases() {
return [];
}
genHeader () {
return '';
}
genBody (idnt: string) {
if (!this.imports.length) {
return '';
}
const body = [`${idnt} // ${this.constructor.name} `];
for (const imp of this.imports) {
body.push(`${idnt} let ${imp.alias}; // ${imp.name}`);
}
return body.join('\n');
}
genPost(arr: string[]): string[] {
for (const def of this.imports) {
const opt = def.options as ITopGenOptions;
const loc = this._loc[opt.scope];
if (!loc) {
throw 'Unknow import for scope:' + opt.scope;
}
//@ts-ignore
const idnt = arr[loc].length - arr[loc].trimLeft().length - 1;
arr.splice(loc + 1, 0, this._genEntry(def, ' '.repeat(idnt)));
}
return arr;
}
_genEntry(def: IImportDefinition, idnt: string = ' ') {
const js = [];
const mn = def.name;
const opt = def.options as ITopGenOptions;
js.push(`${idnt} // `);
js.push(`${idnt} // ${mn}`);
js.push(`${idnt} temp = ${opt.scope}.findScopeProperty(${opt.nameAlias}, true, false);`);
js.push(`${idnt} ${def.alias} = temp['$Bg${mn.name}'];`);
js.push(`${idnt} if (${def.alias} === undefined || typeof ${def.alias} === 'function') {`);
js.push(`${idnt} ${def.alias} = temp.axGetProperty(${opt.nameAlias});`);
js.push(`${idnt} }`);
return js.join('\n');
}
}