Comments 24
А что так не серьезно? Сразу на ассемблере или в машинных кодах.
2) Язык который транспилируется в JS удобно писать на самом JS, потому что это позволяет тестировать/исполнять его на месте, а потом еще и бутстрапить можно.
Не пробовать использовать регулярных выражений для парсинга. Они просто не работают.
Много разных парсеров написал подобным образом, с регулярками сначала действительно не понятно было как их применять даже там где они казалось бы в тему. Там проблема собственно в том, что регулярка не найдя совпадение в нужной позиции начинает искать его на следующих. Можно обрезать строку и использовать ^
, но постоянные обрезания огромной строки плохо влияют на карму парсера. Придумал вариант с добавлением в конец регулярки |
— если на первой позиции совпадение не найдено, происходит гарантированной совпадение с пустотой, ну и проверяем match[0]
, если пусто, то совпадения нет. Пример:
const reSuperCall = /super(?:\.([a-zA-Z][\-\w]*))?!|/g;
_readSuperCall(): ISuperCall | null {
reSuperCall.lastIndex = this._pos;
let match = reSuperCall.exec(this.template)!;
if (match[0]) {
this._pos = reSuperCallOrNothing.lastIndex;
return {
nodeType: NodeType.SUPER_CALL,
elementName: match[1] || null
};
}
return null;
}
Это мелочь конечно, но может кому-то пригодится, так как ситуации когда хочется скатиться к регулярке довольно часто бывают.
И второе: функциональный стиль здесь действительно удобен, но посмотрите ещё раз пример с InputStream — в нём нужно каждой функции передавать общее состояние, это организуется через замыкание и для этого внутри функции при каждом вызове создаётся множество других функций и чем сложнее парсер, тем их будет больше. Если один в один переписать в виде класса (общее состояние будет в this или просто без класса передавать его каждый раз в переменной), то производительность резко подскакивает. В некоторых случаях в десятки раз.
Второе: да, я знаю, но код взят с оригинала, которому уже несколько лет, и, когда автор писал тот код, es6/ts не были такими распространенными, как сейчас.
UPD: немножко неправильно понял регулярное выражение. Но все равно, регулярные выражения не способствуют хорошему переиспользованию кода (в данном случае та часть регулярного выражения, которая отвечает за идентификатор после точки). К примеру, если мы решим добавить какие-то символы к списку разрешенных символов в идентификаторе, мы будем обязаны изменять все регулярные выражения, а не одну функцию.
мы будем обязаны изменять все регулярные выражения, а не одну функцию
зачем?
const reSuperCall = /super(?:\.([a-zA-Z][\-\w]*))?!|/g;
В нем мы видим, что у нас стоит слово «super», а после него какой-то идентификатор (последовательность символов, которая начинается из буквы и может содержать — или букву. Такие последовательности есть и в других регулярных выражениях, например, когда парсим название переменной, название члена класса и т. д. А теперь представим ситуацию, что нам нужно разрешить использование идентификаторов, содержащих в себе символы кириллицы. А это значит, что мы обязаны изменять все регулярные выражения, которые парсят идентификаторы.
В случае использования функций, было бы достаточно использовать функцию, которая парсит идентификатор. Если нужны любые изменения в правила идентификаторов — достаточно изменить эту функцию.
Ну поменять так:
const reSuperCall = RegExp(`super(?:\\.(${namePattern}))?!|`, 'g');
Вообще как только что-то усложняется, ничто не мешает делать без регулярок. В примере выше имя больше нигде не повторялось, мне не было смысла отделять его от super, но в соседнем парсере повторялось и _readName ниже использовался в этих местах, написанных уже как обычно:
const reName = RegExp(namePattern + '|', 'g');
_readName(): string | null {
reName.lastIndex = this._pos;
let name = reName.exec(this.contentNodeValue)![0];
if (name) {
this._pos = reName.lastIndex;
return name;
}
return null;
}
регулярка не найдя совпадение в нужной позицииУ регулярок есть флаг «y».
Напомнило статью товарища impwx Написание парсера с нуля: так ли страшен черт?
После первого прочтения я так сильно вдохновился таким подходом в реализации рекурсивного спуска, что написал свой парсер JSON на python который до сих пор используется в одном крупном опенсорс проекте
А почему там везде tok && tok.type === 'kw' && (!kw || tok.value === kw) && tok — два раза в выражении участвует tok?
Это особенность работы логических операторов в JavaScript. Первый tok
— проверяет tok
на неравенство null
, а последний возвращает tok
, если все условия дали true
. Просто попробуйте запустить такой код:
console.log(false && "hi");
console.log(true && "hi");
Как реализовать язык программирования на JavaScript. Часть 1: Парсер