Макросы
Макросы используются и для превращеня исходного кода NewLang в более привычный синтаксис на основе ключевых слов, так как такой тест гораздо легче воспринимается при последующем чтении исходного кода.
Макросы в NewLang, это один или несколько последовательных терминов, которые заменяются на другой термин или на целую синтаксическую конструкцию (последовательность лексем).
Макросы обработываются во время работы лексера, т.е. перед передачей последовательности лексем в парсер, что позволяет с помощью маросов изменять фрагменты синтаксиса языка, например, при реализации собственных диалектов DSL.
Определение макросов
Определение макросов аналогично определению других объектов и состоит из трех частей <имя макроса> <оператор создания/присвоения> <тело макроса> и завершающая точка с запятой “;”, т.е. применяются обычные операторы ::=(::-), = или :=(:-) для создания нового или переопределения уже существующего объекта, а имя макроса указывается между двумя символами "@@" и может содержать одну или нескольких лексем (терминов).
Все макросы относятся к глобальному пространству имен, поэтому первый термин в имени макроса должен быть уникальным, иначе он будет прекрывать локальные и глобальные переменные при разрешении имен, если они записаны в тексте программы без квалификаторов (сигилов).
С помощью операторов ::- и :- создаются чистые (гигиеничные) макросы, аргументы и переменные в которых гарантированно не пересекаются с пространством имен программы.
Телом макроса могут быть корректное выражение языка, последовательность лексем (которые заключается в двойные собачки "@@",
т.е. @@ лексема1 лексема1 @@
) или обычная текстовая строка (которую нужно указать между тройными собачками "@@@", т.е. @@@ текстовая строка @@@
).
В имени макроса после первого термина могут присутствовать один или несколько шаблонов. Шаблон — это термин, который при сопоставлении последовательности лексем с идентификатором макроса может заменяться любым другим одиночным термином (т.е. фактически это сопоставление по образцу/шаблону).
Для создания термина-шаблона в начале его идентификатора нужно поставить знак доллара (что соответствует квалификатору локальной переменой),
т.е. имя макроса @@ FUNC $name @@
будет соответствовать последовательности лексем как FUNC my_func_name
, так и FUNC other_name_func
.
Для удаления макроса используется специальный синтаксис: @@@@ name @@@@;
или @@@@ два термина @@@@;
,
т.е. необходимо указать идентификатор макроса между четырмя символами "@@@@".
# Тело макроса из текстовой строки (как в препроцессоре С/С++)
@@macro_str@@ := @@@ строка - тело макроса @@@; # Строка для лексера
# Удаления макроса @macro_str
@@@@ macro_str @@@@;
Аргументы макросов и их раскрытие
Макросы можно определять как с аргументами (параметрами в скобках), так и без них. Если макрос был определен с аргументами, то их проверка будет выполнятся макропроцессором при определении и раскрытии макроса. Если макрос был определен без аргументов, то их наличие макропроцессором игнорируется.
Макропроцессор считает макросы с аргументами и без оных идентичными, то нельзя создать два макроса с одинаковыми именами, один из которых будет с аргументами (скобками), а другой без них.
Поэтому, если требуется использовать макрос в двух разных вариантах (с аргументами и без оных), следует определять макрос без аргументов и в этом случае контроль параметров будет выполнятся компилятором.
@@ macro @@ := term; # Макрос без аргументов
macro(args); # ОК -> term(args);
macro; # ОК -> term;
# Но
@@ call() @@ := term();
call(); # ОК -> term();
call; # Ошибка (@call определен с аргументами)
Если при определении макроса указаны аргументы, то место для их вставки в теле макроса записывается как имя локальной переменой, перед которой добавлен символ "@", т.е. @$arg.
Место для вставки числа реально переданных аргументов отмечается лексемой "@$#". Если требуется вставить переданные аргументы в виде словаря, то место для вставки отмечается лексемой "@$*".
Если макрос принимае произвольное количество аргументов (аргуметы макроса завершает многоточие), то место их вставки в тело макроса отмечается лексемой "@$…".
По аналогии с препроцессором С/С++, для соединения двух лексем в одну, в теле макроса используется оператор "@##",
а для преобразование лексемы в текстову строку применяется операторы @#, @#" или @#’, например,
@@macro($arg)@@ := @@ func_ @## @$arg( @#" arg ) @;
, тогда вызов macro(name);
будет преобразован в func_name ("name");
Примеры использования макросов:
# Обычные макросы (тело макроса корректное выражение)
@@ macro @@ := replace();
@@ macro2(arg) @@ := { call(@$arg); call()};
# Тело макросов из последовательности лексем
@@ if(...) @@ := @@ [ @$... ]--> @@; # Выражение может быть не полным
@@ elif(...) @@ := @@ ,[ @$... ]--> @@;
@@ else @@ := @@ ,[...]--> @@;
# Запись условного оператора с использованием
# определенных выше макросов
@if( condition ){
...
} @elif( condition2 ) {
...
} @else {
...
};
Например цикл до 5:
count := 1;
[ 1 ] <-> {
[ count > 5 ] --> {
-- 42 --;
};
count+=1;
};
Будет выглядеть более привычно:
count := 1;
@while( true ) {
@if( count > 5 ) {
@return 42;
};
count += 1;
};
Символьное программирование, часто называемое декларативным программированием, представляет собой парадигму программирования, которая использует математическую логику, отношения и символы для представления знаний и помощи в решении проблем. Вместо того, чтобы сосредотачиваться на описании последовательности операций, необходимых для выполнения вычислений, как в императивном программировании, символическое программирование делает упор на выражении отношений и ограничений между структурами данных, уделяя особое внимание «что», а не «как».
В контексте парадигм программирования символическое программирование часто противопоставляется процедурному или императивному программированию, где программы определяют пошаговые процедуры для достижения своих целей. В то время как императивное программирование фокусируется на описании потока управления с использованием таких конструкций, как циклы и условные выражения, для управления выполнением, символическое программирование сосредоточено на выражении фактов о проблеме и позволяет выполнению естественным образом выводить последующие последствия из предоставленной информации.
Одним из основных преимуществ символьного программирования является его способность обеспечивать более высокий уровень абстракции, что упрощает анализ кода и снижает вероятность ошибок. Непосредственно выражая отношения и ограничения, символическое программирование позволяет разработчикам сосредоточиться на моделировании и спецификации проблем, позволяя базовой системе управлять реализацией и выполнением алгоритмов.
Существует несколько языков программирования и сред, использующих парадигму символьного программирования, например Prolog, Lisp и Haskell. Например, Пролог — это язык логического программирования, который позволяет разработчикам выражать взаимосвязи и факты о проблемных областях, в то время как выполнение автоматически обосновывает и извлекает новые факты на основе исходной информации. В том же духе Лисп, функциональный язык программирования, использует символьные вычисления для манипулирования структурами данных и выполнения сложных операций над ними, используя краткий и математически обоснованный синтаксис. Haskell, еще один функциональный язык программирования, использует строгую типизацию и ленивые вычисления, чтобы обеспечить символическое рассуждение и облегчить эффективное создание программ.
Символьное программирование может быть полезным в широком спектре проблемных областей, включая искусственный интеллект, экспертные системы, программирование логики ограничений и символьную математику, среди других. Более высокий уровень абстракции, обеспечиваемый декларативными языками программирования, облегчает разработку сложных систем, сводя к минимуму вероятность ошибки и улучшая удобство обслуживания. В частности, использование символьного программирования в искусственном интеллекте позволяет разработчикам создавать системы представления знаний и механизмы рассуждения, которые можно применять для обработки естественного языка, машинного обучения и автоматического доказательства теорем.
Различие между символьным программированием и препроцессором
- Задача препроцессора - раскрыть(расширить) макрос, тогда как при символьном программировании нужно сокращать (сворачивать) выражения
- Препроцессор обрабатывает последовательности лексем (плоские данные), тогда как символьное программирование оперирует выражениями (деревом лексем)
- Макрос у препроцессора идентифицируется первым термином, и если макрос не может быть раскрыт, то возникает ошибка. В символьном программировани требуется точное соответствие всего выражения и только в этом случае производится сокращение выражения.
Предпосылки для реализации символьного программирования Компилятор - интерепретатор с REPL и обработка AST как во время компиляции, так и во время выполнения. Необходимо добавить конструкции для определения правил символьного программрования (чистые функции?) Необходимо добавить конструкцию для вычисления выражений в символьном программировании.
Wolfram https://habr.com/ru/articles/772984/
diffRules = {
Sin[x] -> Cos[x],
Cos[x] -> -Sin[x],
x^2 -> 2*x,
x -> 1,
Log[x] -> 1/x
};
diffRules := (
Sin(x) @-> Cos(x),
Cos(x) @-> -Sin(x),
x^2 @-> 2*x,
x @-> 1,
Log(x) @-> 1\x,
);
expr @-> Sin(x) - x^2 + Log(x);
# И применим к нему правила дифференцирования
#expr /. diffRules
#(* 1/x - 2 x + Cos[x] *)
sym := SymEval(expr, diffRules);
res := Eval(sym, x=0.123);
expr = a^2 + 3 * b^3 - c^4 + 2 * x^2 - x + 4*c + 3
И я хочу это выражение линеаризовать, т.е. отбросить все степени выше первой. Я могу сделать это напрямую, как в примерах выше:
expr /. {
a^2 -> 0,
b^3 -> 0,
c^4 -> 0,
x^2 -> 0
}
(* 3 + 4 c - x *)
Но это слишком неудобно. Что если я не знаю ни точную степень, ни имя переменной? Как просто указать, что нужно заменить все места, где встречается возведение в степень на ноль? Это можно сделать при помощи шаблонов вот так:
expr /. Power[_, _] -> 0
(* 3 + 4 c - x *)
Либо вот так:
expr /. _ ^ _ -> 0
(* 3 + 4 c - x *)
:diffRules() := {
{@ Sin(x) @} ::- {@ Cos(x) @};
Cos(x) @-> -Sin(x),
x^2 @-> 2*x,
x @-> 1,
Log(x) @-> 1\x,
};
expr @-> Sin(x) - x^2 + Log(x);
# И применим к нему правила дифференцирования
#expr /. diffRules
#(* 1/x - 2 x + Cos[x] *)
sym := SymEval(expr, diffRules);
res := Eval(sym, x=0.123);