Использование
После установки Ficus можно запустить примеры из ficus/examples и попробовать написать свою программу. Самая простая и известная программа, конечно, Hello, world!. Откройте текстовый редактор и введите:
println("Hello, world!")
Сохраните в файле helloworld.fx, затем запустите его с помощью команды
$ ./bin/ficus -run helloworld.fx
Программа поприветствует мир. В той же директории, куда вы поместили helloworld.fx появится поддиректория __fxbuild__/helloworld/ с несколькими файлами *.c, включая helloworld.c. Это результат работы компилятора. Кроме того, там же находятся файлы приложений helloworld (на Unix) или helloworld.exe (на Windows).
Можно собрать приложение и запустить его отдельными действиями:
$ ./bin/ficus -app helloworld.fx
$ ./__fxbuild__/helloworld/helloworld
Вы уже видели опции компилятора -app и -run, они указывают компилятору собрать и запустить приложение соответственно. Весь список опций можно получить, запустив
$ ficus -h
Подробности пожно посмотреть в Приложении А в конце этого документа.
Хорошо, давайте посмотрим, как писать программы на Ficus.
Программа на Ficus состоит из одного или нескольких файлов с расширением .fx, называемых модулями. Модуль может импортировать другие модули и/или может импортироваться из других модулей, см. раздел Модули. Модули представляют собой текстовые файлы в кодировке UTF-8, содержащие последовательность (возможно рекурсивную) деклараций, директив и выражений. Где это не привнесет неясности, для краткости будем называть все перечисленное выражениями. Набор выражений разделяется ; или переводом каретки (LF/CR/CRLF). Выражения могут занимать несколько строк. В большинстве случаев Ficus автоматически отделяет одно выражение от другого, но иногда могут потребоваться явное добавление точки с запятой или круглых скобок.
С точки зрения компилятора выражения это последовательность токенов. Токенами являются операторы, идентификаторы, ключевые слова, литералы и пр. Отступы и пробелы в почти не имеют значения для компилятора. Оформляйте код, как вам нравится. Например, следующиий фрагмент кода плохо оформлен, но это все ещё корректный код:
if a>b {
a
}else {println("b победил"); b}
Пробел между a и >, > и b, } и else не обязателен, но можно и поставить. Но пробел(ы) требуется, чтобы отделить a от if. Также для отделения println(...) от b требуется ;, поскольку два последовательных выражения в рамках одного блока расположены на одной строке.
В Ficus пробел это пробельный символ (' ', ASCII 0x20), табуляция (ASCII 0x09), перевод каретки (LF/CR/CRLF), комментарий или любая комбинация всего перечисленного.
Комментарии бывают двух типов:
- Блочный комментарий, ограниченный строками
/*и*/. Такие комментарии могут располагаться в любом месте между токенами, могут занимать одну и более строчек и могут быть вложенными.
/* Комментируем вообще всё
/*
Ищем максимум a и b
a и b
*/
if a > b /* давайте сравним */ {a} else {b /*b>=a*/}
*/
- Однострочные комментарии, начинающиеся с
//и продолжающиеся до конца строки
if a > b {
a // a победитель
} else {
// b победитель,
// и мы об этом сообщаем
println("b победитель")
b // println() и b разделены переводом каретки
}
Как выше сказано, иногда перевод каретки может трактоваться как конец одного выражения и начало следущего, хотя предполагалось обратное. Типичные ситуации:
-
перенос строки перед бинарным оператором трактует последний как унарный оператор, например
+,-или*:val diff = a - b
трактуется как val diff = a; -b, что, скорее всего, не то, что ожидалось. В большинстве таких случаев компилятор выдаст сообщение об ошибке, встретив не-void выражение в середине блока кода. Правильный способ переноса таких выражений на несколько строк:
val diff = a -
b
или
val diff = (a // включение выражения в скобки указывает, что мы задали единое выражение
-b)
Можно поискать подозрительные места в коде по регулярному выражению ^\s+[+-*].
-
перенос строки перед открывающей круглой скобкой
(при вызове фукнции:foo ( x, y )Такой код будет трактован как
foo; (x, y), где первое выражение вернет функциюfoo, второе создаст пару(x, y). В этом случае компилятор также сообщит о не-void выраженииfooв середине блока кода. Для исправления достаточно прижать(к имени функции:foo( x, y )или
foo(x, y) -
перенос строки перед открывающей квадратной скобкой
[при доступе к массиву или индексируемой коллекции:mymatrix [ i, j ]Как и в случае с функцией, код будет трактован как
mymatrix; [i, j], где первое выражение вернет матрицуmymatrix, а второе создаст список из двух элементов[x, y]. Компилятор и здесь укажет на ошибку. Поместите[на ту же строку, что иmymatrix, это исправит ситуацию:mymatrix[ i, j ]или
mymatrix[i, j]
Токены
В Ficus используются следующие типы токенов:
-
литералы, представляющие разнообразные скалярные значения:
-
8-, 16-, 32- или 64-битные, знаковые или беззнаковые целые (литералы типов
int8,uint8,int16,uint16,int32,uint32,int,uint64,int64cсоответственно), 16-, 32- или 64-битные числа с плавающей точкой (типовhalf,floatиdoubleсоответственно):42 // десятичное число 0xffu8 // 8-битное беззнаковое число в шестнадцатеричной нотации 12345678987654321i64 // 64-битное целое, // суффикс i... обозначает литерал n-битного знакового целого // суффикс u... обозначает литерал n-битного беззнакового целого 0777 // восьмеричное число 0b11110000 // целое в двоичной нотации 3.14 // число с плавающей точкой двойной точности 1e-5f // число с плавающей точкой одинарной точности // в экспоненциальной нотации 0.25h // 16-битное число с плавющей точкой // (пока такие числа реально в Ficus не поддерживаются) nan // особый литерал 'not a number' (не число) двойной точности // добавление суффикса 'f' дает 'nan' одинарной точности -inff // литерал '-infinity' (минус бесконечность) одинарной точности // удаление суффикса 'f' дает значение двойной точности. -
логические значения (типа
bool)true false -
текстовые строки (типа
string)"abc" "hello, world!\n" // поддерживаются обычные ESC-последовательности в стиле C /* Не-ASCII символы корректно разбираются из UTF-8 и затем сохраняются и обрабатываются как Unicode (4-байтные) символы. Код ниже выведет 9 */ println(length("привет! \U0001F60A")) /* Возможно включение произвольных символов с помощью ASCII или Unicode значений: \ooo — 1-3-разрядный восьмеричный ASCII код, \xXX — 2-разрядный шестнадцатеричный ASCII код, \uXXXX — 4-разрядный шестнадцатеричный Unicode код, \UXXXXXXXX — 8-разрядный шестнадцатеричный Unicode код, */ "Hola \U0001F60A" // Как и в Python, внутри f-строки с помощью интерполяции {} можно использовать значения val r = 10 println(f"площадь окружности радиуса R={r} составляет={3.1415926*r*r}") // строка выше преобразуется парсером в println("площадь окружности радиуса R=" + string(r) + " составляет=" + string(3.1415926*r*r)) // поэтому пользовательские объекты также можно интерполировать, // предоставляя для них функцию string(): type point = { x: int; y: int } // дабы избежать путаницы, литералы '{' and '}' // в f-строке необходимо продублировать fun string(p: point) = f"{{x={p.x}, y={p.y}}}" val pt = point { x=10, y=5 } println(f"pt={pt}") // Возможно использование многострочных строковых литералов. // По умолчанию символы конца строки в сгенерированной строке сохраняются. // \r\n заменяются на \n для кросс-платформенной совместимости. val author="anonymous" f"многострочные строковые литералы ограничены кавычками, как обычные строковые литералы и могут включать в себя значения {author} " // Добавьте \ перед концом строки, // чтобы удалить его и последующие пробелы из литерала val errmsg = "Очень длинное и \ детализированное сообщение об ошибке, \ описывающее, что же пошло не так." // r-строки в основном используются для регулярных выражений, // поскольку в них можно указывать символьные классы // и другие спецсимволы без дублирования '\' val assigment_regexp = Re.compile( r"(?:val|var)\s+([\a_]\w+)\s*=\s*(\d+|\w+)") -
символы (типа
char) — то, из чего сделаны текстовые строки. Символьные литералы выглядять также как односимвольные текстовые литералы, включенные в одиночные кавычки.chr(ord('A')) == 'A' // ~ true -
полиморфные литералы — пустой список, вектор или 1-, 2- и более мерные массивы (типов
't list,'t vector,t [],t [,]и пр. соответственно)[] -
нулевой указатель null (смотри секцию взаимодействия с C/C++)
null
-
-
идентификаторы — определяют все именованные сущности, встроенные или определенные, в коде: значения, переменные, функции, типы, исключения, вариантные теги и др. Идентификатор начинается с подчеркивания
_или буквы (латинской или нет) и содержит 0 и более последовательных подчеркиваний, букв или десятичных цифр, т.е. идентификатор может быть определен регулярным выражением[\a_]\w+. Идентификатор_имеет особое значение, он определяет неиспользуемый параметр функции или, в общем случае, элемент шаблона, который пользователь проигнорировал (шаблоны обсуждаются далее в этом документе). -
ключевые слова — выглядят как идентификаторы и используются для формирования синтаксических конструкций. Нельзя использовать идентификатор с тем же именем, что и ключевое слово.
Список ключевых слов Ficus:
as break catch class continue do else exception
false finally fold for from fun if import inf inff
interface match nan nanf null operator ref return throw
true try type val var when while
Также есть атрибуты, начинающиеся с @. Они используются для задания свойств определяемых символов, циклов, блоков кода и др.
Перечень атрибутов:
@ccode @data @inline @nothrow @pragma
@parallel @private @pure @sync @text @unzip
Имена стандартных типов трактуются как ключевые слова:
int8 uint8 int16 uint16 int32 uint32 int uint64 int64
half float double bool string char list vector cptr exn
Важно заметить, что можно определить функцию или значение с названием, совпадающем со стандартными типами данных. В частности, общей практикой является присвоение имен целевых типов для фукнций преобразования типов:
fun string(set: 't Set.t) =
join_embrace("{", "}", ", ", set.map(repr))
type ratio_t = {n: int; d: int}
fun double(r: ratio_t) = double(r.n)/r.d
-
операторы
Есть некоторое количество унарных и бинарных операторов:
бинарные:
// переопределяемые бинарные операторы + − * / % ** .+ .- .* ./ .** == != > < <= >= <=> === .== .!= .> .< .<= .>= .<=> & | ^ >> << \ // другие бинарные операторы .{...} = += −= *= /= %= &= |= ^= >>= <<= .={...} && || :: :>унарные
// переопределяемые префиксные операторы ~ // другие префиксные операторы + − * .- ! \ // переопределяемые постфиксные операторы 'Переопределяемые операторы можно заключить в скобки
()и использовать как идентификаторы, например для передачи в функцию. Еще такие операторы можно переопределять с помощью ключевого словаoperator:type ratio = { n: int; d: int } operator < (r1: ratio, r2: ratio) { val scale = r1.d*r2.d if scale > 0 {r1.n*r2.d < r2.n*r1.d} else {r1.n*r2.d > r2.n*r1.d} } fun R(n: int, d: int) = ratio {n=n, d=d} val sorted = [R(1,2), R(3,5), R(2,3)].sort((<)) -
Есть также разделители и скобки
-> => <- @ . , : ; [ ] ( ) { }
Операторы будут подробно разъяснены позже в этом руководстве.
Далее посмотрим как из токенов составляются конструкции языка.