Для учебных целей разработан простой компилятор delta реализованный с помощью утилит Lex и Yacc (вернее, их свободных версий flex и bison). В исходном языке специально опущены некоторые существенные возможности (логические переменные, инициализация переменных при описании, цикл for и т.п.) — они оставлены в качестве задач для самостоятельного решения.
Входной язык представляет собой минимальное подмножество языка C, за одним существенным исключением: в языке не используются фигурные скобки для указания границ блоков. Вместо этого каждая конструкция имеет строку-заголовок и строку-конец (аналог закрывающей фигурной скобки в C). Например, цикл while, который в C можно записать как
while (условие) { тело цикла }в учебном языке записывается как
while (условие) тело цикла endwhile(в роли закрывающей фигурной скобки используется оператор endwhile, а открывающая фигурная скобка вообще не нужна).
Кроме того, в число управляющих конструкций учебного языка
добавлен оператор выбора, отсутствующий в C:
if (условие1) ...
elseif (условие2) ...
elseif (условие3) ...
else ...
endif
Например, следующий фрагмент вычисляет функцию signum
— знак числа x:
if (x > 0.) sgn = 1; elseif (x < 0.) sgn = -1; else sgn = 0; endif
Программа может завершаться концом файла или необязательным ключевым словом end. В последнем случае весь текст, следующий за словом end, игнорируется.
Типы данных ограничиваются базовыми типами и динамическими массивами. Базовых типов всего 3: целое число int, вещественное число double и текстовая строка string. Delta — язык с динамической типизацией (как Python), переменные в нем не имеют типов. Типы выражений и значений переменных определяются только в процессе работы программы.
В выражениях можно использовать 4 обычные арифметические операции, операцию возведения в степень ^ или ** и операцию взятия остатка от деления % (как и в C). Присваивание, как и в C/C++, также является операцией. Реализованы также математические стандартные функции sin, cos, exp и т.д. Числовые константы записываются так же, как и в языке C (правда, целочисленные восьмеричные и шестнадцатеричные константы не реализованы). Строковые константы записываются в двойных апострофах: "abc", "x=" и т.п.
Язык состоит из операторов и функций. Оператор может состоять из выражения, после которого ставится точка с запятой. Чаще всего это оператор присваивания:
переменная = выражение;где переменная в левой части может быть простой переменной или элементом массива. Оператор заканчивается символом ";" (точка с запятой). Отметим, что присваивание является операцией и может использоваться внутри любого выражения. Операция присваивания выполняется справа налево. Комментарий обозначается как в C++ "//" или как в Python'е "#". Примеры:
x = 5; y = 2*x + 1; x = y = z = 1000; f(x, y, z); d = gcd(17, 43); a = [1, 2, 3] + [4, 5]; # a == [1, 2, 3, 4, 5] append(a, 6); # a == [1, 2, 3, 4, 5, 6] append(a, [7, 8, 9]); # a == [1, 2, 3, 4, 5, 6, [7, 8, 9]]
В языке для базовых типов имеются операции приведения типа.
x = double("6.125"); # вычислется число по его текстовому # представлению s = string(x + 1); # s == "7.125"После выполнения этого фрагмента строковая переменная s приобретет значение "7.125".
Структурных операторов всего 2 типа: оператор цикла while
и условный оператор if.
Условный оператор может иметь 3 формы:
— оператор "если" if... endif
— оператор "если-иначе"
if... else... endif
— оператор выбора
"если - иначе если - иначе если -... иначе":
if... elseif... elseif...
else... endif
Структурные операторы имеют строку-заголовок и строку-окончание. В окончании к ключевому слову, с которого начинается строка-заголовок, добавляется префикс "end": if-endif, while-endwhile. Фигурные скобки или ключевые слова begin-end для указания границ структурных операторов становятся ненужными, а программа более ясной; кроме того, это позволяет избежать неоднозначностей в грамматике языка и конфликтов при разборе, которые присутствуют, например, в языке C при разборе конструкции
if (условие1) if (условие2) оператор1; else оператор2;(непонятно, к какому if относится else — в C считается, что к внутреннему; при LR-разборе конфликт сдвиг-свертка в данном случае разрешается в пользу сдвига).
Примеры использования структурных операторов.
1. Суммирование массива:
s = 0.; i = 0; while (i < 100) s = s + a[i]; i = i + 1; endwhile2. Определение окончания множественного числа в русском языке (0 мячей, 1 мяч, 2 мяча, 3 мяча, 4 мяча, 5 мячей, 6 мячей...):
numBalls = ""; . . . numBalls = numBalls+n; // число n преобразуется // к текстовому виду и добавляется в конец строки numBalls = numBalls + " мяч"; if (n == 0 || n >= 5) numBalls = numBalls + "ей"; elseif (n >= 2) numBalls = numBalls + "а"; endif
Для выхода из цикла while можно, как и в C,
использовать оператор break;
для перехода к следующей итерации цикла — оператор
continue;
В условиях можно использовать операции сравнения и логические операции, записываемые так же, как и в языке C. Например,
if (y > 0. && x / y > 10.) x = 10. * y; endifВ отличие от C, целочисленные выражения нельзя использовать в качестве логических. Например, фрагмент программы
x = 100; . . . if (x) y = 1; endifявляется ошибочным (впрочем, и в C это дурной стиль!). Вместо этого надо писать
if (x != 0) y = 1; endif(такому стилю хорошо бы следовать и при программировании на C).
Реализация логического типа bool и логических переменных оставлена в качестве задачи для самостоятельного решения.
В программах можно использовать встроенные операторы ввода и вывода, вводящие значения переменных с клавиатуры (стандартного входа) или выводящие значения в стандартный поток вывода: input, output, outputln. Но лучше вместо встроенных операторов пользоваться стандартными функциями scan, print и println, которые также реализованы в языке. Функция scan, так же как и оператор input, всегда вводит целиком строку до символа перевода строки '\n' (не включая этого символа). Пример:
s = scan(); println("s = ", s); x = int(s); y = double(s); println("x = ", x, " y = ", y);Пусть при выполнении этого фрагмента программы человек введет с клавиатуры строку
6.5Тогда будет напечатано:
s = 6.5 x = 6 y = 6.5
Динамические массивы похожи на списки в Python'е. Элементы массива могут иметь различные типы.
a = [1, 2, "abc", [3, 4, 5]];Матрица реализуются как массив массивов. В следующем примере мы определяем матрицу a размера 2x3:
a = []; append(a, [1, 2, 3]); append(a, [4, 5, 6]); # a -- матрица [[1, 2, 3], [4, 5, 6]]Стандартная функция append(a, x) добавляет элемент x в конец массива a, при этом длина массива увеличивается на 1. Функция extend(a, b) дописывает содержимое массива b в конец массива a. Операция + соединяет два массива, ее результатом является новый массив:
a = [1, 2]; append(a, 3); # a == [1, 2, 3] append(a, [4, 5]); # a == [1, 2, 3, [4, 5]] extend(a, [6, 7, 8]); # a == [1, 2, 3, [4, 5], 6, 7, 8] b = a + [9, 10]; # b == [1, 2, 3, [4, 5], 6, 7, 8, 9, 10]Стандартные функции left, right, mid извлекают из массива подмассивы в начале, середине и конце соответственно. Вторым аргументом функции mid является индекс начала подмассива, третьим аргументом — длина подмассива. Если третий аргумент отсутствует или отрицательный, то результатом явлется подмассив, начинающийся с указанной позиции и включающий все элементы до конца массива:
a = [0, 1, 2, 3, 4, 5]; b = left(a, 3); # b == [0, 1, 2] c = right(a, 2); # c == [4, 5] d = mid(a, 1, 3); # d == [1, 2, 3] e = mid(a, 2); # e == [2, 3, 4, 5]Функции left, right, mid аналогично работают и со строками:
s = "012345"; b = left(a, 3); # b == "012" c = right(a, 2); # c == "45" d = mid(a, 1, 3); # d == "123" e = mid(a, 2); # e == "2345"
1. Печать квадратов первых 20 натуральных чисел — файл "square20.d":
n = 1; while (n <= 20) println(n, " ", n*n); n = n+1; endwhile endВывод программы:
1 1 2 4 3 9 4 16 5 25 6 36 7 49 8 64 9 81 10 100 11 121 12 144 13 169 14 196 15 225 16 256 17 289 18 324 19 361 20 400
2. Печать n первых простых чисел — файл "nprimes.d":
# Input an integer number n and print first n prime numbers print("Number of primes: "); s = scan(); n = int(s); k = 0; p = 2; while (k < n) if (p == 2) print(" ", p); k = k+1; p = p+1; else prime = 1; d = 3; while (prime == 1 && d*d <= p) if (p % d == 0) prime = 0; break; endif d = d+2; endwhile if (prime == 1) print(" ", p); k = k+1; if (k % 10 == 0) println(); endif endif p = p+2; endif endwhile println();Вывод программы (для n=50):
Number of primes: 50 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229
3. Приближенное вычисление квадратного корня вещественного числа — файл "sqrt.d":
# # Compute a square root of a number # and compare the result with standard sqrt(x) function # func main() while (0 == 0) # Infinite loop print("Input a number (0 for the end): "); x = double(scan()); if (x == 0.) break; endif y = squareRoot(x); println("squareRoot(x) = ", y); println("standard sqrt(x) = ", sqrt(x)); endwhile endfunc # Compute a square root using Newton iterations func squareRoot(a) println(" in squareRoot: a = ", a); if (a < 0.) println(" Incorrect argument"); return 0.; endif EPS = 0.0000001; x = a; if (x < 1.) x = 1.; endif xPrev = x + 1e100; while (fabs(xPrev - x) > EPS) xPrev = x; x = (x + a/x) / 2.; println(" ", x); endwhile println(" in squareRoot: result = ", x); return x; endfunc # Starter main();Вывод программы для чисел 2 и 10 (для заданного числа печатаются последовательность приближений и окончательный результат):
Input a number (0 for the end): 2 in squareRoot: a = 2 1.5 1.41667 1.41422 1.41421 1.41421 in squareRoot: result = 1.41421 squareRoot(x) = 1.41421 standard sqrt(x) = 1.41421 Input a number (0 for the end): 10 in squareRoot: a = 10 5.5 3.65909 3.19601 3.16246 3.16228 3.16228 in squareRoot: result = 3.16228 squareRoot(x) = 3.16228 standard sqrt(x) = 3.16228
Пример "gcd.d" иллюстрирует возможность возвращения списка (динамического массива) в качестве результата функции. В данном случае функция extEucl(m, n) возвращает список из трех значений:
func main() while (0 == 0) # Infinite loop println("Input 2 integer numbers (two zeroes for the end):"); print("m = "); m = int(scan()); print("n = "); n = int(scan()); if (m == 0 && n == 0) break; endif d = gcd(m, n); println("gcd(m, n) = ", d); println("Extended Euclid algoritm:"); res = extEucl(m, n); println("d = ", res[0], ", u = ", res[1], ", v = ", res[2]); println("d == gcd(m, n) == u*m + v*n\n"); endwhile endfunc # Recursive implementation of gcd func gcd(a, b) if (b == 0) return a; else return gcd(b, a%b); endif endfunc # Extended Euclid Algorithm: # For integers m, n compute d = gcd(m, n) # and u, v such that d = u*m + v*n func extEucl(m, n) a = m; b = n; u1 = 1; v1 = 0; u2 = 0; v2 = 1; while (b != 0) # Invariant: a == u1*m + v1*n # b == u2*m + v2*n # gcd(a, b) == gcd(m, n) q = a/b; r = a%b; a = b; b = r; tmp = u1; u1 = u2; u2 = tmp - q*u2; tmp = v1; v1 = v2; v2 = tmp - q*v2; endwhile return [a, u1, v1]; endfunc main();Пример выполнения программы:
Input 2 integer numbers (two zeroes for the end): m = 19 n = 43 gcd(m, n) = 1 Extended Euclid algoritm: d = 1, u = -9, v = 4 d == gcd(m, n) == u*m + v*n
Пример "gauss.d" иллюстрирует работу с матрицами. В программе вводится квадратная матрица и методом Гаусса приводится к ступенчатому виду. Печатается ступенчатая форма матрицы и ее определитель.
Алгоритмы сортировки иллюстрируют использование
вложенных функций и нелокальных переменных:
"qsort.d" — сортировка QuickSort;
"heapsort.d" — сортировка HeapSort.
Исходные текста проекта
(архивы в tar-gzip формате и в zip-формате):
"Delta.tgz"
"Delta.zip"
Для компиляции проекта следует выполнить команду
make
Для запуска компилятора и выполнения программы "filename.d"
./delta filename.dили просто
delta filename.dесли текущая директория находится в пути или файл "delta" находится в одной из директорий, добавленных в путь. Текст программы читается из указанного файла. Можно также читать программу из входного потока:
delta -std_inputдля окончания ввода программы следует набрать строку
end(можно ввести также символ конца файла — Ctrl+D в Unix'е или Ctrl+Z в MS Windows). После этого программа выполняется, ее ввод и вывод происходит в консоли.
Также есть и другие возможности: распечатать промежуточный код программы, распечатывать таблицы символов и т.п. Полный текст подсказки показывается по команде "delta" без аргументов:
Delta Compiler + Interpreter. Usage: delta [-show_icode] [-show_nametable] [-std_input] [-debug] [source_file] where -show_icode Print intermediate code -show_nametable Print global nametable -std_input Read program from standard input -debug Print a lot of debugging information source_file File with a source program
Компилятор состоит из следующих модулей и файлов: