export const regexpMethods = new Set(["test", "exec", "toString"]) export const regexpProperties = new Set([ "source", "flags", "lastIndex", "global", "ignoreCase", "multiline", "sticky", "unicode", "dotAll", ]) export const regexFailureReason = (error: unknown): string => (error instanceof Error ? error.message : String(error)).replace(/^Invalid regular expression:\s*/i, "") export const escapeRegexHint = 'To match special characters like ( ) [ ] { } + * ? . literally, escape them with a backslash (e.g. "\\\\(") or test for them with String.includes instead.' export const toHostRegex = (arg: unknown, method: string, node: AstNode, extraFlags = ""): RegExp => { if (arg instanceof CodeModeRegExp) return arg.regex if (typeof arg === "string") { try { return new RegExp(arg, extraFlags) } catch (error) { throw new InterpreterRuntimeError( `String.${method} received the string ${JSON.stringify(arg)}, which is not a valid regular expression pattern (${regexFailureReason(error)}). ${escapeRegexHint}`, node, ).as("SyntaxError") } } throw new InterpreterRuntimeError( `String.${method} expects a regular expression (a /pattern/flags literal or new RegExp(...)) or a string pattern, not ${arg === null ? "null" : typeof arg}.`, node, ) } export const matchToValue = (match: RegExpMatchArray): Array => { const result: Array = Array.from(match, (group) => group) if (match.index !== undefined) (result as Record & Array).index = match.index if (match.groups) { const groups: SafeObject = Object.create(null) as SafeObject for (const [key, group] of Object.entries(match.groups)) { if (!isBlockedMember(key)) groups[key] = group } ;(result as Record & Array).groups = groups } return result } export const invokeRegExpMethod = ( value: CodeModeRegExp, name: string, args: Array, node: AstNode, ): unknown => { switch (name) { case "test": return value.regex.test(coerceToString(args[0])) case "exec": { const matched = value.regex.exec(coerceToString(args[0])) return matched === null ? null : matchToValue(matched) } case "toString": return coerceToString(value) default: throw new InterpreterRuntimeError(`RegExp method '${name}' is not available in CodeMode.`, node) } } import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js" import { isBlockedMember, type SafeObject } from "../tool-runtime.js" import { CodeModeRegExp } from "../values.js" import { coerceToString } from "./value.js"