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) ]> })
}
}