Какой-то шаблон регулярных выражений нарушает механизм регулярных выражений javascript

Я написал следующее регулярное выражение:/\D(?!.*\D)|^-?|\d+/g

Я думаю, что это должно работать так:

\D(?!.*\D)    # match the last non-digit
|             # or
^-?           # match the start of the string with optional literal '-' character
|             # or
\d+           # match digits

Но это не так:

var arrTest = '12,345,678.90'.match(/\D(?!.*\D)|^-?|\d+/g);
console.log(arrTest);

var test = arrTest.join('').replace(/[^\d-]/, '.');
console.log(test);

Тем не менее, при игре сPCRE(php)-flavour онлайн вRegex101, Это работает, как я описал.

Я не знаю, думаю ли я, что это должно работать так, как это не работает. Или если есть какой-то шаблон, запрещенный в регулярном выражении javascript.

 Washington Guedes09 авг. 2016 г., 23:25
@ WiktorStribiżew. Человек, это умный, вы делаете все внутри только один метод замены и работает
 Wiktor Stribiżew09 авг. 2016 г., 23:30
JS работает иначе, чем PCRE. Дело в том, что механизм регулярных выражений JS плохо обрабатывает совпадения нулевой длины, индекс просто увеличивается вручную, а следующий символ после совпадения нулевой длины пропускается.
 Wiktor Stribiżew09 авг. 2016 г., 23:59
@Guedes: я добавил отрывок из справочника ECMA, который фактически документирует это поведение.
 Washington Guedes09 авг. 2016 г., 23:38
Спасибо вам обоим, я проголосовал за ваши ответы
 anubhava09 авг. 2016 г., 23:14
Каков ваш ожидаемый результат?
 Washington Guedes09 авг. 2016 г., 23:15
@anubhava. Я хочу это вернуть12345678.90
 Wiktor Stribiżew09 авг. 2016 г., 23:18
Я думаю, что вы можете использоватьvar res = '-12,345,678.90'.replace(/(\D)(?!.*\D)|^-?|\D/g, function($0,$1) { return $1 ? "." : ""; }); - одна операция замены.

Ответы на вопрос(2)

Вы можете изменить порядок шаблонов чередования и использовать его в JS, чтобы он работал:

var arrTest = '12,345,678.90'.match(/\D(?!.*\D)|\d+|^-?/g);
console.log(arrTest);

var test = arrTest.join('').replace(/\D/, '.');

console.log(test);

//=> 12345678.90

RegEx Demo

В этом разница между поведением регулярных выражений в Javascript и PHP (PCRE).

В Javascript:

'12345'.match(/^|.+/gm)
//=> ["", "2345"]

В PHP:

preg_match_all('/^|.+/m', '12345', $m);
print_r($m);
Array
(
    [0] => Array
        (
            [0] =>
            [1] => 12345
        )
    )

Поэтому, когда вы подходите^ в Javascript движок регулярных выражений перемещается на одну позицию вперед и после чередования| совпадает со 2-й позиции и далее на входе.

 anubhava09 авг. 2016 г., 23:28
Это потому, что когда вы соответствуете^ в JS regex движок движется на одну позицию вперед и что-нибудь после чередования| соответствует 2-й позиции на входе. Тест с'12345'.match(/^|.+/gm) который даст["", "2345"]
 Washington Guedes09 авг. 2016 г., 23:24
Это работает, но почему мое регулярное выражение терпит неудачу?
Решение Вопроса

JS работает иначе, чем PCRE. Дело в том, что механизм регулярных выражений JS плохо обрабатывает совпадения нулевой длины, индекс просто увеличивается вручную, а следующий символ после совпадения нулевой длины пропускается.^-? может соответствовать пустой строке, и это соответствует12,345,678.90 начать, пропуская1.

Если мы посмотрим наString#match документация, мы увидим, что каждый звонокmatch с глобальным регулярным выражением увеличивает регулярное выражение объектаlastIndex посленулевой длины совпадение найдено:

В противном случае,Глобальный являетсяправда
а. Вызвать внутренний метод [[Put]] для rx с аргументами "LastIndex"и 0.
б. Пусть A будет новым массивом, созданным как будто выражениемновый массив () гдемассив это стандартный встроенный конструктор с таким именем.
с. ПозволятьpreviousLastIndex быть 0.
д. Позволятьn быть 0.
е. ПозволятьlastMatch бытьправда.
е. Повторите, покаlastMatch являетсяправда
я. Позволятьрезультат быть результатом вызова внутреннего метода [[Call]]Exec сгх какэтот список значений и аргументов, содержащийS.
II. Еслирезультат являетсянользатем установитеlastMatch вложный.
III. В противном случае,результат не являетсяноль
1. ПустьthisIndex быть результатом вызова внутреннего метода [[Get]] длягх с аргументом "LastIndex».
2. ЕслиthisIndex = previousLastIndex затем
а. Вызвать внутренний метод [[Put]] длягх с аргументамиLastIndex" а такжеthisIndex + 1.
б. ЗадаватьpreviousLastIndex вthisIndex+1.

Итак, процесс сопоставления идет от8а до8е инициализация вспомогательных структур, затем вводится блок while (повторяется доlastMatch являетсяправда, внутреннийExec команда соответствует пустому месту в начале строки (8fi ->8fiii) и как результат нетноль, thisIndex установлен наLastIndex предыдущего успешного матча, и так как матч был нулевой длины (в основном,thisIndex = previousLastIndex),previousLastIndex установлен вthisIndex + 1 - который пропускает текущую позицию после успешного совпадения нулевой длины.

Вы можете использовать более простое регулярное выражение внутриreplace метод и используйте обратный вызов, чтобы использовать соответствующие замены:

var res = '-12,345,678.90'.replace(/(\D)(?!.*\D)|^-|\D/g, function($0,$1) {
   return $1 ? "." : "";
});
console.log(res);

Детали шаблона:

(\D)(?!.*\D) - не цифра (включенная в Группу 1), за которой не следует 0+ символов, кроме новой строки и другой не цифры| - или же^- - дефис в начале строки| - или же\D - не цифра

Обратите внимание, что здесь вам даже не нужно делать дефис в начале необязательным.

 Wiktor Stribiżew09 авг. 2016 г., 23:57
Мне потребовалось 16 минут, чтобы добавить (и отформатировать) ссылку на ECMA с пошаговым объяснением, почему это происходит. Надеюсь, это объясняет этот странный JSГлобальный поведение сопоставления регулярных выражений. Обратите внимание, что PCRE просто не увеличивает значение позиции после совпадения нулевой длины, а корректно проверяет следующий символ.
 Wiktor Stribiżew10 авг. 2016 г., 08:25
Если вам не нужно заменять этот минус, зачем вообще использовать альтернативную ветку :)? Просто удалите(^-)| из шаблона и удалить$2 от функции обратного вызова и настроить его тело.
 Washington Guedes10 авг. 2016 г., 04:22
Спасибо :) ... я закончил с.replace(/(^-)|\D(?=.*\D)|(\D)/g, function($0, $1, $2) { return $1 || ($2 ? '.' : ''); }); чтобы избежать замены отрицательного знака.

Ваш ответ на вопрос