UNPKG

@awayfl/avm2

Version:

Virtual machine for executing AS3 code

390 lines (389 loc) 14.4 kB
import { __extends } from "tslib"; import { transformJStoASRegExpMatchArray } from './transformJStoASRegExpMatchArray'; import { ASObject } from './ASObject'; import { addPrototypeFunctionAlias } from './addPrototypeFunctionAlias'; import { Errors } from '../errors'; import { Settings } from '../Settings'; import { XRegExp } from './XRegLookup'; var WARN_REPORT_TABLE = {}; var IS_SUPPORT_LOOKBEHIND = (function () { try { new RegExp('(?<=)'); return true; } catch (_) { return false; } })(); var ASRegExp = /** @class */ (function (_super) { __extends(ASRegExp, _super); function ASRegExp(pattern, flags) { var _this = _super.call(this) || this; _this._flags = ''; _this._useFallback = false; _this._dotall = false; _this._extended = false; _this._captureNames = []; var source; if (pattern === undefined) { pattern = source = ''; } else if (_this.sec.AXRegExp.axIsType(pattern)) { if (flags) { _this.sec.throwError('TypeError', Errors.RegExpFlagsArgumentError); } flags = pattern._flags; source = pattern.source; pattern = pattern.value; } else { pattern = String(pattern); // Escape all forward slashes. source = pattern.replace(/(^|^[/]|(?:\\\\)+)\//g, '$1\\/'); if (flags) { var f = flags; flags = ''; for (var i = 0; i < f.length; i++) { var flag = f[i]; switch (flag) { case 's': // With the s flag set, . will match the newline character. _this._dotall = true; break; case 'x': // With the x flag set, spaces in the regular expression, will be ignored as part of // the pattern. _this._extended = true; break; case 'g': case 'i': case 'm': // Only keep valid flags since an ECMAScript compatible RegExp implementation will // throw on invalid ones. We have to avoid that in ActionScript. flags += flag; } } } _this._flags = flags || ''; pattern = _this._parse(source); } try { _this.value = new RegExp(pattern, flags); } catch (e) { console.log('Unsupported RegExp pattern:' + pattern); _this._useFallback = true; } _this._source = source; return _this; } ASRegExp.classInitializer = function () { var proto = this.dPrototype; var asProto = ASRegExp.prototype; addPrototypeFunctionAlias(proto, '$BgtoString', asProto.ecmaToString); addPrototypeFunctionAlias(proto, '$Bgexec', asProto.exec); addPrototypeFunctionAlias(proto, '$Bgtest', asProto.test); }; // Parses and sanitizes a AS3 RegExp pattern to be used in JavaScript. Silently fails and // returns an unmatchable pattern of the source turns out to be invalid. ASRegExp.prototype._parse = function (pattern) { if (pattern.includes('(?<=') || pattern.includes('(?<!') && !this._extended) { if (!IS_SUPPORT_LOOKBEHIND) { if (!Settings.EMULATE_LOOKBEHIND) { throw new Error('[ASRegExp] Pattern include a lookbehind, but your browser not usupport it:' + pattern); } WARN_REPORT_TABLE['lookbehind'] || console.warn('[ASRegExp] Pattern include a lookbehind, we should use XRegExp polyfill for Safari.\n', pattern); WARN_REPORT_TABLE['lookbehind'] = true; // falling down to XRegExp this._useFallback = true; return pattern; } WARN_REPORT_TABLE['lookbehind'] || console.warn('[ASRegExp] Pattern include a lookbehind, we will use a native .\n', pattern); WARN_REPORT_TABLE['lookbehind'] = true; return pattern; } var result = ''; var captureNames = this._captureNames; var parens = []; var atoms = 0; for (var i = 0; i < pattern.length; i++) { var char = pattern[i]; switch (char) { case '(': result += char; parens.push(atoms > 1 ? atoms - 1 : atoms); atoms = 0; if (pattern[i + 1] === '?') { switch (pattern[i + 2]) { case ':': case '=': case '!': result += '?' + pattern[i + 2]; i += 2; break; default: if (/\(\?P<([\w$]+)>/.exec(pattern.substr(i))) { var name_1 = RegExp.$1; if (name_1 !== 'length') { captureNames.push(name_1); } if (captureNames.indexOf(name_1) > -1) { // TODO: Handle the case were same name is used for multiple groups. } i += RegExp.lastMatch.length - 1; } else { return ASRegExp.UNMATCHABLE_PATTERN; } } } else { captureNames.push(null); } // 406 seems to be the maximum number of capturing groups allowed in a pattern. // Examined by testing. if (captureNames.length > 406) { return ASRegExp.UNMATCHABLE_PATTERN; } break; case ')': if (!parens.length) { return ASRegExp.UNMATCHABLE_PATTERN; } result += char; atoms = parens.pop() + 1; break; case '|': result += char; break; case '\\': result += char; if (/\\|c[A-Z]|x[0-9,a-z,A-Z]{2}|u[0-9,a-z,A-Z]{4}|./.exec(pattern.substr(i + 1))) { result += RegExp.lastMatch; i += RegExp.lastMatch.length; } if (atoms <= 1) { atoms++; } break; case '[': if (/\[[^\]]*\]/.exec(pattern.substr(i))) { result += RegExp.lastMatch; i += RegExp.lastMatch.length - 1; if (atoms <= 1) { atoms++; } } else { return ASRegExp.UNMATCHABLE_PATTERN; } break; case '{': if (/\{[^{]*?(?:,[^{]*?)?\}/.exec(pattern.substr(i))) { result += RegExp.lastMatch; i += RegExp.lastMatch.length - 1; } else { return ASRegExp.UNMATCHABLE_PATTERN; } break; case '.': if (this._dotall) { result += '[\\s\\S]'; } else { result += char; } if (atoms <= 1) { atoms++; } break; case '?': case '*': case '+': if (!atoms) { return ASRegExp.UNMATCHABLE_PATTERN; } result += char; if (pattern[i + 1] === '?') { i++; result += '?'; } break; case ' ': { if (this._extended) { break; } result += char; if (atoms <= 1) { atoms++; } break; } default: { result += char; if (atoms <= 1) { atoms++; } } } // 32767 seams to be the maximum allowed length for RegExps in SpiderMonkey. // Examined by testing. if (result.length > 0x7fff) { return ASRegExp.UNMATCHABLE_PATTERN; } } if (parens.length) { return ASRegExp.UNMATCHABLE_PATTERN; } return result; }; ASRegExp.prototype.ecmaToString = function () { var out = '/' + this._source + '/'; if (this.value.global) out += 'g'; if (this.value.ignoreCase) out += 'i'; if (this.value.multiline) out += 'm'; if (this._dotall) out += 's'; if (this._extended) out += 'x'; return out; }; ASRegExp.prototype.axCall = function (_) { // eslint-disable-next-line return this.exec.apply(this, arguments); }; ASRegExp.prototype.axApply = function (_, argArray) { // eslint-disable-next-line return this.exec.apply(this, argArray); }; Object.defineProperty(ASRegExp.prototype, "source", { get: function () { return this._source; }, enumerable: false, configurable: true }); Object.defineProperty(ASRegExp.prototype, "global", { get: function () { return this.value.global; }, enumerable: false, configurable: true }); Object.defineProperty(ASRegExp.prototype, "ignoreCase", { get: function () { return this.value.ignoreCase; }, enumerable: false, configurable: true }); Object.defineProperty(ASRegExp.prototype, "multiline", { get: function () { return this.value.multiline; }, enumerable: false, configurable: true }); Object.defineProperty(ASRegExp.prototype, "lastIndex", { get: function () { return this.value.lastIndex; }, set: function (value) { this.value.lastIndex = value; }, enumerable: false, configurable: true }); Object.defineProperty(ASRegExp.prototype, "dotall", { get: function () { return this._dotall; }, enumerable: false, configurable: true }); Object.defineProperty(ASRegExp.prototype, "extended", { get: function () { return this._extended; }, enumerable: false, configurable: true }); ASRegExp.prototype.internalStringSearch = function (string) { if (!this._useFallback) { return string.search(this.value); } return XRegExp.searchLb(string, this._source, this._flags); }; ASRegExp.prototype.internalStringReplace = function (string, replace) { if (!this._useFallback) { return string.replace(this.value, replace); } return XRegExp.replaceLb(string, this._source, replace, this._flags); }; // box string matche from string ASRegExp.prototype.internalStringMatch = function (string) { var g = this._flags.includes('g'); if (!this._useFallback) { var res_1 = string.match(this.value); // in Flash match should return a [] for globals if (!res_1) { return g ? [] : null; } return res_1; } var res = XRegExp.matchAllLb(string, this._source, this._flags); if (res.length > 0) { var match = [res[0]]; /** * XRegExp not fully implement lookup behind matching, set index to 0 * @todo Maybe dangerous, implement it! */ match.index = 0; match.input = string; return match; } else if (g) { return []; } return null; }; ASRegExp.prototype.exec = function (str) { if (str === void 0) { str = ''; } var result; if (this._useFallback) { result = XRegExp.execLb(str, this._source, this._flags); } else { result = this.value.exec(str); } if (!result) { return null; } var axResult = transformJStoASRegExpMatchArray(this.sec, result); var captureNames = this._captureNames; if (captureNames) { for (var i = 0; i < captureNames.length; i++) { var name_2 = captureNames[i]; if (name_2 !== null) { // In AS3, non-matched named capturing groups return an empty string. var value = result[i + 1] || ''; result[name_2] = value; axResult.axSetPublicProperty(name_2, value); } } return axResult; } }; ASRegExp.prototype.test = function (str) { if (str === void 0) { str = ''; } return this.exec(str) !== null; }; ASRegExp.UNMATCHABLE_PATTERN = '^(?!)$'; return ASRegExp; }(ASObject)); export { ASRegExp };