melt-umn / silver

An attribute grammar-based programming language for composable language extensions
http://melt.cs.umn.edu/silver/
GNU Lesser General Public License v3.0
59 stars 7 forks source link

Silver generating IDEs: lessons learned #308

Open tedinski opened 5 years ago

tedinski commented 5 years ago

We had a discussion in the lab yesterday that I thought I'd write up to clarify my thoughts, share, and get any feedback.

The short version is this: Silver should not be generating IDEs. (Or IDE plug-ins.) Silver can generate whatever is useful for an IDE plug-in to use, but the plug-in itself should be a separate project, and IDE concerns should not proliferate in Silver host languages or extensions.

I think this point needs to be made because this is a lesson learned from the eclipse IDE, and any Atom IDE support should avoid repeating this mistake.

The problems with generating an IDE

The current eclipse IDE support works by writing an IDE spec in Silver (using IDE-specific code) that we then use to generate an eclipse project structure. There are a multitude of reasons this makes everything go poorly, but they boil down to one overarching problem: the dependency structure.

Generating an IDE from a Silver spec means we get a diagram like this:

+--------+            +----------+
| Silver | depends on |   IDE    |
|  spec  | ---------> | concerns |
+--------+            +----------+

In order to make the IDE behave how we want it to, we have to modify the Silver spec, because the IDE's behavior is a result of that code generation. But the design we actually want is this:

+--------+            +----------+
| Silver | depends on |   IDE    |
|  spec  | <--------- | concerns |
+--------+            +----------+

Here, the IDE does IDE things, but can leverage anything in the Silver spec to do its job, and the Silver spec remains blissfully uncoupled to IDE junk.

The problems this leads to are varied:

  1. One problem is that generating an IDE means that we have a lot of code that's getting put together with string templates in the Silver compiler. This is awful to maintain. Any code we generate this way can't be checked (even for just correct syntax) except by modifying the Silver compiler, rebuilding the Silver compiler, using the rebuilt compiler to generate an IDE, and then building that. We need to minimize generated code as much as possible.

  2. In order to do new IDE things, we have to modify the Silver spec. And we need to also build support for doing those things into Silver! Consider that we have Eclipse "Project Properties" and "Wizards" built into our current IDE support. This is ridiculous. But it was also essential. We had to create new projects somehow. When Lucas wanted to add support for building with --warn-all to the Eclipse plug-in, we needed to add new eclipse project configuration properties and modify the Silver compiler implementation to support that. When we wanted to actually build jars in the eclipse IDE, we added a whole new export function to the IDE specs. We had to modify Silver itself to support a new language feature, to be able to modify the Silver IDE plug-in to use that language feature, to just add a button in the eclipse IDE.

The question at hand

What we discussed yesterday was the syntax coloring problem.

The current eclipse IDE requires that font declarations be put into Silver code, and that Silver's lexer classes get annotated with font modifiers. This means we're modifying the core of the Silver compiler to include Eclipse code highlighting concerns.

The way this should have worked is that the separate Silver plug-in project should have contained a mapping that said "oh, any terminal with keyword lexer class should get highlighted this way" and so on. We may still use lexer classes to categorize terminals and determine coloring, but the actual mapping of a lexer class to styling stays external to the Silver spec. (You can maybe think of this a "metadata" versus "policy." One the one hand, you mark some things as "keywords," on the other, you say "keywords get this styling," and you keep these two concepts distinct.)

It sounded as if we were about to repeat this mistake with Atom support. Yesterday we were chatting about plans to add Atom styling lexer classes, and then figure out how to go about injecting the lexer classes into the Silver specs, seemingly because we were again generating an Atom plug-in from Silver.

What the IDE plug-in should probably look like

  1. The plug-in should be a separate project. (If we want it to be easy to create new plug-ins for new host languages, we should just write a Python script that generates the project structure, a bit like React or Rails do, and not have Silver do this itself.)

  2. Silver can be modified to generate whatever information the IDE wants, but this should avoid requiring anything too IDE-specific in the Silver specs. E.g. Translating Silver grammars to Treesitter? Fine. Generating terminal/lexer class metadata in JSON format or something? Okay. You can just add a flag to enable these things from otherwise unmodified Silver specs. Writing attributes in Silver that compute things the IDE wants like code folding spans? Probably ok, but we should talk about dependency structure later maybe.

  3. To avoid having to write too much code in each plug-in, we can build a shared runtime. But instead of being forced to generate or build things into the runtime by default, we can instead build specific implementations for the IDE plug-in we're immediately concerned with, and only later abstract this stuff into the shared runtime.

+--------+        +----------+
| Silver |        | SV & Cop |
|  spec  | -----> | runtimes |
+--------+        +----------+
    ^                  ^
    |                  |
    |                  |
+---------+        +---------+
|  IDE    |        | SV-IDE  |
| plug-in | -----> | runtime |
|---------+        +---------+

The end result is that we shouldn't have any dependency arrows between these projects except the ones we see in this diagram.

I'm getting a headache, so I'm going to just pause here, click post, and see if this makes any sense to anyone else.

ericvanwyk commented 5 years ago

Thanks, Ted. I agree with you about these dependency structure issues - that all seems obvious. But I don't see how that fits with the short version statement that Silver should not generate IDEs. Certainly the Eclipse thing is a mess. But I don't think the Atom work is heading in the same direction as the Eclipse thing. It would seem natural to write extensions to Silver that allow one to write rather IDE specific things in a Silver specification. This would keep the Silver host language free of domain-specific IDE stuff. In the object language specification, one might even have separate grammar for the IDE specs - think having abstractsyntax, concretesyntax, and atom_highligher grammars.

How about joining us at 4pm in the group meeting today and we could discuss this?

tedinski commented 5 years ago

Ok, post-discussion re-framing:

The problem is:

  1. Generally the simple way to do things with the IDE is let IDE plug-in developers do IDE things in the most natural way. So eclipse plug-ins should just write java/xml stuff. Atom plug-ins should just write javascript/json stuff. And so on. We don't want to have to extend Silver with "atom toolbar button" language extensions and then modify host languages (either directly, or even by writing extensions to the host language typically). The point here is just we're creating complexity and there's a much easier way. So we 100% want people to be able to modify their IDE plug-in directly.

  2. "Generating a plug-in from a Silver spec" means that can't happen. They'd have to generate the plug-in, then modify the plug-in to do something custom, then deal with somehow repeating this modification when the generation process repeats?

So the solution is: separate metadata from policy.

  1. Silver can freely generate metadata, or anything else useful for a plug-in project to use.

  2. Plug-ins should be separate projects, where policy resides, wholly written by the user, so that IDE stuff isn't tightly coupled to the Silver spec.

  3. We can make it easier to start a separated plug-in project by having a "new project builder" script that creates the project skeleton. (Leading to confusion about to word "generation" but the point is that this script never get re-run. It just helps you get started, and then you own and can freely modify the result forever.)

  4. We can still simplify IDE plug-in development effort by creating a "silver-ide runtime" project. This is just an ordinary library that the initial "project skeleton" can make use of.

So, for example with Eclipse:

  1. Silver parsers now track token lists, instead of just building trees, this is useful for doing syntax highlighting. But this is the sort of "Silver does something that an eclipse plug-in can use" kind of code generation, and doesn't have any dependency on eclipse at all.

  2. We're about 90% of the way there towards having the "Eclipse support for Silver" plug-in consist basically of 1 short XML file and a bunch of boilerplate project structure.

  3. We put nearly all of the code now in the silver-eclipse runtime project. This makes it easy to produce new eclipse plug-ins for other languages implemented with Silver, because it only takes writing up one XML file.

  4. Right now we don't have a "eclipse plug-in project skeleton" generator, because Silver is still doing that, but that's what we'd like to fix, if anyone gets around to it.