A few years ago I developed a modified syntax for C# called Enhanced C#, along with a Lisp-style macro system. It’s a long story, but basically I reformulated the C# language in a way that makes it more friendly to macros. For example, the language defines a new expression that looks like this:
identifier (expression1, expression2) { statements; }
This is used by the unless
macro, for instance, to reverse the usual if
statement logic:
define (unless ($cond) { $(..actions); })
{
if (!$cond) {
$(..actions);
}
}
unless(authentication_failed) {
Console.WriteLine($"Logged in as {user.Name}");
}
This compiles to plain C# code that one compiles as usual:
if (!authentication_failed) {
Console.WriteLine($"Logged in as {user.Name}");
}
Another example of a macro-friendly syntax change is that method parameters don’t need to be variable declarations; they can be any expression whatsoever.
The newest feature, the “macro” macro, makes it much easier to use because you can write a macro in Enhanced C# and immediately call that macro in the same file (just like in all Lisp languages). Previously, writing new macros was a clumsier process that required a separate project to hold them.
Here’s an example using macro
. This macro’s job is to convert a UTF-8 string into a byte array, e.g. var hello = stringToBytes("hi!")
becomes var hello = new byte[] { (byte) 'h', (byte) 'i', (byte) '!' };
macro stringToBytes($str) {
var s = (string) str.Value;
var bytes = Encoding.UTF8.GetBytes(s).Select(
b => quote((byte) $(LNode.Literal((char) b))));
return quote(new byte[] { $(..bytes) });
}
This macro uses the quote
macro to generate a syntax tree for the cast to (byte)
and for the literal characters, created with the LNode.Literal
method.
LNode
is short for “Loyc node”, a reference to the underlying syntax tree, called a Loyc tree. A Loyc tree is a simple (ish) data structure inspired by the Lisp family of languages. Loyc trees can theoretically represent syntax in any language, and in fact the macro processor (called LeMP) is language-agnostic and currently supports three separate syntaxes: EC#, and LES versions 2 and 3. (to support additional languages I would need volunteers to help, as I’ve got my hands full with two other projects atm). This is cool, but as far as I can tell, it is not possible for a multi-language preprocessor to support a hygienic macro system, so it doesn’t.
A typical use of Enhanced C# is generating sequences of similar but repetitive code. For example, last week I wrote this code to generate some methods:
define isUnsigned($T) {
$T `staticMatches` uint || $T `staticMatches` ulong;
}
public partial struct JsonWriter {
// This is a new macro, unreleased; it'll replace the old unroll macro
##unroll($T in (int, uint, long, ulong)) {
public $T Sync(Symbol? name, $T savable) {
_s.WriteProp(name == null ? "" : name.Name, (long) savable, !isUnsigned($T));
return savable;
}
}
...
}
The generated methods look like this:
public uint Sync(Symbol? name, uint savable) {
_s.WriteProp(name == null ? "" : name.Name, (long) savable, !(true || false));
return savable;
}
Anyway, the reason I dropped by today is that I’ve never actually written a program in Lisp or Clojure; I did everything based on just reading about them. So I’ve been learning things like “controlling macro execution order is hard and not very intuitive”, e.g. I’ve been patching some built-in macros to reverse execution order, and made a macro called #preprocessChild
that overrides execution order for specific parameter(s) of other macros.
There are a lot of built-in macros light now, but their naming conventions (and behavior conventions) are not settled and not entirely consistent because I’ve been a bit indecisive over the past few years about what the rules should be
So, I wonder if any Clojure developers are (1) experts in using macros and (2) might like to help design, implement or just play with macros in C#. If so, please head on over to the LeMP home page and look around, install the Visual Studio extension, try out the LeMPDemo.exe in the released zip file, or ask me any questions you have.