105 karma · joined September 15, 2014
It has been years since I've written a proper parser but before that every time I had to write one I tried the latest and greatest first. ANTLR, coco/r, combinators. All the generated ones seemed to have a fatal flaw that hand writing didnt have. For example good error handling seemed almost impossible, very slow due to Infinite look ahead or they were almost impossible to debug to find an error in the input schema.
In the end hand crafting seems to be faster and simpler. Ymmv.
My point about the article was mostly that all the formal theory is nice but all it does is scare away people, while parsing is probably the simplest thing about writing a compiler.
When it's really one of the most simple things if you divide it in parts and look at it from a tokenizer (string to list of tokens) and parser on top. Where the tokenizer can usually be very simple: a loop, large switch on the current character, where a choice is made on "what can this be", and making it into a formal token or error. Then a simple recursive parser that can almost be a 1 to 1 copy of the (E)BNF.
For large and small companies surely the best way to maintain their users
Or is everything more like a initial render on the server and pure wasm from that time on?
Last but not least, what you actually end up wanting to build is an ast, as at some point you want to do something with the input, for most parsers you then have to implement even more code to build up the ast.
It is much easier to hand write it. In the end its faster to write and usually faster to run.
Every few years I evaluate the new options for the languages I use (c#, pascal), every time so far I am disappointed with the tooling. Maybe one year.
In Elements we spent a lot of time on getting it right, and I think we have most edge cases covered for user code. The way generics are used in the Swift runtime library seems impossible to map perfectly on top of the concept of generics as C# has them though.
I'd hate to have the same happening for rust.
If that's there, it's a generic, else it's an identifier, and somewhere up the parsing chain will take that up as LessThan.
* Error handling always ends up being non-existent or of the quality of "begin, for, if, while, repeat, identifier, number, float expected" with no good way to override what happens
* Recovery is usually impossible
* Parser generator generates a full model that doesn't match what we need
* Working around the quirks of the input language ends up being more tricky than hand writing (almost every language has some ambigiuty)
* Slow: With ANTLR it's really easy to make it do gigantic amount of look aheads in complex languages, which isn't even really needed
I always end up going back to a simple hand crafted parser which is easier to read and write.
I'd even go as far as saying the literature on the subject is overrated or out of date.
And not everything is set in stone. Not all compilers have a single symbol table. Sometimes it makes sense to have a type/global table and local table, and check the current class scope on the fly. Sometimes not. It doesn't really.
Abstraction doesn't make sense until you actually have multiple implementations. And you might have rewritten it four times by then.
My advice like above. Just start writing
As a fun project, as I already had code to generate llvm bitcode from .NET, I now do mem2reg (convert stack spots to SSA registers), dead code elimination, constant folding and other small optimizations. That part now works, and I managed to create a simple x86_64 coff object file (with everything needed to link to it, including pdata, xdata) that returns the "max" value for a given integer.
That is about all that works for now, and I don't get to spend much time on it, but the end goal is to have a "good enough" codegenerator for non optimized cases, that could potentially be faster than llvm (to emit). The primary goal is to learn how to do this though :)