My best month in years

I’ve just had the most productive month since I was laid off in April, and the most productive month without pay in years. Why?

It’s hard to say exactly, but a number of factors came together.

First and foremost, moral support by Maksim Volkau was invaluable. Thank you so much! Sometimes people complain about how users of open-source projects can be demanding and not contribute anything back, but I think that demanding users are not nearly as bad as having no users at all. I make open source software for one reason: I want to make the world a better place. Simple as that. The fact that I have no users and no participants is therefore a massive hole in my life. Jonathan van der Cruysse used to be a heavy user, but his work tapered off a couple of years back for reasons unknown to me, so it was a big boost to have Maksim come in with some bugs and feature requests. On very, VERY rare occasions I get nice message from a lurker, but only a bug report can prove someone’s actually using my software. So, to those rare individuals who step up with a bug report, thank you so much.

Another factor is that I quit looking for a job. Suffice it to say that I am giving up a bunch of EI income in order to do this.

The third factor was that I wanted to make an Blazor version of LeMP, so users could try it online, but first I wanted a macro for building other macros, which the online version would definitely need. For this macro to be as powerful as I wanted it to be, I had to add some new features to LeMP, which was more work than expected - I ended up making a set of related general-purpose classes with names like CollectionWithChangeEvents, because I wanted to use just one of those classes for an internal design I thought I wanted to use in LeMP - I decided to also write very similar classes (ListWithChangeEvents and DictionaryWithChangeEvents) for completeness. But ultimately I didn’t need these classes in the final design, so basically I wasted my time. Still, the classes I built and unit-tested will no doubt be useful for someone, someday. I hope. And then of course I added a macro macro - there was already a define macro for doing simple syntactic transforms, but macro gives you the full power of the C# language. It’s been possible since the beginning to define your own macros, but other than Jonathan I don’t know of anyone who bothered.

Old And Busted*

define change_temporarily macro

(* not actually busted)

New Hotness: complex compile-time logic

macro change_temporarily($lhs = $rhs)
{
    using Loyc.Ecs;
    
    var tempOld = LNode.Id("old_" + context.IncrementTempCounter());
    
    matchCode(lhs) {
      case $prop.$member:
        // Avoid evaluating a property or function call twice in the output.
        // Also support `ref` attribute: it causes the prop var to be a ref var.
        var @ref = LNode.List().AddIfNotNull(node.AttrNamed(EcsCodeSymbols.Ref));
        unless (@ref.IsEmpty && prop.IsId && prop.Name.Name.ToLower() == prop.Name.Name)
        {
            var tempProp = LNode.Id(EcsValidators.KeyNameComponentOf(prop).Name
                                    + context.IncrementTempCounter());
            var propRef = quote([$(..@ref)] $prop);
            return quote {
                [$(..@ref)] var $tempProp = $propRef;
                var $tempOld = $tempProp.$member;
                $tempProp.$member = $rhs;
                on_finally { $tempProp.$member = $tempOld; }
            };
        }
    }
    // In simple cases, use the old logic
    return quote {
        var $tempOld = $lhs;
        $lhs = $rhs;
        on_finally { $lhs = $tempOld; }
    };
}  

void Example() {
    ref change_temporarily(GetParent().Mode = Mode.Awesome);
    AwesomeModeIsTemporarilyActive();
}

// Generated code (LeMP v2.9.0.1)
void Example() {
	ref var GetParent12 = ref GetParent();
	var old_11 = GetParent12.Mode;
	GetParent12.Mode = Mode.Awesome;
	try {
		AwesomeModeIsTemporarilyActive();
	} finally {
		GetParent12.Mode = old_11;
	}
}

I also wanted to release semantic version 29 before the web version of LeMP, and to to that I had to finish any breaking changes I wanted to do for version 29, and one thing led to another…

Over at Future of Coding slack, I wanted to post “two-minute week” for the past month, so I started thinking of demos to show. And I thought I wanted a demo that used a switch expression. Problem: EC# doesn’t support switch expressions yet. Solution: implement those too. Problem: C# 8 switch (and is) patterns are poorly documented, hideously ambiguous, incompatible with LL(k), and, dare I say, worse than my older Enhanced C# design in some ways. Solution: spend 2 full days on it.

Of course, Enhanced C# is a liberalization of C#, so the obvious question was which features of C# 9 patterns should be included throughout EC#. I decided against adding unary relational operators (like < 0) everywhere, but in favor of adding the when operator everywhere, as well as a variant of the switch operator that is not followed by braces.

And then I thought “why stop there?” and added a where operator too, because I think some people (e.g. me) would be interested in writing Haskell-style code where you write your code top-down instead of bottom-up. Take, for instance, the code in compileTime for error handling when you give it some invalid code. What I wanted to write was some code to print an error message:

context.Sink.Error(errorLocation, "{0} - in «{1}»"
        .Localized(e.Message, lineOfCodeFromOutput));

But first, I need to figure out the errorLocation and a lineOfCodeFromOutput to include in the error message. At this point programmers usually insert code above this point to retrieve the desired information. I’m thinking, no, that harms code readability! Instead what we should do is write the code in a more natural top-down order:

// (I'd prefer the shorter syntax $errorLocation, but 
// `var` is what I think the Roslyn team would choose)
context.Sink.Error(var errorLocation, "{0} - in «{1}»"
  .Localized(e.Message, var lineOfCodeFromOutput)) where
{
    Microsoft.CodeAnalysis.Text.TextSpan range = e.Diagnostics[0].Location.SourceSpan;
    var C#Location = new IndexRange(range.Start, range.Length);
    var errorLocation = outputLocationMapper.FindRelatedNodes(C#Location, 10)
        .FirstOrDefault(p => !p.A.Range.Source.Text.IsEmpty);
    lineOfCodeFromOutput = codeText.Substring(var lineStart, (var lineEnd) - lineStart) where {
      int column = e.Diagnostics[0].Location
        .GetLineSpan().StartLinePosition.Character;
      lineStart = range.Start - column;
      lineEnd = codeText.IndexOf('\n', lineStart);
      if (lineEnd < lineStart)
        lineEnd = codeText.Length;
      string line = codeText.Substring(lineStart, lineEnd - lineStart);
    }
}

The new release won’t actually include the macro necessary to use this feature, but it’s a macro that you could theoretically write yourself. You don’t even need the new version, as Enhanced C# already has a “user-defined operator” feature - you just have to write expr `where` {...} instead of the slightly better-looking expr where {...}.

Anyway, while working on C# 8/9 pattern matching I realized that if I didn’t want to write a bunch of ugly custom parser code again, I would need some kind of new feature in my parser generator to give me better control over code generation for recognizers. So I spent two days adding new LLLPG operators called recognizer and nonrecognizer to help me out.

And that’s where we are today. In the coming days I’ll be producing videos for Future of Coding, figuring out how to write printers more effectively by implementing a printer for TypeScript, and releasing semantic version 29 of Enhanced C#, LeMP, LLLPG and the Loyc .NET Libraries.

Did I somehow regain my hope — hope that more work would lead to renewed interest in my software and my ideas? Maybe. Hope is a fragile thing. [Edit: on second thought, it wasn’t fragile when I started the EC# project 8 years ago; time has a way of wearing me down.] I can feel the despair clawing at my psyche from below. A couple of times in the past month, I remembered the bad times, like when I would write a ten-page article on a new feature, post it to Reddit and get two upvotes and zero traction. So far I’ve managed to refocus my mind on more positive thoughts. I worry that my success won’t last much longer, but I am confident that I will at least manage to post the videos, release v29 and continue work on the web version of LeMP.

Comments