The Super Tiny Compiler — теперь на русском

    Если очень кратко — это перевод на русский проекта The Super Tiny Compiler — проекта призванного помочь с изучением основ компилирования на рабочем примере.

    image

    Если хотите подробностей — прошу под кат. Если же нет — можно идти напрямую к переводу, он на гитхабе.

    Что это, зачем это, почему это


    Для тех кто не знает об этом проекте — это работающий компилятор Lisp-подобного языка в Си-подобный, написанный на JS. Процентов 90 кода покрыто подробными комментариями, и самих комментариев, в общем то, в 4 раза больше чем кода. В начале объясняются основы, терминология, а потом сам код.

    А зачем это переводить? Английский же — язык программистов!


    Всё началось с того что ссылка на этот проект у меня больше года провалялась в папке «на почитать». И вроде и штука интересная (10к+ звёзд на гитхабе, шутка ли), и мне интересно, но как как-то всё не находилось сил посмотреть и вникнуть. Почему? Да потому что оно на английском. А тут дело не в сложности, а в том что после 8-ми часового рабочего дня мозг напрочь отказывается читать на не родном языке что-то ещё. Вот протестует и всё тут. Поэтому решено было сделать перевод — и себе прочитать заодно, и другим помочь.



    800+ форков. Из них — много попыток перевести на китайский, но на русском я ничего не нашёл (может оно и есть). Кстати, объясните, вот зачем люди форкают проекты а потом ничего в них не меняют?

    А ты переводчик?


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

    Что же касается перевода — он не дословный, незначительные слова могли быть упущены, кое-что добавлено от себя. Но при этом я старался передать суть на все 100%, так что в плане информативности перевод, вроде, получился равнозначным оригиналу.


    Пример перевода. Скриншот кликабельный.

    Так как оригинальный проект выложен на гитхабе — то и перевод я не стал закидывать целиком сюда. Для желающих ознакомиться: ссылка на перевод, ссылка на оригинал.

    Приятного чтения!

    P. S. Замечания принимаю хоть в комментариях, хоть в виде pull-реквестов, хоть в личку. Можете вообще делать форк и вносить изменения :)

    По посту — надо ли (и как?) вешать плашку «перевод» на этот пост?
    Поделиться публикацией
    Комментарии 11
      +3
      Кстати, объясните, вот зачем люди форкают проекты а потом ничего в них не меняют?
      Backup
        0
        Ещё вариант — кнопкой ошибаются в гитхабе.
        +3
        Я когда-то перевёл целую книгу о том, как реализовывать Лиспы на Лиспе. Там есть и глава о том, как их компилировать в Си. Извиняюсь за саморекламу, но темы пересекаются.
          0
          Ого. Серьёзная работа, снимаю шляпу
          0

          В этом примере компилятора объясняется как написать конкретный парсер но совершенно не объясняется почему автор решил делать так и так. А ведь у тех кто не знаком с теорией компиляторов обязательно возникнет вопрос почему автор решил сделать отдельно токинизатор и отдельно парсер токенов для получения синтаксического дерева когда можно было например сделать все в одном проходе.


          пример однопроходного парсера
          const Code = '(substract (add 4 21) (mul 2 2))';
          
          let Position = 0;
          function parseExpr() {
              let number;
              if (parseSpace() && (number = parseNumber()) && parseSpace()) {
                  return number;
              }
              let callExpr;
              if(callExpr = parseCallExpr()){
                  return callExpr;
              }
          }
          function parseCallExpr(){
              let name;
              let args;
              if (parseSpace() && parseToken('(') && parseSpace() && (name = parseName()) && parseSpace() && (args = parseArguments()) && parseSpace() && parseToken(')') && parseSpace()) {
                  return {
                      type: 'CallExpression',
                      name,
                      args
                  };
              }
              return null;
          }
          
          function parseArguments() {
              let expr;
              let args;
              if ((expr = parseExpr()) && ((args = parseArguments()) || true)) {
                  return [expr, ...(args || [])];
              }
              return null;
          }
          
          function parseSpace(){
              let char = Code[Position];
              while (/ /.test(char)) char = Code[++Position];
              return true;
          }
          
          function parseNumber() {
              let current = Position
              let char = Code[current];
              var value = "";
              while (/[0-9]/.test(char)) {
                  value += char;
                  char = Code[++current];
              }
              if (value.length === 0) {
                  return null;
              } else {
                  Position = current;
                  return {
                      type: "NumberLiteral",
                      value: value
                  };
              }
          }
          function parseName() {
              let current = Position
              let char = Code[current];
              var value = "";
              while (/[a-zA-Z]/.test(char)) {
                  value += char;
                  char = Code[++current];
              }
              if (value.length === 0) {
                  return null;
              } else {
                  Position = current;
                  return {
                      type: 'Identifier',
                      value: value
                  };
              }
          }
          
          function parseToken(token) {
              let current = Position;
              let char = Code[current];
          
              let i = 0;
              char = token[i];
              while (Code[current] === char && i < token.length) {
                  i++;
                  current++;
                  char = token[i];
              }
          
              if (i !== token.length) {
                  return false;
              } else {
                  Position = current;
                  return true;
              }
          }
          
          console.log(JSON.stringify(parseExpr()));

          Или например всю трансформацию можно было бы сделать в одном проходе. В общем намного полезней было бы если бы автор объяснил почему он решил делать так и так а не просто прокомментировал из разряда "делай так и получишь это".

            +1
            Ну, никто не утверждает что это лучший в мире пример. Но это сильно лучше чем ничего, или чем сухие формулы и объяснения.
            +1
            > токенизер
            «токенизатор» звучит все же лучше и привычней.
              +2
              Спасибо за ссылку на проект и за перевод. Я стараюсь отслеживать появление ресурсов по разработке компиляторов для начинающих, но этот проект по какой-то причине упустил из виду. Позволю себе небольшую критику английского варианта.

              На мой взгляд, разумно начать с причин, по которым с построением компиляторов важно знакомиться неспециалистам. Причины эти сегодня не так уж очевидны, помимо совсем уж тривиального — “изучение устройства инструмента может помочь в практической с ним работе”. Казалось бы, языков и компиляторов уже и так существует великое множество. А для исключительных случаев есть перенацеливаемые системы gcc и LLVM.

              Подробный ответ включал бы в себя DSL и порождение кода для экзотических архитектур (бытует расхожее мнение, что архитектура команд это нечто неизменное, и если хочется нового — следует брать RISCV, созданную специалистами мирового класса. В качестве возражения можно привести подходы с синтезируемой системой команд, ASIP, CGRA, HLS).

              Новые подходы к компиляции часто требуют перестройки архитектуры существующего компилятора. Так произошло, например, с популяризацией SSA. И, скорее всего, именно по такой причине в gcc и LLVM до сих пор отсутствует работа с графовым представлением, подобным sea of nodes, не говоря уже о более нишевых и более современных представлениях программы. Реализации современных подходов, и пусть это не кажется теперь странным, чаще можно найти в небольшом DSL-компиляторе.

              В коде данного проекта учебного компилятора можно встретить visitors. На мой взгляд, это неоднозначный выбор. Разумеется, использование данного шаблона проектирования — данность, обусловленная, во многом, преподаванием ООП/Java. Более того, visitors до сих пор можно встретить в реализациях некоторых “промышленных” компиляторов. Но начинающим, и это мое убеждение, следует демонстрировать вещи в наиболее прозрачной и естественной для соответствующего предмета изучения нотации. В хорошем учебнике по компиляторам вы не найдёте visitors. Сопоставление с образцом, которое этот шаблон с успехом заменяет, используется в наши дни даже в таких популярных и практичных языках, как Scala.

              Любопытно, что автор так и не воспользовался в своей реализации visitors методом exit, который соответствует обходу дерева снизу вверх. А ведь именно такой порядок обхода используется в проекте для реализации генератора кода. Здесь автор обошёлся лишь рекурсией и switch. Почему же не сделать того же и в остальных частях программы?

              Для знакомых с Лисп-подобными языками в качестве возможной альтернативы данному проекту я бы посоветовал очень хорошее введение Essentials of Compilation An Incremental Approach (https://jeapostrophe.github.io/courses/2017/spring/406/notes/book.pdf ). Не теряют актуальности и сегодня известные учебники Вирта (https://www.ozon.ru/context/detail/id/4803779/ ) и Эппеля (https://www.cs.princeton.edu/~appel/modern/ml/ ).

              Комментарий получается слишком пространным, но мне все еще не даёт покоя вопрос: а где же подобные введения на русском языке, оригиналы, а не переводы? Ведь предположение о том, что русскоязычные разработчики менее компетентны в области компиляторов, чем автор рассмотренного учебного компилятора, является абсолютно несостоятельным…
                +2
                Спасибо, рад что мой труд не прошёл даром.

                а где же подобные введения на русском языке, оригиналы, а не переводы
                ИМХО, вопрос менталитета. Американцы: «я выучил крутую штуку. пойду напишу в блог, расскажу всем что оно такое». Наши (СНГ): «я выучил крутую штуку. хрен я что кому подскажу, я мучился с документацией — вот и все остальные пусть мучаются». Как-то так. Не зря же есть шутка о зарубежных форумах и о наших — мол на зарубежном тебе всё подскажут и разжуют, а на наших назовут 10 причин почему ты дебил и вопрос твой говно.
                  0

                  Тут еще от форума зависит. Например, на форум CMS Opencart, как и везде, заходят новички с вопросами "как сделать...". И большинство ответов звучат типа "купи у меня вот этот модуль (ссылка), там это сделано". Хотя, если объяснить, там дел на 10 минут. На форумах по WordPress такого намного меньше, хотя они оба бесплатные, к обоим есть и платные, и бесплатные модули и плагины.

                    0
                    Ну, там где одно время участвовал я, был простой критерий проверки «качеств, позволяющих стать компетентным». Спрашивающий должен был ответить на вопрос (желательно с листингами) как он пытался решить проблему до того, как обратился на форум, и что из этого вышло.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое