function makeSticky( re: RegExp ): RegExp { if ( re.sticky ) return re; const flags = re.flags.includes( 'y' ) ? re.flags : re.flags + 'y'; return new RegExp( re.source, flags ); } export class BrowserMatcher { readonly type: string; private _re: RegExp; constructor( type: string, re: RegExp ) { this.type = type; this._re = makeSticky( re ); } matchLength( source: string, offset: number ): number { this._re.lastIndex = offset; const result = this._re.exec( source ); if ( result && result.index === offset ) return result[ 0 ].length; return -1; } } export class BrowserLexer { readonly modes = new Map(); addMatchers( ...matchers: BrowserMatcher[] ): void { const list = this.modes.get( 'default' ) ?? []; list.push( ...matchers ); this.modes.set( 'default', list ); } } export function cLikeLexer(): BrowserLexer { const l = new BrowserLexer(); l.addMatchers( new BrowserMatcher( 'SINGLE_LINE_COMMENT', /\/\/.*/ ), new BrowserMatcher( 'MULTI_LINE_COMMENT', /\/\*[\s\S]*?\*\// ), new BrowserMatcher( 'DOUBLE_QUOTED_STRING', /"(?:[^"\\]|\\.)*"/ ), new BrowserMatcher( 'SINGLE_QUOTED_STRING', /'(?:[^'\\]|\\.)*'/ ), new BrowserMatcher( 'C_INSTRUCTION', /\#.*/ ), new BrowserMatcher( 'NUMBER', /(?=\.\d|\d)(?:\d+)?(?:\.?\d*)(?:[eE][+-]?\d+)?/ ), new BrowserMatcher( 'NULL', /\bnull\b/ ), new BrowserMatcher( 'BOOL', /\b(?:true|false)\b/ ), new BrowserMatcher( 'BREAK', /\r\n|\r|\n/ ), new BrowserMatcher( 'WHITESPACE', /\s+/ ), new BrowserMatcher( 'LOGIC', /\b(?:if|else|switch|do|while|for|break|continue|return|case)\b/ ), new BrowserMatcher( 'BRACKET', /[()[\]{}]/ ), new BrowserMatcher( 'ACCESS_MODIFIER', /\b(?:public|protected|private)\b/ ), new BrowserMatcher( 'CLASS', /\bclass\b/ ), new BrowserMatcher( 'OPERATOR', /==|\+\+|--|[+\-*\/^|~&%<>=!.:,;]/ ), new BrowserMatcher( 'CFUNCTION', /[a-zA-Z_]\w*(?=\s*\()/ ), new BrowserMatcher( 'CWORD', /[a-zA-Z_]\w*/ ), new BrowserMatcher( 'ANY_SYMBOL', /./ ), ); return l; }