This page has moved to ecsharp.net.
GitHub doesn't support HTTP redirects, so you'll be redirected via JavaScript in 5 seconds.LeMP Macro Reference: Standard Macros
20 Mar 2016LeMP namespace
Note: the LeMP namespace is normally imported automatically for you.
assert
assert(condition);
Translates assert(expr)
to System.Diagnostics.Debug.Assert(expr, "Assertion failed in Class.MethodName: expr")
.
You can change the assert method with #snippet
as shown in the following example:
class Class {
void Bar() {
#snippet #assertMethod = MyAssert;
assert(BarIsOpen)
}
void Foo() {
assert(FoodIsHot);
}
}
// Output of LeMP
class Class
{
void Bar()
{
MyAssert(BarIsOpen, "Assertion failed in `Class.Bar`: BarIsOpen")
}
void Foo()
{
System.Diagnostics.Debug.Assert(FoodIsHot, "Assertion failed in `Class.Foo`: FoodIsHot");
}
}
alt class: Algebraic Data Type
alt class Color {
alt this(byte Red, byte Green, byte Blue, byte Opacity = 255);
}
void DrawLine(Color c) {
if (c.Opacity > 0) {
(var r, var g, var b) = c;
...
}
}
Expands a short description of an ‘algebraic data type’ (a.k.a. disjoint union) into a set of classes with a common base class. All data members are read-only, and for each member (e.g. Item1
and Item2
above), a With()
method is generated to let users create modified versions. Example:`
// A binary tree
public partial abstract alt class Tree<T>
where T: IComparable<T>
{
alt this(T Value);
alt Leaf();
alt Node(Tree<T> Left, Tree<T> Right);
}
// Output of LeMP
public partial abstract class Tree<T> where T: IComparable<T>
{
public Tree(T Value)
{
this.Value = Value;
}
public T Value
{
get;
private set;
}
public abstract Tree<T> WithValue(T newValue);
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public T Item1
{
get {
return Value;
}
}
}
public partial abstract static partial class Tree
{
public static Tree<T> New<T>(T Value) where T: IComparable<T>
{
return new Tree<T>(Value);
}
}
class Leaf<T> : Tree<T> where T: IComparable<T>
{
public Leaf(T Value) : base(Value)
{
}
public override Tree<T> WithValue(T newValue)
{
return new Leaf<T>(newValue);
}
}
class Node<T> : Tree<T> where T: IComparable<T>
{
public Node(T Value, Tree<T> Left, Tree<T> Right) : base(Value)
{
this.Left = Left;
this.Right = Right;
}
public Tree<T> Left
{
get;
private set;
}
public Tree<T> Right
{
get;
private set;
}
public override Tree<T> WithValue(T newValue)
{
return new Node<T>(newValue, Left, Right);
}
public Node<T> WithLeft(Tree<T> newValue)
{
return new Node<T>(Value, newValue, Right);
}
public Node<T> WithRight(Tree<T> newValue)
{
return new Node<T>(Value, Left, newValue);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public Tree<T> Item2
{
get {
return Left;
}
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public Tree<T> Item3
{
get {
return Right;
}
}
}
static partial class Node
{
public static Node<T> New<T>(T Value, Tree<T> Left, Tree<T> Right) where T: IComparable<T>
{
return new Node<T>(Value, Left, Right);
}
}
Backing fields: [field]
[field] int X { get; set; }
[field _y] int Y { get; set; }
[field Int32 _z] int Z { get; set; }
// Output of LeMP
int _x;
int X
{
get {
return _x;
}
set {
_x = value;
}
}
int _y;
int Y
{
get {
return _y;
}
set {
_y = value;
}
}
Int32 _z;
int Z
{
get {
return _z;
}
set {
_z = value;
}
}
Creates a backing field for a property. In addition, if the body of the property is empty, a getter is added.
concatId
concatId(Sq, uare);
a `##` b; // synonyn
// Output of LeMP
Square;
ab;
Concatenates identifiers and/or literals to produce an identifier. For example, the output of a `##` b
is ab
.
Note: concatId
cannot be used directly as a variable or method name unless you use $(out concatId(...))
.
this-constructors
class Foo {
public this(int x) : base(x)
{
}
}
// Output of LeMP
class Foo
{
public Foo(int x) : base(x)
{
}
}
Supports the EC# ‘this()’ syntax for constructors by replacing ‘this’ with the name of the enclosing class.
$(out …)
// DollarSignIdentity macro
$(out expression);
// Output of LeMP
expression;
$(out ...)
allows you to use a macro in Enhanced C# in places where macros are ordinarily not allowed, such as in places where a data type or a method name are expected. The out
attribute is required to make it clear you want to run this macro and that some other meaning of $
does not apply. Examples:
$(out Foo) number;
int $(out concatId(Sq, uare))(int x) => x*x;
// Output of LeMP
Foo number;
int Square(int x) => x * x;
Contract attributes
See documentation here.
Method Forwarding
Type SomeMethod(Type param) ==> target.Method;
Type Prop ==> target._;
Type Prop { get ==> target._; set ==> target._; }
This is really handy for implementing the adapter pattern or the wrapper pattern.
==>
forwards a call to another method. The target method must not include an argument list; the method parameters are forwarded automatically. If the target expression includes an underscore (_
), it is replaced with the name of the current function. Examples:
Type SomeMethod(Type param) ==> target.Method;
int Compute(int x) ==> base._;
Type Property ==> target._;
Type Prop { get ==> target; set ==> target; }
// Output of LeMP
Type SomeMethod(Type param)
{
return target.Method(param);
}
int Compute(int x)
{
return base.Compute(x);
}
Type Prop
{
get {
return target.Prop;
}
}
Type Prop
{
get {
return target;
}
set {
target = value;
}
}
in-range operator combinations
// Variations of 'in' operator
x in range;
x in lo..hi;
x in lo...hi;
x in ..hi;
x in lo..._;
// Output of LeMP
range.Contains(x);
x.IsInRangeExcludeHi(lo, hi);
x.IsInRange(lo, hi);
x < hi;
x >= lo;
Converts an ‘in’ expression to a normal C# expression using the following rules (keeping in mind that the EC# parser treats ..<
as an alias for ..
):
x in _..hi
andx in ..hi
becomex.IsInRangeExcl(hi)
x in _...hi
andx in ...hi
becomex.IsInRangeIncl(hi)
x in lo.._
andx in lo..._
become simplyx >= lo
x in lo..hi
becomesx.IsInRangeExcludeHi(lo, hi)
x in lo...hi
becomesx.IsInRange(lo, hi)
x in range
becomesrange.Contains(x)
The first applicable rule is used.
includeFile (a.k.a. #include)
includeFile("Filename.cs")
includeFile("Filename.les")
Reads source code from the specified file, and inserts the syntax tree in place of the macro call. The input language is determined automatically according to the file extension. If the file extension is not recognized, the current input language is assumed.
For nostalgic purposes (to resemble C/C++), #include
is a synonym of includeFile
.
match
match (var) { case pattern: handler }; // EC# syntax
match (var) { pattern => { handler }; }; // LES syntax (also works in EC#)
Attempts to match and deconstruct an object against a “pattern”, such as a tuple or an algebraic data type. There can be multiple ‘case’ blocks, and a default
. Example:
match (obj) {
case is Shape(ShapeType.Circle, $size,
Location: $p is Point<int>($x, $y)):
DrawCircle(size, x, y);
}
// Output of LeMP
do
if (obj is Shape) {
Shape tmp_0 = (Shape) obj;
if (ShapeType.Circle.Equals(tmp_0.Item1)) {
var size = tmp_0.Item2;
var tmp_1 = tmp_0.Location;
if (tmp_1 is Point<int>) {
Point<int> p = (Point<int>) tmp_1;
var x = p.Item1;
var y = p.Item2;
DrawCircle(size, x, y);
break;
}
}
}
while (false);
break
is not expected at the end of each handler (case
code block), but it can be used to exit early from a case
. You can associate multiple patterns with the same handler using case pattern1, pattern2:
in EC#, but please note that (due to a limitation of plain C#) this causes code duplication since the handler will be repeated for each pattern.
matchCode
matchCode (var) {
case expression: handler; // C# style
case { statement; }: handler; // C# style
expression => handler; // LES style
};
Attempts to match and deconstruct a Loyc tree against a series of cases with patterns, e.g. case $a + $b:
expects a tree that calls +
with two parameters, placed in new variables called a and b. break
is not required or recognized at the end of each case’s handler (code block). Use $(...x)
to gather zero or more parameters into a list x
. Use case pattern1, pattern2:
in EC# to handle multiple cases with the same handler.
Note: Currently there is an inconsistency where you can use break
to exit match
but you cannot use break
to exit matchCode
, because the latter produces a simple if-else
chain as its output. It is likely that in the future matchCode
’s output will be wrapped in do { } while (false)
so that the break;
statement works the same way as it does in match
and switch
.
Example:
Console.WriteLine("Input an expression plz");
string str = Console.ReadLine();
LNode tree = EcsLanguageService.Value.Parse(
str, null, ParsingService.Exprs);
Console.WriteLine(Eval(tree));
dynamic Eval(LNode code)
{
dynamic value;
matchCode(code) {
case $x + $y:
return Eval(x)+Eval(y);
case $x * $y:
return Eval(x)*Eval(y);
case $x == $y:
return Eval(x) == Eval(y);
case $x ? $y : $z:
return Eval(x) ? Eval(y) : Eval(z);
default:
if (code.IsLiteral)
return code.Value;
}
}
// Output of LeMP
Console.WriteLine("Input an expression plz");
string str = Console.ReadLine();
LNode tree = EcsLanguageService.Value.Parse(str, null, ParsingService.Exprs);
Console.WriteLine(Eval(tree));
dynamic Eval(LNode code)
{
dynamic value;
{
LNode x, y, z;
if (code.Calls(CodeSymbols.Add, 2) && (x = code.Args[0]) != null && (y = code.Args[1]) != null)
return Eval(x) + Eval(y);
else if (code.Calls(CodeSymbols.Mul, 2) && (x = code.Args[0]) != null && (y = code.Args[1]) != null)
return Eval(x) * Eval(y);
else if (code.Calls(CodeSymbols.Eq, 2) && (x = code.Args[0]) != null && (y = code.Args[1]) != null)
return Eval(x) == Eval(y);
else if (code.Calls(CodeSymbols.QuestionMark, 3) && (x = code.Args[0]) != null && (y = code.Args[1]) != null && (z = code.Args[2]) != null)
return Eval(x) ? Eval(y) : Eval(z);
else if (code.IsLiteral)
return code.Value;
}
}
nameof
nameof(id_or_expr)
Converts the “key” name component of an expression to a string. Example:
TODO: remove this feature, since C# 6 got it.
nameof(Ant.Banana<C>(Dandilion));
// Output of LeMP
"Banana";
namespace
without braces
namespace Foo;
Surrounds the remaining code in a namespace block.
namespace Normal {
class C {}
}
namespace NoBraces;
class C {}
// Output of LeMP
namespace Normal
{
class C
{
}
}
namespace NoBraces
{
class C
{
}
}
??=
A ??= B;
// Output of LeMP
A = A ?? B;
Assign A = B only when A is null. Caution: currently, A is evaluated twice.
Null-dot (?.
)
if (a.b?.c.d ?? false) {
Good();
}
// Output of LeMP
if ((a.b != null ? a.b.c.d : null) ?? false) {
Good();
}
a.b?.c.d
means (a.b != null ? a.b.c.d : null)
.
TODO: remove this, since it was added to C# 6. Note: you can use noMacro(e)
to disable macros in an expression e
.
on_finally, on_throw, etc.
on_finally { _obj = null; }
on_return (result) { Trace.WriteLine(result); }
on_throw_catch(exc) { MessageBox.Show(exc.Message); }
on_throw(exc) { _success = false; }
return _obj.ReadFile(filename);
// Output of LeMP
try {
try {
try {
{
var result = _obj.ReadFile(filename);
Trace.WriteLine(result);
return result;
}
} catch (Exception exc) {
_success = false;
throw;
}
} catch (Exception exc) {
MessageBox.Show(exc.Message);
}
} finally {
_obj = null;
}
These are explained in a separate page.
=:
if (int.Parse(text)=:num > 0)
positives += num;
// Output of LeMP
if (#var(@``, num = int.Parse(text)) > 0)
positives += num;
This macro isn’t useful yet - the feature that it represents is not yet functional.
quote
var str = quote("Hello, world!");
var code = quote {
Console.WriteLine($str);
};
// Output of LeMP
var str = LNode.Literal("Hello, world!");
var code = LNode.Call(LNode.Call(CodeSymbols.Dot, LNode.List(LNode.Id((Symbol) "Console"), LNode.Id((Symbol) "WriteLine"))), LNode.List(str));
Macro-based code quote mechanism, to be used as long as a more complete compiler is not availabe. If there is a single parameter that is braces, the braces are stripped out. If there are multiple parameters, or multiple statements in braces, the result is a call to #splice()
. Note that some code, such as the macro processor, recognizes #splice
as a signal that you want to insert a list of things into an outer list:
a = 1;
#splice(b = 2, c = 3);
d = 4;
// Output of LeMP
a = 1;
b = 2;
c = 3;
d = 4;
The output refers unqualified to CodeSymbols
and LNode
so you must have using Loyc.Syntax
at the top of your file. The substitution operator $(expr) causes the specified expression to be inserted unchanged into the output. using Loyc.Collections
is also recommended so that you can use VList<LNode>
, the data type that LNode
itself uses to store a list of LNode
s.
rawQuote
rawQuote($foo);
// Output of LeMP
LNode.Call(CodeSymbols.Substitute, LNode.List(LNode.Id((Symbol) "foo")));
Behaves the same as quote(code)
except that the substitution operator $
is treated the same as all other code, instead of being recognized as a request for substitution.
Range operators (..
and ...
)
lo..hi;
..hi;
lo.._;
lo..hi;
..hi;
lo.._;
// Output of LeMP
Range.ExcludeHi(lo, hi);
Range.UntilExclusive(hi);
Range.StartingAt(lo);
Range.ExcludeHi(lo, hi);
Range.UntilExclusive(hi);
Range.StartingAt(lo);
replace
replace (x => xxx) { Foo.x(); }
replace (Polo() => Marco(),
Marco($x) => Polo($x));
if (Marco(x + y)) Polo();
// Output of LeMP
Foo.xxx();
if (Polo(x + y))
Marco();
Finds one or more patterns in a block of code and replaces each matching expression with another expression. The braces are omitted from the output (and are not matchable). This macro can be used without braces, in which case it affects all the statements/arguments that follow it in the current statement or argument list.
The alternate name replacePP
additionally preprocesses the input and output arguments, and is useful to get around problems with macro execution order. This behavior is not the default, since the final output will be macro-processed a second time.
scope(…)
scope(exit) { ... }; scope(success) {..}; scope(failure) {...}
An homage to D, this is equivalent to on_finally
et al.
Set or create member
Type Method(set Type member) {}
Type Method2(public Type Member2) {}
// Output of LeMP
Type Method(Type member)
{
this.member = member;
}
public Type Member2;
Type Method2(Type member2)
{
Member2 = member2;
}
Automatically assigns a value to an existing field, or creates a new field with an initial value set by calling the method. This macro can be used with constructors and methods. This macro is activated by attaching one of the following modifiers to a method parameter: set, public, internal, protected, private, protectedIn, static, partial
.
#setTupleType
#setTupleType(BareName);
#setTupleType(TupleSize, BareName);
#setTupleType(TupleSize, BareName, Factory.Method)
Configures the type and creation method for tuples, either for a specific size of tuple, or for all sizes at once. Example:
#setTupleType(2, Pair);
var pair = (1234, "bad password");
#useDefaultTupleTypes;
var tupl = (1234, "bad password");
// Output of LeMP
var pair = Pair.Create(1234, "bad password");
var tupl = Tuple.Create(1234, "bad password");
#useDefaultTupleTypes
Reverts to using Tuple
and Tuple.Create
for all arities of tuple.
static if
// Normally this variable is predefined
#set #inputFile = "Foo.cs";
static if (#get(#inputFile) `tree==` "Foo.cs")
WeAreInFoo();
else
ThisIsNotFoo();
var t = static_if(true, T(), F());
// Output of LeMP
WeAreInFoo();
var t = T();
A very basic “compile-time if” facility. It can’t do very much yet.
stringify
stringify(expr);
Console.WriteLine(stringify(u+me=luv));
// Output of LeMP
"expr";
Console.WriteLine("u + me = luv");
Converts an expression to a string (note: original formatting is not preserved.)
tree==
x `tree==` y;
x + 1 `tree==` x + "1";
x + 777 `tree==` x+777;
// Output of LeMP
false;
false + "1";
false + 777;
Returns the literal true if two or more syntax trees are equal, or false if not.
Tuple macro
(x,);
(x, y);
(x, y, z);
// Output of LeMP
Tuple.Create(x);
Tuple.Create(x, y);
Tuple.Create(x, y, z);
Create a tuple.
Tuple type shortcut
#<int, string, double> tuple;
// Output of LeMP
Tuple<int,string,double> tuple;
#<...>
is a shortcut for Tuple<...>
or whatever data type is currently configured for tuples, but it isn’t really recommended to use it since its meaning is less than obvious.
Tuple deconstruction
(a, b, var c) = expr;
// Output of LeMP
a = expr.Item1;
b = expr.Item2;
var c = expr.Item3;
Extracts components of a tuple or an alt class
.
unroll
unroll ((X, Y) in ((X, Y), (Y, X)))
{
DoSomething(X, Y);
DoSomethingElse(X, Y);
DoSomethingMore(X, Y);
}
// Output of LeMP
DoSomething(X, Y);
DoSomethingElse(X, Y);
DoSomethingMore(X, Y);
DoSomething(Y, X);
DoSomethingElse(Y, X);
DoSomethingMore(Y, X);
Produces variations of a block of code, by replacing an identifier left of in
with each of the corresponding expressions on the right of in
. The braces are omitted from the output.
#useSymbols
#useSymbols;
void Increment()
{
if (dict.Contains(@@Counter))
dict[@@Counter]++;
else
dict[@@Counter] = 1;
}
// Output of LeMP
static readonly Symbol sy_Foo = (Symbol) "Foo";
void Increment()
{
if (dict.Contains(sy_Foo))
dict[sy_Foo]++;
else
dict[sy_Foo] = 1;
}
Replaces each symbol in the code that follows with a static readonly
variable named sy_X
for each symbol @@X
. The #useSymbols; statement should be placed near the top of a class definition.
Multi-using
using System(, .Collections.Generic, .Linq);
using Loyc(, .Math, .Collections, .Syntax);
// Output of LeMP
using System;
using System.Collections.Generic;
using System.Linq;
using Loyc;
using Loyc.Math;
using Loyc.Collections;
using Loyc.Syntax;
Generates multiple using-statements from a single one.
“with” statement
with (Some.Thing) { .Member = 0; .Method(); }
// Output of LeMP
{
var tmp_2 = Some.Thing;
tmp_2.Member = 0;
tmp_2.Method();
}
Use members of a particular object with a shorthand “prefix-dot” notation.
Caution: if used with a value type, a copy of the value is made; you won’t be editing the original.