Всем доброго времени суток. Некоторое время назад я натолкнулся в этом форуме на код, написанный на шаблонах C++, который позволяет контролировать размерности физических типов во время компиляции: Обеспечение семантического контроля над размерностями станда
. Некоторое другое время назад я начал увлекаться языком Nemerle и мне захотелось решить эту же задачу средствами Nemerle.
Немного о Nemerle (для тех, кто не знает)
Nemerle — это (относительно) новый язык для платформы .NET (под FW 2.0), синтаксис которого похож на C#. Но в нём имеются и некоторые возможности отсутствующие в C# напрочь. А именно:
Удобная работа с лямбдами и туплами
Сопоставление по образцу
Автоматический вывод типов (т.е. язык позволяет очень редко указывать тип выражений явно, при этом статическая типизация сохраняется)
Синтаксические макросы (убедительная просьба не путать с макросами Си)
Как видно, очень многое в язык пришло из мира функционального приграммирования. Последний пункт говорит о том, что на Nemerle мы можем писать довольно мощный метакод (код, создающий другой код). При этом код не выглядит громоздким (и не является таковым) благодаря наличию квази-цитирования.
— там описано, как поставить его себе на машину и использовать + немного написано о самом языке. Я использую для работы набор шаблонов проектов для студии
. А вот и тестовый пример (тоже внаглую украденный оттуда):
using System.Console;
using Oyster.Units;
using Oyster.Units.Macros;
def m1 = Cgs.Mass(1); // 1 грамм
def m2 = Si.Mass(m1); // Автоматическое преобразование в килограммы. В результате получаем 0.001 кг
WriteLine($"Mass in SI: $m2, in CGS: $m1");
def l1 = Cgs.Length(100); // 100 см
def l2 = Si.Length(l1); // Автоматическое преобразование. Результат - 1 м
WriteLine($"Length in SI: $l2, in CGS: $l1");
def t1 = Cgs.Time(1); // 1 с
def t2 = Si.Time(t1); // Тоже 1 с, т.к. единицы измерения времени в системах СИ и СГС совпадают.
WriteLine($"Time in SI: $t2, in CGS: $t1");
// Теперь немного более сложный пример. Предыдущие величины (масса, длина, время) являются
// базовыми (ортами) в обеих системах единиц. Здесь же мы имеем дело с производными величинами -
// скоростью, енергией и мощностью
def v1 = Cgs.Velocity(10); // 10 см/с
def v2 = Si.Velocity(v1); // Результат преобразования - 0.1 м/с
WriteLine($"Velocity in SI: $v2, in CGS: $v1");
def e1 = Si.Energy(0.0001); // 10e-4 Дж.
def e2 = Cgs.Energy(e1); // e2 = 1000 эрг
WriteLine($"Energy in SI: $e1, in CGS: $e2");
// Это - пример работы с внесистемными единицами. В данном случае мощность переводится из ваттов
// в лошадиные силы
def p1 = Si.Power(1000); // 1000 Вт
def p2 = Ext.Power(p1); // 1.35962 лс
WriteLine($"Power in SI: $p1, in Ext system: $p2");
// Пример работы с простейшими арифметическими действиями
def a1 = Si.Acceleration(1); // 1 м/с2
def a2 = Cgs.Acceleration(a1 + Cgs.Acceleration(200)); // 1 м/с2 + 200 см/с2 = 300 см/с2, как и должно было быть
WriteLine($"Acceleration: a1 = $a1, a2 = $a2");
// Сила
def f1 = Si.Force(10); // 10 Н
def f2 = Cgs.Force(f1*2); // 10 Н * 2 = 2000000 дин
def f3 = Si.Force(m1*l2/(t1*t1)); // 1 г * 1 м / (1 с * 1 с) = 0.001 Н
// А вот так уже не скомпилируется - не та размерность
//def f4 = Si.Force(m1*l2/t1);
// Вот что выдаёт ncc:
// Main.n(122,10,122,18): error : each overload has an error during call:
// Main.n(122,10,122,18): error : overload #1, method Oyster.Units.Si.Force..ctor(value : System.Double) : void
// Main.n(122,10,122,18): error : in argument #1 (value) of Oyster.Units.Si.Force, needed a System.Double, got _N_TempUnitType5439: _N_TempUnitType5439 is not a subtype of System.Double [simple require]
// Main.n(122,10,122,18): error : overload #2, method Oyster.Units.Si.Force..ctor(value : Oyster.Units.AbstractUnitType_1_1_-2_0_0_0_0) : void
// Main.n(122,10,122,18): error : in argument #1 (value) of Oyster.Units.Si.Force, needed a Oyster.Units.AbstractUnitType_1_1_-2_0_0_0_0, got _N_TempUnitType5439: _N_TempUnitType5439 is not a subtype of Oyster.Units.AbstractUnitType_1_1_-2_0_0_0_0 [simple require]
WriteLine($"Force: f1 = $f1, f2 = $f2, f3 = $f3");
// Здесь ошибка компиляции - пытаемся складывать енергию, массу и скорость
//WriteLine($"ERROR = $(e1 + e2 + v1 + v2 + m1 + m2)");
// Вот что выдаёт ncc (точнее, мой макрос):
// Main.n(135,52,135,64): error : Expression { e1 + e2 [+] v1 } can't be compiled because units are having different dimensions.
// Здесь происходит преобразование на лету. В результате имеем 0.0002 Дж - результат имеет тот же тип,
// что и левый операнд
WriteLine($"Result = $(e1 + e2)");
// Здесь выводится единица, т.к. e1 и e2 содержат одно и то же значение, но выраженное в разных
// системах единиц
WriteLine($"Result = $(e1/e2)")
Исходники в соседних сообщениях. Они разбиты на 2 группы:
Исходники макробиблиотеки
Описание физических величин на простеньком DSL-е
Макробиблиотека должна быть скомпилирована в отдельную сборку. Описание физических величин — в другой отдельной сборке. Я использовал для компиляции билд 0.9.2.6168 (т.е. первоапрельский).
Немного оффтопа
Основная цель, которую я преследовал при написании этого кода — изучение возможностей Nemerle (да и самого языка с библиотекой — за время написания кода я узнал много нового для себя). Мне хотелось понять, насколько сложно, например, переписать библиотеку с метакодом, написанную на C++, на Nemerle. А также хотелось показать другим участникам форума, что эта задача вполне разрешима штатными средствами Nemerle.
Этот код не идеален, поскольку я не использую его в реальном приложении. Сейчас я вижу как минимум следующие модификации, которые можно произвести с кодом:
Рефакторинг
Переписывание кода на генерацую value-types — это может ускорить код по понятным причинам. Для этого можно использовать интерфейсы и немного покрутить с генериками
Добавление большего количества метаинформации к генерируемым классам для формирования более информативных сообщений об ошибках
Переписывание макрооператоров. Сейчас, например, при использовании макрооператоров из пространства имён Oyster.Units.Macros невозможно пользоваться т.н. каррингом в стиле Nemerle
, т.е. выражениями вида _ + _
Добавление синтаксического сахара вроде физических литералов (например 100kg * 15sec или что-то вроде того)
В то же время, в целом я доволен кодом. Мне кажется, что он понятнее, чем тот же код на C++. Да и описание физических величин имхо выглядит удобнее (см. исходники).
Надеюсь, сообщение оказалось полезным. И сорри всем, кто дочитал до этой строчки, за объём текста
В последних билдах Nemerle разработчики исправили довольно много багов, поэтому исходный код библиотеки был откорректирован соответствующим образом. Собственно, было внесено два основных изменения:
Макро-атрибут UnitsDefinition стал атрибутом уровня сборки (баг, о котором я говорил
Наконец-то исправлен досадный баг с операторами преобразования типа и генериками, что позволяет с использованием моей библиотеки писать не
def f3 = Si.Force(m1*l2/(t1*t1));
а
def f3 : Si.Force = m1*l2/(t1*t1);
Т.е. не вызывать конструктор явно, а использовать неявное преобразование типа.
Кроме того, я добавил в код чуть больше комментариев и переписал все русские комментарии из примера использования на английском, потому что проект выложен в форум примеров на nemerle.org (наконец-то у меня добрались до этого руки).
, как подоспела следующая. В версии 0.03 использован workaround, предложенный разработчиками Nemerle для обхода бага и, как результат, я избавился от боксинга в конструкторе, т.е. теперь решение действительно на 100% на value-типах
Новую версию забираем тут: Oyster.Units.0.03.zip. Для сборки надо использовать Nemerle билд r6176 — не ниже (как и для предыдущей версии).
PS: Исходники текстом не выкладываю — они почти не изменились.
Re[3]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, Oyster, Вы писали:
O>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):
Ну вот и следующий билд библиотеки подоспел: Oyster.Units.0.04.zip (собирать на Nemerle версии r6192, не ниже).
В этом билде разработчики языка исправили кое-какие баги, поэтому код библиотеки тоже был исправлен местами. Но самое главное, конечно, не это. Самое главное то, что этот билд добавляет возможность работы с физическими литералами, о которых в этом топике уже писали. В общем, теперь можно писать код вроде такого:
def m3 = 1 g;
def m4 = Si.Mass(m1);
WriteLine($"Mass in SI: $m4, in CGS: $m3");
def x1 = Si.Area(1 cm * 10 m);
WriteLine($"Area of 1 cm * 10 m = $x1 m")
1 g, 1 cm и 10 m из примера выше — это как раз физические литералы. Задаются они непосредственно в DSL вот таким вот образом:
basisUnits (Si)
(
Mass[ kg ],
Length[ m ],
Time[ sec ],
Temperature[ K ],
CurrentStrength[ A ],
LightIntensity,
QuantityOfSubstance
)
Т.е. после любого имени юнита можно указать имя литерала в квадратных скобках (уникальное в пределах данного описания). При этом в библиотеку, в которой находится описание юнитов, будет скомпилирован соответствующий макро-оператор, доступный при подключении пространства имён Oyster.Units.Macros.
У решения всё равно есть некоторые недостатки:
На данный момент нельзя навесить атрибут на сборку из макро-атрибута, который сам навешен на сборку. Не знаю, исправят ли это в будущем (т.е. баг ли это), но сейчас макро-атрибут UnitsDefinition (в котором задаётся описание юнитов) задан с MacroTargets.Class (т.е. его обязательно надо вешать на какой-то — неважно, какой — класс).
Принципиально нельзя использовать юнит-литералы из той же сборки, в которой расположено описание юнитов. Это связано с тем, что юнит-литералы определяются как макро-операторы (использована идея
Vermicious Knid-а), а оператор нельзя создать и использовать в той же сборке (поскольку информация об операторе должна быть известна на этапе парсинга, а создаётся он в макросе, который выполняется уже после парсинга).
В описании физических юнитов заданы не все литералы (лень). Надеюсь, описание будет наполнено теми людьми, которым действительно будет нужна эта библиотека (если таковые найдутся).
Тем не менее, все эти недостатки достаточно невелики и не мешают пользоваться библиотекой.
В заключение хочу сказать, что библиотека подобралась к своему логическому завершению и вряд ли будет развиваться дальше. Возможно, будут исправляться ошибки, но не более того.
Я тут баг в коде нашёл, результатом которого стало заваливание тестового приложения в рантайме на последних билдах Nemerle. Баг пофиксан: Oyster.Units.0.06.zip (билдить на r6251 и выше).
В общем, хотя реально сама библиотека будет бесполезна для 99% программистов и проектов, но 3 огромные задачи ты ею решил:
1. Нашел кучу багов в компиляторе.
2. Создал изумительный пример разработки DSL. Очень советую, кстати, запостить его в форум Немерла посвященный примерам. Можно со ссылкой на С++-ный аналог.
3. Доказал приемущество макросов над шаблонным метапрограммированием и вообще их гибкость.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Недавно у меня снова выдалась свободная минутка, и я поработал над кодом библиотеки. Соответственно, последний претерпел некоторые изменения.
Самым главным нововведением является, безусловно, генерация value-типов вместо reference-типов, что должно ускорить работу с этими самыми физическими величинами.
Замечание: К сожалению, работа с value-типами пока что неполноценна, так как в компиляторе имеется досадный баг, не позволяющий комфортно работать с генериками при генерации типов. Неполноценность заключается в том, что при передаче одного физического значения в конструктор другого происходит боксинг. Такое поведение будет исправлено сразу, как пофиксят баг в компиляторе.
Кроме этого, в библиотеку были внесены и другие изменения:
Произведён рефакторинг и исправление ошибок. Последних оказалось неприятно много (что и неудивительно для решения, собранного на скорую руку).
Немного изменился генерирующий макрос. Теперь это макро-атрибут сборки, и работать с ним в итоге стало существенно удобнее (его можно вешать как в сборке, в которой производятся сами вычисления, так и в подключаемой сборке).
Формат DSL также немного поменялся. Например, теперь можно определять юниты в корневом пространстве имён (Oyster.Units) и задавать алиас и коэффициент нормализации одновременно. Это позволяет, например, поместить все внесистемные единицы в одно пространство имён (то, о чём говорил Кодт
Новую версию проекта можно забрать тут: Oyster.Units.0.02.zip. Билдить рекомендуется или на последней версии Nemerle из SVN, или на снапшоте r6176 и выше. Для тех, кому лень качать проект, я выложил новые исходники (кроме тестового примера — он изменился несущественно) в ответе на это сообщение.
PS: Направления дальнейшего развития библиотеки — избавиться от боксинга и ввести физические литералы (то, о чём так много говорили в этом топике).
Re[5]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, Oyster, Вы писали:
O>А ведь ты прав! Очень даже изящное решение получается, спасибо!
Удалил старое сообщение? Видимо заметил пример в конце. Ну да ладно.
O>Интересненько? А точно получится использовать унарный постфиксный (не префиксный) оператор?
Точно. Это в общем-то фича языка — нет разницы между постфиксными и префиксными операторами.
O>Кстати, в таком случае встаёт вопрос генерации макроса внутри макроса чтобы не хардкодить.
Боюсь, что генерация это слишком сложный путь. Одно дело сгенерировать код, а другое дело его скомпилировать. Можно конечно компилировать "настройки" в отдельную сборку и оттуда использовать.
Но есть идея и более простая. И макрос, и оператор можно зарегистрировать вручную через API компилятора(см. модуль MacroRegistry). Макрос это по сути экземпляр некого класса, реализовывающего интерфейс IMacro. Нужно просто написать собственную реализацию IMacro вручную(т.е. не использовать встроенную в компилятор генерацию этого класса), которая в зависимости от того как ее инициализируют будет вести себя как разные макросы(т.е. с разными именами и генерировать различный код).
Вот дамп кода(на псевдо-немерле , выглядит жутко конечно, но понять можно ) методов макро-оператора kg. Кстати стоит обратить внимание, что по сути это вполне обычный макрос, а оператором его делает атрибут сборки. В модуле LibrariesLoader есть функции которые сканируют сборку на предмет нахождения в ней макросов и таких атрибутов, и регистрируют их в MacroRegistry.
Здравствуйте, vdimas, Вы писали:
V>Описание самих величин — понятнее.
Дык... Именно они особо важны в прикладном смысле. Именно их в последствии будет несметное множество (если конечно вообще это кому-то надо ).
V> А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.
Дык, в С++ 90% нужного функционала уже встроено в язык. А ему пришлось писать все на универсальном средстве. Он же типы вручную генерирует.
V>Жаль... я именно это и имел ввиду. Спасибо за своевременный ответ.
Здравствуйте, CrystaX, Вы писали:
CX>Было бы очень интересно и познавательно вести разработку параллельно — на C++ и на Nemerle. Исключительно из спортивного интереса.
Мужики я преклоняюсь перед вашей настойчивостью и самоотверженностью, но такие усилия лучше было бы направить на что-то более полезное.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: [Nemerle] Семантический контроль над размерностями
using System;
using Nemerle.Utility;
namespace Oyster.Units
{
/// Automatically attached to all concrete unit type implemetationspublic class ConcreteUnitTypeAttribute : Attribute
{
[Accessor] _normalizationCoef : double;
public this(normalizationCoef : double)
{
_normalizationCoef = normalizationCoef
}
}
}
UnitTypeBase.n:
using Nemerle.Utility;
namespace Oyster.Units
{
/// Base class for all unit typesabstract public class UnitTypeBase
{
/// Unit value as double
[Accessor] _value : double;
/// Constructs given unit type instance from any other unit type instanceprotected this(value : UnitTypeBase)
{
// Perform normalization-denormalization herethis(value._value * value.NormalizationCoef / NormalizationCoef)
}
/// Constructs given unit type instance from doubleprotected this(value : double)
{
_value = value
}
/// Unit type normalization coefficientabstract public NormalizationCoef : double { get; }
/// ToString() overrideoverride public ToString() : string
{
_value.ToString()
}
}
}
CompileTimeUtil.n:
using System;
using System.Collections.Generic;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Utility;
using NST = Nemerle.Compiler.NamespaceTree;
namespace Oyster.Units.Macros
{
/// Used at compile time by Units macros - stores some intermediate informationmodule CompileTimeUtil
{
/// Units NS namepublic UnitsNamespace = "Oyster.Units";
/// Stores list of base units (such as mass or length) registered in the systempublic mutable BaseUnits : list[string];
/// Stores list of all registered units including aliasespublic mutable AllUnits : list[string];
/// Stores powers list for given base unit or aliaspublic mutable PowersMap : Dictionary[string, list[int]] = Dictionary();
/// Generates (or gets) given unit type with given or automatically generated name,
/// given base units powers and given normalization coefficientpublic GetUnitType(env : GlobalEnv, typeName : string, baseUnitPowers : list[int], normalizationCoef : double) : TypeInfo
{
// Determine name of the type to be generated
def typeName = match (typeName)
{
| null => Macros.NewSymbol("TempUnitType")
| _ => Macros.UseSiteSymbol(typeName)
};
// Try to find this type by name among existing ones
def unitType = env.LookupType([ typeName.Id ]);
if (unitType.HasValue)
{
unitType.Value
}
else
{
// Generate new type
def abstractTypeName = Macros.UseSiteSymbol(GetAbstractUnitType(baseUnitPowers).Name);
def unitType = DefineInCustomNamespace(<[ decl:
[ConcreteUnitType($(normalizationCoef : double))]
public class $(typeName : name) : $(abstractTypeName : name)
{
public this(value : $(abstractTypeName : name))
{
base(value)
}
public this(value : double)
{
base(value)
}
override public NormalizationCoef : double
{
get { $(normalizationCoef : double) }
}
static public @:>(value : $(typeName : name)) : double
{
value.Value
}
static public @:(value : double) : $(typeName : name)
{
$(typeName : name)(value)
}
static public @*(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value * value)
}
static public @/(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value / value)
}
static public @+(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value + value)
}
static public @-(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value - value)
}
static public @*(value : double, unit : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value * unit.Value)
}
static public @/(value : double, unit : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value / unit.Value)
}
static public @+(value : double, unit : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value + unit.Value)
}
static public @-(value : double, unit : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value - unit.Value)
}
static public @++(value : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value.Value + 1)
}
static public @--(value : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value.Value - 1)
}
static public @+(value : $(typeName : name)) : $(typeName : name)
{
value
}
static public @-(value : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(0 - value.Value)
}
}
]>);
unitType.Compile();
unitType
}
}
/// Generates (or gets) abstract unit type for given base unit powers
GetAbstractUnitType(baseUnitPowers : list[int]) : TypeInfo
{
def env = GlobalEnv.Core;
// Try to find given type
def abstractTypeName = $"$UnitsNamespace.AbstractUnitType$(GenerateUnitTypeSuffix(baseUnitPowers))";
def abstractType = env.LookupType([ abstractTypeName ]);
if (abstractType.HasValue)
{
abstractType.Value
}
else
{
// Generate abstract type
def abstractType = DefineInCustomNamespace(<[ decl:
abstract public class $(abstractTypeName : usesite) : UnitTypeBase
{
protected this(value : $(abstractTypeName : usesite))
{
base(value)
}
protected this(value : double)
{
base(value)
}
}
]>);
abstractType.Compile();
abstractType
}
}
/// Defines given class in custom global namespacepublic DefineInCustomNamespace(decl : ClassMember.TypeDeclaration) : TypeBuilder
{
def builder = GlobalEnv.Core.Define(decl);
// Register newly created type manually - otherwise it will be unbound
NST.ExactPath(NString.Split(builder.Name, '.')).Value = NST.TypeInfoCache.Cached(builder);
builder
}
/// Increases power for given unit into listpublic ModifyBaseUnitPower(baseUnitPowers : list[int], unitName : string, f : int -> int) : list[int]
{
// Maybe prepare list full of zeroes
def baseUnitPowers =
if (baseUnitPowers.Length != 0) baseUnitPowers else List.Map(BaseUnits, fun(_) { 0 });
List.Map2(BaseUnits, baseUnitPowers, fun(n, i) { if (n == unitName) f(i) else i })
}
/// Generates abstract unit type suffix basing on base unit powerspublic GenerateUnitTypeSuffix(baseUnitPowers : list[int]) : string
{
| [] => string.Empty
| _ => "_" + baseUnitPowers.Head.ToString() + GenerateUnitTypeSuffix(baseUnitPowers.Tail)
}
/// Parses abstract unit type suffixpublic ParseUnitTypeSuffix(typeName : string) : list[int]
{
List.Map(NString.Split(typeName.Substring(typeName.IndexOf('_') + 1), '_'), int.Parse)
}
/// Returns PExpr typepublic GetExprType(ctx : Typer, expr : PExpr) : TypeInfo
{
ctx.TypeExpr(expr).ty.FixedValue.TypeInfo
}
/// Returns normalization coef if type is concrete unit typepublic GetUnitTypeCoef(t : TypeInfo) : option[double]
{
| _ is TypeBuilder =>
// Get attribute using Nemerle approach
mutable value = None();
foreach (attr in t.GetModifiers().GetCustomAttributes())
{
| <[ ConcreteUnitType($(coef : double)) ]> => value = Some(coef)
};
value
| _ =>
// "Usual" attribute gathering
def attr = Attribute.GetCustomAttribute(t.SystemType, typeof(ConcreteUnitTypeAttribute));
def attr = attr :> ConcreteUnitTypeAttribute;
if (attr == null) None() else Some(attr.NormalizationCoef)
}
/// Used inside ariphmetic op macrospublic OpMacroBody(
ctx : Typer,
lexpr : PExpr,
rexpr : PExpr,
opName : string,
opExpr : PExpr,
someFun : TypeInfo * TypeInfo * double * double -> PExpr) : PExpr
{
def ltype = GetExprType(ctx, lexpr);
def rtype = GetExprType(ctx, rexpr);
match ((GetUnitTypeCoef(ltype), GetUnitTypeCoef(rtype)))
{
| (Some(lcoef), Some(rcoef)) =>
someFun(ltype, rtype, lcoef, rcoef)
| _ =>
def macroPath = NST.ExactPath(["Oyster", "Units", "Macros", opName]);
def value = macroPath.Value;
macroPath.Value = NST.TypeInfoCache.No();
def resexpr = ctx.TypeExpr(opExpr);
macroPath.Value = value;
<[ $(resexpr : typed) ]>
}
}
/// Used inside * and / macrospublic MulDivMacroBody(
ctx : Typer,
lexpr : PExpr,
rexpr : PExpr,
opName : string,
opExpr : PExpr,
powersFun : int * int -> int,
coefFun : double * double -> double,
coefOpExpr : PExpr) : PExpr
{
OpMacroBody(
ctx,
lexpr,
rexpr,
opName,
opExpr,
fun (ltype, rtype, lcoef, rcoef)
{
def powers = List.Map2(
ParseUnitTypeSuffix(ltype.BaseType.Name),
ParseUnitTypeSuffix(rtype.BaseType.Name),
powersFun);
def concreteType = GetUnitType(ctx.Env, null, powers, 1.0);
<[ $(concreteType.FullName : usesite)(double.op_Multiply($coefOpExpr, $(coefFun(lcoef, rcoef) : double))) ]>
})
}
/// Used inside + and - macrospublic AddSubMacroBody(
ctx : Typer,
lexpr : PExpr,
rexpr : PExpr,
opName : string,
opExpr : PExpr,
ctorFun : PExpr -> PExpr) : PExpr
{
OpMacroBody(
ctx,
lexpr,
rexpr,
opName,
opExpr,
fun (ltype, rtype, lcoef, rcoef)
{
match (ltype.BaseType.Name)
{
| s when s == rtype.BaseType.Name =>
def coef = rcoef / lcoef;
def paramExpr = ctorFun(<[ double.op_Multiply($rexpr.Value, $(coef : double)) ]>);
<[ $(ltype.FullName : usesite)($paramExpr) ]>
| _ =>
Message.FatalError(
$"Expression { $lexpr [$opName] $rexpr } can't be compiled "
"because units are having different dimensions.")
}
})
}
}
}
UnitTypesMacro.n:
using System;
using System.Collections.Generic;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Macros;
using System.Console;
namespace Oyster.Units.Macros
{
using CT = CompileTimeUtil;
/// Declares unit types with convertion rules
macro unittypes(namespaceName, body)
syntax ("unittypes", namespaceName, body)
{
// Parse namespace name
def namespaceName = match (namespaceName)
{
| <[ $(n : name) ]> => n.Id
| _ => Message.FatalError($"Invalid unittypes syntax: expected namespace name part, got $namespaceName")
};
// Prepare normalization coefficients map
def coefSubMap = Dictionary();
// Parse units
match (body)
{
| <[ { .. $unitDecls } ]> =>
// Check if BaseUnits must be initialized
def initBaseUnits = match (CT.BaseUnits)
{
| null =>
CT.BaseUnits = [];
true
| _ => false
};
mutable units = [];
// Process all phys types and add info into PhysTypeHelper
mutable index = 0;
mutable calcBaseUnitPowers = initBaseUnits;
foreach (<[ $decl ]> in unitDecls)
{
def unitCoef = match (decl)
{
| <[ $(n : name) ]> => (n.Id, 1.0)
| <[ $(n : name) : ($coefExpr) ]> =>
// Function parses simple coefficient exprs
def parseCoefExpr(expr)
{
| <[ $(d : double) ]> => d
| <[ $lhs * $(d : double) ]> => parseCoefExpr(lhs) * d
| <[ $lhs / $(d : double) ]> => parseCoefExpr(lhs) / d
| _ => Message.FatalError($"Invalid unittypes syntax: expected \"UnitName [: (coefficient expr)]\", got $decl")
};
(n.Id, parseCoefExpr(coefExpr))
| <[ $(n : name) = $aliasExpr ]> =>
// Aliases are started - remember it
when (calcBaseUnitPowers)
{
// Fill base units list
CT.BaseUnits = units.Reverse();
// Prepare powers lists
CT.BaseUnits.Iter(fun(unitName) { CT.PowersMap[unitName] = CT.ModifyBaseUnitPower([], unitName, _ + 1) });
calcBaseUnitPowers = false
};
// Prepare powers list
def oldN = n.Id == "SpecificGravity";
def parseAliasExpr(expr)
{
| <[ $(n : name) ^ $(x : int) ]> => List.Map(CT.PowersMap[n.Id], _ * x)
| <[ $lhs * $(n : name) ^ $(x : int) ]> =>
List.Map2(parseAliasExpr(lhs), parseAliasExpr(<[ $(n : name) ^ $(x : int) ]>), _ + _)
| <[ $lhs / $(n : name) ^ $(x : int) ]> =>
List.Map2(parseAliasExpr(lhs), parseAliasExpr(<[ $(n : name) ^ $(x : int) ]>), _ - _)
| <[ $(n : name) ]> => parseAliasExpr(<[ $(n : name) ^ 1 ]>)
| <[ $lhs * $(n : name) ]> => parseAliasExpr(<[ $lhs * $(n : name) ^ 1 ]>)
| <[ $lhs / $(n : name) ]> => parseAliasExpr(<[ $lhs / $(n : name) ^ 1 ]>)
| _ => Message.FatalError($"Invalid unittypes syntax: expected \"UnitName = alias\", got $decl")
};
CT.PowersMap[n.Id] = parseAliasExpr(aliasExpr);
(n.Id, 1.0)
| _ => Message.FatalError($"Invalid unittypes syntax: expected \"UnitName [: (coefficient expr)]\", got $decl")
};
units ::= unitCoef[0];
// Add new normalization coefficient into map
coefSubMap[unitCoef[0]] = unitCoef[1];
index++
};
units = units.Reverse();
when (initBaseUnits)
{
CT.AllUnits = units
}
// Generate corresponding concrete types
def units = if (units.Length >= CT.BaseUnits.Length) CT.AllUnits else units;
foreach (n in units)
{
def powers = CT.PowersMap[n];
mutable coef = 0.0;
when (!coefSubMap.TryGetValue(n, out coef))
{
coef = 1.0;
// Calculate coefficient from base unit coefficients on the fly
List.Iter2(
powers,
CT.BaseUnits,
fun (power : int, name)
{
when (power != 0)
{
def f = if (power > 0) _ * _ else _ / _;
coef = f(coef, Math.Pow(coefSubMap[name], Math.Abs(power)));
}
})
}
_ = CT.GetUnitType(
GlobalEnv.Core,
$"$(CT.UnitsNamespace).$namespaceName.$n",
powers,
coef)
}
| _ => Message.FatalError($"Invalid unittypes syntax: expected semicolon-separated type names, got $body")
};
<[ () ]>
}
}
OperatorMacros.n:
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Typedtree;
using Nemerle.Macros;
namespace Oyster.Units.Macros
{
using CT = CompileTimeUtil;
/// Multiplies two units. Inserts usual * if operands are of wrong type
macro @*(lexpr, rexpr)
{
CT.MulDivMacroBody(
ImplicitCTX(),
lexpr,
rexpr,
"*",
<[ $lexpr * $rexpr ]>,
_ + _,
_ * _,
<[ double.op_Multiply($lexpr.Value, $rexpr.Value) ]>)
}
/// Divides two units. Inserts usual / if operands are of wrong type
macro @/(lexpr, rexpr)
{
CT.MulDivMacroBody(
ImplicitCTX(),
lexpr,
rexpr,
"/",
<[ $lexpr / $rexpr ]>,
_ - _,
_ / _,
<[ double.op_Division($lexpr.Value, $rexpr.Value) ]>)
}
/// Sums two units. Inserts usual + if operands are of wrong type.
/// Throws compiler error if unit dimensions are not equal
macro @+(lexpr, rexpr)
{
CT.AddSubMacroBody(
ImplicitCTX(),
lexpr,
rexpr,
"+",
<[ $lexpr + $rexpr ]>,
fun(arg2Expr) { <[ double.op_Addition($lexpr.Value, $arg2Expr) ]> })
}
/// Subtracts two units. Inserts usual - if operands are of wrong type.
/// Throws compiler error if unit dimensions are not equal
macro @-(lexpr, rexpr)
{
CT.AddSubMacroBody(
ImplicitCTX(),
lexpr,
rexpr,
"-",
<[ $lexpr - $rexpr ]>,
fun(arg2Expr) { <[ double.op_Subtraction($lexpr.Value, $arg2Expr) ]> })
}
}
На всякий случай вот solution для VS 2005, которым пользовался я: Oyster.Units.zip. Напомню, что проекты были изготовлены с использованием шаблонов проектов, доступных тут: Re: Микро-аддин для VS 2005
Здравствуйте, Oyster, Вы писали:
O>Переписывание кода на генерацую value-types — это может ускорить код по понятным причинам. Для этого можно использовать интерфейсы и немного покрутить с генериками
Собсно, без value-типов временный незачет, ждем-с окончательной версии.
O>Добавление синтаксического сахара вроде физических литералов (например 100kg * 15sec или что-то вроде того)
Это было бы такое WOW , что вполне возможно, я бы прыгнул на Немерле.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[2]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, vdimas, Вы писали:
V>Собсно, без value-типов временный незачет, ждем-с окончательной версии.
Почему? Боишься за перфоманс? Советую для начала попробовать — много маленьких объектов должны выделяться в управляемой куче быстро.
Я сделаю это изменение, когда будет время. Как ты понимаешь, времени у нас у всех не так много по жизни... я и так слишком много времени потратил на этот код (в основном борясь с собственным незнанием языка). Да, и поверь мне на слово — эту задачу можно решить на value-types — просто это будет чуть сложнее, возможно.
И всё-таки мне бы хотелось услышать твоё общее мнение о коде. Как тебе описание самих величин, например? Имхо как минимум понятнее, чем на C++, и менее подвержено человеческим ошибкам при вводе.
O>>Добавление синтаксического сахара вроде физических литералов (например 100kg * 15sec или что-то вроде того) V>Это было бы такое WOW , что вполне возможно, я бы прыгнул на Немерле.
Так прыгни и напиши
Это можно сделать — на Nemerle довольно серьёзные средства программирования, т.ч. для добавления своего синтаксиса. Единственное "но" — это выглядело бы как-то так (можно и с пробелами и без):
physops {
def res = 100 kg * 7 sec
}
Или так (имхо ненамного удобнее):
def res = kg 100 * sec 7
Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):
def res = 100 kg * 7 sec
Re[4]: [Nemerle] Семантический контроль над размерностями
O>И всё-таки мне бы хотелось услышать твоё общее мнение о коде. Как тебе описание самих величин, например? Имхо как минимум понятнее, чем на C++, и менее подвержено человеческим ошибкам при вводе.
Описание самих величин — понятнее. А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.
O>>>Добавление синтаксического сахара вроде физических литералов (например 100kg * 15sec или что-то вроде того) V>>Это было бы такое WOW , что вполне возможно, я бы прыгнул на Немерле.
[...]
O>
O>def res = kg 100 * sec 7
O>
O>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):
O>
O>def res = 100 kg * 7 sec
O>
Жаль... я именно это и имел ввиду. Спасибо за своевременный ответ.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[4]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, vdimas, Вы писали:
V>Описание самих величин — понятнее. А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.
Хмм... не сказал бы. Мне было тяжело понять код на C++, хотя опыт написания кода на C++ у меня есть.
O>>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):
V>Жаль... я именно это и имел ввиду. Спасибо за своевременный ответ.
Здравствуйте, vdimas, Вы писали:
V>Описание самих величин — понятнее. А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.
Здравствуйте, Vermicious Knid, Вы писали:
VK>Удалил старое сообщение? Видимо заметил пример в конце. Ну да ладно.
+1. Я просто не сразу понял
VK>Точно. Это в общем-то фича языка — нет разницы между постфиксными и префиксными операторами.
Да, я понял это, но не сразу. Спасибо
VK>Но есть идея и более простая. И макрос, и оператор можно зарегистрировать вручную через API компилятора(см. модуль MacroRegistry). Макрос это по сути экземпляр некого класса, реализовывающего интерфейс IMacro. Нужно просто написать собственную реализацию IMacro вручную(т.е. не использовать встроенную в компилятор генерацию этого класса), которая в зависимости от того как ее инициализируют будет вести себя как разные макросы(т.е. с разными именами и генерировать различный код).
+1. Интересная идея. Так действительно проще.
Re: [Nemerle] Семантический контроль над размерностями
Немного отдаёт неряшливостью, что внесистемные единицы помещены в систему Ext.
На то они и внесистемные...
Например, одних только давлений сколько...
— бар (100кПа = 1000гПа)
— ат (1кгс/см2 = 981гПа = 1000ммвс)
— атм (760ммрс = 1015гПа)
— просто мм ртутного и водяного столба, не говоря уж о psi и т.п.
Не заводить же под каждое из них свою систему?
Posted via RSDN NNTP Server 2.0
Перекуём баги на фичи!
Re[4]: [Nemerle] Семантический контроль над размерностями
O>>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):
VK>А по-моему выйдет. VK>
Здравствуйте, Кодт, Вы писали:
К>Немного отдаёт неряшливостью, что внесистемные единицы помещены в систему Ext. К>На то они и внесистемные...
К>Например, одних только давлений сколько... К>- бар (100кПа = 1000гПа) К>- ат (1кгс/см2 = 981гПа = 1000ммвс) К>- атм (760ммрс = 1015гПа) К>- просто мм ртутного и водяного столба, не говоря уж о psi и т.п. К>Не заводить же под каждое из них свою систему?
Да, ты прав. Я тупо скопировал постановку задачи с C++, поэтому вот так вот. На самом деле, можно немного доработать напильником и делать специальные алиасы в каком-то одном пространстве имён.
Хотя... с точки зрения CLR пространств имён вообще не существует — это просто часть имени класса. Так что не уверен, что это надо. Разве что немного поменять синтаксис самого макроса для удобства...
Re: [Nemerle] Семантический контроль над размерностями
Здорово! Хоть у меня времени и нет практически, а все ж выделю — на выходных поизучаю код.
У меня уже почти готова следующая версия. Основные отличия от предыдущей:
1. Отсутствует ограничение на количество базовых типов (ортов). Более того, орты задаются теперь не номером в векторе, а неявно (с помощью препроцессора, увы).
2. Появились типизированные константы для перевода единиц в/из системы измерения с частично совпадающими ортами (отдельное спасибо Егору за высказанные замечания по переводу электромагнитных величин из СИ в СГС).
3. Наконец, библиотека просто стала на порядок удобнее в использовании.
Тем не менее, у нее есть некоторые недостатки. Фактически игра ведется на грани возможностей языка. Поэтому мне было очень интересно посмотреть на твой код.
Предлагаю следующий вариант развития событий. У меня есть определенные мысли по развитию подобного инструмента, я их частично воплотил в новой версии. Было бы очень интересно и познавательно вести разработку параллельно — на C++ и на Nemerle. Исключительно из спортивного интереса. Весьма возможно, что кое-что из того, что мне далось с таким трудом на C++, будет легко сделать на Nemerle.
В течении недели я надеюсь причесать новую версию и довести ее до состояния, достаточного для выкладывания в public domain.
... << RSDN@Home 1.1.4 stable rev. 510>>
Re[3]: [Nemerle] Семантический контроль над размерностями
Да куда он от нас денется... Я вот только свою инструментальную либу парсеров закончу на C# и с собой возьму. А то, насколько я понял, чтобы сделать на Немерле совсем уж эдакое, возможно, придется самим парсить и разбирать свои DSL. Иными словами я бы не хотел шарахаться от задач только потому, что они вдруг могут потребовать небольшого парсинга, выходящего за рамки регэкспов.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[5]: [Nemerle] Семантический контроль над размерностями
Да куда он от нас денется... Я вот только свою инструментальную либу парсеров закончу на C# и с собой возьму. А то, насколько я понял, чтобы сделать на Немерле совсем уж эдакое, возможно, придется самим парсить и разбирать свои DSL. Иными словами я бы не хотел шарахаться от задач только потому, что они вдруг могут потребовать небольшого парсинга, выходящего за рамки регэкспов.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[2]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, CrystaX, Вы писали:
CX>Предлагаю следующий вариант развития событий. У меня есть определенные мысли по развитию подобного инструмента, я их частично воплотил в новой версии. Было бы очень интересно и познавательно вести разработку параллельно — на C++ и на Nemerle. Исключительно из спортивного интереса. Весьма возможно, что кое-что из того, что мне далось с таким трудом на C++, будет легко сделать на Nemerle.
Интересная идея. И я вижу много чего, чего можно было бы в мой код добавить. Но... время, всё упирается во время. Мне, к сожалению, не нужно этот код использовать в своих приложениях, поэтому я не знаю, смогу ли развивать код дальше.
Здравствуйте, VladD2, Вы писали:
VD>А это не может вызвать проблем? Не окажется, что постфикстные операторы интерпретированы не врено?
Могут быть проблемы в случае, если существует такой же бинарный оператор. Например, такой код:
def x = 100 -
При компиляции выдаёт ошибки:
error : parse error near operator `-': unexpected end of token sequence
error : parse error near separator or closing bracket: expecting expression
Причём проблема именно в существовании такого же бинарного оператора, потому что такой код выдаёт те же ошибки:
struct SomeValue
{
public Value : double;
this(value : double)
{
Value = value;
}
static public @-(value : SomeValue) : SomeValue
{
SomeValue(-value.Value)
}
};
def x = SomeValue(100);
def y = x -
Re[3]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, Oyster, Вы писали:
O>Интересная идея. И я вижу много чего, чего можно было бы в мой код добавить. Но... время, всё упирается во время. Мне, к сожалению, не нужно этот код использовать в своих приложениях, поэтому я не знаю, смогу ли развивать код дальше.
Согласен, с временем тяжело. Но ведь никто нас не гонит, не так ли? У этой библиотеки есть большой потенциал (далеко не только в области вычислений физических величин) и жалко было бы его упускать.
O>Тут Влад прав
— лучше направить усилия на что-то более нужное (в моём случае).
Ты знаешь, после того, как я написал эту библиотеку, я как-то самопроизвольно начал раскладывать в своих проектах все используемые величины по ортам — и это очень сильно помогает. Физические величины — это только пример, не более того.
Вот более жизненный пример. Какая-нибудь система учета, оперирующая понятиями 1) деньги и 2) различные грузы (например, сахар и мука). Введем орты нашей системы координат — деньги, килограмм-сахар и килограмм-мука.
Теперь цена на сахар — это не то же самое, что цена на муку и помножив руб/кгсах на кгмука я получу не руб, а руб*кгмука/кгсах, о чем мне тут же напомнит компилятор. Конечно же, сейчас трудно себе представить такое практическое применение моей библиотеки, но с новой версией это будет намного проще.
В общем, я беру таймаут на неделю. Затем выложу новую версию. Если будет интересно с ней поиграться — you are welcome.
... << RSDN@Home 1.1.4 stable rev. 510>>
Re[3]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, VladD2, Вы писали:
CX>>Было бы очень интересно и познавательно вести разработку параллельно — на C++ и на Nemerle. Исключительно из спортивного интереса.
VD>Мужики я преклоняюсь перед вашей настойчивостью и самоотверженностью, но такие усилия лучше было бы направить на что-то более полезное.
Эта библиотека (по крайней мере заложенный в ней потенциал) — очень полезный инструмент. Более развернутый ответ см. здесь
Здравствуйте, CrystaX, Вы писали:
CX>Ты знаешь, после того, как я написал эту библиотеку, я как-то самопроизвольно начал раскладывать в своих проектах все используемые величины по ортам — и это очень сильно помогает. Физические величины — это только пример, не более того.
Ну мне как-то всё равно не надо это в работе, если честно Есть и более важные вещи.
На этот код я убил, наверное, около двух рабочих дней. Больше убивать не хочется... Возможно, если интерес снова проснётся, я и займусь им, но гарантий на этот счёт никаких дать не могу.
CX>В общем, я беру таймаут на неделю. Затем выложу новую версию. Если будет интересно с ней поиграться — you are welcome.
Помнится, ты ещё говорил, что на выходных попробуешь с Nemerle покопаться... настоятельно рекомендую это сделать
Re[5]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, Oyster, Вы писали:
O>Ну мне как-то всё равно не надо это в работе, если честно Есть и более важные вещи.
Я не настаиваю.
O> Помнится, ты ещё говорил, что на выходных попробуешь с Nemerle покопаться... настоятельно рекомендую это сделать
Копаться я и раньше начал. Я твой код хочу детально разобрать. А Nemerle — что ж, язык неплохой, но мне интересен пока только как объект познания, на практике его применить не могу.
... << RSDN@Home 1.1.4 stable rev. 510>>
Re[6]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, CrystaX, Вы писали:
CX>Копаться я и раньше начал. Я твой код хочу детально разобрать.
Ну тогда ты эта... обращайся... если что
CX>А Nemerle — что ж, язык неплохой, но мне интересен пока только как объект познания, на практике его применить не могу.
Да я тоже... пока что. Вроде намечается проектик на одного человека, на который можно будет выбить Nemerle. Скрещиваю пальцы на удачу
Re[3]: [Nemerle] Семантический контроль над размерностями
using System.Collections.Generic;
namespace Oyster.Units.Macros
{
/// Stores compile-time information for UnitsDefinition macromodule CompileTimeInfo
{
/// List of basis unit names in correct orderpublic mutable BasisUnits : list[string] = [];
/// List of all common units (including basis ones)public mutable CommonUnits : list[string] = [];
/// Map of common unit powerspublic PowersMap : Dictionary[string, list[int]] = Dictionary();
}
}
IUnit.n:
namespace Oyster.Units
{
/// Interface for all unitspublic interface IUnit {}
}
NormCoefAttribute.n:
using System;
namespace Oyster.Units
{
/// Unit normalization coefficient attribute
[AttributeUsage(AttributeTargets.Class)]
public class NormCoefAttribute : Attribute
{
public NormCoef : double;
public this (normCoef : double)
{
NormCoef = normCoef
}
}
}
Здравствуйте, Oyster, Вы писали:
O>Новую версию забираем тут: Oyster.Units.0.03.zip. Для сборки надо использовать Nemerle билд r6176 — не ниже (как и для предыдущей версии).
Для того, чтобы проинсталлировать билд, что должно уже стоять на машине? Необходимо ли наличие питона, antlr и ряда прочих вещей, которые проверяются в ./configure? Какую версию make.exe следует использовать?
Здравствуйте, <Аноним>, Вы писали:
А>Для того, чтобы проинсталлировать билд, что должно уже стоять на машине? Необходимо ли наличие питона, antlr и ряда прочих вещей, которые проверяются в ./configure? Какую версию make.exe следует использовать?
У меня WinXP и я просто поставил cygwin, как советуют сами разработчики. Поэтому я не знаю, что ещё надо ставить — ставил почти всё по умолчанию (кроме make — см. ниже).
Знаю точно, что нужен gmake, а не стандартный make, — это было написано где-то в доках.
Re[3]: установка Nemerle
От:
Аноним
Дата:
10.04.06 10:00
Оценка:
Здравствуйте, Oyster, Вы писали:
O>У меня WinXP и я просто поставил cygwin, как советуют сами разработчики. Поэтому я не знаю, что ещё надо ставить — ставил почти всё по умолчанию (кроме make — см. ниже).
O>Знаю точно, что нужен gmake, а не стандартный make, — это было написано где-то в доках.
Спасибо.
Случаем не встречались с такой проблемой: make останавливается на 3-ем stage после компиляции ncc.exe с ошибкой
*** No rule to make target '../../ncc/out.stage/', needed by 'Nemerle.dll'. Stop.
Здравствуйте, Vermicious Knid, Вы писали:
VK>Но есть идея и более простая. И макрос, и оператор можно зарегистрировать вручную через API компилятора(см. модуль MacroRegistry). Макрос это по сути экземпляр некого класса, реализовывающего интерфейс IMacro. Нужно просто написать собственную реализацию IMacro вручную(т.е. не использовать встроенную в компилятор генерацию этого класса), которая в зависимости от того как ее инициализируют будет вести себя как разные макросы(т.е. с разными именами и генерировать различный код).
Как ты уже знаешь, эта идея мне понравилась. В общем, я начал думать о том, как реализовать unit-алиасы с её помощью и столкнулся с маленькой проблемкой. Дело в том, что в текущей версии библиотеки
unit-типы можно определять как в той же сборке, где они используются, так и в другой сборке, и я хочу сохранить такое поведение. Вот в варианте с другой сборкой и возникает проблема — надо как-то создать и загеристрировать макросы при загрузке сборки компилятором.
Я думал над этой проблемой и пришёл к такому вот решению:
Unit-алиасы (такие как kg — клиограммы — или m — метры, — например) сохраняются в неком атрибуте уровня сборки (или в любом другом хранилище в сборке с unit-типами).
Также в этой сборке создаётся псевдо-макрос, который ничего не умеет и в конструкторе которого создаются и регистрируются макросы для unit-алиасов. На сборку цепляется ContainsMacroAttribute и, как результат, конструктор этого псевдо-макроса выполняется при загрузке сборки LibraryLoader-ом.
Конечно, возможен и другой вариант — генерация классов макросов для unit-алиасов, но этого не хочется делать из-за желания использовать код повторно.
Собственно, мой вопрос звучит так — можно ли то же самое сделать как-то иначе? Т.е. существует ли какой-нибудь другой способ выполнить код при загрузке сборки кроме ContainsMacroAttribute и конструктора макроса?
Re[7]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, Oyster, Вы писали:
O>Конечно, возможен и другой вариант — генерация классов макросов для unit-алиасов, но этого не хочется делать из-за желания использовать код повторно.
На самом деле, мы можем просто создавать типы для макросов, регистрировать их ручками, если в той же сборке, и не мучаться с добавлением ещё каких-то вспомогательных атрибутов (кроме [ContainsMacro] и [Operator]). Естественно, это возможно только при условии, если мы можем из макроса добавить атрибуты сборке — я ещё не разбирался с этим вопросом. Кстати, это утверждение также справедливо для варианта из предыдущего сообщения.
При таком подходе снова встаёт вопрос — а могу ли я просто создать новый макрос внутри макроса, используя знакомый мне синтаксис macro и не переопределяя IMacro вручную?
Re[2]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, VladD2, Вы писали:
O>>Знаю точно, что нужен gmake, а не стандартный make, — это было написано где-то в доках. VD>Стандартного мэйка нет в природе.
Да, глупость сказал... Я имел в виду, "нужен не тот make, который ставится с cygwin по умолчанию, а gmake".
Здравствуйте, VladD2, Вы писали:
VD>2. Создал изумительный пример разработки DSL. Очень советую, кстати, запостить его в форум Немерла посвященный примерам. Можно со ссылкой на С++-ный аналог.
Да, это я как раз и хотел сделать. Вот найду время, поперевожу некоторые комменты на англицкий, получу разрешение у CrystaX и сделаю, мабуть.
Здравствуйте, Oyster, Вы писали:
O>Да, это я как раз и хотел сделать. Вот найду время, поперевожу некоторые комменты на англицкий, получу разрешение у CrystaX и сделаю, мабуть.
Давай. Зело полезное дело!
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: [Nemerle] Семантический контроль над размерностями