Компилятор на js. Часть 1

в 16:32, , рубрики: compilers construction, html, javascript

Доброго времени суток! Напишем простой компилятор в js с отладкой. Это первая (вводная) часть, в которой будет описана структура. С каждой частью будем добавлять новые фичи к компилятору и усложнять его.

Структура

Нам нужен массив mainArray, который послужит нам хранилищем для операторов и конструкций нашего языка. Также для заполнения этого массива нам понадобится регулярное выражение, в котором мы пропишем нахождение отдельных элементов в строке. Например, мы хотим объявлять переменную вот так:

var name;

Регулярное выражение:

reg  =  /var|;|s+|[a-zA-Z$_0-9]+|[^a-zA-Z$_0-9]+/g // кириллицу пока не трогаем

Для каждого зарезервированного слова, оператора или имён напишем обработчики handlers.
Создадим функцию инициализации init(type) для конструкций объявления переменной и её использовании, и ещё для точки с запятой.Также нам нужна функция toOutput, которая будет брать строки str из элементов mainArray и собирать js-код.

Каждая конструкция имеет свой тип (номер) и статус (status — текущее состояние). Например, пишем var, создаём такой объект: {type :0, status :0, iscorrect :false, str :'var' }, затем добавляем имя и получаем: {type :0, status :1, iscorrect :true, str :'var name' }. Таким образом, легко определить правильность написанного.

Получаем такой код (~120 строк):

Array.prototype.last = function() {
  var l = this.length - 1;
  if (l >= 0) {
    return this[l];
  }
  return null;
};
var compile = function() {
  var run = function(str) {
    var mainArray = [];
    var last;
    try {
      var reg =  /var|;|s+|[a-zA-Z$_0-9]+|[^a-zA-Z$_0-9]+/g;
      var r = reg;
      var arr;
      var output = "";
      while ((arr = r.exec(str)) !== null || toOutput()) {
        last = mainArray.last();
        defWord(arr[0]);
      }
      return output;
    } catch (e) {
      return[e.toString()];
    }
    function defWord(w) {
      if (w[0] == " " || w[0]== "n") { // пока игнорируем 
        return;
      }
      switch(w) {
        case "var":
          var_handler();
          break;
        case ";":
          semicolon_handler();
          break;
        default:
          w_handler(w);
      }
    }
    function toOutput() {
      var l = mainArray.length;
      for (var i = 0, last;i < l;i++) {
        last = mainArray[i];
        if (last.iscorrect) {         // если конструкция корректна
          output += last.str;
        } else {
          throw "незаконченная конструкция : " + last.str;
        }
      }
      return 0;
    }
    function isConflict() {     // можем ли мы создать что-нибудь?
      if (last === null || last.type == 1) {
        return 0;
      }
      return 1;
    }
    function var_handler() {
      if (!isConflict()) {
        init(0);
      } else {
        throw "что-то не так";
      }
    }
    function semicolon_handler() {
      if(last!=null){ if (last.type === 0) {
        if (last.status == 1) {
          init(1);
          return;
        }
      } else {
        if (last.type == 1) {
          return;
        } else {
          if (last.type == 2) {
            if (last.status === 0) {
              init(1);
              return;
            }
          }
        }
      }
        throw'неожиданное ";" ';
      }
    }
    function w_handler(w) {
      if (/^[A-Za-z_$][w$]*$/.test(w)) {
        if (last != null) {
          if (last.type === 0) {
            if (last.status === 0) {
              last.str += w;
              last.iscorrect = 1;
              last.status = 1;
              return;
            }
          }
        }
        if (!isConflict()) {
          var l = init(2);
          l.str = w;
          return;
        }
      } else {
        throw "неизвестное выражение :" + w;
      }
      throw "так нельзя! :" + w;
    }
    function init(type) {
      switch(type) {
        case 0:
          mainArray.push({type:0, status:0, str:"var ", iscorrect:0});
          break;
        case 1:
          mainArray.push({type:1, status:0, str:";", iscorrect:1});
          break;
        case 2:
          mainArray.push({type:2, status:0, str:"", iscorrect:1});
          break;
      }
      return mainArray.last();
    }
  };
  return run;
}();

Давайте попробуем ещё добавить строки к нашему компилятору.Сперва находим " или ', затем подменяем регулярное выражение (не забываем про backslash и newline):

reg2 = /.*?[^\n](?=")|n/g

+ поддержка строк
Array.prototype.last = function() {
    var l = this.length - 1;
    if (l >= 0) {
        return this[l];
    }
    return null;
};
var compile = function() {
    var run = function(str) {
        var mainArray = [];
        var last;
        try {
            var reg = /var|""|''|"|'|;|s+|[a-zA-Z$_0-9]+|[^a-zA-Z$_0-9]+/g;
            var reg1=/.*?[^\n](?=")|n/g;
            var reg2=/.*?[^\n](?=')|n/g;
            var currentReg=0;  // тек. номер регулярного выражения
            var r = reg;
            var arr;
            var instring=0;
            var output = "";
            while ((arr = r.exec(str)) !== null || toOutput()) {
                last = mainArray.last();
                defWord(arr[0]);
            }
            return output;
        } catch (e) {
            return[e.toString()];
        }
        function defWord(w) {
            if(instring){
                if(w[0]=="n") throw "неожиданный перевод строки"
                last.str+=w;
                reg.lastIndex=(currentReg==1)?reg1.lastIndex:reg2.lastIndex;
                r=reg;
                instring=0;
                return;
            }else   if (w[0] == " " || w[0] == "n") { // пока игнорируем
                return;
            }
            switch(w) {
                case "var":
                    var_handler();
                    break;
                case ";":
                    semicolon_handler();
                    break;
                case '"':
                    string_handler(0);
                    break;
                case "'":
                    string_handler(1);
                    break;
                case "''":
                    string_handler(2);
                    break;
                case '""':
                    string_handler(3);
                    break;
                default:
                    w_handler(w);
            }
        }
        function toOutput() {
            var l = mainArray.length;
            for (var i = 0, last;i < l;i++) {
                last = mainArray[i];
                if (last.iscorrect) {
                    output += last.str;
                } else {
                    throw "незаконченная конструкция : " + last.str;
                }
            }
            return 0;
        }
        function isConflict() {
            if (last === null || last.type == 1) {
                return 0;
            }
            return 1;
        }
        function var_handler() {
            if (!isConflict()) {
                init(0);  // var
            } else {
                throw "что-то не так";
            }
        }
        function string_handler(i){
            if(last!==null && last.type==3){
                if(last.status===1 && i===0){
                    last.status=3;
                    last.iscorrect=1;
                    last.str+='"';
                }else if(last.status===2 && i===1){
                    last.status=3;
                    last.iscorrect=1;
                    last.str+="'";
                }else throw "строка неверно определена!"+last.str;
            }else{
                if (!isConflict()) {
                    var l= init(3); // string
                    if(i===0){
                        reg1.lastIndex = reg.lastIndex;
                        currentReg=1;
                        instring=1;
                        r=reg1;
                        l.status=1;
                        l.str='"';
                    }else if(i===1){
                        reg2.lastIndex = reg.lastIndex;
                        currentReg=2;
                        instring=1;
                        r=reg2;
                        l.status=2;
                        l.str="'";
                    } else{
                        l.iscorrect=1;
                        l.status=3;
                        l.str=(i==3)?'""':"''";
                    }
                }else {
                    throw "что-то не так";
                }
            }
        }
        function semicolon_handler() {
            if(last!=null){ if (last.type === 0) {
                if (last.status == 1) {
                    init(1);
                    return;
                }
            } else if (last.type == 1) {
                return;
            }else if(last.type==3){
                init(1);
                return;

            }else {
                if (last.type == 2) {
                    if (last.status === 0) {
                        init(1);
                        return;
                    }
                }
            }
                throw'неожиданное ";" ';
            }
        }
        function w_handler(w) {
            if (/^[A-Za-z_$][w$]*$/.test(w)) {
                if (last !== null) {
                    if (last.type === 0) {
                        if (last.status === 0) {
                            last.str += w;
                            last.iscorrect = 1;
                            last.status = 1;
                            return;
                        }
                    }
                }
                if (!isConflict()) {
                    var l = init(2);
                    l.str = w;
                    return;
                }
            } else {
                throw "неизвестное выражение :" + w;
            }
            throw "так нельзя! :" + w;
        }
        function init(type) {
            switch(type) {
                case 0:
                    mainArray.push({type:0, status:0, str:"var ", iscorrect:0});
                    break;
                case 1:
                    mainArray.push({type:1, status:0, str:";", iscorrect:1});
                    break;
                case 2:
                    mainArray.push({type:2, status:0, str:"", iscorrect:1});
                    break;
                case 3:
                    mainArray.push({type:3, status:0, str:"", iscorrect:0});
                    break;
            }
            return mainArray.last();
        }
    };
    return run;
}();

Чем больше будет кода, тем больше объектов будет в mainArray, поэтому в некоторых случаях можно удалять завершённые конструкции.Плюсом нашей структуры является получение в любой момент отдельного элемента.

Демо: jsfiddle

Можете оставлять в комментариях пожелания или более интересные идеи. Спасибо.

Автор: iDennis

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js