CoolFace
Apppublic

strong-tie/inbound-calls

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
tokenizer.js361 linesDownload Raw Back to dist
1"use strict";2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {3    if (k2 === undefined) k2 = k;4    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });5}) : (function(o, m, k, k2) {6    if (k2 === undefined) k2 = k;7    o[k2] = m[k];8}));9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {10    Object.defineProperty(o, "default", { enumerable: true, value: v });11}) : function(o, v) {12    o["default"] = v;13});14var __importStar = (this && this.__importStar) || function (mod) {15    if (mod && mod.__esModule) return mod;16    var result = {};17    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);18    __setModuleDefault(result, mod);19    return result;20};21Object.defineProperty(exports, "__esModule", { value: true });22exports.tokenizer = void 0;23const util = __importStar(require("./util"));24const types_1 = require("./types");25const sets = __importStar(require("./sets"));26/**27 * Valid opening characters for capture group names.28 */29const captureGroupFirstChar = /^[a-zA-Z_$]$/i;30/**31 * Valid characters for capture group names.32 */33const captureGroupChars = /^[a-zA-Z0-9_$]$/i;34const digit = /\d/;35/**36 * Tokenizes a regular expression (that is currently a string)37 * @param {string} regexpStr String of regular expression to be tokenized38 *39 * @returns {Root}40 */41exports.tokenizer = (regexpStr) => {42    let i = 0, c;43    let start = { type: types_1.types.ROOT, stack: [] };44    // Keep track of last clause/group and stack.45    let lastGroup = start;46    let last = start.stack;47    let groupStack = [];48    let referenceQueue = [];49    let groupCount = 0;50    const repeatErr = (col) => {51        throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: Nothing to repeat at column ${col - 1}`);52    };53    // Decode a few escaped characters.54    let str = util.strToChars(regexpStr);55    // Iterate through each character in string.56    while (i < str.length) {57        switch (c = str[i++]) {58            // Handle escaped characters, inclues a few sets.59            case '\\':60                if (i === str.length) {61                    throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: \\ at end of pattern`);62                }63                switch (c = str[i++]) {64                    case 'b':65                        last.push({ type: types_1.types.POSITION, value: 'b' });66                        break;67                    case 'B':68                        last.push({ type: types_1.types.POSITION, value: 'B' });69                        break;70                    case 'w':71                        last.push(sets.words());72                        break;73                    case 'W':74                        last.push(sets.notWords());75                        break;76                    case 'd':77                        last.push(sets.ints());78                        break;79                    case 'D':80                        last.push(sets.notInts());81                        break;82                    case 's':83                        last.push(sets.whitespace());84                        break;85                    case 'S':86                        last.push(sets.notWhitespace());87                        break;88                    default:89                        // Check if c is integer.90                        // In which case it's a reference.91                        if (digit.test(c)) {92                            let digits = c;93                            while (i < str.length && digit.test(str[i])) {94                                digits += str[i++];95                            }96                            let value = parseInt(digits, 10);97                            const reference = { type: types_1.types.REFERENCE, value };98                            last.push(reference);99                            referenceQueue.push({ reference, stack: last, index: last.length - 1 });100                            // Escaped character.101                        }102                        else {103                            last.push({ type: types_1.types.CHAR, value: c.charCodeAt(0) });104                        }105                }106                break;107            // Positionals.108            case '^':109                last.push({ type: types_1.types.POSITION, value: '^' });110                break;111            case '$':112                last.push({ type: types_1.types.POSITION, value: '$' });113                break;114            // Handle custom sets.115            case '[': {116                // Check if this class is 'anti' i.e. [^abc].117                let not;118                if (str[i] === '^') {119                    not = true;120                    i++;121                }122                else {123                    not = false;124                }125                // Get all the characters in class.126                let classTokens = util.tokenizeClass(str.slice(i), regexpStr);127                // Increase index by length of class.128                i += classTokens[1];129                last.push({130                    type: types_1.types.SET,131                    set: classTokens[0],132                    not,133                });134                break;135            }136            // Class of any character except \n.137            case '.':138                last.push(sets.anyChar());139                break;140            // Push group onto stack.141            case '(': {142                // Create group.143                let group = {144                    type: types_1.types.GROUP,145                    stack: [],146                    remember: true,147                };148                // If this is a special kind of group.149                if (str[i] === '?') {150                    c = str[i + 1];151                    i += 2;152                    // Match if followed by.153                    if (c === '=') {154                        group.followedBy = true;155                        group.remember = false;156                        // Match if not followed by.157                    }158                    else if (c === '!') {159                        group.notFollowedBy = true;160                        group.remember = false;161                    }162                    else if (c === '<') {163                        let name = '';164                        if (captureGroupFirstChar.test(str[i])) {165                            name += str[i];166                            i++;167                        }168                        else {169                            throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: Invalid capture group name, character '${str[i]}'` +170                                ` after '<' at column ${i + 1}`);171                        }172                        while (i < str.length && captureGroupChars.test(str[i])) {173                            name += str[i];174                            i++;175                        }176                        if (!name) {177                            throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: Invalid capture group name, character '${str[i]}'` +178                                ` after '<' at column ${i + 1}`);179                        }180                        if (str[i] !== '>') {181                            throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: Unclosed capture group name, expected '>', found` +182                                ` '${str[i]}' at column ${i + 1}`);183                        }184                        group.name = name;185                        i++;186                    }187                    else if (c === ':') {188                        group.remember = false;189                    }190                    else {191                        throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: Invalid group, character '${c}'` +192                            ` after '?' at column ${i - 1}`);193                    }194                }195                else {196                    groupCount += 1;197                }198                // Insert subgroup into current group stack.199                last.push(group);200                // Remember the current group for when the group closes.201                groupStack.push(lastGroup);202                // Make this new group the current group.203                lastGroup = group;204                last = group.stack;205                break;206            }207            // Pop group out of stack.208            case ')':209                if (groupStack.length === 0) {210                    throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: Unmatched ) at column ${i - 1}`);211                }212                lastGroup = groupStack.pop();213                // Check if this group has a PIPE.214                // To get back the correct last stack.215                last = lastGroup.options ?216                    lastGroup.options[lastGroup.options.length - 1] :217                    lastGroup.stack;218                break;219            // Use pipe character to give more choices.220            case '|': {221                // Create array where options are if this is the first PIPE222                // in this clause.223                if (!lastGroup.options) {224                    lastGroup.options = [lastGroup.stack];225                    delete lastGroup.stack;226                }227                // Create a new stack and add to options for rest of clause.228                let stack = [];229                lastGroup.options.push(stack);230                last = stack;231                break;232            }233            // Repetition.234            // For every repetition, remove last element from last stack235            // then insert back a RANGE object.236            // This design is chosen because there could be more than237            // one repetition symbols in a regex i.e. `a?+{2,3}`.238            case '{': {239                let rs = /^(\d+)(,(\d+)?)?\}/.exec(str.slice(i)), min, max;240                if (rs !== null) {241                    if (last.length === 0) {242                        repeatErr(i);243                    }244                    min = parseInt(rs[1], 10);245                    max = rs[2] ? rs[3] ? parseInt(rs[3], 10) : Infinity : min;246                    i += rs[0].length;247                    last.push({248                        type: types_1.types.REPETITION,249                        min,250                        max,251                        value: last.pop(),252                    });253                }254                else {255                    last.push({256                        type: types_1.types.CHAR,257                        value: 123,258                    });259                }260                break;261            }262            case '?':263                if (last.length === 0) {264                    repeatErr(i);265                }266                last.push({267                    type: types_1.types.REPETITION,268                    min: 0,269                    max: 1,270                    value: last.pop(),271                });272                break;273            case '+':274                if (last.length === 0) {275                    repeatErr(i);276                }277                last.push({278                    type: types_1.types.REPETITION,279                    min: 1,280                    max: Infinity,281                    value: last.pop(),282                });283                break;284            case '*':285                if (last.length === 0) {286                    repeatErr(i);287                }288                last.push({289                    type: types_1.types.REPETITION,290                    min: 0,291                    max: Infinity,292                    value: last.pop(),293                });294                break;295            // Default is a character that is not `\[](){}?+*^$`.296            default:297                last.push({298                    type: types_1.types.CHAR,299                    value: c.charCodeAt(0),300                });301        }302    }303    // Check if any groups have not been closed.304    if (groupStack.length !== 0) {305        throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: Unterminated group`);306    }307    updateReferences(referenceQueue, groupCount);308    return start;309};310/**311 * This is a side effecting function that changes references to chars312 * if there are not enough capturing groups to reference313 * See: https://github.com/fent/ret.js/pull/39#issuecomment-1006475703314 * See: https://github.com/fent/ret.js/issues/38315 * @param {(Reference | Char)[]} referenceQueue316 * @param {number} groupCount317 * @returns {void}318 */319function updateReferences(referenceQueue, groupCount) {320    // Note: We go through the queue in reverse order so321    // that index we use is correct even if we have to add322    // multiple tokens to one stack323    for (const elem of referenceQueue.reverse()) {324        if (groupCount < elem.reference.value) {325            // If there is nothing to reference then turn this into a char token326            elem.reference.type = types_1.types.CHAR;327            const valueString = elem.reference.value.toString();328            elem.reference.value = parseInt(valueString, 8);329            // If the number is not octal then we need to create multiple tokens330            // https://github.com/fent/ret.js/pull/39#issuecomment-1008229226331            if (!/^[0-7]+$/.test(valueString)) {332                let i = 0;333                while (valueString[i] !== '8' && valueString[i] !== '9') {334                    i += 1;335                }336                if (i === 0) {337                    // Handling case when escaped number starts with 8 or 9338                    elem.reference.value = valueString.charCodeAt(0);339                    i += 1;340                }341                else {342                    // If the escaped number does not start with 8 or 9, then all343                    // 0-7 digits before the first 8/9 form the first character code344                    // see: https://github.com/fent/ret.js/pull/39#discussion_r780747085345                    elem.reference.value = parseInt(valueString.slice(0, i), 8);346                }347                if (valueString.length > i) {348                    const tail = elem.stack.splice(elem.index + 1);349                    for (const char of valueString.slice(i)) {350                        elem.stack.push({351                            type: types_1.types.CHAR,352                            value: char.charCodeAt(0),353                        });354                    }355                    elem.stack.push(...tail);356                }357            }358        }359    }360}361//# sourceMappingURL=tokenizer.js.map