Актуальный файл с грамматикой: grammar.ypp
Программа — это последовательность объявлений фрагментов кода.
Может быть объявлен фрагмент кода одного из двух видов: атомарный и структурированный.
Объявление атомарного фрагмента кода имеет следующий вид:
import <lib_func_name> ( <params> ) as <code_id> ;
Объявление означает, что в подключаемой библиотеке пользовательских фрагментов кода будет использована подпрограмма с именем lib_func_name и параметрами соответствующих типов. Во фрагментированной программе она будет доступна как фрагмент кода code_id.
Детали:
lib_func_name — это имя подпрограммы во внешней библиотекеparams — список типов аргументов подпрограммы через запятую (0 и более штук). Допустимые типы — см. ниже.code_id — имя фрагмента кода, по которому он будет доступен во фрагментированной программеПримеры:
import my_func ( int, int, name ) as my_func;
import other_func (value, name, name) as my_func2;
Объявление структурированного фрагмента кода имеет следующий вид:
sub <code_id> ( <params> ) <body>
Объявление означает, что описывается фрагментированная подпрограмма (она же — структурированный фрагмент кода) с именем <code_id>, параметрами <params> и телом <body>. Фрагментированная подпрограмма может быть применена к набору фрагментов данных с получением структурированного фрагмента вычислений.
Детали:
code_id — имя фрагментированной подпрограммы во фрагментированной программеparams — ноль и более параметров, перечисленных через запятую. Каждый параметр описывается типом и именем, например int x или value field_size (список допустимых типов см. ниже).body — Тело фрагментированной подпрограммы. Структуру тела см. ниже.Пример:
sub my_subroutine(int x, name y) { /* sub body here */ }
Тело — это аналог блока кода из традиционных языков программирования. Тело бывает у подпрограммы, цикла, условного оператора, и т.п. Во фрагментированной программе тело описывает множество (неупорядоченное) операторов. Каждый оператор описывает фрагменты вычислений, а структурированные операторы могут включать и описание фрагментов данных.
Тело имеет следующую структуру:
{
df x, y, z; // optional data fragments declaration
<operator1>
<operator2>
...
}
То есть, тело начинается с открывающей фигурной скобки, вслед за которой сначала описываются фрагменты данных (это описание может отсутствовать), затем описываются операторы тела. Тело заканчивается закрывающей фигурной скобкой.
Если тело состоит из единственного оператора и не содержит фрагментов данных, то фигурные скобки могут быть опущены.
В языке предусмотрены операторы следующих видов:
cf — применение фрагмента кодаlet — оператор внесения значений в контекстfor — оператор примитивно-рекурсивного перечисленияwhile — оператор частично-рекурсивного перечисленияif — условный операторОператор cf обозначает применение некоторого фрагмента кода (атомарного или структурированного) к набору фактических аргументов — фрагментов данных, констант и выражений над ними. Оператор имеет следующий вид:
cf <cf_id>: <code_id> ( <args_expressions> );
Детали:
<cf_id> — это идентификатор (в т.ч. индексированный) фрагмента вычислений в текущем контексте имен (в текущем теле)<code_id> — это имя фрагмента кода, который будет применён к аргументам<args_expressions> — это список фактических аргументов, которые будут подставлены значениями в формальные параметры фрагмента кода. Аргументы перечисляются через запятую. Их количество должно совпадать с количеством формальных параметров фрагмента кода. Виды выражений см. ниже.Идентификатор фрагменту вычислений может не назначаться. В этом случае терминал cf, идентификатор и двоеточие опускаются, и оператор имеет вид:
<code_id> ( <args_expressions> ) ;
Семантика: Оператор предписывает вычислить значения всех выражений из числа <args_expressions> в соответствии с их типами, после чего исполнить указанный фрагмент кода. Исполнением атомарного фрагмента кода является запуск импортированной процедуры. Исполнением структурированного фрагмента кода является порождение всех фрагментов вычислений, определённых в его теле.
Примечание: значения выражений приводятся к типам, определённым при объявлении соответствующего фрагмента кода. Подробнее о приведении типов см. ниже.
Примеры:
cf init: set(x, 5);
cf a[i]: func(x, y[i], i);
calc(x, y[i], z);
Оператор let позволяет определить именованый параметр и присвоить ему значение. Это значение будет видно в контексте тела оператора так же, как видны параметры в подпрограммах. Оператор имеет вид:
let <name1>=<expr1>, <name2>=<expr2>, ... <body>
Детали:
<nameX> — имя параметра, вносимого в контекст<exprX> — выражение, значение которого будет значением параметра<body> — тело оператора, в котором данные параметры будут иметь значенияСемантика: Оператор предписывает вычислить значения всех выражений, после чего породить все фрагменты вычислений, определённые в его теле.
Пример:
let x=y[k][l], message="Success" { /* body */ }
Оператор for позволяет определить множество фрагментов, и размер этого множества является параметром. Оператор имеет следующий вид:
for <counter> = <first_expr> .. <last_expr> <body>
Детали:
<counter> — имя параметра, например, i или step<first_expr> — выражение, определяющее минимальное значение счётчика цикла <counter><last_expr> — выражение, определяющее максимальное значение счётчика цикла <counter><body> — тело оператораСемантика: Оператор предписывает вычислить значения выражений <first_expr> и <last_expr>, после чего породить все фрагменты вычислений, определенные в его теле в нескольких экземплярах, по одному экземпляру для каждого целочисленного значения от <first_expr> до <last_expr> включительно. Сами эти выражения также приводятся к целочисленным значениям (подробнее о приведении типов см. ниже). При порождении каждого экземпляра тела оператора параметр <counter> будет определён и иметь уникальное целочисленное значение в указанном диапазоне.
Примечание: количество "итераций" определяется динамически, но всегда перед тем, как оператор будет раскрыт (т.е. порождено множество его тел). Это отличает оператор for от оператора while, в котором число итераций определяется динамически в процессе исполнения оператора.
Важно: Несмотря на знакомое ключевое слово for этот оператор не задаёт цикл в традиционном понимании. Оператор не накладывает никаких ограничений на порядок выполнения "итераций", он просто описывает множество. Слово for было выбрано по той причине, что классический оператор арифметического цикла for позволяет описывать примитивно-рекурсивные функции, и оператор for в языке LuNA обладает этим же свойством. На этом сходства заканчиваются.
Пример:
for i=1..x[j] { /* body */ }
Оператор while позволяет определить множество фрагментов в частично-рекурсивном стиле. Оператор имеет следующий вид:
while <cond_expr> , <counter> = <start_expr> .. out <out_df_id> <block>
Детали:
<cond_expr> — условное выражение<counter> — имя счётчика итераций<start_expr> — начальное значение счётчика<out_df_id> — идентификатор фрагмента данных (возможно, индексированный), он является выходным параметром оператора<body> — тело оператораСемантика: Оператор предписывает вычислить значение выражения <start_expr>. При этом заданном значении счётчика <counter> вычисляется значение условного выражения <cond_expr>. Это значение приводится к логическому типу. Если значение ложно, то значение фрагмента данных <out_df_id> устанавливается равным значению <counter> и оператор считается выполненным. Если значение истинно, то порождается тело оператора, в контексте которого так же определено значение счётчика <counter>, а также порождается новый оператор while как копия старого, но со значением <start_expr> на единицу большем, чем было (старый оператор while уничтожается). Таким образом, процесс вычислений продолжается до тех пор, пока условное выражение не примет ложное значение. Подробнее о логических значениях см. ниже.
Важно: Несмотря на последовательную природу работы оператора while реальные вычисления не обязательно будут следовать той же последовательности! Оператор while действительно последовательно порождает фрагменты вычислений, описанные в его теле, но после этого порождённые фрагменты вычислений будут выполняться по мере удовлетворения информационных зависимостей, а не в порядке порождения. В особенности это касается случая, когда вычисляемые в теле оператора фрагменты данных не влияют на значение условного выражения.
Пример:
while ( x[i] < 0 ) , i = 0 .. out N { /* body */ }
Оператор if позволяет описывать условные фрагменты, т.е. такие фрагменты, которые существуют только в случае, если некоторое условие выполняется. Оператор имеет следующий вид:
if <cond_expr> <body>
Детали:
<cond_expr> — это условное выражение<body> — это тело оператораСемантика: Оператор предписывает вычислить значение выражения <cond_expr>, а затем привести его к логическому типу. Если логическое значение — ложь, то оператор считается исполненным. Если значение — истина, то тело оператора порождается.
Пример:
if x[i] != N { /* body */ }
В языке LuNA определены следующие типы выражений:
int — целое числоreal — вещественное числоstring — строкаvalue — пользовательский тип (атомарных) фрагментов данныхname — идентификатор (возможно, индексированный) фрагмента данных.Тип name используется для того, чтобы обозначать выходные фрагменты данных в атомарных фрагментах кода. Этот тип соответствует ссылочному типу в традиционных языках программирования. Он позволяет задать имя фрагмента, по которому ему может быть присвоено значение.
Кроме того тип name используется для передачи части имени в структурированные фрагменты кода (фрагментированные подпрограммы). Например, если подпрограмма вырабатывает значения для набора фрагментов с разными индексами, например, x[1], x[2], x[3], ..., то x является частью имени каждого из этих фрагментов данных. Тип name можно использовать для реализации этой идеи. Если в подпрограмму передан параметр типа name, то он может быть достроен до нового идентификатора путём добавления к нему индексов. Полученные новые идентификаторы могут использоваться в подпрограмме для обозначения как входных, так и выходных параметров.
Например:
import copy(value, name) as copy;
sub my_routine(name x) {
copy(x[0], x[1]);
}
В этом примере подпрограмма my_routine имеет единственный параметр x типа name. Этот параметр используется в теле подпрограммы для конструирования имён x[0] и x[1], первое из которых является входным, а второе — выходным.
Выражения строятся обычным для языков программирования способом. Поддерживаются следующие операторы:
+ сложение- вычитание* умножение/ деление% остаток от деления (для целых чисел)< меньше<= меньше или равно> больше>= больше или равно== равно!= не равно&& логическое и|| логическое или( и ) скобки для задания приоритетов операций? и : тернарный оператор из языка C. Имеет вид <cond> ? <expr1> : <expr2>int(...) приведение к целочисленному типуreal(...) приведение к вещественному типуstring(...) приведение к строковому типуПриведение типов осуществляется по типу операндов, как, например, в языке C.
Логического типа как такового в языке нет, вместо него используется целочисленный тип. При этом считается, что нулевое значение соответствует лжи, а любое ненулевое значение — истине. Это соответствует логическим значениям языка C.