Skip to content

Latest commit

 

History

History
249 lines (211 loc) · 13 KB

README.md

File metadata and controls

249 lines (211 loc) · 13 KB

pepelang

Небольшой интерпретируемый функциональный язык программирования, написанный на F#. Примеры программ - в папке examples, краткая документация - ниже.

Сборка

Весь проект написан на платформе .NET версии .NET 6.0. Для сборки проекта необходимо:

  1. Установить .NET SDK с поддержкой версии .NET 6.0.
  2. Собрать проект либо в Visual Studio, открыв файл ppl/ppl.sln, либо через CLI, прописав в директории ppl команду dotnet build.

Запуск

Для запуска программы, на языке pepelang необходимо запустить собранный бинарник и прописать первым аргументом командной строки путь к файлу с исходным кодом:

Пример:

bin\Debug\net6.0\ppl.exe ../examples/helloworld.ppl

Документация

Далее - краткое введение в язык.

Структура программы.

Программы на языке состоят из списка выражений, которые вычисляются по одному по очереди. Каждое выражение заканчивается символом ;. Программа должна содержать хотя бы одно выражение. Программа на языке записывается в одном исходном файле с расширением .ppl. Программы могут вызывать программы из других файлов с помощью конструкции import. Пример:

import some-name;

Данная конструкция сначала ищет в стандартном наборе библиотек, есть ли библиотека под названием "some-name", и если нет, то ищет файл с названием "some-name.ppl" в папке с файлом, в котором был написана конструкция. Если такой файл существует, программа из него выполняется и всё окружение в исполненной программе, передается в текущую. Можно считать, что когда мы вызываем конструкцию import, он просто выполняет программу из другого файла внутри нашей.

Применение операторов и функций.

Язык pepelang - функциональный, поэтому программы в основном состоят из применения различных функций. Общий синтаксис применения функции выглядит так:

{<func-name> <arg1> <arg2> ... <argn>};

Пример:

{square 2};    // 4
{sum 3 5};     // 8

В языке так же есть операторы - это те же функции, но с особыми именами - их имена могут состоять только из символов из набора "+-=*&|!></~" Пример:

{+ 5 6};       // 11

Конструкция let и let-in

Для определения переменных, функций и операторов используется конструкция let и let-in. Синтаксис Let выглядит так:

let <name> <arg1> <arg2> ... <argn> = <expr>;

Без аргументов конструкция let просто присваивает имени name результат выражения expr. При указании аргументов, выражение автоматически оборачивается в функцию от указанных аргументов, то есть к примеру запись

let twice x = {+ x x};

объявляет функцию twice, которая прибавляет то, что ей подали к самому себе. Стоит отметить, что все функции и операторы, объявленные таким способом являются каррированными, то есть запись

let sum x y = {+ x y};

создает функцию sum, которая является функцией от одного аргумента, возвращающая функцию от одного аргумента, которая в свою очередь уже считает сумму двух чисел. Таким же образом можно объявить оператор - для этого надо использовать в имени оператора только особые символы. После того, как мы прописали конструкцию let, она создает именованную переменную, которая будет жить всё оставшееся время программы. Когда нам необходимо создать временную переменную или функцию, стоит использовать конструкцию let-in. Вот ее общий вид:

let <name> <arg1> <arg2> ... <argn> = <expr1> in <expr2>;

Работает так же как и let, но создает лишь временную переменную, вычисляет с ее помощью значение expr2 и удаляет переменную name из окружения.

Базовые типы

В языке есть следующие базовые типы данных:

  1. int - знаковое 32-битное целое число.
12;
-14;
  1. string - строка
"Hello world!";
  1. bool - булевые тип
true;
false;
  1. none - тип с одним значением None
None;
  1. float - вещественный тип данных
1.64;
0.23;
13.;
.43;
  1. tuple - кортеж из других(любых) выражений
(1, 2);
(1.023, "Meow");
((2, 3), (1, 2, 3, (1.02, 13) ), None);
  1. literal - вспомогательный тип данных. По сути представляет собой строку без пробелов и табуляций. В коде начинает с символа %
%nl;
%some_string;

Лямбда функции

Лямбда функции определяются следующим образом:

\<var-name> -> <expr>;

Пример:

\x -> {+ x 2};
\x -> \y -> {+ x y};

Типы и ограничения типов

В языке pepelang функции могут принимать аргументы любого типа и возвращать любой тип, однако иногда возникает надобность проверки объектов на соответствие какому-то типу, для этого в языке есть ограничения типов, они могут использоваться либо в функции std.match_type, либо в сопоставлении с образцом, в конструкции match (про них дальше). Чтобы объявить ограничение типа используется ключевое слово type. Чтобы обозначить, что на каком то месте может стоять объект либо одного типа, либо другого используется конструкция choice - choice <type1> | <type2> | <type3> .... Чтобы обозначить, что на каком то месте может стоять любой объект, используется специальный символ _. Примеры:

type IntOrString = choice int | string;
type TupleOf3 = (_, _, _);
type Vec2 = (float, float);
type Mat2x2 = (Vec2, Vec2);
type Complex = (IntOrString | Vec2, TupleOf3, (_, _));

Сопоставление с образцом

Сопоставление с образцом выглядит следующим образом:

match <exp> with
| <exp1> -> <exp1_>
| <exp2> -> <exp2_>
    ...
| <expn> -> <expn_> $;

Где слева от стрелок стоят образцы сопоставления, а справа - выражения для вычисления. Выражение exp по очереди сравнивается с каждым образцом и при совпадении вычисляет соответствующее выражение для вычисления. Примеры:

match x with
| _x of int -> "int"
| n of None -> "none"
| (x of choice int | float, y of choice int | float) -> "num tuple2"
| _ -> "something other" $;

Внутренние функции и операторы

В языке помимо обычных функций и операторов, представляющих из себя выражения, в которые можно подставить значения переменных, чтобы вычислить их значение, присутствут так же "внутренние" функции и операторы. Это особый вид выражения, который внутри себя содержит функцию F#, которая принимает на вход список выражений и возвращает выражение. В коде на языке pepelang невозможно определить внутреннюю функцию, это можно сделать только в коде на F# и добавить ее в составе отдельной библиотеки в программу. При применении внутренние функции и операторы ведут себя не так, как обычные. Внутренние функции и операторы не каррированные - при применении их к списку аргументов, функция получает их в виде списка.

Стандартная библиотека

Стандартная библиотека подключается в каждый файл с исходным кодом. Она содержит некоторые функции и операторы для работы с базовыми типами данных.

Операторы

В стандартной библиотеке находятся следующие операторы:

+, -, *, /, <, >, <=, >=, =, !=, ||, &&, !, >>
  • >> - оператор композиции функций.

Функции

  1. std.mod - остаток от деления. Принимает два аргумента.
{std.mod 5 3};    // 2
  1. std.match_type - соответсвует ли выражение типу или ограничению типа. Принимает два аргумента.
{std.match_type 5 int};                      // true
{std.match_type (2, 3.0) (int, float)};      // true
{std.match_type (2, (None) ) (int, float)};  // false
  1. std.print - вывод выражений. Принимает произвольное количество аргументов. Если на вход поступает литерал %nl выводит символ перехода на новую строку.
{std.print "hello world!" %nl};
{std.print (1, 2, 3) %nl "meow" %nl};
  1. std.read_line - считывает строку из потока ввода. Игнорирует все аргументы.
let s = {std.read_line};
  1. std.to_int - конвертирует выражение в число. Принимает один аргумент.
{std.to_int "35"};  // 35
{std.to_int 35.4};  // 35
  1. std.to_float - конвертирует выражение в вещественное число. Принимает один аргумент.
{std.to_float "35.4"};  // 35.4
  1. std.parse - парсит строку и пытается конвертировать ее в выражение из базовых типов. Принимает один аргумент - строку.
{std.parse "3"};                      // 3
{std.parse "(1, 2, (3.45, None) )"};  // (1, 2, (3.45, None))
  1. std.id - функция, которая возвращает первый ее аргумент.
{std.id 3}; // 3

Примеры программ

Вычисление факториала:

let fact n = if {= n 0} then 1 else {* n {fact {- n 1} } };

{std.print "Enter n:"};
let n = {std.parse {std.read_line} };

if {std.match_type n int} then
    {std.print {fact n} %nl}
else
    {std.print "Incorrect input" %nl };

Больше примеров в папке examples. Там - реализация списков, двоичных деревьев поиска и линз.