Доброго времени суток! Напишем простой компилятор в 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' }. Таким образом, легко определить правильность написанного.
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