LeMP Macro Reference: Standard Macros
Updated 23 Nov 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");
}
}
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.
code== operator
x `code==` y;
(x + 1) `code==` (x + "1");
(x + 777) `code==` (x+777);
// Output of LeMP
false;
false;
true;
Returns the literal true if two or more syntax trees are equal, or false if not. This macro is largely superceded by the `staticMatches`
operator.
concatId
concatId(Sq, uare);
a `##` b; // synonyn
// Output of LeMP
Square;
ab; // synonyn
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 Property { get {
return target.Property;
} }
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
.
includeFileText
string content = includeFileText("Filename.txt")
Converts a text file into a string literal.
includeFileBinary
byte[] content = includeFileBinary("Filename.doc")
Converts a binary file into a byte array literal.
macro_scope
macro_scope {
define Foo() { ReplacedByAMacro(); }
Foo(); // replaced
}
Foo();
Creates a scope for local macros and local $variables to be defined. The call itself (macro_scope
and braces) disappear from the output.
Note: macros defined in an inner scope do not shadow macros in an outer scope. If two macros can apply to a given node, LeMP calls both of them, and reports an ambiguity error if both of them modify that node.
See also: reset_macros
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_10 = (Shape) obj;
if (ShapeType.Circle.Equals(tmp_10.Item1)) {
var size = tmp_10.Item2;
var tmp_11 = tmp_10.Location;
if (tmp_11 is Point<int>) {
Point<int> p = (Point<int>) tmp_11;
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.
#importMacros(LeMP.CSharp6);
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 (?.
)
#ecs;
#importMacros(LeMP.CSharp6);
void Example() {
if (a.b?.c.d ?? false) {
ItsTrue();
}
// Note: the #trivia currently shown in the output
// is automatically erased when printing in C# mode
if ((F(x)?.c.d ?? 0) > 0) {
Positive();
}
}
// Output of LeMP
void Example() {
if ((a.b != null ? a.b.c.d : null) ?? false) {
ItsTrue();
}
{
var F_12 = F(x);
// Note: the #trivia currently shown in the output
// is automatically erased when printing in C# mode
if (((([#trivia_isTmpVar] F_12) != null ? F_12.c.d : null) ?? 0) > 0) {
Positive();
}
}
}
</div>
Eliminates the C# 6 ?.
operator from the output by replacing it with equivalent code. Lowercase identifiers like a.b
are assumed to be local variables or fields, so a temporary variable is not created to hold their value. Consequently, such variables will be evaluated twice.
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"))).SetStyle(NodeStyle.Operator), 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"))).SetStyle(NodeStyle.Operator);
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);
reset_macros
define Foo() { Food(); }
Foo();
reset_macros {
define Foo() { Football(); }
Foo(); // replaced
}
Foo();
While processing the arguments of reset_macros
,
- locally-created macros defined outside of
reset_macros
are forgotten - scoped properties are reset to predefined values
- the list of open namespaces is reset to defaults
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.
stringify
stringify(expr);
Console.WriteLine(stringify(luv=u+me));
// Output of LeMP
"expr";
Console.WriteLine("luv = u + me");
Converts an expression to a string (note: original formatting is not preserved.)
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
.
#useSymbols
#useSymbols;
void Increment()
{
if (dict.Contains(@@Counter))
dict[@@Counter]++;
else
dict[@@Counter] = 1;
}
// Output of LeMP
static readonly Symbol sy_Counter = (Symbol) "Counter";
void Increment()
{
if (dict.Contains(sy_Counter))
dict[sy_Counter]++;
else
dict[sy_Counter] = 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.
unless
unless (Fingers.Cold && Fingers.Dead) { Hold(Gun); }
// Output of LeMP
if (!@`'&&`(Fingers.Cold, Fingers.Dead)) { Hold(Gun); }
Executes a block of statements when the specified condition is false.
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_13 = Some.Thing;
tmp_13.Member = 0;
tmp_13.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.