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

Данная статья предназначена в первую очередь для проверки основных концепций нового языка программирования, а также для получения обратной связи от читателей Хабра. Ведь согласно наблюдению «Хабр-ума палата», не замыленный взгляд со стороны очень сильно помогает в проработке новых идей.

Этот проект очень долго был без собственного названия и в публикациях назывался просто и абстрактно «новый язык». Но после нескольких статей, временное название «новый язык» постепенно превратилось в имя собственное NewLang, которое я и решил в конечном итоге оставить (что еще раз подтверждает поговорку, что нет ничего более постоянного, чем что-то временное).


NewLang — это язык программирования высокого уровня в котором можно сочетать стандартные алгоритмические конструкции с декларативным программированием и тензорными вычислениями для задач машинного обучения.

Основной особенностью языка является легкий, логичный и непротиворечивый синтаксис, который основан не на использовании зарезервированных ключевых слов, а на строгой системе грамматических правил с использованием знаков препинания (в список которых входят и операторы языка). Основные свойства и особенности языка:
  • Возможность работы как в режиме интерпретатора, так и компилятора.
  • Динамическая и статическая типизация с возможностью указания типов в явном виде.
  • Статическая типизация является условно строгой (автоматическое приведение типов отсутствует, но допускается преобразование между некоторыми типами данных, например, целое число может быть автоматически преобразовано в вещественное, но не наоборот)
  • Автоматическое управление памятью.
  • ООП в виде явного наследования классов и «утиная» типизация.
  • На уровне синтаксиса поддержка нескольких типов функций (обычные и чистые функции без побочных эффектов).
  • Необязательные и именованные параметры функций.
  • Возможны вставки кода на языке реализации (С/С++).
  • Простая интеграция с уже существующими программными библиотеками (в том числе импорт нативных переменных и функций из С/С++).
  • Имеется REPL read-eval-print loop — «цикл: чтение — вычисление — вывод».


Зачем нужен NewLang?


У всех современных языков программирования происходит постоянное развитие (читай усложнение) синтаксиса по мере выхода новых версий. Это является своего рода, платой за появление новых возможностей и воспринимается пользователями как естественное явление.

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

У NewLang сложность языковых конструкций естественно ограничена за счет разделения синтаксиса языка на две части, что упрощает его изучение и использование. Основной синтаксис — для написания программ в объектно-ориентированном (императивном) и декларативном стилях, который основан не на зарезервированных ключевых словах, а на строгих грамматических правилах и Расширенный синтаксис — когда основного синтаксиса становится недостаточно, или требуется использовать языковую конструкцию языка реализации.

Еще одно неудобство современных мейнстримовых языков, большинство из них были созданы до начала эпохи машинного обучения, поэтому тензорные вычисления у них выполнены в виде отдельных библиотек, а не встроены в основной синтаксис языка и систему базовых типов. У NewLang тензорные вычисления доступны «из коробки» (используется библиотека libtorch), а арифметические типы данных являются скалярами (тензорами нулевой размерности).

Основной синтаксис


Основной синтаксис NewLang — простой и логичный за счет того, что он построен исключительно на грамматических правилах и не использует каких либо зарезервированных ключевых слов, а все буквенно-символьные последовательности рассматриваются как идентификаторы в которых можно использовать любые не-ASCII символы.

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


Названия встроенных типов или имена служебных функций системной библиотеки определяются конкретной реализацией языка, поэтому не являются зарезервированными ключевыми словами и при необходимости могут быть переопределены, например, для создания собственного, предметно-ориентированного диалекта (DSL — domain-specific language), если в этом возникнет необходимость. Но сама структура программы и логика выполняемого алгоритма все равно останутся понятны всем, кто знаком с правилами основного синтаксиса NewLang.

Пример скрипта Hello world! на NewLang
#!./nlc --eval 
# Определение функции hello
hello(str) := { 
  printf := @import('printf(format:Format, ...):Int');  # Импорт стандартной C функции
  printf('%s\n', $str);  # Вызов C функции с проверкой типов аргументов по строке формата
};
hello('Привет, мир!'); # Вызвать функцию

Вывод: Привет, мир!

Расширенный синтаксис


Расширенный синтаксис — это возможность вставить в текст программы NewLang исходный код на языке реализации. Сейчас это С/С++, что позволяет использовать любые возможности этого мощного языка программирования.

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

Еще немного примеров:


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

Создание переменных
> scalar := 42
42

> tensor := [1,2,3,4,5,]  # Тип тензора выводится автоматически
[1, 2, 3, 4, 5,]:Char

> str := '$1 string'
$1 string


Арифметические операции
> tensor * 2
[2, 4, 6, 8, 10,]:Short

> tensor * 20
[20, 40, 60, 80, 100,]:Short

> tensor * 0.5
[0.5, 1, 1.5, 2, 2.5,]:Double

> tensor / 2 # Результат деления — число с плавающей точкой
[0.5, 1, 1.5, 2, 2.5,]:Double

> tensor // 2 # Целочисленное деление без остатка
[0, 1, 1, 2, 2,]:Char

> tensor % 2 # Целочисленный остаток от деления
[1, 0, 1, 0, 1,]:Char


Строковые операции
> str = 'сцепеление строк ' ++ str
сцепеление строк $1 string

> str('строка как шаблон')
сцепеление строк строка как шаблон string


Преобразование тензоров


В эпоху машинного обучения тензоры являются основными элементами вычислений, поэтому для конвертирования данных в тензоры используется отдельная синтаксическая конструкция, состоящая из двойных квадратных скобок [[ данные ]]. Подробнее про особенности преобразования типов можно прочитать далее.
> tstr := [["Тест"]]   # Создать тензор из строки широких символов
[1058, 1077, 1089, 1090,]:Int

> t2 := [[ "Тест" ]]:Int[2,2] # Тоже самое, но тензор двухмерный
[
  [1058, 1077,], [1089, 1090,],
]:Int

> StrWide(tstr) # Конвертировать тензор обратно в строку
Тест

> Double(t2)    # Изменить тип данных тезора
[
  [1058, 1077,], [1089, 1090,],
]:Double

> t3 := [[ t2 ]]:Char[4] # Преобразовать тип данных тензора и его размерность
[34, 53, 65, 66,]:Char


Синтаксис NewLang:


При разработке синтаксиса я старался придерживаться уже сложившихся правил, чтобы не создавать множественных смыслов, зависящих от контекста. И одновременно «объять необъятное»

Основы


  • Операторы разделяются точкой с запятой «;».
  • Отступы и переводы строк игнорируются (очень хотелось иметь возможность автоматического форматирование кода).
  • Многострочные комментарии в исходном коде соответствуют стилю С/С++ и должны располагаться между символами /* и */. Вложенность многострочных комментариев не поддерживается.
  • Однострочные комментарии начинаются с символа «#» до перевода строки, что соответствует комментариям в стиле Python и Bash.
  • Последовательность выполняемых команд, которая должна выполняться как единое целое, заключается в фигурные скобки «{}».
  • Программные вставки расширенного синтаксиса на языке реализации заключается в фигурные скобки со знаком процента %{ /* тут может быть любой код на C/C++*/ %}.


Создания объектов и присвоения новых значений


Для создания объектов и присвоения им новых значений в NewLang используется сразу три разных оператора. Оператор «::=» используется только для создания новых объектов, а если объект с таким именем уже существует, то генерируется ошибка.

Оператор «:=» используется для тех же целей, но если объект с таким именем уже существует, то ошибки не происходит, а новое значение присваивается уже существующему объекту. И последний оператор «=» применяется только для присвоения значения уже существующим объектам, и если объект с указанным именем отсутствует, то тоже происходит ошибка.

Использование трех разных операторов для создания/изменения объектов позволяет более гибко контролировать подобные операции и выявлять логические ошибки в коде на более раннем этапе.

var ::= 1.0; # Создать новую переменную var без указания типа
var = 100; # Присвоить новое значение уже существующей переменной
printf := @import('printf(format:Format, ...):Int'); /* Создать новый или переопределить 
объект printf, который будет результатом выполнения глобальной функции @import */


Идентификаторы объектов и модификаторы


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

В NewLang существует возможность указания области видимости и времени жизни объекта с помощью модификатора — специального символа перед именем переменной. Это может показаться немного похожим на венгерскую нотацию, но в отличие от нее, модификатор не имеет отношения к типу объекта и не является частью имени идентификатора. К тому же в качестве модификаторов используется строго определённые символы, назначение которых определено заранее.

Так, символ «$» в начале имени обозначает локальную переменную, время жизни которой ограничено текущей областью видимости и при её завершении локальная переменная уничтожается. Символ «@» обозначает глобальную переменную, а сам объект сохраняет свое состояние даже после выхода из текущей области видимости. Так же обозначаются и имена типов данных, например, при создания новых типов, а в качестве модификатора используется символа двоеточия «:»

Семантика обращения к аргументам функций очень похоже на работу с аргументами в bash скриптах, где $1 или $arg — порядковый номер или имя аргумента (происходит обращение к локальным переменным в текущей области видимости).

Использование модификаторов является обязательным только в двух случаях:
  1. При создании нового типа данных, так как типы всегда создаются в глобальной области видимости, а их символьные имена должны быть уникальными
  2. При обращении к объектам NewLang внутри программных вставок кода на языке реализации, так как они используется как маркеры при поиске идентификаторов NewLang в коде С/С++.
В остальных случаях, для обращения к переменным указывать их модификаторы не обязательно. И если при обращении к объекту модификатор не указан, то сперва ищется локальная переменная, а потом глобальная с таким же именем. Причем, локальная переменная будет перекрывать глобальную.

Так же следует иметь в виду, что компилятор может генерировать код для прямого обращения к локальным объектам уже на этапе компиляции, тогда как для обращения к глобальным объектам, или если модификатор области видимости отсутствует, компилятор вынужден каждый раз встраивать runtime вызов функции поиска объекта в таблице символов.

Система типов


Так как система типов языка динамическая, то явное указание типа не влияет на размер переменной и является только своего рода логическим ограничением на возможность присвоения переменной значения другого типа.

Информация о типах используется при проверке их совместимости, когда существующему объекту присваивается значение другого типа. Такая операция возможна только когда типы совместимы между собой и допускают автоматическое приведение. Это справедливо как во время парсинга/компиляции исходного теста, так и во время выполнения в режимах интерпретатора и/или скомпилированного файла.

Арифметические типы:


Арифметические типы данных являются тензорами — массивами чисел одного типа с произвольным количеством измерений и одинаковым размером столбцов в каждом. Единичное число тоже тензор нулевого размера.

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

Проблемы беззнаковых чисел (из интернета)
Во-первых, вычитание двух беззнаковых чисел, например 3 и 5. 3 минус 5 равно 4294967294, т.к. -2 не может быть представлено как беззнаковое число. Во-вторых, непредвиденное поведение может возникнуть при смешивании целочисленных значений со знаком и без знака. С++ может свободно преобразовывать числа со знаком и без знака, но не проверяет диапазон, чтобы убедиться, что вы не переполняете свой тип данных.

В C++ всё же есть несколько случаев, когда можно (или необходимо) использовать беззнаковые числа. Во-первых, числа без знака предпочтительнее при работе с битами. Во-вторых, использование беззнаковых чисел связаных с индексацией массивов.

Но это мой случай, так как индекс может быть отрицательным и даже не числом, а диапазоном или многоточием.
З.Ы. И даже зная об этом, все равно умудрился недавно словить баг с отрицательными индексами у словарей!


Имена встроенных арифметических типов говорят сами за себя: Char, Short, Int, Long, Float, Double, ComplexFloat, ComplexDouble. Отдельным типом идет логический тип Bool, который может принимать значения только 0 или 1 (false/true соответственно), и в зависимости от выполняемой операции может быть отнесен к целочисленным типам, так и не входить в их состав.
(данный подход интерпретации логического типа данных был взят из библиотеки Torch)
// Treat bool as a distinct "category," to be consistent with type promotion
// rules (e.g. `bool_tensor + 5 -> int64_tensor`). If `5` was in the same
// category as `bool_tensor`, we would not promote. Differing categories
// implies `bool_tensor += 5` is disallowed.
//
// NB: numpy distinguishes "unsigned" as a category to get the desired
// `bool_tensor + 5 -> int64_tensor` behavior. We don't, because:
// * We don't want the performance hit of checking the runtime sign of Scalars.
// * `uint8_tensor + 5 -> int64_tensor` would be undesirable.


В будущем планируется добавить классы чисел для длинной арифметики и дробей, для чего зарезервированы названия типов BigNum, Currency и Fraction.

Доступ к элементам тензора происходит по целочисленному индексу, который начинается с 0. Для многомерного тензора, индексы элемента перечисляются в квадратных скобках через запятую. Поддерживается доступ к элементам через отрицательный индекс, который обрабатывается точно так же, как в Python (-1 последний элемент, -2 предпоследний и т.д.).

Литерал тензор в тексте программы записывается в квадратных скобках с обязательной завершающей запятой, т. е. [1, 2,] — это литерал одномерный тензор из двух чисел. После закрывающей скобки тип тензора может быть указан в явном виде. Если тип не указан, то он выводится автоматически на основании указанных данных и выбирается минимально возможный байтовый размер, который позволяет сохранить все значения без потери точности.

Примеры:
$var_char := 123; # Тип Char выводится автоматически
$var_short := 1000; # Тип Short выводится автоматически
$var_bool := [0, 1, 0, 1,]; # Тензор из 4 элементов. Тип Bool выводится автоматически
$tensor[10,10]:Int := 1; # Тензор Int размером 2x2 инициализированный 1
$scalar := $tensor[5,5]; # Присвоить скаляру значение указанного элемента тензора


Строковые типы данных:


Поддерживаются два типа строк, StrWide — символьные (широкие) и StrChar — байтовые. Различия между ними заключается в типе единичного элемента. У символьных строк единичным элементом является широкий символ wchar_t, а у байтовой строки единичным элементом является один байт (точнее char, т. е. байт со знаком). Символьные строки литералы в исходном тексте записывается в «двойных кавычках», а байтовые строки в 'одинарных кавычках'.

Количество элементов символьной строки возвращается в широких символах, а размер байтовой строки в байтах, поэтому и обращение к элементу строки по индексу происходит соответственно либо к символу, либо к байту.

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

Например:
$template := "${name} $1"; # Обычная строка
$result := $template("шаблон", name = "Строка"); # result = "Строка шаблон"


Составные типы данных:


Словарь


Словарь — набор данных произвольного типа с доступом к отдельным элементам по целочисленному индексу или по имени элемента при его наличии (он похож и на tuple и на структуру одновременно). Словари от тензоров отличаются тем, что являются только одномерными массивами, но каждый элемент может содержать произвольное количество элементов любого типа, в том числе и другие словари.

Доступ к элементам словарей происходит по имени элемента, которое записывается через точку от имени переменной, либо по целочисленному индексу. Индекс также начинается с 0 и как у тензоров, тоже может быть отрицательным.

Литерал с типом «словарь» в тексте программы записывается в круглых скобках с обязательной завершающей запятой, т. е. (,) — пустой словарь, (1, 2= «2», name=3,).

Перечисление


Перечисление — это не отдельный тип данных, а обычный словарь, у которого все элементы имеют уникальные имена и целочисленные значение, которое явно указывается при определении или вычисляется автоматически (на единицу больше предыдущего элемента). У перечислений тип значения указывается сразу после закрывающей скобки через двоеточие (ONE=1, TWO=, THREE=): Int.

Классы


Класс (реализовано частично) — тип данных, с помощью которого реализуется один из принципов ООП — наследование. При создании экземпляра класса создается новая переменная, у которой сохраняется информацию о своем родителе и которая наследует от него свойства и методы. Тип данных «класс» аналогичен словарю, но все свойства обязаны иметь имена (хотя доступ к свойствам класса по индексу так же возможен).
Литерал с типом «Класс» в тексте программы записывается в круглых скобках без завершающей запятой, т. е. () — пустой класс, (1, 2= «2», name=3).

Пока остальные детали классов до конца не реализованы, поэтому описывать их не буду, т. к. в итоговом варианте синтаксис классов и определения их методов могут поменяться.

Функции


Синтаксис NewLang поддерживать несколько типов функций (а в будущем и методов классов): обычные функции, чистые функции и простые чистые функции.

Для всех типов функций поддерживаются аргументы по умолчанию. При создании функции, её аргументы указываются как в Питоне, т. е. вначале идут обязательные аргументы, потом аргументы со значениями по умолчанию, где имя аргумента отделяется от его значения по умолчанию знаком равно «=». Если функция допускает обработку произвольного количества аргументов, то последним в списке параметров указывается многоточие.

Обычная функция


Обычная функция — такие функции являются именно обычными функциями в понимании С/С++. Внутри них можно писать совершенно любой код, включая проверки условий, циклы, вызовы других функций и т.д.

Внутри обычной функции можно обращаться к локальным и глобальным объектам, и они могут содержаться вставки на языке реализации, например, для вызова функций из внешних библиотек.

Вставки на языке реализации оформляются в виде %{ %} и могут содержать любой текст на С/С++, а прямо из него можно обращаться к локальным и глобальным объектам NewLang так же, как и в обычном синтаксисе, указывая первым символом имени соответствующий модификатор ($ для локальных объектов и @ для глобальных).

Технически, такая программная вставка просто переносится трансплайтером непосредственно в исходный текст генерируемого файла, а все идентификаторы NewLang специальным образом декорируются (добавляются специальные маркеры для их идентификации), после этого исходный текст подается на вход обычному компилятору С++. Для локальных объектов трансплайтер может генерировать код для прямого доступа к объекту на этапе компиляции, а для работы с глобальными объектами вынужден использовать runtime вызовы функции поиска в таблице символов.

Например:
print(str) := { 
    %{ 
        printf("%s", static_cast<const char *>($str)); /* Прямой вызов С функции */ 
    %} 
};


Чистые функции


Чистая функция — это тоже обычная функция, только в том смысле, какой в него вкладывает функциональное программирование. Создания чистой функции происходит с помощью оператора «:-». У чистой функции отсутствует доступ к контексту и глобальным объектам, поэтому она может обрабатывать только те данные, которые были ей переданы в качестве аргументов.

Программные вставки на языке реализации внутри чистых функций не запрещены и могут использоваться, например, для отладки. Но делается это на страх и риск разработчика. Именно он отвечает за их «чистоту», например при вызове функций из внешних библиотек.

Sum(arg1, arg2) :- {$arg1+$arg2;}; # Вернуть сумму аргументов


Так как в языке отсутствует оператор возврата данных из текущего блока выполнения (аналог оператора return <данные>), то возвращаемым значением функции / блока кода всегда является результат выполнения последней операции.

Простые чистые функции


Простые чистые функции — отдельный класс чистых функций, которые предназначены только для вычисления логического результата (т. е. они являются предикатами) и их отличает упрощенная формой записи. Тело простой чистой функции состоит из последовательности операторов, которые разделяются запятыми и заканчиваются, как и любое выражение, точкой с запятой. Все операторы простой чистой функции всегда приводятся к булевому значению, а итоговый результат функции вычисляется по одной из возможных логических операций: И, ИЛИ и исключающее ИЛИ.

Например:
func_and(arg1, arg2) &&= arg1==3, arg2 > 0;  # Простая чистая функция Логическое И
func_or(arg1, arg2) ||= arg1==3, arg2 > 0; # Простая чистая функция Логическое ИЛИ
func_xor(arg1, arg2) ^^= arg1==3, arg2 > 0;  # Простая чистая функция Исключающее ИЛИ


Специальные типы данных:


None


None (пустой тип) — не содержит значения (точнее имеет одно значение None) и совместим с любым другим типом данных. Указывается в тексте программы как один подчерк «_». Значение None имеют не инициализированные переменные и при попытке чтения из такой переменной возникает ошибка.

Тип переменной может быть явно указан или выведен автоматически из присваиваемого значения. Присвоить новое значение уже инициализированной переменной можно только для совместимого типа, так как неявное преобразование типов не допускаются.

$var := _; # Создать переменную со значением None
$var2 := var; # Ошибка!!! Нельзя прочитать неинициализированную переменную var
$var = 1000; # У переменной будет тип Short (минимальный размер для хранения значения)
$var = 0,5; # Ошибка!!! Short ← Float не совместимы
$var = _; # Очистить значение переменной
$var = 0,5; # Теперь можно, т. к. None совместим с любым типом


Диапазон (Range)


Диапазон (реализовано частично) — специальный тип данных, являющейся приблизительным аналогом типа «генератор» в Python. К диапазону можно обращаться как к итератору и он будет поочередно выдавать элементы в указанном интервале с заданным шагом. Диапазон в тексте программы указывается как два или три элемента через две точки, например 1..5 — диапазон от единицы до пяти с шагом по умолчанию 1. В качестве параметров диапазона можно указывать не только литералы, но и имена переменных. Например, 0,1..$stop..0,1 — диапазон от значения 0,1 до значения, указанного в переменной $stop с шагом 0,1.

Диапазон для целых чисел можно использовать в качестве индекса у тензоров (точнее, у любых объектов, которые допускают доступ к своим элементам по индексу, т. е. тензоры, словари и текстовые строки). Фактический, это поведение аналогично slice в языке Python и array[1:5] в Python означает тоже самое, что и array[1..5] в NewLang.

В качестве индекса у тензоров еще можно указать произвольное количество измерений с помощью многоточия, т. е.
$tensor[…, 0] = 0; # Обнулить все первые элементы в каждом измерении.


Итераторы


Итераторы (в разработке) — самый сложный и неоднозначный тип данных для работы с элементами коллекций. Для работы с итераторами зарезервированы символы "!" и "?", но сами итераторы пока не реализованы.

Преобразование типов


Явное приведение типов


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

Так как символьные названия типов относятся к деталям реализации, то явное преобразование в конкретный тип данных производится с помощью вызова функции с системным именем, т. е. Bool(), StrWide(), Long и т. д. Причем у тензоров при таком преобразовании изменяется только тип данных, но размерность тензора не меняется.

Для преобразования любого типа данных в строку ещё можно использовать оператор конкатенации строк, которой преобразует любой тип данных в строковое представление. Но так как строковых типов два (байтовые и широкие строки), то тип строки определяется первым аргументом в операторе конкатенации, т. е.
"" ++ 123 # "123" - Строка широких символов
'' ++ 123 # '123' - Байтовая строка

Или преобразовать любое значение в строковое с помощью строки-шаблона:
val := 12345;
"$1"(val) # Будет строка "12345"


Tensor comprehensions


В эпоху машинного обучения тензоры являются основным элементом вычислений, поэтому для конвертирования данных в тензоры используется отдельная синтаксическая конструкция, состоящая из двойных квадратных скобок [[ данные ]]. Фактически это оператор и функция времени выполнения в зависимости от указанных между двойные квадратными скобками выражения.

Чтобы преобразовать любую переменную в тензор (с учетом допустимости такого преобразования), её достаточно указать между двойными квадратными скобками. Выражение [[ varibale ]] — преобразует переменную varibale в одномерный тензор с автоматическим выводом типа данных. Для преобразования в одномерный тензор конкретного типа используется выражение [[ varibale ]]:Type, где Type — любой из арифметических типов.

Если требуется преобразовать переменную не в одномерный тензор, а в тензор конкретного типа и заданной размерности, то это делается выражением [[ varibale ]]:Type[2,2], которая вернет тензор с размерностью 2х2 и типом Type у элементов.

Внутри двойных квадратных скобок может быть не только любое выражение, но и литерал или диапазон. В этом случае, они также раскрываются в тензор по таким же правилам. В будущем планирую добавить возможность указания сразу нескольких значений через запятую для их объединения в один тензор.

Примеры:
>[[(1,2,3)]]  # Тензор из словаря
[1, 2, 3,]:Char

>[['first second']]  # Байтовая строка в тензор
[102, 105, 114, 115, 116, 32, 115, 101, 99, 111, 110, 100,]:Char

> [[(first='first', space=32, second='second')]]  # Получаем тензор из словаря с такими же данными
[102, 105, 114, 115, 116, 32, 115, 101, 99, 111, 110, 100,]:Char

>[[ 0 ... ]]:Double[10,2]   # Тензор заданного формата с нулями
[
  [0, 0,], [0, 0,], [0, 0,], [0, 0,], [0, 0,], [0, 0,], [0, 0,], [0, 0,], [0, 0,], [0, 0,],
]:Double

>[[ rand() ... ]]: Int[3,2]  # Тензор со случайными данными
[
  [1804289383, 846930886,], [1681692777, 1714636915,], [1957747793, 424238335,],
]:Int

>[[ 0..10 ]]: Int[5,2]  # Тензор из диапзона
[
  [0, 1,], [2, 3,], [4, 5,], [6, 7,], [8, 9,],
]:Int

>[[ 0..0.99..0.1 ]]  # Или даже так
[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9,]:Double


Операторы и управляющие конструкции


Операторы:


Все операторы имеют парный оператор с присвоением значения.
  • + и += сложение арифметических типов данных
  • — и -= вычитание арифметических типов данных
  • / и /= деление (результат число с плавающей точкой)
  • // и //= целочисленное деление с округлением к меньшему числу (как в Python)
  • * и *= умножение
  • ** и **= возведение в степень (он же используется и для повторения текстовых строк)
  • ++ и ++= конкатенация строк с автоматическим приведением аргументов к стоковому типу (символ инкремента специально используется вместо одиночного плюса для того, чтобы в явном виде разделить конкатенацию строк и операторы арифметического сложения)


Операторы сравнения:

  • <, >, <=, >= классические для сравнения скаляров
  • ==, != операторы сравнения с автоматическим приведением совместимых типов для любых объектов
  • ===, !== оператор точного сравнения для любых объектов (автоматического приведения типов не выполняется)


Проверки типов (в разработке):


Проверка имени класса «~» — немного похож на оператор instanceof в Java. Левым оператором должен быть проверяемый объект, а правым оператором — название типа, строка литерал или объект строкового типа с именем класса. Результатом операции будет истина, если правый операнд содержит название класса проверяемого объекта или он присутствует в иерархии наследования у проверяемого класса.

name := "class"; # Строка с именем класса
var ~ :class; 
var ~ "class";
var ~ name; 

(field1=«value», field2=2, field3=«33»,) ~~ (); # Истина (т. е. левый операнд словарь)
(field1=«value», field2=2, field3=«33»,) ~~ (field1=_); # Тоже истина (т. к. поле field1 присутствует у левого операнда)


Утиная типизация «~~» — приблизительный аналог функции isinstance() в Python, который для простых типов сравнивает совместимость типа левого операнда по отношению к правому, а для словарей и классов в левом операнде проверяется наличие всех имен полей, присутствующих у правого операнда. т. е.
(field1=«value», field2=2, field3=«33»,) ~~ (); # Истина (т. е. левый операнд словарь)
(field1=«value», field2=2, field3=«33»,) ~~ (field1=_); # Тоже истина (т. к. поле field1 присутствует у левого операнда)


Строгая утиная типизация «~~~» — для простых типов сравнивается идентичности типов без учета совместимости, а для составных типов происходит строгое сравнение всех свойств. Для данной операции, пустой тип совместим только с другим пустим типом!

Управляющие конструкции (в разработке)


Условный оператор


В качестве оператора проверки условия используется синтаксическая конструкция, соответствующая по смыслу термину «следует», т. е. тире и угловая скобка «->». Такая запись условного оператора очень похожа на математическую и легко объединяется в последовательности для проверки множественных условий вида «else if».

В общем случае условный оператор имеет вид:
условие -> действие;
или  
(условие) -> {действие};
или  
(условие1 || условие2) -> {действие} -> {действие иначе};

Или расширенный вариант «else if», для наглядности записанный с отступами:
(условие1) -> {действие1}
        (условие2) -> {действие2}
        (условие3) -> {действие3}
        -> {действие_иначе};


Операторы циклов (в планах)


Операторы циклов пока в разработке, т. к. плотно связаны с итераторами.

Пока планирую для циклов использовать конструкции: (условие) <--> {тело цикла};

Или так: (условие) ->> {тело цикла};

И хотя синтаксис мне не очень нравится, но я решил пока не ломать над этим голову и планирую попробовать несколько вариантов оформления циклов.

Операторы прерывания потока выполнения команд (реализовано частично)


Оператором прерывания потока выполнения команд и возврата из текущей функции, т. е. самым близким аналогом оператора return является оператор два символа минус «--». Но в отличие от классического return, оператор возврата не возвращает значения, т. к. значение из любой функции или блока кода возвращается всегда и им является результат выполнения самой последней операции (или None, если такая операция отсутствует).

Пока не придумал, как оформлять оператор прерывания потока выполнения в случае ошибки (при его выполнении будет происходить генерация исключения), поэтому, если будут предложения, пишите в комментариях к статье (и про оформление циклов тоже).

Обработка ошибок (в планах)


В самом начале работ я ориентировался на классический вариант обработки исключений, который в обычных языках программирования обычно оформляется ключевыми словами trycatchfinally с различными вариациями. Но в условиях жестких ограничений на синтаксис языка, и невозможности использовать ключевые слова, комбинировать символы для указания разных типов блоков при обработке исключений, было бы крайне сомнительной затеей. Ведь основная цель разработки NewLang — простота и понятность кода, а тут с самого начала могут появиться комбинации скобочек, стрелочек, палочек и других подобных символов.

И тут в голову пришла очень простая мысль. А ненужно повторять логику обработки ошибок из классических языков программирования! Ведь основная цель подобных синтаксических конструкций — выделить участок кода где возможно возникновение ошибки, перехватить и обработать правильный тип исключения. Ведь классические языки программирования изначально были жестко привязаны к машинному представлению данных в оперативной памяти компьютера и тип данных для них играл принципиально важное значение. Но это не является ограничением для языков с динамической типизацией!

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

Наверно, это проще показать на примере:

$error := {{ # начало блока try
	call_or_exception1(); 
	call_or_exception2(); 
}}; # конец блока try
# Обычные условные операторы вместо типизированных блоков catch
($error ~ :type1)->{ код обработки ошибки 1}
($error ~ :type2)->{ код обработки ошибки 2};


Самое удивительное, что при таком подходе значительно упрощается и семантика блоков try finally, которые вообще становятся не нужны.

Исходный код на Java:
    try {
        try {
            throw new Exception("a");
        } finally {
            throw new IOException("b");
        }
    } catch (IOException ex) {
        System.err.println(ex.getMessage());
    } catch (Exception ex) {
        System.err.println(ex.getMessage());
    }


Эквивалентный ему на NewLang:

$catch := {{  
	$finally := {{  
		Error1("1");  
	}};
	Error2("2"); 	# Строка выполнится даже при возникновении исключении Error1
	$finally;		# Error1 вернется, если не будет Error2
}}
($catch ~ :Error1) -> printf(«%s», $catch)
($catch ~ :Error2) -> printf(«%s», $catch);


Как все это попробовать?


Сейчас сборка проекта реализована только под Linux и если кроме текстового описания захочется в живую поэкспериментировать на своей машине, то придется собрать интерпретатор из исходников самостоятельно.

Так как текущий вариант предназначен первую очередь для отработки концепции, то часть из описанных возможностей пока не реализована (алгоритмические конструкции, наследование классов, итераторы, некоторые операции и т. д).

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

Сборка REPL из исходников (пока только под Linux)


Подготовка репозитория

  • Скачать исходники
  • Скачать и развернуть архив libtorch в каталоге contrib (PyTorch Build: Stable (1.10.*) -> Your OS: Linux -> Package: LibTorch -> Language: C++ / Java -> Compute Platform: CPU -> Download here (cxx11 ABI): libtorch-cxx11-abi-shared-with-deps-1.10.2+cpu.zip)
  • Активировать и скачать исходники субмодулей (git submodule init && git submodule update)
  • В каталоге contrib запустить файл build.sh для сборки библиотеки libffi
  • В каталоге core запустить файл compile_syntax.sh для генерации файлов парсера и лексического анализатора. Может потребоваться установка утилит flex и bison. Если что, у меня установлены flex 2.6.4 и bison (GNU Bison) 3.7.4

Собрать

  • Юнит-тесты (newlang_test): в каталоге core выполнить команду make CONF=UnitTest
  • Интерпретатор (nlc): в каталоге core выполнить команду make CONF=Debug


Утилита nlc (NewLangCompiler)


В текущее время nlc поддерживать работу только в режиме интерпретатора (несмотря на название). Для тестирования и простой проверки компилятор не нужен, хотя на первых порах я делал именно его. Но трудоемкость работ по постоянной переделке под новый синтаксис оказалась очень высокой, поэтому на время первичной отладки языковых конструкций было принято волевое решение ограничиться интерпретатором, как более простым и быстрым способом проверки различных гипотез, а разработку настоящего компилятора (в виде трансплайтера на язык С++), отложить до окончательной проработки синтаксиса.

Планы на будущее


Естественное, одна статья и несколько маленьких примеров не дают исчерпывающей информации о возможностях языка. Да и сами возможности пока не раскрыты в полной мере. Ведь текущая версия, это скорее тестовая платформа для проверки декларируемых концепций и основного синтаксиса.

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

Если говорить о планах (естественно, в будущих версиях что-то может добавиться или измениться порядок их реализации), но в настоящий момент роадмап развития NewLang мне видится следующим образом:
  • Доделать стандартные управляющие конструкции, обработку ошибок и итераторы.
  • Доработать систему типов с учетом множественного наследования классов.
  • Реализовать длинную арифметику и дроби.
  • Сделать какую нибудь логическую игру (крестики нолики, судоку или что-то похожее) с алгоритмическим выбором следующего хода и его вычислением с помощью машинного обучения.
  • Написать много разных примеров для оценки синтаксиса.
  • Доработать синтаксис с учетом полученного опыта и обратной связи.
  • Восстановить работоспособность компилятора для генерации исполняемых файлов.
  • Сделать очередную большую чистку кода.
  • Переработать и задокументировать получившуюся семантику языка с учетом всех возможностей и выпустить первую полнофункциональную версию NewLang.


Комментарии (38)


  1. Gordon01
    18.02.2022 10:49
    +1

    $error := {{ # начало блока try
    	call_or_exception1(); 
    	call_or_exception2(); 
    }}; # конец блока try
    # Аналоги блоков catch
    ($error ~ :type1)->{ код обработки ошибки 1}
    ($error ~ :type2)->{ код обработки ошибки 2};

    Кажется, вы изобрели pattern matching но не до конца:

    match VALUE {
      PATTERN => EXPRESSION,
      PATTERN => EXPRESSION,
      PATTERN => EXPRESSION,
    }
    
    let x = 1;
    
    match x {
      1 => println!("one"),
      2 => println!("two"),
      3 => println!("three"),
      _ => println!("anything"),
    }


    1. rsashka Автор
      18.02.2022 10:56
      -1

      Не, это аналог блока if ... else if, так как оператор ~, это оператор сравнение типов, а сама конструкция является полным аналогом:

      if (<condition>) {
          EXPRESSION;
      } else if (<condition>) {
          EXPRESSION;
      }

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


      1. Gordon01
        18.02.2022 11:14
        +4

        ($error ~ :type1)->{ код обработки ошибки 1}
        ($error ~ :type2)->{ код обработки ошибки 2};

        Но у вас один и тот же предикат ($error) сравнивается с разными образцами.

        Классический paattern matching. Да и синтаксис получился очень похожим, разве что более перегруженным.


        1. rsashka Автор
          18.02.2022 11:20

          С этой точки зрения да, похоже.

          Но что делать, если потребуется дополнить проверку типа переменной каким либо дополнительным условием? Тогда нужно будет либо расширять лексические конструкции pattern matching (через какие нибудь | ), либо возвращаться на классический if ... else if, а вопросы оптимизации такой операции отдать на откуп компилятору.


          1. Gordon01
            18.02.2022 11:35
            +4

            Вот так: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#extra-conditionals-with-match-guards

            let num = Some(4);
            
            match num {
              Some(x) if x < 5 => println!("less than five: {}", x),
              Some(x) => println!("{}", x),
              None => (),
            }

            Это все еще в хаскеле было.


            1. rsashka Автор
              18.02.2022 11:41

              А я не утверждаю, что изобрел новую синтаксическую конструкцию. Более того, все они уже так или иначе используются в других языках, и я специально старался придерживаться не противоречивой стилистики по отношению к существующим синтаксисам.


              1. Gordon01
                18.02.2022 11:45

                Так и не надо изобретать, надо аккуратно взять то, что придумали в хаскеле)

                Тем более, всем понравилось и в других языках синтаксис идентичный.


                1. rsashka Автор
                  18.02.2022 12:10
                  -2

                  Такой синтаксис не подходит из-за использования зарезервированных ключевых слов. Да и вряд ли приведенный вами пример относится к легким в понимании при беглом просмотре.


                  1. Gordon01
                    18.02.2022 12:38
                    +2

                    Да и вряд ли приведенный вами пример относится к легким в понимании при беглом просмотре.

                    Это индустриальный стандарт много лет.


                    1. rsashka Автор
                      18.02.2022 12:49

                      Так и Python и C/C++ тоже много лет индустриальные стандарты. Тем не менее, некоторые места будут довольно сложными для понимания "с ходу". А про новые дополнения в синтаксис вообще лучше не говорить.


                      1. Gordon01
                        18.02.2022 13:02

                        Ну окей, ваш язык, ваше право.

                        Только вот наколенный синтаксис скорее всего хуже общепринятого. Раст не похож на конформистский язык, но тем не менее, перенял синтаксис pattern matching из хаскеля почти 1-в-1. Думаю, уж они то понимают, что делают.


                      1. rsashka Автор
                        18.02.2022 13:17

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

                        Напишите, если у вас будут мысли, как можно оформить основные алгоритмические конструкции без использования ключевых слов (проверка условий и циклы обычные и со счетчиком).

                        Тогда вам даже код писать не придется, что бы у вас тоже было полное моральное право называть NewLang "своим" в части предложенного синтаксиса.


                      1. FinePeopleSpace
                        18.02.2022 20:29

                        Напишите, если у вас будут мысли, как можно оформить основные алгоритмические конструкции без использования ключевых слов (проверка условий и циклы обычные и со счетчиком).

                        ( ) Цикл

                        { } Ветвление

                        [ ] Блок, список, стек

                        . Внутрь, к полю, к методу

                        ! Наружу, выход из цикла

                        ------- например: -------

                        Неделя (1..7) +1. или:

                        Неделя (1..7*) +1. или:

                        Неделя (1..7*Неделя) +1

                        { День = 1..5} print [работаем]

                        { День = 6 | 7 } print [выходной]

                        ! +1* = 52


                      1. rsashka Автор
                        18.02.2022 20:46

                        Спасибо за предложение, хотя некоторые из ваших конструкций будут конфликтовать с остальным синтаксисом языка (например, использование квадратных скобок).

                        Но некоторые мои мысли пересекаются с вашими, например вообще отказаться от счетного цикла, заменив его обычным циклом по условию с итератором диапазона, на псевдокоде примерно вот так:

                        День := iter(1..5); # Итератор диапазона
                        (i := next(День)) <--> { # Цикл "while"
                          print(i);
                        }


                      1. FinePeopleSpace
                        19.02.2022 11:13
                        +1

                        конфликтовать с остальным синтаксисом языка (например, использование квадратных скобок

                        Семантически -- массив, кортеж, мап, лист, json... — это одно и тоже. Поэтому квадратные скобки (как семантический маркер) могут помочь программисту понять на мета уровне. А это важно.

                        Синтаксическое противоречие (перегрузка) снимается парс-анализом. Например, звёздочка в скобках:

                        ( )... ( *)... (..*)

                        { }... { *}... {..*}

                        [ ]... [ *]... [..*]... [..*имя]

                        даёт "Оскар", ступеньку, расширяемость списка. Это может пригодиться для ИИ и его способности "расширять сознание", моделировать зеркальные нейроны, рефлексию и тп.

                        Допустим, венецианское зеркало даёт 40 отражений. Ставят свечу между двумя зеркалами и видят 40 отражений свечи. То есть, это механическая модель семантики рефлексии. Программно её можно отобразить:

                        а[..*а]

                        С какой целью вы хотите убрать ключевые слова? Я полагаю, чтобы увеличить абстракцию — семанто-ёмкость кода, смысловую фрактальность:

                        а[...] — это и именованный блок кода, и крейт, и трейт, и массив, и поля объекта, и строка/столбец таблицы, и html (div/span)... и ещё стек/куча всего-чего.


                      1. rsashka Автор
                        19.02.2022 11:45

                        Так в том то и дело, что не нужно "куча всего". Нужно одно, максимам два конкретных значения, которые можно было бы понять сразу без ключевых слов и поиска звездочек в конце длинного списка значений.

                        Идеализированная цель отказа от использования ключевых слов в синтаксисе, приблизить чтение текста программы к чтению обычного текста за счет использования знаков препинания при описании логики работы программы.

                        Конечно запятая человек может вычленять ключевые управляющие слова языка и слеш или учитывать форматирование запятая чтобы на их основе понимать синтаксические конструкции программы запятая хотя при обычном чтении мы привыкли опирается на семантику знаков препинания точка Мы конечно можем писать обычным текстом знаки препинания точка Но согласитесь запятая что такой текст открытая скобка именно этот текст закрытая скобка будет очень не удобно читать точка


                  1. nihlete
                    18.02.2022 12:45
                    +2

                    Меньше ключевых слов должно же быть плюсом.

                    P.s. поддерживаю @Gordon01 что pattern matching вроде как известная языковая конструкция.


                    1. rsashka Автор
                      18.02.2022 12:58

                      А я с ним не спорю про известность.

                      Изначальная идея была в новом способе обработки исключений, которая заключается в том, что для языков с динамической типизацией не требуются множественные типизированные блоки catch, так как они легко заменяется на обычные операторы сравнения.

                      А как оформить само сравнение, if ... else if, switch ... case или pattern matching, это уже дело десятое.


                    1. rsashka Автор
                      19.02.2022 12:46

                      Кстати, если говорить насчет pattern matching, то это будет хорошим дополнением к условному оператору. А его синтаксис можно нормально вписать в общую идеологию, например, вот так:

                      val => {
                        match1 -> call1(),
                        match2 -> {call2_1(); call2_2(); call2_3();},
                        match3, match4, match5 -> call_multy(),
                        _ -> call_any(),
                      };

                      Так что теперь @Gordon01от отцовства не отвертится и может считаться идейным вдохновителем добавления pattern matching в синтаксис NewLang!


  1. Gordon01
    18.02.2022 11:04
    +9

    Основной синтаксис NewLang — простой и логичный за счет того, что он построен исключительно на грамматических правилах и не использует каких либо зарезервированных ключевых слов, а все буквенно-символьные последовательности рассматриваются как идентификаторы в которых можно использовать любые не-ASCII символы.

    Имхо, это не плюс, а большой минус, читать этот рассыпанный мешок символов пунктуации не удобно, это похоже на С++, где с этим огромные проблемы. Хотя, даже, скорее на Perl, который известен тем, что он write-only. Никто никогда не будет даже пытаться осознать, что означает последовательность

    a :== { "^ ->>> print « $#% STR %#& » ° degree™convert <<<- ^" }

    printf(str) := { %{ std::cout « $str; %} };

    Написав это я имел в виду:

    printf = (str) std::cout(str);

    Я даже « с клавиатуры ввести не могу. Что вообще происходит?

    iostream — одна из худших вещей в С++, зачем вы пытаетесь ее перенести?

    Для создания объектов и присвоения им новых значений в NewLang используется сразу три разных оператора.

    И все они — символ пунктуации. Зачем? Вы хотите переизобрести Brainfuck?

    Так, символ «$» в начале имени обозначает локальную переменную, время жизни которой ограничено текущей областью видимости и при её завершении локальная переменная уничтожается. Символ «@» обозначает глобальную переменную, а сам объект сохраняет свое состояние даже после выхода из текущей области видимости.

    Зачем? В 99,9% я буду использовать локальные переменные. Зачем вы приносите самое плохое из PHP/Perl?


    1. rsashka Автор
      18.02.2022 11:11
      -4

      iostream — одна из худших вещей в С++, зачем вы пытаетесь ее перенести?

      Я ничего не переношу. Наоборот, вы процитировали пример кода на С++, который можно вставить между %{ %}, но нужно ли это делать, решать вам. А это просто демонстрация того, что у разработчика остается возможность использования обычного С/С++ в одном и том же контексте и не более того.

      Что касается модификаторов имени, вы можете их использовать, но это делать не обязательно. Спасибо, дополню статью, чтобы насчет их было понятнее.


    1. Marlene14
      19.02.2022 18:26
      +1

      Вообще то символ $ не плохой сам по себе. Если конкретно вам он не нравится используйте другой язык. Я люблю PHP и знак доллара тоже люблю и таких как я - много. Но я не говорю, что отсутствие знака доллара в C# портит C#. Я люблю C# без знака доллара перед переменными.

      А вообще при разработке Дарта учитывали простоту синтаксиса, обратите на него внимание.

      Я вообще за то чтобы в языке можно было начинать переменную со знака доллара. Т е как в JavaScript. Это очень удобно.


  1. KoJIx03HUk
    18.02.2022 11:14

    NewLang

    Это название сразу же вызвало ассоциацию с Новоязом (NewSpeak) из романа Джорджа Оруэлла "1984".


    1. rsashka Автор
      18.02.2022 11:14

      Да, интересная ассоциация :-)


      1. sophist
        18.02.2022 15:00
        +1

        Вот вам и идея для промоушена в русскоязычном сегменте :)


  1. vines
    18.02.2022 12:22
    -3

    Я человек простой: вижу новый язык -- ставлю минус. =)


    1. rsashka Автор
      18.02.2022 12:44
      +1

      А я наоборот стараюсь найти рациональное зерно, да и плюса не жалко.


    1. Marlene14
      19.02.2022 18:52
      -2

      Карма возвращает значение мгновенно.


  1. codecity
    18.02.2022 13:18
    +1

    Простая интеграция с уже существующими программными библиотеками (в том числе импорт нативных переменных и функций из С/С++).

    Вот это самое важное. Можно ли подробнее об интеграции с библиотеками и написании библиотек? Правильно ли я понял, что можно использовать только в C-стиле - переменные и функции, но не классы C++? Так же и по поводу библиотек NewLang и их вызова из других языков - есть такая функциональность?


    1. rsashka Автор
      18.02.2022 13:40
      +1

      Тут все и просто и сложно одновременно.

      Импорт нативных С функций сделан из коробки, а их вызов происходит с помощью библиотеки libffi с автоматической проверкой и преобразованием типов данных.

      Пример использования обычных файловых функции можно посмотреть в тесте TEST(Eval, Types) в файле: https://github.com/rsashka/newlang/blob/master/core/test/eval_test.cpp

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

      Сложно, потому что сейчас импортируются объявления, помеченные только extern "C". Я пробовал использовать clang для создания декорированного имён функций или класса. В принципе это возможно, хотя сейчас это задача не первоочередной важности, но в будущем я планирую это сделать. Еще есть проблемы с перехватом ошибок (сишные функции не кидают иссключений, а ловить SIGTERM или SIGABRT та еще проблема).

      Сейчас модуль NewLang, это обычная динамическая библиотека (файл .so) скомпилированная из С/С++ файла (когда работал компилятор). В нем не используется ни сборщик мустора, ни глобальный объект синхронизации. Поэтому нет никаких препятствий для использования модулей NewLang из других языков программирования.


  1. alexrodygin
    18.02.2022 13:30
    +2

    Это комбайн для ML, бэка или для фронта или для всего сразу? Как обстоит дело с поддержкой DSL или с внедрением генерируемого текста (например HTML) в строку?


    1. rsashka Автор
      18.02.2022 14:03
      -1

      Скорее всего для ML и бэка, так как вряд ли для фронта имеет смысл использовать решение с ML, да и с экономической точки зрения, это вряд ли будет целесообразно.

      Поддержка DSL закладывается изначально идеологически, хотя сейчас это никак и не реализовано на практике.

      Синтаксис для встраивание форматированного текста сделан как в Python с использованием тройных кавычек. Этот момент уже реализован в лексере и парсере, но я его не проверял и не тестировал.


      1. alexrodygin
        18.02.2022 15:26
        +1

        По поводу названия есть такой комментарий — лет через 5-10, NewLang будет еще «New» или уже нет?


        1. rsashka Автор
          18.02.2022 15:38
          -1

          Знаете, если через 5-10 лет ответ на этот вопрос будет иметь смысл (язык будет доведен до рабочего состояния и реально использоваться), то ответ на него будет не важен, т.к. термин NewLang будет нарицательным, а не переводится по частям.

          И это будет уже из области "Монти Пайтон", С, D, С#, за каждым названием есть какая то история.


  1. soulwish
    19.02.2022 13:34
    +2

    Выглядит интересно. Но планируемая кодогенерация в C++ звучит немного пугающе. И вставки кода C/C++ явно намекают, что будет "лобовая" кодогенерация.


    1. rsashka Автор
      19.02.2022 13:45
      -1

      На самом деле все не так уж и страшно. Да, сейчас трансплайтер генерирует код "в лоб" без какого либо анализа, но принципиальная привязка к С/С++ как таковая отсутствует.

      Я начинал эксперименты на Python, и вставки кода там работали точно по такой же схеме. Правда, когда начал делать компилятор, пришлось перейти на C/C++, тем более он для меня привычнее.

      Но конечная цель, JIT кодогенерация под LLVM, а это гораздо проще сделать с С/С++, чем с любого другого языка.


  1. Wesha
    19.02.2022 23:32

    Новый язык программирования

    Вот прямо просится


    1. rsashka Автор
      20.02.2022 07:29
      -1

      Посыл понятен, но есть как минимум три фактических несоответствия. Во первых цель совершенно другая, во вторых, другие языки программирования не рассматриваются как "конкуренты", тем более, что допускаются программные вставки на одном из них, да и с цифрами на картинке ошиблись как минимум на два порядка.