LeMP Home Page
Introduction
LeMP is an open-source LISP-style macro processor for C#, comparable to sweet.js for Javascript. Install it in Visual Studio to help you write boilerplate.
publicstaticpartialclassExtensionMethods{defineGenerateInRangeMethods($Num){// Returns true if num in range lo..hipublicstaticboolIsInRange(this$Numnum,$Numlo,$Numhi)=>num>=lo&&num<=hi;publicstatic$NumPutInRange(this$Numn,$Nummin,$Nummax){if(n<min)returnmin;if(n>max)returnmax;returnn;}}GenerateInRangeMethods(int);GenerateInRangeMethods(long);GenerateInRangeMethods(double);}
// Generated from Untitled.ecs by LeMP 2.6.4.0.publicstaticpartialclassExtensionMethods{// Returns true if num in range lo..hipublicstaticboolIsInRange(thisintnum,intlo,inthi)=>num>=lo&&num<=hi;publicstaticintPutInRange(thisintn,intmin,intmax){if(n<min)returnmin;if(n>max)returnmax;returnn;}// Returns true if num in range lo..hipublicstaticboolIsInRange(thislongnum,longlo,longhi)=>num>=lo&&num<=hi;publicstaticlongPutInRange(thislongn,longmin,longmax){if(n<min)returnmin;if(n>max)returnmax;returnn;}// Returns true if num in range lo..hipublicstaticboolIsInRange(thisdoublenum,doublelo,doublehi)=>num>=lo&&num<=hi;publicstaticdoublePutInRange(thisdoublen,doublemin,doublemax){if(n<min)returnmin;if(n>max)returnmax;returnn;}}
LeMP helps you solve the repetition-of-boilerplate problem, and it allows you to transform code at compile-time in arbitrary ways.
Example: compile-time code execution and code manipulation
LeMP can be used for compile-time calculations, reflection and code generation. It is possible to use ordinary reflection, but this example shows off more features of LeMP. It scans a syntax tree at compile-time and produces a list of the function names that it saw.
compileTimeAndRuntime{// This code is executed at compile-time and also included in the outputusingSystem;usingSystem.Collections.Generic;usingSystem.Linq;}compileTime{// This code is executed at compile-time but not included in the outputstaticList<string>GetFunctionNames(LNodeListinput){varfunctionNames=newList<string>();// Scan the list of statements we were given and find functions among themforeach(LNodepartininput){// Use the matchCode macro to detect anything that looks like a functionmatchCode(part){// $name means "create a variable representing the code at this location".// [$(..attributes)] means "capture attributes attached to this construct".case{[$(..attributes)]$retVal$name($(..argList))=>$(body);}:functionNames.Add(name.Name.Name);}}returnfunctionNames;}staticLNodeWithMetadata(LNodeinput){// quote { } produces a single node when the input was a list. // Convert input back to a list.varnames=GetFunctionNames(input.AsList(CodeSymbols.Splice));returnquote{staticstring[]metadata=new[]{$(..names.Select(name=>LNode.Literal(name)))};$input;};}}// This creates a user-defined macro called withMetadatadefinewithMetadata({$(..code);}){// `precompute` runs an expression at compile time and replaces itself with // its own result, which can either be a primitive (e.g. number or string)// or a syntax treeprecompute(WithMetadata(quote{$code;}));}classExample{withMetadata{publicstaticintSquare(intx)=>x*x;publicstaticintCube(intx)=>x*x*x;publicstaticintDoNothing(){}staticintvariable;}}
Output:
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;classExample{staticstring[]metadata=new[]{"Square","Cube","DoNothing"};publicstaticintSquare(intx)=>x*x;publicstaticintCube(intx)=>x*x*x;publicstaticintDoNothing(){}staticintvariable;}
Need help using these features? Please ask on StackOverflow (put the lemp
tag on your question and I will see it.)
Note: compileTime
and precompute
are based on the C# Interactive engine, which has some limitations (e.g. namespaces are not supported). Also, notice that using
statements are ignored at compile-time unless they are inside a compileTimeAndRuntime
or compileTime
block. This is because assemblies used by your project are not automatically referenced at compile time, so the namespaces that you use at runtime might not exist at compile-time.
Example: using
A really simple example is ‘using’ statements:
usingSystem;usingSystem.Linq;usingSystem.Text;usingSystem.Collections;usingSystem.Collections.Generic;usingSystem.IO;usingLoyc.Collections;usingLoyc.MiniTest;usingLoyc.Syntax;
Luckily, Visual Studio can add these for us. But wouldn’t it be nice if half the screen wasn’t ‘using’ statements every time you open a file? There is a LeMP macro that lets you collapse these onto a couple of lines:
usingSystem(.Linq,.Text,.Collections(,.Generic),.IO,);usingLoyc(.Collections,.MiniTest,.Syntax);
The comma ,
before the closing )
adds an “empty” parameter to the list, which indicates that using System
itself is one of the outputs you want to produce.
Example: Small data types
I like to create a lot of small data types, rather than using a few huge ones. And when you’re making small data types, C# is annoying. A simple type isn’t hard:
publicclassPerson{publicstringName;publicDateTimeDateOfBirth;publicList<Person>Children;};
But this simplicity has a big price:
- There’s no constructor, so you must always use property-initializer syntax to create one of these. That could get old fast. And if you ever add a constructor later, you might have to change every place where you created one of those types.
- Since there’s no constructor, you can’t easily validate that valid values are used for the fields, and none of your fields have mandatory initialization.
- Many of the best developers say you should make your fields read-only if possible. And the style police say you should make them properties instead of fields.
So, you probably need a constructor. But adding a constructor is a pain!
publicclassPerson{publicstringName{get;}publicDateTimeDateOfBirth{get;}publicList<Person>Children{get;}publicPerson(stringname,DateTimedateOfBirth,List<Person>children){Name=name;DateOfBirth=dateOfBirth;Children=children;// TODO: Add validation code}}
It’s too much repetition!
- You repeat the class name twice.
- You repeat each data type twice.
- You repeat each property name twice.
- You repeat the name of each constructor parameter twice.
- You repeat “public” for each field (and more, if they are properties)
LeMP solves these problems with a combination of (1) a macro, and (2) a little syntactical “makeover” of C#. In LeMP you’d write this:
publicclassPerson{publicthis(publicstringName{get;},publicDateTimeDateOfBirth{get;},publicList<Person>Children{get;}){// TODO: Add validation code}}
Your output file will contain exactly the code listed above, and there is no repetition except for public .. { get; }
(but you might not want everything to be public anyway). Great!
What’s going on? Enhanced C# includes two syntax changes to support this, each with a supporting macro:
- To reduce repetition and ambiguity, Enhanced C# allows
this
as a constructor name (a feature borrowed from the D language). A macro changesthis
intoPerson
so that plain C# understands it. - Enhanced C# allows property definitions as method parameters (or wherever an expression is allowed). A macro is programmed to notice properties, and visibility attributes (like
public
) on variables. When it notices one of those, it responds by transferring it out to the class, and putting a normal argument in the constructor. Finally, it adds a statement at the beginning of the constructor, to assign the value of the argument to the property or field.
Example: parsing
The biggest macro packaged with LeMP is a parser generator called LLLPG. This example defines EmailAddress.Parse()
, which parses an email address into UserName
and Domain
parts:
usingLoyc.Syntax;// Get the Loyc.Syntax package from NuGet#importMacros(Loyc.LLPG);structEmailAddress{publicEmailAddress(publicUStringUserName,publicUStringDomain){}publicoverridestringToString(){returnUserName+"@"+Domain;}LLLPG(lexer(inputSource:src,inputClass:LexerSource)){// LexerSource provides the runtime APIs that LLLPG uses. This is// static to avoid reallocating the helper object for each address.[ThreadStatic]staticLexerSource<UString>src;publicstaticruleEmailAddressParse(UStringemail)@{{if(src==null)src=newLexerSource<UString>(email,"",0,false);elsesrc.Reset(email,"",0,false);}UsernameChars('.'UsernameChars)*{intat=src.InputPosition;}'@'DomainCharSeq('.'DomainCharSeq)*EOF{UStringuserName=email.Substring(0,at);UStringdomain=email.Substring(at+1);returnnewEmailAddress(userName,domain);}}staticruleUsernameChars()@{('a'..'z'|'A'..'Z'|'0'..'9'|'!'|'#'|'$'|'%'|'&'|'\''|'*'|'+'|'/'|'='|'?'|'^'|'_'|'`'|'{'|'|'|'}'|'~'|'-')+};staticruleDomainCharSeq()@{('a'..'z'|'A'..'Z'|'0'..'9')['-'?('a'..'z'|'A'..'Z'|'0'..'9')]*};}}

Learn more
Learn more about LeMP in these published articles:
- Avoid tedious coding with LeMP
- Using LeMP as a C# code generator
- C# Gets Pattern Matching, Algebraic Data Types, Tuples and Ranges
Macro reference manual
More links
Help wanted
Do you have time to make LeMP better?
Integration into Visual Studio is basic at the moment; help wanted if you have skill in writing extensions.