2012-04-27 10 views
3

Я пишу рудиментарный лексер, используя регулярные выражения в JavaScript, и у меня есть два регулярных выражения (один для строк с одиночными кавычками и один для строк с двойными кавычками), которые я хочу объединить в один. Это мои два регулярных выражения (я добавил символы ^ и $ для целей тестирования):Как объединить эти два регулярных выражения в один?

var singleQuotedString = /^'(?:[^'\\]|\\'|\\\\|\\\/|\\b|\\f|\\n|\\r|\\t|\\u[0-9A-F]{4})*'$/gi; 
var doubleQuotedString = /^"(?:[^"\\]|\\"|\\\\|\\\/|\\b|\\f|\\n|\\r|\\t|\\u[0-9A-F]{4})*"$/gi; 

Теперь я попытался объединить их в одно регулярное выражение следующим образом:

var string = /^(["'])(?:[^\1\\]|\\\1|\\\\|\\\/|\\b|\\f|\\n|\\r|\\t|\\u[0-9A-F]{4})*\1$/gi; 

Однако, когда я проверить ввод "Hello"World!" он возвращает true вместо false:

alert(string.test('"Hello"World!"')); //should return false as a double quoted string must escape double quote characters 

Я решил, что проблема в [^\1\\], которая должна соответствовать любому символу, кроме \1 (который является либо одиночной, либо двойной кавычкой - разделителем строки) и \\ (что является символом обратной косой черты).

Правильное выражение правильно отфильтровывает обратную косую черту и сопоставляет ограничители, но оно не отфильтровывает разделитель внутри строки. Любая помощь будет оценена. Обратите внимание, что я упомянул railroad diagrams Крокфорда, чтобы написать регулярные выражения.

ответ

6

Вы не можете обратиться к согласованной группе внутри класса символов: (['"])[^\1\\]. Попробуйте что-нибудь подобное вместо этого:

(['"])((?!\1|\\).|\\[bnfrt]|\\u[a-fA-F\d]{4}|\\\1)*\1 

(вам нужно добавить еще несколько побегов, но вы получите мой дрейф ...)

Быстрое объяснение:

(['"])    # match a single or double quote and store it in group 1 
(     # start group 2 
    (?!\1|\\).  # if group 1 or a backslash isn't ahead, match any non-line break char 
    |    # OR 
    \\[bnfrt]  # match an escape sequence 
    |    # OR 
    \\u[a-fA-F\d]{4} # match a Unicode escape 
    |    # OR 
    \\\1    # match an escaped quote 
)*     # close group 2 and repeat it zero or more times 
\1     # match whatever group 1 matched 
+0

Я никогда не знал, что вы можете сделать что-то вроде '(?! \ 1 | \\) .'. В [MDN] (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/RegExp «RegExp - MDN») говорится, что '' x (?! Y) 'соответствует' x ', только если' x 'не следует' y''. –

+1

@Aadit M Shah - Плохая аналогия. Вы всегда находитесь в позиции между символами в регулярных выражениях, как курсор. Утверждения сохраняются, и '(?!)' И '(? =)' Всегда ссылаются на то, что справа от текущей позиции. Его несущественное, что находится слева от него, кроме как если бы оно не соответствовало, вы не были бы на этом утверждении. – sln

+0

Имеет смысл. Возможно, страница MDN должна быть обновлена. –

0

Ну, вы всегда можете просто создать больший регулярное выражение только с помощью оператора чере на небольших регулярных выражений

/(?:single-quoted-regex)|(?:double-quoted-regex)/ 

Или явно:

var string = /(?:^'(?:[^'\\]|\\'|\\\\|\\\/|\\b|\\f|\\n|\\r|\\t|\\u[0-9A-F]{4})*'$)|(?:^"(?:[^"\\]|\\"|\\\\|\\\/|\\b|\\f|\\n|\\r|\\t|\\u[0-9A-F]{4})*"$)/gi; 

Наконец, если вы хотите избегайте дублирования кода, вы можете динамически создавать это регулярное выражение, используя конструктор new Regex.

var quoted_string = function(delimiter){ 
    return ('^' + delimiter + '(?:[^' + delimiter + '\\]|\\' + delimiter + '|\\\\|\\\/|\\b|\\f|\\n|\\r|\\t|\\u[0-9A-F]{4})*' + delimiter + '$').replace(/\\/g, '\\\\'); 
    //in the general case you could consider using a regex excaping function to avoid backslash hell. 
}; 

var string = new RegExp('(?:' + quoted_string("'") + ')|(?:' + quoted_string('"') + ')' , 'gi'); 
+0

я хоть этого; и тогда я подумал, что должен быть лучший способ сделать это. =) –

+0

Не знаете ли вы, почему '[^ \ 1 \\]' работает не так, как ожидалось? –

+0

@AaditMShah: Я просто не думаю, что вы можете использовать обратные ссылки внутри классов.В любом случае я записал версию, которая не дублирует регулярное выражение, но теперь вам нужно решить, считаете ли вы его более читаемым или нет;) – hugomg

2

Это тоже должно работать (raw regex).
Если скорость является фактором, это метод «развернутый», который считается самым быстрым для такого рода вещей.

(['"])(?:(?!\\|\1).)*(?:\\(?:[\/bfnrt]|u[0-9A-F]{4}|\1)(?:(?!\\|\1).)*)*/1 

Expanded

(['"])   # Capture a quote 
(?: 
    (?!\\|\1).    # As many non-escape and non-quote chars as possible 
)* 

(?:      
    \\      # escape plus, 
    (?: 
     [\/bfnrt]   # /,b,f,n,r,t or u[a-9A-f]{4} or captured quote 
     | u[0-9A-F]{4} 
     | \1 
    ) 
    (?:     
     (?!\\|\1).   # As many non-escape and non-quote chars as possible 
    )* 
)* 

/1    # Captured quote 
Смежные вопросы