mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-19 00:31:06 +00:00
166 lines
5.6 KiB
TypeScript
166 lines
5.6 KiB
TypeScript
import {
|
|
surroundingAgent,
|
|
} from '../host-defined/engine.mts';
|
|
import {
|
|
JSStringValue,
|
|
ObjectValue,
|
|
Value,
|
|
wellKnownSymbols,
|
|
type Arguments,
|
|
type FunctionCallContext,
|
|
} from '../value.mts';
|
|
import { Q } from '../completion.mts';
|
|
import type { ParseNode } from '../parser/ParseNode.mts';
|
|
import type { ValueEvaluator } from '../evaluator.mts';
|
|
import {
|
|
isDecimalDigit, isLineTerminator, isWhitespace,
|
|
} from '../parser/Lexer.mts';
|
|
import { isAsciiLetter, isControlEscape, isSyntaxCharacter } from '../parser/RegExpParser.mts';
|
|
import { bootstrapConstructor } from './bootstrap.mts';
|
|
import { UnicodeEscape } from './JSON.mts';
|
|
import {
|
|
Get,
|
|
IsRegExp,
|
|
Realm,
|
|
RegExpAlloc,
|
|
RegExpInitialize,
|
|
SameValue,
|
|
type FunctionObject,
|
|
type OrdinaryObject,
|
|
|
|
Assert, isLeadingSurrogate, isTrailingSurrogate, StringToCodePoints, UTF16EncodeCodePoint, type CodePoint, type RegExpMatcher, type RegExpRecord,
|
|
} from '#self';
|
|
|
|
export interface RegExpObject extends OrdinaryObject {
|
|
readonly OriginalSource: JSStringValue;
|
|
readonly OriginalFlags: JSStringValue;
|
|
readonly RegExpMatcher: RegExpMatcher;
|
|
readonly RegExpRecord: RegExpRecord;
|
|
readonly parsedPattern: ParseNode.RegExp.Pattern;
|
|
}
|
|
export function isRegExpObject(o: Value): o is RegExpObject {
|
|
return 'RegExpMatcher' in o;
|
|
}
|
|
/** https://tc39.es/ecma262/#sec-regexp-constructor */
|
|
function* RegExpConstructor([pattern = Value.undefined, flags = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
|
// 1. Let patternIsRegExp be ? IsRegExp(pattern).
|
|
const patternIsRegExp = Q(yield* IsRegExp(pattern));
|
|
let newTarget;
|
|
// 2. If NewTarget is undefined, then
|
|
if (NewTarget === Value.undefined) {
|
|
// a. Let newTarget be the active function object.
|
|
newTarget = surroundingAgent.activeFunctionObject;
|
|
// b. If patternIsRegExp is true and flags is undefined, then
|
|
if (patternIsRegExp === Value.true && flags === Value.undefined) {
|
|
// i. Let patternConstructor be ? Get(pattern, "constructor").
|
|
const patternConstructor = Q(yield* Get(pattern as ObjectValue, Value('constructor')));
|
|
// ii. If SameValue(newTarget, patternConstructor) is true, return pattern.
|
|
if (SameValue(newTarget, patternConstructor) === Value.true) {
|
|
return pattern;
|
|
}
|
|
}
|
|
} else { // 3. Else, let newTarget be NewTarget.
|
|
newTarget = NewTarget;
|
|
}
|
|
let P;
|
|
let F;
|
|
// 4. If Type(pattern) is Object and pattern has a [[RegExpMatcher]] internal slot, then
|
|
if (isRegExpObject(pattern)) {
|
|
// a. Let P be pattern.[[OriginalSource]].
|
|
P = pattern.OriginalSource;
|
|
// b. If flags is undefined, let F be pattern.[[OriginalFlags]].
|
|
if (flags === Value.undefined) {
|
|
F = pattern.OriginalFlags;
|
|
} else { // c. Else, let F be flags.
|
|
F = flags;
|
|
}
|
|
} else if (patternIsRegExp === Value.true) { // 5. Else if patternIsRegExp is true, then
|
|
// a. Else if patternIsRegExp is true, then
|
|
P = Q(yield* Get(pattern as ObjectValue, Value('source')));
|
|
// b. If flags is undefined, then
|
|
if (flags === Value.undefined) {
|
|
// i. Let F be ? Get(pattern, "flags").
|
|
F = Q(yield* Get(pattern as ObjectValue, Value('flags')));
|
|
} else { // c. Else, let F be flags.
|
|
F = flags;
|
|
}
|
|
} else { // 6. Else,
|
|
// a. Let P be pattern.
|
|
P = pattern;
|
|
// b. Let F be flags.
|
|
F = flags;
|
|
}
|
|
// 7. Let O be ? RegExpAlloc(newTarget).
|
|
const O = Q(yield* RegExpAlloc(newTarget as FunctionObject));
|
|
// 8. Return ? RegExpInitialize(O, P, F).
|
|
return Q(yield* RegExpInitialize(O, P, F));
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-regexp.escape */
|
|
function* RegExp_escape([S = Value.undefined]: Arguments) {
|
|
if (!(S instanceof JSStringValue)) {
|
|
return surroundingAgent.Throw('TypeError', 'NotAString', S);
|
|
}
|
|
let escaped = '';
|
|
const cpList = StringToCodePoints(S.stringValue());
|
|
for (const cp of cpList) {
|
|
if (escaped === '' && (isDecimalDigit(String.fromCodePoint(cp)) || isAsciiLetter(cp))) {
|
|
const numericValue = cp;
|
|
const hex = numericValue.toString(16);
|
|
Assert(hex.length === 2);
|
|
escaped += `\u{005C}x${hex}`;
|
|
} else {
|
|
escaped += EncodeForRegExpEscape(cp);
|
|
}
|
|
}
|
|
return Value(escaped);
|
|
}
|
|
|
|
const table67: Record<number, string> = {
|
|
9: 't',
|
|
10: 'n',
|
|
11: 'v',
|
|
12: 'f',
|
|
13: 'r',
|
|
};
|
|
function EncodeForRegExpEscape(cp: CodePoint) {
|
|
const ch = String.fromCharCode(cp);
|
|
if (cp === 0x002F || isSyntaxCharacter(ch)) {
|
|
return `\u{005C}${UTF16EncodeCodePoint(cp)}`;
|
|
} else if (isControlEscape(cp)) {
|
|
return `\u{005C}${table67[cp]!}`;
|
|
}
|
|
const otherPunctuators = ",-=<>#&!%:;@~'`\u{0022}";
|
|
const toEscape = StringToCodePoints(otherPunctuators);
|
|
if (toEscape.includes(cp) || isWhitespace(ch) || isLineTerminator(ch) || isLeadingSurrogate(cp) || isTrailingSurrogate(cp)) {
|
|
const cpNum = cp;
|
|
if (cpNum <= 0xFF) {
|
|
const hex = cpNum.toString(16);
|
|
return `\u{005C}x${hex.padStart(2, '0')}`;
|
|
}
|
|
let escaped = '';
|
|
const codeUnits = UTF16EncodeCodePoint(cp);
|
|
for (const cu of codeUnits) {
|
|
escaped += UnicodeEscape(cu);
|
|
}
|
|
return escaped;
|
|
}
|
|
return UTF16EncodeCodePoint(cp);
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-get-regexp-@@species */
|
|
function RegExp_speciesGetter(_args: Arguments, { thisValue }: FunctionCallContext) {
|
|
return thisValue;
|
|
}
|
|
|
|
export function bootstrapRegExp(realmRec: Realm) {
|
|
const proto = realmRec.Intrinsics['%RegExp.prototype%'];
|
|
|
|
const cons = bootstrapConstructor(realmRec, RegExpConstructor, 'RegExp', 2, proto, [
|
|
[wellKnownSymbols.species, [RegExp_speciesGetter]],
|
|
['escape', RegExp_escape, 1],
|
|
]);
|
|
|
|
realmRec.Intrinsics['%RegExp%'] = cons;
|
|
}
|