Создание компилятора языка для .NET Framework - Определение языка

ОГЛАВЛЕНИЕ

Определение языка

Языки программирования начинаются с определенной цели. Эта цель может быть чем угодно, например выразительными возможностями (скажем, Visual Basic®), продуктивностью (скажем, Python, нацеленный на получение максимального эффекта от каждой строки кода), специализацией (скажем, Verilog, являющийся языком описания оборудования, используемым производителями процессоров), или просто удовлетворением личных предпочтений автора. (Например, создатель Boo любит .NET Framework, но не удовлетворен ни одним из доступных языков..

После того, как цель определена, можно разрабатывать язык – думайте о ней как о чертеже для языка. Компьютерные языки должны быть очень точными, чтобы программист мог безошибочно выразить именно то, что нужно и чтобы компилятор мог верно понять его, создав исполняемый код именно для того, что выражено. Чертеж языка должен быть четко определен, чтобы устранить двусмысленности в ходе применения компилятора. Для этого используется метасинтаксис – синтаксис, используемый для описания синтаксиса языков. Существует достаточно много метасинтаксисов, что позволяет подобрать соответствующий личным вкусам. Я буду определять язык Good for Nothing («Бесполезный»), используя метасинтаксис, именуемый EBNF (Extended Backus-Naur Form).

Стоит упомянуть, что EBNF обладает весьма достойными истоками: он связан с Джоном Бэкусом (John Backus), обладателем Премии Тьюринга и ведущим разработчиком FORTRAN. Подробный рассказ о EBNF находится за пределами темы этой статьи, но я могу объяснить базовые концепции.

Определение языка для Good for Nothing показано на рис. 1. В соответствии с моим определением языка, оператор (stmt) может представлять из себя объявления переменных, назначения, циклы, чтение целых чисел из командной строки или печать на экране – и все это можно указывать много раз, разделяя точкой с запятой. Выражения (expr) могут быть строками, интегралами, арифметическими выражениями или идентификаторами. Идентификаторы (ident) могут именоваться с использованием буквы алфавита в качестве первой буквы, за которой следуют символы или цифры. Ну, и так далее. Попросту говоря, я определил синтаксис языка, предоставляющий базовые арифметические возможности, небольшую систему типов и простое взаимодействие с пользователем на основе консоли.

Figure 1 Good for Nothing Language Definition

<stmt> := var <ident> = <expr>
  | <ident> = <expr>
  | for <ident> = <expr> to <expr> do <stmt> end
  | read_int <ident>
  | print <expr>
  | <stmt> ; <stmt>

<expr> := <string>
  | <int>
  | <arith_expr>
  | <ident>

<arith_expr> := <expr> <arith_op> <expr>
<arith_op> := + | - | * | /

<ident> := <char> <ident_rest>*
<ident_rest> := <char> | <digit>

<int> := <digit>+
<digit> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

<string> := " <string_elem>* "
<string_elem> := <any char other than ">

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

Вот пример программы языка Good for Nothing:

var ntimes = 0;
print "How much do you love this company? (1-10) ";
read_int ntimes;
var x = 0;
for x = 0 to ntimes do
  print "Developers!";
end;
print "Who said sit down?!!!!!";

Можно сравнить эту простую программу с определением языка, чтобы лучше понять, как работает грамматика. На этом определение языка завершено.