Написання парсера - це, залежно від мови програмування, досить складне завдання. У сутності, він повинен перетворити фрагмент коду (який ми аналізуємо, розглядаючи символи) у "абстрактне синтаксичне дерево" (AST). AST - це структуроване представлення програми в пам'яті, і воно "абстрактне" в тому сенсі, що не має значення, з яких саме символів складається вихідний код, але вірно відображає семантику програми. Я написав окрему сторінку, щоб описати наше AST.
Наприклад, для такого тексту програми:
sum = lambda(a, b) {
a + b;
};
print(sum(1, 2));
наш парсер створить наступне AST, як об'єкт JavaScript:
{
type: "prog",
prog: [
// first line:
{
type: "assign",
operator: "=",
left: { type: "var", value: "sum" },
right: {
type: "lambda",
vars: [ "a", "b" ],
body: {
// the body should be a "prog", but because
// it contains a single expression, our parser
// reduces it to the expression itself.
type: "binary",
operator: "+",
left: { type: "var", value: "a" },
right: { type: "var", value: "b" }
}
}
},
// second line:
{
type: "call",
func: { type: "var", value: "print" },
args: [{
type: "call",
func: { type: "var", value: "sum" },
args: [ { type: "num", value: 1 },
{ type: "num", value: 2 } ]
}]
}
]
}
Основна складність у написанні парсера полягає в неправильній організації коду. Парсер повинен працювати на вищому рівні, ніж просте читання символів з рядка. Кілька порад, як не писати спагетті:
Пишіть багато функцій і робіть їх невеликими. В кожній функції виконуйте одну річ і робіть це добре.
Не намагайтеся використовувати регулярні вирази для синтаксичного аналізу. Вони не працюють. Регулярні вирази можуть бути корисними у лексері, але я раджу обмежитися дуже простими речами.
Не намагайтеся вгадати. Якщо ви не впевнені, як парсити щось, викидайте помилку і переконайтеся, що повідомлення про помилку містить місце помилки (рядок/стовпчик).
Щоб спростити код, я розбив його на три частини, які далі розділені на багато невеликих функцій: