Из интереса я хочу научиться писать синтаксический анализатор для простого языка, чтобы в конечном итоге написать интерпретатор для моего собственного небольшого кода для игры в гольф, как только я понял, как это работает вообще.
Итак, я начал читать статью Дугласа Крокфорда Приоритет сверху вниз.
Примечание. Вероятно, вы должны прочитать статью, если хотите более глубокое понимание контекста фрагментов кода ниже
Мне трудно понять, как должен работать оператор var
и оператор присваивания =
.
D.C. определяет оператор присваивания, например
var assignment = function (id) {
return infixr(id, 10, function (left) {
if (left.id !== "." && left.id !== "[" &&
left.arity !== "name") {
left.error("Bad lvalue.");
}
this.first = left;
this.second = expression(9);
this.assignment = true;
this.arity = "binary";
return this;
});
};
assignment("=");
Примечание: [[значение]] относится к токену, упрощенному к его значению
Теперь, если функция выражения достигает, например, [[t]],[[=]],[[2]]
, результат [[=]].led
- это что-то вроде этого.
{
"arity": "binary",
"value": "=",
"assignment": true, //<-
"first": {
"arity": "name",
"value": "t"
},
"second": {
"arity": "literal",
"value": "2"
}
}
D.C. делает функцию assignment
, потому что
мы хотим, чтобы он делал два дополнительных бита бизнеса: рассмотрите левый операнд, чтобы убедиться, что он является надлежащим значением l, и установить член назначения, чтобы мы могли позже быстро идентифицировать.
Что имеет смысл для меня до такой степени, что он вводит
var
, который определяется следующим образом.
Оператор var определяет одну или несколько переменных в текущем блоке. Каждое имя может необязательно сопровождаться = и инициализирующим выражением.
stmt("var", function () {
var a = [], n, t;
while (true) {
n = token;
if (n.arity !== "name") {
n.error("Expected a new variable name.");
}
scope.define(n);
advance();
if (token.id === "=") {
t = token;
advance("=");
t.first = n;
t.second = expression(0);
t.arity = "binary";
a.push(t);
}
if (token.id !== ",") {
break;
}
advance(",");
}
advance(";");
return a.length === 0 ? null : a.length === 1 ? a[0] : a;
});
Теперь, если парсер достигает набора токенов типа [[var]],[[t]],[[=]],[[1]]
, сгенерированное дерево будет выглядеть примерно так.
{
"arity": "binary",
"value": "=",
"first": {
"arity": "name",
"value": "t"
},
"second": {
"arity": "literal",
"value": "1"
}
}
Ключевой частью моего вопроса является часть if (token.id === "=") {...}
.
Я не понимаю, почему мы называем
t = token;
advance("=");
t.first = n;
t.second = expression(0);
t.arity = "binary";
a.push(t);
а не
t = token;
advance("=");
t.led (n);
a.push(t);
в части ...
.
который назвал бы наши операторы [[=]]
led
(функция назначения), которая делает
убедитесь, что это правильный lvalue, и установить член назначения, чтобы мы могли быстрее быстро идентифицировать операторы присваивания. например,
{
"arity": "binary",
"value": "=",
"assignment": true,
"first": {
"arity": "name",
"value": "t"
},
"second": {
"arity": "literal",
"value": "1"
}
}
так как нет оператора с a lbp
между 0 и 10, вызов expression(0) vs. expression (9)
не имеет значения. (!(0<0) && !(9<0) && 0<10 && 9<10)
)
И условие token.id === "="
не позволяет назначать члену объекта как token.id
либо '['
, либо '.'
, а t.led
не будет вызываться.
Мой короткий вопрос:
Почему мы не вызываем, необязательно, после следующего объявления переменной, доступную функцию led
операторов присваивания. Но вместо этого вручную установите члены first
и second
оператора, но не член assignment
?
Вот два скрипта, разбирающих простую строку. Используя оригинальный код и один с помощью присваивания операторов led
.