[Nemerle] Семантический контроль над размерностями
От: Oyster Украина https://github.com/devoyster
Дата: 05.04.06 08:03
Оценка: 160 (12)
Всем доброго времени суток. Некоторое время назад я натолкнулся в этом форуме на код, написанный на шаблонах C++, который позволяет контролировать размерности физических типов во время компиляции: Обеспечение семантического контроля над размерностями станда
Автор: CrystaX
Дата: 21.11.05
. Некоторое другое время назад я начал увлекаться языком Nemerle и мне захотелось решить эту же задачу средствами Nemerle.

Немного о Nemerle (для тех, кто не знает)

Nemerle — это (относительно) новый язык для платформы .NET (под FW 2.0), синтаксис которого похож на C#. Но в нём имеются и некоторые возможности отсутствующие в C# напрочь. А именно:


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

Больше про Nemerle можно почитать в теме Изучаем макросы Nemerle
Автор: VladD2
Дата: 18.02.06
— там описано, как поставить его себе на машину и использовать + немного написано о самом языке. Я использую для работы набор шаблонов проектов для студии
Автор: Oyster
Дата: 06.03.06
. Также те, комы не лень разгребать километровые посты, могут узнать много полезного о Nemerle в форуме Философия программирования.


Код на Nemerle решает ровно те же задачи, что и код, описанный в Обеспечение семантического контроля над размерностями станда
Автор: CrystaX
Дата: 21.11.05
. А вот и тестовый пример (тоже внаглую украденный оттуда):

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 группы:


Макробиблиотека должна быть скомпилирована в отдельную сборку. Описание физических величин — в другой отдельной сборке. Я использовал для компиляции билд 0.9.2.6168 (т.е. первоапрельский).



Немного оффтопа

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

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


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

Надеюсь, сообщение оказалось полезным. И сорри всем, кто дочитал до этой строчки, за объём текста
Исходники макробиблиотеки
От: Oyster Украина https://github.com/devoyster
Дата: 05.04.06 08:06
Оценка:
ConcreteUnitTypeAttribute.n:

using System;
using Nemerle.Utility;

namespace Oyster.Units
{
    /// Automatically attached to all concrete unit type implemetations
    public 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 types
    abstract public class UnitTypeBase
    {
        /// Unit value as double
        [Accessor] _value : double;
        

        /// Constructs given unit type instance from any other unit type instance
        protected this(value : UnitTypeBase)
        {
            // Perform normalization-denormalization here
            this(value._value * value.NormalizationCoef / NormalizationCoef)
        }
        
        /// Constructs given unit type instance from double
        protected this(value : double)
        {
            _value = value
        }
        

        /// Unit type normalization coefficient
        abstract public NormalizationCoef : double { get; }
        
        /// ToString() override
        override 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 information
    module CompileTimeUtil
    {
        /// Units NS name
        public UnitsNamespace = "Oyster.Units";

        /// Stores list of base units (such as mass or length) registered in the system
        public mutable BaseUnits : list[string];

        /// Stores list of all registered units including aliases
        public mutable AllUnits : list[string];

        /// Stores powers list for given base unit or alias
        public 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 coefficient
        public 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 namespace
        public 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 list
        public 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 powers
        public GenerateUnitTypeSuffix(baseUnitPowers : list[int]) : string
        {
            | [] => string.Empty
            | _ => "_" + baseUnitPowers.Head.ToString() + GenerateUnitTypeSuffix(baseUnitPowers.Tail)
        }

        /// Parses abstract unit type suffix
        public ParseUnitTypeSuffix(typeName : string) : list[int]
        {
            List.Map(NString.Split(typeName.Substring(typeName.IndexOf('_') + 1), '_'), int.Parse)
        }

        /// Returns PExpr type
        public GetExprType(ctx : Typer, expr : PExpr) : TypeInfo
        {
            ctx.TypeExpr(expr).ty.FixedValue.TypeInfo
        }

        /// Returns normalization coef if type is concrete unit type
        public 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 macros
        public 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 / macros
        public 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 - macros
        public 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) ]> })
    }
}
Описание физических величин
От: Oyster Украина https://github.com/devoyster
Дата: 05.04.06 08:08
Оценка:
UnitDef.n:

using Oyster.Units.Macros;

public module UnitDef
{
    public Def() : void
    {
        // SI
        unittypes Si {
            // Base units
            Mass;
            Length;
            Time;
            Temperature;
            CurrentStrength;
            LightIntensity;
            QuantityOfSubstance;

            // Aliases
            Area = Length ^ 2;
            Volume = Area * Length;
            Velocity = Length / Time;
            Acceleration = Velocity / Time;
            AngularVelocity = Velocity;
            AngularAcceleration = Acceleration;
            Density = Mass / Volume;

            Force = Mass * Acceleration;
            Pressure = Mass / Length / Time ^ 2;
            SpecificGravity = Pressure / Length;

            LinearMomentum = Mass * Velocity;
            MomentOfInertia = Mass * Area;

            Energy = Force * Length;
            Power = Energy / Time;

            DynamicViscosity = Mass / Length / Time;
            KinematicViscosity = DynamicViscosity;

            HeatCapacity = Energy / Temperature;
            Entropy = HeatCapacity;
            SpecificHeat = HeatCapacity / Mass;
            SpecificEntropy = SpecificHeat;

            Charge = Time * CurrentStrength;
            LinearDensityOfCharge = Charge / Length;
            SurfaceDensityOfCharge = Charge / Area;
            ElectricFluxDensity = SurfaceDensityOfCharge;
            PackedDensityOfCharge = Charge / Volume;

            Voltage = Power / CurrentStrength;
            ElectricForce = Force / CurrentStrength;
            Resistance = Voltage / CurrentStrength;
            SpecificResistance = Resistance * Length;

            Permittance = Time / Resistance;

            CurrentDensity = CurrentStrength / Length ^ 2
        };

        // CGS
        unittypes Cgs {
            Mass : (1.0 / 1000.0);
            Length : (1.0 / 100.0);
            Time;
            Temperature;
            CurrentStrength : (1.0 / 3000000000.0);
            LightIntensity;
            QuantityOfSubstance
        };

        // EXT
        unittypes Ext {
            Power : (1471.0 / 2.0)
        }
    }
}
Все исходники в solution для VS 2005
От: Oyster Украина https://github.com/devoyster
Дата: 05.04.06 08:47
Оценка:
На всякий случай вот solution для VS 2005, которым пользовался я: Oyster.Units.zip. Напомню, что проекты были изготовлены с использованием шаблонов проектов, доступных тут: Re: Микро-аддин для VS 2005
Автор: Oyster
Дата: 06.03.06
.
Re: [Nemerle] Семантический контроль над размерностями
От: vdimas Россия  
Дата: 05.04.06 09:32
Оценка:
Здравствуйте, Oyster, Вы писали:

O>
  • Переписывание кода на генерацую value-types — это может ускорить код по понятным причинам. Для этого можно использовать интерфейсы и немного покрутить с генериками

    Собсно, без value-типов временный незачет, ждем-с окончательной версии.

    O>
  • Добавление синтаксического сахара вроде физических литералов (например 100kg * 15sec или что-то вроде того)
    Это было бы такое WOW , что вполне возможно, я бы прыгнул на Немерле.
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
  • Re[2]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 09:43
    Оценка:
    Здравствуйте, 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[3]: [Nemerle] Семантический контроль над размерностями
    От: Vermicious Knid  
    Дата: 05.04.06 10:12
    Оценка: 54 (3)
    Здравствуйте, Oyster, Вы писали:

    O>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):


    А по-моему выйдет.
    namespace My.Phys
    {                    
        [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "kg", true, 283, 284)]
        [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "sec", true, 283, 284)]
    
        macro @kg(val)
        {
            <[ do_something_crazy_with_kg($val) ]>
        }
    
        macro @sec(val)
        {
            <[ do_something_crazy_with_sec($val) ]>
        }
    }


    using System;
    using My.Phys;
    
    module Main
    {
        do_something_crazy_with_kg(val : double) : double
        {
            val
        }
    
        do_something_crazy_with_sec(val : double) : double
        {
            val
        }
    
        Main() : void
        {
            Console.WriteLine(100.0 kg);
            Console.WriteLine(100.0 kg);
            Console.WriteLine(20.0 sec);
            Console.WriteLine(100.0 kg * 20.0 sec);
        }
    }
    Re[4]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 11:04
    Оценка:
    Здравствуйте, Vermicious Knid, Вы писали:

    VK>А по-моему выйдет.


    А ведь ты прав! Очень даже изящное решение получается, спасибо!

    Кстати, в таком случае встаёт вопрос генерации макроса внутри макроса чтобы не хардкодить.
    Re[3]: [Nemerle] Семантический контроль над размерностями
    От: vdimas Россия  
    Дата: 05.04.06 11:10
    Оценка:
    Здравствуйте, Oyster, Вы писали:


    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] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 11:27
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Описание самих величин — понятнее. А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.


    Хмм... не сказал бы. Мне было тяжело понять код на C++, хотя опыт написания кода на C++ у меня есть.

    O>>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):


    V>Жаль... я именно это и имел ввиду. Спасибо за своевременный ответ.


    Я ошибся : Re[3]: [Nemerle] Семантический контроль над размерностями
    Автор: Vermicious Knid
    Дата: 05.04.06
    .
    Re[4]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 11:50
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Описание самих величин — понятнее. А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.


    Кстати, должен заметить, что мой код избавлен от двух первых проблем, описанных тут: Направления дальнейшего развития
    Автор: CrystaX
    Дата: 21.11.05
    . Так что это уже не точно тот же код.
    Re[5]: [Nemerle] Семантический контроль над размерностями
    От: Vermicious Knid  
    Дата: 05.04.06 12:04
    Оценка: 7 (1)
    Здравствуйте, Oyster, Вы писали:

    O>А ведь ты прав! Очень даже изящное решение получается, спасибо!


    Удалил старое сообщение? Видимо заметил пример в конце. Ну да ладно.

    O>Интересненько? А точно получится использовать унарный постфиксный (не префиксный) оператор?


    Точно. Это в общем-то фича языка — нет разницы между постфиксными и префиксными операторами.

    using Nemerle.English;
    def yoda = true not;
    def reallyObfuscated = yoda !;
    def ohmy = 23 ~;


    O>Кстати, в таком случае встаёт вопрос генерации макроса внутри макроса чтобы не хардкодить.


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

    Но есть идея и более простая. И макрос, и оператор можно зарегистрировать вручную через API компилятора(см. модуль MacroRegistry). Макрос это по сути экземпляр некого класса, реализовывающего интерфейс IMacro. Нужно просто написать собственную реализацию IMacro вручную(т.е. не использовать встроенную в компилятор генерацию этого класса), которая в зависимости от того как ее инициализируют будет вести себя как разные макросы(т.е. с разными именами и генерировать различный код).

    Вот дамп кода(на псевдо-немерле , выглядит жутко конечно, но понять можно ) методов макро-оператора kg. Кстати стоит обратить внимание, что по сути это вполне обычный макрос, а оператором его делает атрибут сборки. В модуле LibrariesLoader есть функции которые сканируют сборку на предмет нахождения в ней макросов и таких атрибутов, и регистрируют их в MacroRegistry.

    Public, Sealed, Macro, SpecialName  My.Phys.kgMacro : System.Object, Nemerle.Compiler.IMacro {
        Private, Static method My.Phys.kgMacro..cctor() : void {
        {
              def customs = typeof (kgMacro).GetCustomAttributes (false);
              foreach (in ((x is MacroUsageAttribute), customs)) my_usage = x;
              keywords = []
        }
    
        Public, SpecialName method My.Phys.kgMacro.get_IsInherited() : bool {
            my_usage != null && my_usage.Inherited
        }
    
        Public, SpecialName method My.Phys.kgMacro.get_Keywords() : list[string] {
            keywords
        }
    
        Public, SpecialName method My.Phys.kgMacro.get_Usage() : Nemerle.MacroUsageAttribute {
            my_usage
        }
    
        Public, SpecialName method My.Phys.kgMacro.get_IsTailRecursionTransparent() : bool {
            false
        }
        Public method My.Phys.kgMacro.GetName() : string {
            "kg"
        }
        Public method My.Phys.kgMacro.GetNamespace() : string {
            "My.Phys"
        }
        Public method My.Phys.kgMacro.SyntaxExtension() : 
    (Nemerle.Compiler.GrammarElement * list[Nemerle.Compiler.Parsetree.SyntaxElement] 
    -> list[Nemerle.Compiler.Parsetree.SyntaxElement]) {
            (null, ((Nemerle.Utility.Identity.[list[SyntaxElement], list[SyntaxElement]] () : object) 
    :> list[SyntaxElement] -> list[SyntaxElement]))
        }
        Public method My.Phys.kgMacro.Run(_N_944 : Nemerle.Compiler.Typer, parms : 
    list[Nemerle.Compiler.Parsetree.SyntaxElement]) : Nemerle.Compiler.Parsetree.PExpr {
            match (parms) {
                  | [SyntaxElement.Expression (val)] => 
                    <[ do_something_crazy_with_kg ($(val)) ]>
                  | _  => 
                      def len = List.Length (parms);
                      def types = parms.ToString (", ");
    Message.FatalError ("macro `" + "kg" + "' expects following list of arguments: (" + 
    "PExpr" + ") got some " + len.ToString () + " parameters [" + types + "]")
            }
        }
        Public method My.Phys.kgMacro.CallTransform(trans_p : list[Nemerle.Compiler.Parsetree.PExpr]) : 
    list[Nemerle.Compiler.Parsetree.SyntaxElement] {
        {
              mutable trans_res = [];
              match (trans_p) {
                | x :: trans_p => 
                  {
                    trans_res = SyntaxElement.Expression (x) :: trans_res;
                    match (trans_p) {
                          | x :: _  => 
                            trans_res = SyntaxElement.Expression (x) :: trans_res
                          | [] => () }
                  }
                | [] => ()
            };
              List.Rev (trans_res)
        }
    }
    Re[6]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 12:13
    Оценка:
    Здравствуйте, Vermicious Knid, Вы писали:

    VK>Удалил старое сообщение? Видимо заметил пример в конце. Ну да ладно.


    +1. Я просто не сразу понял

    VK>Точно. Это в общем-то фича языка — нет разницы между постфиксными и префиксными операторами.


    Да, я понял это, но не сразу. Спасибо

    VK>Но есть идея и более простая. И макрос, и оператор можно зарегистрировать вручную через API компилятора(см. модуль MacroRegistry). Макрос это по сути экземпляр некого класса, реализовывающего интерфейс IMacro. Нужно просто написать собственную реализацию IMacro вручную(т.е. не использовать встроенную в компилятор генерацию этого класса), которая в зависимости от того как ее инициализируют будет вести себя как разные макросы(т.е. с разными именами и генерировать различный код).


    +1. Интересная идея. Так действительно проще.
    Re[6]: [Nemerle] Семантический контроль над размерностями
    От: Vermicious Knid  
    Дата: 05.04.06 12:14
    Оценка: 1 (1)
    Здравствуйте, Vermicious Knid, Вы писали:

    VK>Вот дамп кода(на псевдо-немерле , выглядит жутко конечно, но понять можно ) методов макро-оператора kg.


    Вот еще дамп из рефлектора, чтобы было понятнее:
    public sealed class kgMacro : IMacro
    {
          // Methods
          static kgMacro();
          public kgMacro();
          public list<SyntaxElement> CallTransform(list<PExpr> trans_p);
          public string GetName();
          public string GetNamespace();
          public PExpr Run(Typer _N_944, list<SyntaxElement> parms);
          public Tuple<GrammarElement, Function<list<SyntaxElement>, list<SyntaxElement>>> SyntaxExtension();
    
          // Properties
          public bool IsInherited { get; }
          public bool IsTailRecursionTransparent { get; }
          public list<string> Keywords { get; }
          public MacroUsageAttribute Usage { get; }
    
          // Fields
          [Immutable]
          private static list<string> keywords;
          private static MacroUsageAttribute my_usage;
    }
    Re: [Nemerle] Семантический контроль над размерностями
    От: Кодт Россия  
    Дата: 05.04.06 13:38
    Оценка:
    Немного отдаёт неряшливостью, что внесистемные единицы помещены в систему Ext.
    На то они и внесистемные...

    Например, одних только давлений сколько...
    — бар (100кПа = 1000гПа)
    — ат (1кгс/см2 = 981гПа = 1000ммвс)
    — атм (760ммрс = 1015гПа)
    — просто мм ртутного и водяного столба, не говоря уж о psi и т.п.
    Не заводить же под каждое из них свою систему?
    Posted via RSDN NNTP Server 2.0
    Перекуём баги на фичи!
    Re[4]: [Nemerle] Семантический контроль над размерностями
    От: vdimas Россия  
    Дата: 05.04.06 13:39
    Оценка:
    Здравствуйте, Vermicious Knid, Вы писали:


    O>>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):


    VK>А по-моему выйдет.

    VK>
    VK>namespace My.Phys
    VK>{                    
    VK>    [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "kg", true, 283, 284)]
    VK>    [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "sec", true, 283, 284)]
    VK>


    Что это за шаманство?
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Re[2]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 13:42
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    К>Немного отдаёт неряшливостью, что внесистемные единицы помещены в систему Ext.

    К>На то они и внесистемные...

    К>Например, одних только давлений сколько...

    К>- бар (100кПа = 1000гПа)
    К>- ат (1кгс/см2 = 981гПа = 1000ммвс)
    К>- атм (760ммрс = 1015гПа)
    К>- просто мм ртутного и водяного столба, не говоря уж о psi и т.п.
    К>Не заводить же под каждое из них свою систему?

    Да, ты прав. Я тупо скопировал постановку задачи с C++, поэтому вот так вот. На самом деле, можно немного доработать напильником и делать специальные алиасы в каком-то одном пространстве имён.

    Хотя... с точки зрения CLR пространств имён вообще не существует — это просто часть имени класса. Так что не уверен, что это надо. Разве что немного поменять синтаксис самого макроса для удобства...
    Re[5]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 13:43
    Оценка: 8 (1)
    Здравствуйте, vdimas, Вы писали:

    VK>>А по-моему выйдет.

    VK>>namespace My.Phys
    VK>>{                    
    VK>>    [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "kg", true, 283, 284)]
    VK>>    [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "sec", true, 283, 284)]

    V>Что это за шаманство?

    Задание приоритетов для своих операторов — надо же их как-то задавать
    Re: [Nemerle] Семантический контроль над размерностями
    От: CrystaX Россия https://crystax.me/
    Дата: 05.04.06 21:02
    Оценка:
    Здравствуйте, Oyster, Вы писали:

    Здорово! Хоть у меня времени и нет практически, а все ж выделю — на выходных поизучаю код.
    У меня уже почти готова следующая версия. Основные отличия от предыдущей:
    1. Отсутствует ограничение на количество базовых типов (ортов). Более того, орты задаются теперь не номером в векторе, а неявно (с помощью препроцессора, увы).
    2. Появились типизированные константы для перевода единиц в/из системы измерения с частично совпадающими ортами (отдельное спасибо Егору за высказанные замечания по переводу электромагнитных величин из СИ в СГС).
    3. Наконец, библиотека просто стала на порядок удобнее в использовании.

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

    Предлагаю следующий вариант развития событий. У меня есть определенные мысли по развитию подобного инструмента, я их частично воплотил в новой версии. Было бы очень интересно и познавательно вести разработку параллельно — на C++ и на Nemerle. Исключительно из спортивного интереса. Весьма возможно, что кое-что из того, что мне далось с таким трудом на C++, будет легко сделать на Nemerle.

    В течении недели я надеюсь причесать новую версию и довести ее до состояния, достаточного для выкладывания в public domain.
    ... << RSDN@Home 1.1.4 stable rev. 510>>
    Re: [Nemerle] Семантический контроль над размерностями
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 06.04.06 01:46
    Оценка: :)
    Здравствуйте, Oyster, Вы писали:

    Ну, ты герой! Такой объем работы в научных целях.

    Я бы ни в жизнь на такое не решился.
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re: Описание физических величин
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 06.04.06 01:46
    Оценка: +1
    Здравствуйте, Oyster, Вы писали:

    ...

    А вот здесь действительно видна сила языка. Вместо птичьего языка имеется внятный DSL. Это окупает любые сложности в разработке макросов!
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[3]: [Nemerle] Семантический контроль над размерностями
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 06.04.06 01:46
    Оценка:
    Здравствуйте, Oyster, Вы писали:

    O>Имхо как минимум понятнее, чем на C++, и менее подвержено человеческим ошибкам при вводе.


    Это оОооочень скромно сказано.
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[6]: [Nemerle] Семантический контроль над размерностями
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 06.04.06 01:46
    Оценка:
    Здравствуйте, Vermicious Knid, Вы писали:

    Уменя порой впечатление, что ты входишь в группу разработчиков языка.
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[6]: [Nemerle] Семантический контроль над размерностями
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 06.04.06 01:46
    Оценка:
    Здравствуйте, Vermicious Knid, Вы писали:

    VK>Точно. Это в общем-то фича языка — нет разницы между постфиксными и префиксными операторами.


    VK>
    VK>using Nemerle.English;
    VK>def yoda = true not;
    VK>def reallyObfuscated = yoda !;
    VK>def ohmy = 23 ~;
    VK>


    А это не может вызвать проблем? Не окажется, что постфикстные операторы интерпретированы не врено?
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[4]: [Nemerle] Семантический контроль над размерностями
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 06.04.06 01:46
    Оценка: +1
    Здравствуйте, vdimas, Вы писали:

    V>Описание самих величин — понятнее.


    Дык... Именно они особо важны в прикладном смысле. Именно их в последствии будет несметное множество (если конечно вообще это кому-то надо ).

    V> А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.


    Дык, в С++ 90% нужного функционала уже встроено в язык. А ему пришлось писать все на универсальном средстве. Он же типы вручную генерирует.

    V>Жаль... я именно это и имел ввиду. Спасибо за своевременный ответ.


    Как видишь
    Автор: Vermicious Knid
    Дата: 05.04.06
    вишло. И очень даже просто.

    ЗЫ

    Кстати, кто-то намчто-то обещал
    Автор: vdimas
    Дата: 05.04.06
    сделать если выйдет.
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[2]: [Nemerle] Семантический контроль над размерностями
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 06.04.06 01:46
    Оценка: +1
    Здравствуйте, Кодт, Вы писали:

    К>Немного отдаёт неряшливостью, что внесистемные единицы помещены в систему Ext.

    К>На то они и внесистемные...

    Дык цель то была опробовать Немерле в бою.
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[2]: [Nemerle] Семантический контроль над размерностями
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 06.04.06 01:46
    Оценка: +1
    Здравствуйте, CrystaX, Вы писали:

    CX>Было бы очень интересно и познавательно вести разработку параллельно — на C++ и на Nemerle. Исключительно из спортивного интереса.


    Мужики я преклоняюсь перед вашей настойчивостью и самоотверженностью, но такие усилия лучше было бы направить на что-то более полезное.
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[5]: [Nemerle] Семантический контроль над размерностями
    От: vdimas Россия  
    Дата: 06.04.06 06:20
    Оценка:
    Здравствуйте, VladD2, Вы писали:


    VD>Кстати, кто-то намчто-то обещал
    Автор: vdimas
    Дата: 05.04.06
    сделать если выйдет.


    Да куда он от нас денется... Я вот только свою инструментальную либу парсеров закончу на C# и с собой возьму. А то, насколько я понял, чтобы сделать на Немерле совсем уж эдакое, возможно, придется самим парсить и разбирать свои DSL. Иными словами я бы не хотел шарахаться от задач только потому, что они вдруг могут потребовать небольшого парсинга, выходящего за рамки регэкспов.
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Re[5]: [Nemerle] Семантический контроль над размерностями
    От: vdimas Россия  
    Дата: 06.04.06 06:22
    Оценка:
    Здравствуйте, VladD2, Вы писали:


    VD>Кстати, кто-то намчто-то обещал
    Автор: vdimas
    Дата: 05.04.06
    сделать если выйдет.


    Да куда он от нас денется... Я вот только свою инструментальную либу парсеров закончу на C# и с собой возьму. А то, насколько я понял, чтобы сделать на Немерле совсем уж эдакое, возможно, придется самим парсить и разбирать свои DSL. Иными словами я бы не хотел шарахаться от задач только потому, что они вдруг могут потребовать небольшого парсинга, выходящего за рамки регэкспов.
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Re[2]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 06.04.06 06:24
    Оценка:
    Здравствуйте, CrystaX, Вы писали:

    CX>Предлагаю следующий вариант развития событий. У меня есть определенные мысли по развитию подобного инструмента, я их частично воплотил в новой версии. Было бы очень интересно и познавательно вести разработку параллельно — на C++ и на Nemerle. Исключительно из спортивного интереса. Весьма возможно, что кое-что из того, что мне далось с таким трудом на C++, будет легко сделать на Nemerle.


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

    Тут Влад прав
    Автор: VladD2
    Дата: 06.04.06
    — лучше направить усилия на что-то более нужное (в моём случае).
    Re[7]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 06.04.06 07:43
    Оценка:
    Здравствуйте, 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] Семантический контроль над размерностями
    От: CrystaX Россия https://crystax.me/
    Дата: 06.04.06 08:40
    Оценка:
    Здравствуйте, Oyster, Вы писали:

    O>Интересная идея. И я вижу много чего, чего можно было бы в мой код добавить. Но... время, всё упирается во время. Мне, к сожалению, не нужно этот код использовать в своих приложениях, поэтому я не знаю, смогу ли развивать код дальше.


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

    O>Тут Влад прав
    Автор: VladD2
    Дата: 06.04.06
    — лучше направить усилия на что-то более нужное (в моём случае).


    Ты знаешь, после того, как я написал эту библиотеку, я как-то самопроизвольно начал раскладывать в своих проектах все используемые величины по ортам — и это очень сильно помогает. Физические величины — это только пример, не более того.
    Вот более жизненный пример. Какая-нибудь система учета, оперирующая понятиями 1) деньги и 2) различные грузы (например, сахар и мука). Введем орты нашей системы координат — деньги, килограмм-сахар и килограмм-мука.
    Теперь цена на сахар — это не то же самое, что цена на муку и помножив руб/кгсах на кгмука я получу не руб, а руб*кгмука/кгсах, о чем мне тут же напомнит компилятор. Конечно же, сейчас трудно себе представить такое практическое применение моей библиотеки, но с новой версией это будет намного проще.

    В общем, я беру таймаут на неделю. Затем выложу новую версию. Если будет интересно с ней поиграться — you are welcome.
    ... << RSDN@Home 1.1.4 stable rev. 510>>
    Re[3]: [Nemerle] Семантический контроль над размерностями
    От: CrystaX Россия https://crystax.me/
    Дата: 06.04.06 08:42
    Оценка:
    Здравствуйте, VladD2, Вы писали:

    CX>>Было бы очень интересно и познавательно вести разработку параллельно — на C++ и на Nemerle. Исключительно из спортивного интереса.


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


    Эта библиотека (по крайней мере заложенный в ней потенциал) — очень полезный инструмент. Более развернутый ответ см. здесь
    Автор: CrystaX
    Дата: 06.04.06
    ... << RSDN@Home 1.1.4 stable rev. 510>>
    Re[4]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 06.04.06 08:49
    Оценка:
    Здравствуйте, CrystaX, Вы писали:

    CX>Ты знаешь, после того, как я написал эту библиотеку, я как-то самопроизвольно начал раскладывать в своих проектах все используемые величины по ортам — и это очень сильно помогает. Физические величины — это только пример, не более того.


    Ну мне как-то всё равно не надо это в работе, если честно Есть и более важные вещи.

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

    CX>В общем, я беру таймаут на неделю. Затем выложу новую версию. Если будет интересно с ней поиграться — you are welcome.


    Помнится, ты ещё говорил, что на выходных попробуешь с Nemerle покопаться... настоятельно рекомендую это сделать
    Re[5]: [Nemerle] Семантический контроль над размерностями
    От: CrystaX Россия https://crystax.me/
    Дата: 06.04.06 09:07
    Оценка:
    Здравствуйте, Oyster, Вы писали:

    O>Ну мне как-то всё равно не надо это в работе, если честно Есть и более важные вещи.


    Я не настаиваю.

    O> Помнится, ты ещё говорил, что на выходных попробуешь с Nemerle покопаться... настоятельно рекомендую это сделать


    Копаться я и раньше начал. Я твой код хочу детально разобрать. А Nemerle — что ж, язык неплохой, но мне интересен пока только как объект познания, на практике его применить не могу.
    ... << RSDN@Home 1.1.4 stable rev. 510>>
    Re[6]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 06.04.06 09:48
    Оценка:
    Здравствуйте, CrystaX, Вы писали:

    CX>Копаться я и раньше начал. Я твой код хочу детально разобрать.


    Ну тогда ты эта... обращайся... если что

    CX>А Nemerle — что ж, язык неплохой, но мне интересен пока только как объект познания, на практике его применить не могу.


    Да я тоже... пока что. Вроде намечается проектик на одного человека, на который можно будет выбить Nemerle. Скрещиваю пальцы на удачу
    Re[3]: [Nemerle] Семантический контроль над размерностями
    От: Кодт Россия  
    Дата: 06.04.06 13:29
    Оценка:
    > Дык цель то была опробовать Немерле в бою.

    Ну это да... только "опробовать в бою" это аспект не для Исходников...
    Здесь вроде как обсуждаются решения с прикладной позиции.

    P.S. Не завести ли нам форум по Немерле?
    Posted via RSDN NNTP Server 2.0
    Перекуём баги на фичи!
    Re[4]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 06.04.06 14:38
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    К>P.S. Не завести ли нам форум по Немерле?


    Завести!!!
    Re[4]: [Nemerle] Семантический контроль над размерностями
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 06.04.06 14:46
    Оценка: +1
    Здравствуйте, Кодт, Вы писали:

    К>Ну это да... только "опробовать в бою" это аспект не для Исходников...


    Думаю Oyster просто решил разместить свой вариант рядом с уже опубликованным С++-ным.

    К>P.S. Не завести ли нам форум по Немерле?


    Незнаю. Возможно. Но пока рановато наврено.
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Версия 0.02
    От: Oyster Украина https://github.com/devoyster
    Дата: 10.04.06 05:49
    Оценка: 27 (1)
    Недавно у меня снова выдалась свободная минутка, и я поработал над кодом библиотеки. Соответственно, последний претерпел некоторые изменения.

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

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


    Кроме этого, в библиотеку были внесены и другие изменения:


    Новую версию проекта можно забрать тут: Oyster.Units.0.02.zip. Билдить рекомендуется или на последней версии Nemerle из SVN, или на снапшоте r6176 и выше. Для тех, кому лень качать проект, я выложил новые исходники (кроме тестового примера — он изменился несущественно) в ответе на это сообщение.

    PS: Направления дальнейшего развития библиотеки — избавиться от боксинга и ввести физические литералы (то, о чём так много говорили в этом топике).
    Исходники: Oyster.Units
    От: Oyster Украина https://github.com/devoyster
    Дата: 10.04.06 05:55
    Оценка:
    UnitsDefinition.n:

    [assembly: Oyster.Units.Macros.UnitsDefinition([
        // SI
        basisUnits (Si)
        (
            Mass,
            Length,
            Time,
            Temperature,
            CurrentStrength,
            LightIntensity,
            QuantityOfSubstance
        ),
    
        basisAliases (Si)
        (
            Area = (Length ^ 2),
            Volume = Area * Length,
            Velocity = Length / Time,
            Acceleration = Velocity / Time,
            AngularVelocity = Velocity,
            AngularAcceleration = Acceleration,
            Density = Mass / Volume,
    
            Force = Mass * Acceleration,
            SpecificGravity = Mass / (Length ^ 2) / (Time ^ 2),
            Pressure = SpecificGravity * Length,
    
            LinearMomentum = Mass * Velocity,
            MomentOfInertia = Mass * Area,
    
            Energy = Force * Length,
            Power = Energy / Time,
    
            DynamicViscosity = Mass / Length / Time,
            KinematicViscosity = DynamicViscosity,
    
            HeatCapacity = Energy / Temperature,
            Entropy = HeatCapacity,
            SpecificHeat = HeatCapacity / Mass,
            SpecificEntropy = SpecificHeat,
    
            Charge = Time * CurrentStrength,
            LinearDensityOfCharge = Charge / Length,
            SurfaceDensityOfCharge = Charge / Area,
            ElectricFluxDensity = SurfaceDensityOfCharge,
            PackedDensityOfCharge = Charge / Volume,
    
            Voltage = Power / CurrentStrength,
            ElectricForce = Force / CurrentStrength,
            Resistance = Voltage / CurrentStrength,
            SpecificResistance = Resistance * Length,
    
            Permittance = Time / Resistance,
    
            CurrentDensity = CurrentStrength / (Length ^ 2)
        ),
    
        // CGS
        units (Cgs)
        (
            Mass - 1.0 / 1000.0,
            Length - 1.0 / 100.0,
            Time,
            Temperature,
            CurrentStrength - 1.0 / 3000000000.0,
            LightIntensity,
            QuantityOfSubstance
        ),
    
        // Other units
        units (_)
        (
            HorsePower = Power - 1471.0 / 2.0
        )
    ])]
    Исходники: Oyster.Units.Macros
    От: Oyster Украина https://github.com/devoyster
    Дата: 10.04.06 05:55
    Оценка:
    CompileTimeInfo.n:

    using System.Collections.Generic;
    
    namespace Oyster.Units.Macros
    {
        /// Stores compile-time information for UnitsDefinition macro
        module CompileTimeInfo
        {
            /// List of basis unit names in correct order
            public mutable BasisUnits : list[string] = [];
    
            /// List of all common units (including basis ones)
            public mutable CommonUnits : list[string] = [];
    
            /// Map of common unit powers
            public PowersMap : Dictionary[string, list[int]] = Dictionary();
        }
    }

    IUnit.n:

    namespace Oyster.Units
    {
        /// Interface for all units
        public 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
            }
        }
    }

    OpMacros.n:

    using Nemerle.Collections;
    using Nemerle.Compiler;
    using Nemerle.Compiler.Parsetree;
    using Nemerle.Internal;
    using Nemerle.Macros;
    
    namespace Oyster.Units.Macros
    {
        /// Contains some operator-specific helper functionality
        module OpUtil
        {
            /// Used inside ariphmetic op macros
            public Body(
                ctx : Typer,
                lexpr : PExpr,
                rexpr : PExpr,
                opFun : PExpr * PExpr -> PExpr,
                bodyFun : TypeInfo * TypeInfo * double * double * list[int] * list[int] -> PExpr) : PExpr
            {
                match ((Util.GetExprType(ctx, lexpr), Util.GetExprType(ctx, rexpr)))
                {
                    | (Some(ltype), Some(rtype)) =>
                        match ((Util.ParseUnitSuffix(ltype), Util.ParseUnitSuffix(rtype)))
                        {
                            | (lpowers, rpowers) when lpowers.Length > 0 && rpowers.Length > 0 =>
                                def getCoef(t) { Util.GetUnitNormCoef(t).Value };
                                bodyFun(ltype, rtype, getCoef(ltype), getCoef(rtype), lpowers, rpowers)
                            | _ => opFun(lexpr, rexpr)
                        }
                    | _ => opFun(lexpr, rexpr)
                }
            }
    
            /// Used inside + and - macros
            public AddSubBody(
                ctx : Typer,
                lexpr : PExpr,
                rexpr : PExpr,
                opName : string,
                opFun : PExpr * PExpr -> PExpr) : PExpr
            {
                Body(
                    ctx,
                    lexpr,
                    rexpr,
                    opFun,
                    fun (ltype, _, lcoef, rcoef, lpowers, rpowers)
                    {
                        if (lpowers.Equals(rpowers))
                        {
                            def ctorParam = opFun(
                                <[ $lexpr.Value ]>,
                                <[ double.op_Multiply($rexpr.Value, $(rcoef / lcoef : double)) ]>);
                            <[ $(Util.UseSiteExpr(ltype.FullName))($ctorParam) ]>
                        }
                        else
                        {
                            Message.FatalError(
                                $"Expression { $lexpr [$opName] $rexpr } can't be compiled "
                                 "because units are having different dimensions.")
                        }
                    })
            }
    
            /// Used inside * and / macros
            public MulDivBody(
                ctx : Typer,
                lexpr : PExpr,
                rexpr : PExpr,
                opFun : PExpr * PExpr -> PExpr,
                powersFun : int * int -> int,
                coefFun : double * double -> double) : PExpr
            {
                Body(
                    ctx,
                    lexpr,
                    rexpr,
                    opFun,
                    fun (_, _, lcoef, rcoef, lpowers, rpowers)
                    {
                        def powers = List.Map2(lpowers, rpowers, powersFun);
                        def t = TypeGenerator.GenerateUnitType(ctx.Env, "", 1.0, powers);
                        def opExpr = opFun(<[ $lexpr.Value ]>, <[ $rexpr.Value ]>);
                        <[ $(t.Name : usesite)(double.op_Multiply($opExpr, $(coefFun(lcoef, rcoef) : double))) ]>
                    })
            }
        }
    
    
        /// Units addition macro-operator
        macro @+(lexpr, rexpr)
        {
            OpUtil.AddSubBody(ImplicitCTX(), lexpr, rexpr, "+", Util.AddExpr)
        }
    
        /// Units subtraction macro-operator
        macro @-(lexpr, rexpr)
        {
            OpUtil.AddSubBody(ImplicitCTX(), lexpr, rexpr, "-", Util.SubExpr)
        }
    
        /// Units multiply macro-operator
        macro @*(lexpr, rexpr)
        {
            OpUtil.MulDivBody(ImplicitCTX(), lexpr, rexpr, Util.MulExpr, _ + _, _ * _)
        }
    
        /// Units divide macro-operator
        macro @/(lexpr, rexpr)
        {
            OpUtil.MulDivBody(ImplicitCTX(), lexpr, rexpr, Util.DivExpr, _ - _, _ / _)
        }
    }

    TypeGenerator.n:

    using Nemerle.Compiler;
    using Nemerle.Compiler.Parsetree;
    
    using NST = Nemerle.Compiler.NamespaceTree;
    
    namespace Oyster.Units.Macros
    {
        /// Contains type generation methods
        module TypeGenerator
        {
            /// Contains already initialized environments
            mutable InitedEnvs : list[GlobalEnv] = [];
    
            /// Generates new unit type
            public GenerateUnitType(env : GlobalEnv, name : string, coef : double, powers : list[int]) : TypeBuilder
            {
                def nameIsEmpty = name == "";
    
                // Form type name
                def n = if (nameIsEmpty) Macros.NewSymbol("Unit") else Macros.UseSiteSymbol(name);
    
                // Try to find this type before creating it
                when (env.LookupType([ n.Id ]).HasValue)
                {
                    Message.FatalError($"Type $(env.CurrentNamespace).$name already exists")
                };
    
                def ifaceName = Util.UseSiteExpr(GetUnitInterface(powers).FullName);
                def t = env.Define(<[ decl:
                    [NormCoef($(coef : double))]
                    public struct $(n : name)/*['a]*/ : $ifaceName //where 'a : $(ifaceName : name)
                    {
                        _value : double;
    
                        public this(value : double)
                        {
                            _value = value
                        }
                        public this(unit : $ifaceName)
                        {
                            _value = unit.Value * unit.NormCoef / $(coef : double)
                        }
    
                        public Value : double
                        {
                            get { _value }
                        }
                        public NormCoef : double
                        {
                            get { $(coef : double) }
                        }
    
                        override public ToString() : string
                        {
                            _value.ToString()
                        }
    
                        static public @:>(unit : $(n : name)) : double
                        {
                            unit._value
                        }
                        static public @:(value : double) : $(n : name)
                        {
                            $(n : name)(value)
                        }
                        // TODO : Type conversion
    
                        static public @++(unit : $(n : name)) : $(n : name)
                        {
                            $(n : name)(unit.Value + 1)
                        }
                        static public @--(unit : $(n : name)) : $(n : name)
                        {
                            $(n : name)(unit.Value - 1)
                        }
                        static public @+(unit : $(n : name)) : $(n : name)
                        {
                            unit
                        }
                        static public @-(unit : $(n : name)) : $(n : name)
                        {
                            $(n : name)(0 - unit.Value)
                        }
                    }
                ]>);
    
                // Define all simple arithmetic operations
                def defineSimpleOp(opName, valueType, opFun, isLeft)
                {
                    mutable args = [ <[ parameter: value : $valueType ]>, <[ parameter: unit : $(n : name) ]> ];
                    mutable opArgs = [ <[ value ]>, <[ unit.Value ]> ];
                    when (!isLeft)
                    {
                        args = args.Reverse();
                        opArgs = opArgs.Reverse();
                    };
                    
                    t.Define(<[ decl:
                        static public $(opName.name : name)( .. $args ) : $(n : name)
                        {
                            $(n : name)($(opFun(opArgs.Head, opArgs.Tail.Head)))
                        }
                    ]>)
                };
                def defineOp(opName, opFun)
                {
                    [ true, false ].Iter(defineSimpleOp(opName, <[ double ]>, opFun, _))
                };
    
                defineOp(<[ @+ ]>, Util.AddExpr);
                defineOp(<[ @- ]>, Util.SubExpr);
                defineOp(<[ @* ]>, Util.MulExpr);
                defineOp(<[ @/ ]>, Util.DivExpr);
                
                t.Compile();
                t
            }
    
            /// Generates or gets unit interface for given powers list
            GetUnitInterface(powers : list[int]) : TypeInfo
            {
                def env = Util.UnitsEnv;
    
                def ifaceName = $"$(Util.UnitInterfacePrefix)$(Util.GetUnitSuffix(powers))";
                match (env.LookupType([ ifaceName ]))
                {
                    | Some(t) => t
                    | _ =>
                        // Create new interface
                        def ifaceName = Macros.UseSiteSymbol(ifaceName);
                        def t = env.Define(<[ decl:
                            public interface $(ifaceName : name) : IUnit
                            {
                                // We must declare all members here because of compiler bug
                                Value : double { get; }
                                NormCoef : double { get; }
                            }
                        ]>);
                        t.Compile();
                        t
                }
            }
        }
    }

    UnitsDefinition.n:

    using System;
    using Nemerle;
    using Nemerle.Collections;
    using Nemerle.Compiler;
    
    namespace Oyster.Units.Macros
    {
        using CTI = CompileTimeInfo;
    
        /// Assembly macro-attribute used to define unit types with custom DSL.
        [MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Assembly)]
        macro UnitsDefinition(defExpr)
        {
            // Parse top-level declaration elements
            match (defExpr)
            {
                | <[ [ .. $unitDefs ] ]> =>
                    // Parse every unit definition block
                    foreach (<[ $blockName ($nsName) ( .. $units ) ]> in unitDefs)
                    {
                        // Define some settings basing on block name
                        def (addToBasis, addToAliases) =
                            match (blockName)
                            {
                                | <[ basisUnits ]> => (true, true)
                                | <[ basisAliases ]> => (false, true)
                                | <[ units ]> => (false, false)
                                | _ => Message.FatalError($"Expected block name, got $blockName")
                            };
    
                        // Extract namespace name
                        def nsName =
                            match (nsName)
                            {
                                | <[ _ ]> => ""
                                | <[ $(n : name) ]> => n.Id
                                | _ => Message.FatalError($"Expected namespace name, got $nsName")
                            };
    
                        // Proceed with units. unitsList will contain tuples (name, coef, [powers]) at the end
                        mutable unitsList = [];
                        foreach (unitDef in units)
                        {
                            def parseUnit(unit)
                            {
                                | <[ $(unitName : name) = $aliasDef - $coefDef ]> =>
                                    def unitName = unitName.Id;
    
                                    // Gather powers list
                                    mutable havingDefinedAlias = true;
                                    def powers =
                                        match (aliasDef)
                                        {
                                            | <[ _ ]> =>
                                                havingDefinedAlias = false;
                                                if (addToBasis) [] else CTI.PowersMap[unitName]
                                            
                                            | _ =>
                                                def parseAlias(alias)
                                                {
                                                    | <[ ($(n : name) ^ $(k : int)) ]> => CTI.PowersMap[n.Id].Map(_ * k)
    
                                                    | <[ $e * ($n ^ $k) ]> =>
                                                        List.Map2(parseAlias(e), parseAlias(<[ ($n ^ $k) ]>), _ + _)
    
                                                    | <[ $e / ($n ^ $k) ]> =>
                                                        List.Map2(parseAlias(e), parseAlias(<[ ($n ^ $k) ]>), _ - _)
    
                                                    | <[ $(n : name) ]> => parseAlias(<[ ($(n : name) ^ 1) ]>)
    
                                                    | <[ $e * $(n : name) ]> => parseAlias(<[ $e * ($(n : name) ^ 1) ]>)
    
                                                    | <[ $e / $(n : name) ]> => parseAlias(<[ $e / ($(n : name) ^ 1) ]>)
    
                                                    | _ => Message.FatalError($"Expected unit alias, got $aliasDef")
                                                };
                                                def powers = parseAlias(aliasDef);
    
                                                // Global aliases must be added immediately
                                                when (addToAliases)
                                                {
                                                    CTI.PowersMap[unitName] = powers
                                                };
    
                                                powers
                                        };
    
                                    // Parse coefficient
                                    def coef =
                                        match (coefDef)
                                        {
                                            | <[ _ ]> => if (addToAliases || !havingDefinedAlias) 1.0 else double.NaN
                                            | _ =>
                                                def parseCoef(coef)
                                                {
                                                    | <[ $(d : double) ]> => d
                                                    | <[ $e * $(d : double) ]> => parseCoef(e) * d
                                                    | <[ $e / $(d : double) ]> => parseCoef(e) / d
                                                    | _ => Message.FatalError($"Expected unit normalization coefficient, got $coefDef")
                                                };
                                                parseCoef(coefDef)
                                        };
    
                                    (unitName, coef, powers)
    
                                | <[ $(unitName : name) - $coefDef ]> => parseUnit(<[ $(unitName : name) = _ - $coefDef ]>)
    
                                | <[ $(unitName : name) = $aliasDef ]> => parseUnit(<[ $(unitName : name) = $aliasDef - _ ]>)
                                        
                                | <[ $_ - $_ ]> => Message.FatalError($"Expected unit definition, got $unitDef (transformed into $unit)")
    
                                | <[ $e ]> => parseUnit(<[ $e - _ ]>)
                            };
                            unitsList ::= parseUnit(unitDef)
                        };
                        unitsList = unitsList.Reverse();
    
                        def names = unitsList.Map(fun(n, _, _) { n });
    
                        // Maybe initialize basis units list
                        when (addToBasis)
                        {
                            // Calculate powers for basis types
                            unitsList = unitsList.Map(
                                fun(n, coef, _)
                                {
                                    (n, coef, unitsList.Map(fun(n2, _, _) { if (n == n2) 1 else 0 }))
                                });
    
                            // Add all powers to hash
                            unitsList.Iter(fun(n, _, powers) { CTI.PowersMap[n] = powers });
    
                            // Initialize BasisUnits
                            CTI.BasisUnits = names
                        };
    
                        // Maybe add units to global aliases list
                        when (addToAliases)
                        {
                            CTI.CommonUnits = CTI.CommonUnits.Append(names)
                        };
    
                        // Generate all new types for units in given NS.
                        // Maybe also generate additional aliases if all basis units are declared
                        mutable basisCoef = [];
                        when (!addToAliases && CTI.BasisUnits.ForAll(names.Contains))
                        {
                            def additionalNames = CTI.CommonUnits.Filter(fun(n) { !names.Contains(n) });
                            unitsList = unitsList.Append(additionalNames.Map(fun(n) { (n, double.NaN, CTI.PowersMap[n]) }));
    
                            // Fill local coef hash
                            basisCoef = CTI.BasisUnits.Map(fun(n) { unitsList.Find(fun(n2, _, _) { n == n2 }).Value[1] })
                        };
    
                        def env = Util.GetUnitsEnv(nsName);
    
                        foreach ((unitName, coef, powers) in unitsList)
                        {
                            def coef =
                                if (double.IsNaN(coef))
                                {
                                    // Try to obtain coefficient automatically
                                    List.FoldLeft2(basisCoef, powers, 1.0, fun(c, p : int, r) { Math.Pow(c, p) * r })
                                }
                                else
                                {
                                    coef
                                };
    
                            // Generate type for given namespace
                            _ = TypeGenerator.GenerateUnitType(env, unitName, coef, powers)
                        }
                    }
                | _ => Message.FatalError($"Expected comma-separated unit declaration blocks, got $defExpr")
            }
        }
    }

    Util.n:

    using System;
    using Nemerle.Compiler;
    using Nemerle.Compiler.Parsetree;
    using Nemerle.Utility;
    
    namespace Oyster.Units
    {
        /// Contains different utilitary fields/methods
        module Util
        {
            // Some constants
            public UnitsNS = "Oyster.Units";
            public UnitInterfacePrefix = "IUnit_";
            public UnitInterfaceFullPrefix : string = $"$UnitsNS.$UnitInterfacePrefix";
    
            /// Units environment
            public UnitsEnv : GlobalEnv = GetUnitsEnv("");
    
    
            /// Creates new GlobalEnv for units global namespace
            public GetUnitsEnv(nsName : string) : GlobalEnv
            {
                def nsName = if (nsName == "") "" else $".$nsName";
                GlobalEnv($"$UnitsNS$nsName&$UnitsNS&&")
            }
    
            /// Generates PExpr for dot-separated names
            public UseSiteExpr(name : string) : PExpr
            {
                def prepareExpr(l)
                {
                    | h :: [] => <[ $(h : usesite) ]>
                    | h :: t => <[ $(prepareExpr(t)).$(h : usesite) ]>
                    | _ => Message.FatalError($"Expecting dot-separated class name, got $name")
                };
                prepareExpr(GetTypeNameList(name).Reverse())
            }
    
            /// Returns namespace path list for given full dot-separated type name
            public GetTypeNameList(name : string) : list[string]
            {
                NString.Split(name, '.')
            }
    
            /// Generates unit interface suffix basing on base unit powers
            public GetUnitSuffix(powers : list[int]) : string
            {
                powers.ToString("_")
            }
    
            /// Parses unit interface suffix
            public ParseUnitSuffix(t : TypeInfo) : list[int]
            {
                def isIfaceName(s) { s.StartsWith(UnitInterfaceFullPrefix) };
                def name =
                    if (t is TypeBuilder)
                    {
                        // Get for newly generated type
                        match (t.GetSuperTypes().Find( fun(super) { isIfaceName(super.TypeInfo.FullName) }))
                        {
                            | Some(iface) => iface.TypeInfo.Name
                            | _ => ""
                        }
                    }
                    else
                    {
                        // Get for existing type
                        def iface = Array.Find(t.SystemType.GetInterfaces(), fun (i) { isIfaceName(i.FullName) });
                        if (iface != null) iface.Name else ""
                    };
                if (name == "") [] else NString.Split(name, '_').Tail.Map(int.Parse)
            }
    
            /// Returns PExpr type
            public GetExprType(ctx : Typer, expr : PExpr) : option[TypeInfo]
            {
                match (ctx.TypeExpr(expr).ty.Hint)
                {
                    | Some(t) => Some(t.TypeInfo)
                    | _ => None()
                }
            }
    
            /// Returns normalization coefficient if type is concrete unit type
            public GetUnitNormCoef(t : TypeInfo) : option[double]
            {
                | t is TypeBuilder =>
                    // Get attribute using Nemerle approach
                    match (t.FindAttribute(GlobalEnv.Core.LookupType(GetTypeNameList($"$UnitsNS.NormCoefAttribute")).Value))
                    {
                        | Some(<[ NormCoef($(coef : double)) ]>) => Some(coef)
                        | _ => None()
                    }
                | _ =>
                    // "Usual" attribute gathering
                    match (Attribute.GetCustomAttribute(t.SystemType, typeof(NormCoefAttribute)))
                    {
                        | null => None()
                        | attr => Some((attr :> NormCoefAttribute).NormCoef)
                    }
            }
    
            // Different expression generators
            public AddExpr(lexpr : PExpr, rexpr : PExpr) : PExpr
            {
                <[ $lexpr + $rexpr ]>
            }
            public SubExpr(lexpr : PExpr, rexpr : PExpr) : PExpr
            {
                <[ $lexpr - $rexpr ]>
            }
            public MulExpr(lexpr : PExpr, rexpr : PExpr) : PExpr
            {
                <[ $lexpr * $rexpr ]>
            }
            public DivExpr(lexpr : PExpr, rexpr : PExpr) : PExpr
            {
                <[ $lexpr / $rexpr ]>
            }
        }
    }
    Re[2]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 10.04.06 05:55
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Собсно, без value-типов временный незачет, ждем-с окончательной версии.


    А вот и value-типы
    Автор: Oyster
    Дата: 10.04.06
    . Только это ещё не окончательная версия, я надеюсь...
    Re[2]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 10.04.06 05:55
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    К>Немного отдаёт неряшливостью, что внесистемные единицы помещены в систему Ext.

    К>На то они и внесистемные...

    Замечание учтено: Версия 0.02
    Автор: Oyster
    Дата: 10.04.06
    .
    Версия 0.03 - скажем "нет" боксингу :)
    От: Oyster Украина https://github.com/devoyster
    Дата: 10.04.06 08:26
    Оценка: 66 (4)
    Не успел я выложить версию 0.02
    Автор: Oyster
    Дата: 10.04.06
    , как подоспела следующая. В версии 0.03 использован workaround, предложенный разработчиками Nemerle для обхода бага и, как результат, я избавился от боксинга в конструкторе, т.е. теперь решение действительно на 100% на value-типах

    Новую версию забираем тут: Oyster.Units.0.03.zip. Для сборки надо использовать Nemerle билд r6176 — не ниже (как и для предыдущей версии).

    PS: Исходники текстом не выкладываю — они почти не изменились.
    Re: Версия 0.03 - скажем "нет" боксингу :)
    От: Аноним  
    Дата: 10.04.06 08:52
    Оценка:
    Здравствуйте, Oyster, Вы писали:

    O>Новую версию забираем тут: Oyster.Units.0.03.zip. Для сборки надо использовать Nemerle билд r6176 — не ниже (как и для предыдущей версии).


    Для того, чтобы проинсталлировать билд, что должно уже стоять на машине? Необходимо ли наличие питона, antlr и ряда прочих вещей, которые проверяются в ./configure? Какую версию make.exe следует использовать?
    Re[2]: Версия 0.03 - скажем "нет" боксингу :)
    От: Oyster Украина https://github.com/devoyster
    Дата: 10.04.06 08:58
    Оценка:
    Здравствуйте, <Аноним>, Вы писали:

    А>Для того, чтобы проинсталлировать билд, что должно уже стоять на машине? Необходимо ли наличие питона, 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.
    Re[4]: установка Nemerle
    От: Oyster Украина https://github.com/devoyster
    Дата: 10.04.06 11:05
    Оценка:
    Здравствуйте, <Аноним>, Вы писали:

    А>Случаем не встречались с такой проблемой: make останавливается на 3-ем stage после компиляции ncc.exe с ошибкой

    А>*** No rule to make target '../../ncc/out.stage/', needed by 'Nemerle.dll'. Stop.

    Нет, у меня всегда компилится нормально... Можно попробовать отписать им на форум.
    Re[6]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 10.04.06 20:26
    Оценка:
    Здравствуйте, Vermicious Knid, Вы писали:

    VK>Но есть идея и более простая. И макрос, и оператор можно зарегистрировать вручную через API компилятора(см. модуль MacroRegistry). Макрос это по сути экземпляр некого класса, реализовывающего интерфейс IMacro. Нужно просто написать собственную реализацию IMacro вручную(т.е. не использовать встроенную в компилятор генерацию этого класса), которая в зависимости от того как ее инициализируют будет вести себя как разные макросы(т.е. с разными именами и генерировать различный код).


    Как ты уже знаешь, эта идея мне понравилась. В общем, я начал думать о том, как реализовать unit-алиасы с её помощью и столкнулся с маленькой проблемкой. Дело в том, что в текущей версии библиотеки
    Автор: Oyster
    Дата: 10.04.06
    unit-типы можно определять как в той же сборке, где они используются, так и в другой сборке, и я хочу сохранить такое поведение. Вот в варианте с другой сборкой и возникает проблема — надо как-то создать и загеристрировать макросы при загрузке сборки компилятором.

    Я думал над этой проблемой и пришёл к такому вот решению:

    1. Unit-алиасы (такие как kg — клиограммы — или m — метры, — например) сохраняются в неком атрибуте уровня сборки (или в любом другом хранилище в сборке с unit-типами).
    2. Также в этой сборке создаётся псевдо-макрос, который ничего не умеет и в конструкторе которого создаются и регистрируются макросы для unit-алиасов. На сборку цепляется ContainsMacroAttribute и, как результат, конструктор этого псевдо-макроса выполняется при загрузке сборки LibraryLoader-ом.

    Конечно, возможен и другой вариант — генерация классов макросов для unit-алиасов, но этого не хочется делать из-за желания использовать код повторно.

    Собственно, мой вопрос звучит так — можно ли то же самое сделать как-то иначе? Т.е. существует ли какой-нибудь другой способ выполнить код при загрузке сборки кроме ContainsMacroAttribute и конструктора макроса?
    Re[7]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 10.04.06 21:15
    Оценка:
    Здравствуйте, Oyster, Вы писали:

    O>Конечно, возможен и другой вариант — генерация классов макросов для unit-алиасов, но этого не хочется делать из-за желания использовать код повторно.


    На самом деле, мы можем просто создавать типы для макросов, регистрировать их ручками, если в той же сборке, и не мучаться с добавлением ещё каких-то вспомогательных атрибутов (кроме [ContainsMacro] и [Operator]). Естественно, это возможно только при условии, если мы можем из макроса добавить атрибуты сборке — я ещё не разбирался с этим вопросом. Кстати, это утверждение также справедливо для варианта из предыдущего сообщения.

    При таком подходе снова встаёт вопрос — а могу ли я просто создать новый макрос внутри макроса, используя знакомый мне синтаксис macro и не переопределяя IMacro вручную?
    Версия 0.04
    От: Oyster Украина https://github.com/devoyster
    Дата: 14.04.06 05:43
    Оценка: 54 (3)
    Ну вот и следующий билд библиотеки подоспел: 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.

    У решения всё равно есть некоторые недостатки:


    Тем не менее, все эти недостатки достаточно невелики и не мешают пользоваться библиотекой.


    В заключение хочу сказать, что библиотека подобралась к своему логическому завершению и вряд ли будет развиваться дальше. Возможно, будут исправляться ошибки, но не более того.
    Re[2]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 14.04.06 05:46
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Это было бы такое WOW , что вполне возможно, я бы прыгнул на Немерле.


    Так прыгнул бы или нет?
    Автор: Oyster
    Дата: 14.04.06
    Re[3]: [Nemerle] Семантический контроль над размерностями
    От: vdimas Россия  
    Дата: 14.04.06 09:10
    Оценка:
    Здравствуйте, Oyster, Вы писали:


    V>>Это было бы такое WOW , что вполне возможно, я бы прыгнул на Немерле.


    O>Так прыгнул бы или нет?
    Автор: Oyster
    Дата: 14.04.06


    Доделываю хвосты и прыгаю
    Нельзя же бросать текучку...
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Re[3]: Версия 0.03 - скажем "нет" боксингу :)
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 15.04.06 23:54
    Оценка:
    Здравствуйте, Oyster, Вы писали:

    O>Знаю точно, что нужен gmake, а не стандартный make, — это было написано где-то в доках.


    Стандартного мэйка нет в природе.
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re: Версия 0.04
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 15.04.06 23:54
    Оценка: +2
    Здравствуйте, Oyster, Вы писали:

    В общем, хотя реально сама библиотека будет бесполезна для 99% программистов и проектов, но 3 огромные задачи ты ею решил:
    1. Нашел кучу багов в компиляторе.
    2. Создал изумительный пример разработки DSL. Очень советую, кстати, запостить его в форум Немерла посвященный примерам. Можно со ссылкой на С++-ный аналог.
    3. Доказал приемущество макросов над шаблонным метапрограммированием и вообще их гибкость.

    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[4]: [Nemerle] Семантический контроль над размерностями
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 16.04.06 00:02
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    O>>Так прыгнул бы или нет?
    Автор: Oyster
    Дата: 14.04.06


    V>Доделываю хвосты и прыгаю

    V>Нельзя же бросать текучку...

    Ну, вот мы и тебя "проникли".
    Хотя доделывать хвосты можно вечно.
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[4]: Версия 0.03 - скажем "нет" боксингу :)
    От: Oyster Украина https://github.com/devoyster
    Дата: 16.04.06 07:51
    Оценка:
    Здравствуйте, VladD2, Вы писали:

    O>>Знаю точно, что нужен gmake, а не стандартный make, — это было написано где-то в доках.

    VD>Стандартного мэйка нет в природе.

    Да, глупость сказал... Я имел в виду, "нужен не тот make, который ставится с cygwin по умолчанию, а gmake".
    Re[2]: Версия 0.04
    От: Oyster Украина https://github.com/devoyster
    Дата: 16.04.06 07:58
    Оценка:
    Здравствуйте, VladD2, Вы писали:

    VD>2. Создал изумительный пример разработки DSL. Очень советую, кстати, запостить его в форум Немерла посвященный примерам. Можно со ссылкой на С++-ный аналог.


    Да, это я как раз и хотел сделать. Вот найду время, поперевожу некоторые комменты на англицкий, получу разрешение у CrystaX и сделаю, мабуть.
    Re[3]: Версия 0.04
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 16.04.06 17:38
    Оценка:
    Здравствуйте, Oyster, Вы писали:

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


    Давай. Зело полезное дело!
    ... << RSDN@Home 1.2.0 alpha rev. 637>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[5]: [Nemerle] Семантический контроль над размерностями
    От: vdimas Россия  
    Дата: 18.04.06 10:04
    Оценка:
    Здравствуйте, VladD2, Вы писали:

    V>>Доделываю хвосты и прыгаю

    V>>Нельзя же бросать текучку...

    VD>Ну, вот мы и тебя "проникли".


    Что не удивительно, просидев года на .Net 1.1 мне даже переход на 2.0 показался глотком свежего воздуха.
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Версия 0.05
    От: Oyster Украина https://github.com/devoyster
    Дата: 08.05.06 12:24
    Оценка: 79 (4)
    Очередной билд: Oyster.Units.0.05.zip (билдить на r6235 и выше).

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


    Кроме того, я добавил в код чуть больше комментариев и переписал все русские комментарии из примера использования на английском, потому что проект выложен в форум примеров на nemerle.org (наконец-то у меня добрались до этого руки).
    Версия 0.06
    От: Oyster Украина https://github.com/devoyster
    Дата: 09.05.06 08:43
    Оценка: 24 (2)
    Я тут баг в коде нашёл, результатом которого стало заваливание тестового приложения в рантайме на последних билдах Nemerle. Баг пофиксан: Oyster.Units.0.06.zip (билдить на r6251 и выше).
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.