Skip to content

Commit eac665c

Browse files
dyclaude
andcommitted
Fix regex char-class parsing (#38)
`/` inside a `[...]` character class is literal, not a terminator. Track a `cls` flag while scanning the pattern so `/[^/]+$/` and `/[\]/]/` parse correctly. Closes #38. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent a488b56 commit eac665c

2 files changed

Lines changed: 16 additions & 2 deletions

File tree

feature/regex.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,20 @@
1111
*/
1212
import { token, skip, err, next, idx, cur } from '../parse.js';
1313

14-
const SLASH = 47, BSLASH = 92;
14+
const SLASH = 47, BSLASH = 92, LBRACK = 91, RBRACK = 93;
1515

1616
token('/', 140, a => {
1717
// left operand = division or assignment; `//` `/*` `/?` `/+` = not a regex start
1818
const c = cur.charCodeAt(idx);
1919
if (a || c === SLASH || c === 42 || c === 43 || c === 63) return;
20-
const pattern = next(c => c === BSLASH ? 2 : c && c !== SLASH); // \x = 2 chars, else 1 until /
20+
// \x = 2 chars; `/` inside a [...] class is literal (classes don't nest), else `/` ends pattern
21+
let cls = false;
22+
const pattern = next(c =>
23+
c === BSLASH ? 2 :
24+
c === LBRACK ? (cls = true, 1) :
25+
c === RBRACK ? (cls = false, 1) :
26+
c && (cls || c !== SLASH)
27+
);
2128
cur.charCodeAt(idx) === SLASH || err('Unterminated regex');
2229
skip();
2330
const flags = next(c => c === 103 || c === 105 || c === 109 || c === 115 || c === 117 || c === 121); // gimsuy

test/feature/regex.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ test('regex: in expressions', t => {
4545
is(run('x.replace(/=>/g, ":")', { x: 'a=>b=>c' }), 'a:b:c')
4646
})
4747

48+
test('regex: character classes', t => {
49+
// `/` inside [...] is literal, not a terminator
50+
is(parse('/[^/]+$/'), ['//', '[^/]+$'])
51+
is(parse('/[\\]/]/')[1], '[\\]/]')
52+
is(run('"a/b".match(/([^/]+)$/)')[1], 'b')
53+
})
54+
4855
test('regex: JSON serializable', t => {
4956
const ast = parse('/abc/gi')
5057
const json = JSON.stringify(ast)

0 commit comments

Comments
 (0)