Pull to refresh

Comments 24

Может, всё же не стоило оригинальное название переводить? А то получился «лзык» %)
Возможно, но λ в этом случае будем читать как я, да и слово "λзык" неплохо выглядит.
Или перевести, как "Ѣзыкъ" какой-нибудь.
Можно через юс малый — "ѧзык". Но тогда потеряется связь с лямбда-функциями.
UFO just landed and posted this here

А что так не серьезно? Сразу на ассемблере или в машинных кодах.

UFO just landed and posted this here
А почему бы и нет? Для учебных целей вполне подходит. Тем более после этого будет транспилятор в JS.
UFO just landed and posted this here
1) Пробовали делать бенчмарки современных движков JS? На практике разница до С++ будет несколько раз всего, про другие, более медленные, я вообще молчу.
2) Язык который транспилируется в JS удобно писать на самом JS, потому что это позволяет тестировать/исполнять его на месте, а потом еще и бутстрапить можно.
UFO just landed and posted this here
Не пробовать использовать регулярных выражений для парсинга. Они просто не работают.

Много разных парсеров написал подобным образом, с регулярками сначала действительно не понятно было как их применять даже там где они казалось бы в тему. Там проблема собственно в том, что регулярка не найдя совпадение в нужной позиции начинает искать его на следующих. Можно обрезать строку и использовать ^, но постоянные обрезания огромной строки плохо влияют на карму парсера. Придумал вариант с добавлением в конец регулярки | — если на первой позиции совпадение не найдено, происходит гарантированной совпадение с пустотой, ну и проверяем 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 или просто без класса передавать его каждый раз в переменной), то производительность резко подскакивает. В некоторых случаях в десятки раз.

Регулярные выражения не очень удобны даже в вышеописанном случае: как ваше регулярное выражение отработает на строке вида «super(15 * (5 + 6))»? В этом и весь недостаток регулярных выражений для подобных задач. В регулярных выражениях **нет рекурсивности**. Единственное место в паркинге ЯП — лексер.

Второе: да, я знаю, но код взят с оригинала, которому уже несколько лет, и, когда автор писал тот код, 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");

Вот жесть, привык не писать такие вещи и забыл. Спасибо!

Sign up to leave a comment.

Articles