ANTLR Mega Tutorial
tomassetti.me
tomassetti.me
1) ANTLR is a good tool for generating "happy path" parsers. With a grammar specification, it easily generates a parser that accepts or rejects a piece of source code. However, it's not easy to use the hooks to generate high quality diagnostic error messages.
2) ANTLR was not good for speculative parsing or probabilistic parsing which would be the basis of today's generation of tools such as "Intellisense" not giving up on parsing when there's an unclosed brace or missing variable declaration.
The common theme to the 2 bullet points above is that a high quality compiler written by hand will hold multiple "states" of information and an ANTLR grammar file doesn't really have an obvious way to express that knowledge. A pathological example would be the numerous "broken HTML" pages being successfully parsed by browsers. It would be very hard to replicate how Chrome/Firefox/Safari/IE doesn't choke on broken HTML by using ANTLR to generate an HTML parser.
In short, ANTLR is great for prototyping a parser but any industrial-grade parser released into the wild with programmers' expectations of helpful error messages would require a hand-written parser.
Lastly, the lexing (creating the tokens) and parsing (creating the AST) is a very tiny percentage of the total development of a quality compiler. Therefore, ANTLR doesn't save as much time as one might think.
I welcome any comments about v4 that makes those findings obsolete.
In short, ANTLR is great for prototyping a parser but any industrial-grade parser released into the wild with programmers' expectations of helpful error messages would require a hand-written parser.
What an odd statement, in light of the innumerable deployments of Bison / ANTLR parsers you certainly use at least once a day (if you spend any time at all in a terminal).
Which ones? Awk is one; in fact Awk was almost co-developed with yacc. But it gives generally bad error messages. (There are multiple implementations, but most of them use a yacc-style LR(1) grammar and give bad error messages.)
I don't know of any others. Certainly ANTLR generates bad C/C++ code, so I would be very surprised if it's used in anything in a typical Unix/Linux terminal.
I have been blogging about Unix and parsing here, after writing a very complete bash parser by hand: http://www.oilshell.org/blog/
My conclusion is also that generic parser generators are not good enough for production quality parsers. This is borne out by evidence in the wild.
In other words, "real" languages don't use parser generators. Look at the top 10 languages, as well as emerging languages. Which one of them use parser generators?
Clang, GCC, v8, PHP, C# / Roslyn, Go, Perl, TypeScript, Dart, etc. all use hand-written parsers. Not sure about Rust and Swift, but I think they are hand-written. Java is an exception, but interestingly it has a "real" grammar, and then a LALR(1) optimized for parser generators:
http://trevorjim.com/is-java-context-free/
Python uses ITS OWN parser generator, not a generic one, which is a very important distinction.
I'm not sure about Ruby, I think it might be a yacc core with A LOT of ad-hoc parsing, so it may not count. Just like bash uses yacc for about 1/4 of the language, and ad hoc hand-written parsers for the other 3/4.
Anyway, I don't think parser generators are as widely used as you think, but I would be happy to be corrected.
The thing to remember is that the vast majority of parsers out there are not for these production language compilers. Compare the number of people you know that have built parsers for a DSL, data, documents, or whatever to the number of people you know that built compilers. ANTLR's niche is for your everyday parsing needs. It generates fast ALL(*) parsers in a multitude of languages and accepts all grammars without complaint, with the minor constraint that it cannot handle indirect left recursion. (Direct left recursion as an expressions is totally okay.) For a speed shootout with other tools, see OOPSLA paper http://www.antlr.org/papers/allstar-techreport.pdf
Excellent discussion!
Currently, jq (pretty much, awk for json https://stedolan.github.io/jq/) uses bison, but sometimes has unhelpful syntax error messages. I'm not the maintainer or anything, but trying to trigger error messages more precisely (i.e. diagnosis) was a nightmare.
While a generator can't match a custom parser for control, is it plausible to have arbitrarily precise hooks in ANTLR for diagnostic messages?
BTW unfortunately deadlink http://stackoverflow.com/questions/16503217/antlr-for-commer...
The vast majority of those people that I know of have not even been aware of tools like ANTLR, and usually resort to regexp-based abominations instead unless they happen to know about bison/yacc (slightly more likely than ANTLR).
I agree those are cases where a tool like ANTLR might very well be preferable to someone hacking together a regexp-driven mess, but my personal experience is that the people most likely to know about these tools are also the people most likely to know how to handwrite reasonably clean parsers.
Some subset certainly will opt for the tools some of the time (I have done that myself, even though I much prefer handwritten parsers; I've also written parser generation tools myself, out of frustration with the existing ones... Im still frustrated enough by my own tools to end up handwriting parsers... (EDIT: Clarified; I could use a good parser to check my English sometimes) ), but the parser-generation field is a peculiar one.
I have no doubt that sooner or later someone will crack this nut (maybe ANTLR will be that tool one day) and handwriting parsers will go the way of handwriting assembler and be something you do only in rare cases, but we're far from there still.
Note that I'm not saying ANTLR is a bad tool. I'm saying this is a space where a lot of the potential audience are a bunch of very difficult to convince and picky people (myself included...), and where a large other part of the audience doesn't know the field at all and are unaware of the tools.
I ported the POSIX shell grammar to both ANTLR v3 and v4 (which was basically changing yacc-style BNF to EBNF). But as mentioned, I discovered that the grammar only covers about 1/4 of the language. bash generates code with the same grammar using yacc, but fills in the rest with hand-written code. Every other shell I've encountered uses a hand-written parser. bash says they regret using yacc here:
http://www.aosabook.org/en/bash.html
I agree with you that there is a Pareto or long tail distribution in parser use cases. Most languages CAN use something like ANTLR or bison. But the parent was making a different claim:
What an odd statement, in light of the innumerable deployments of Bison / ANTLR parsers you certainly use at least once a day (if you spend any time at all in a terminal).
I would say that is FALSE, because most parsers that your fingers pass through are HAND-WRITTEN, because of the Pareto distribution. 99% of anyone's usage is of probably a dozen or so parsers, and they are either hand-written or generated by custom code generators, not general-purpose code generators like ANTLR or yacc.
-----
As feedback from a user of parsing tools, you might also be interested in my article here:
https://news.ycombinator.com/item?id=13628412
Someone is asking if there are any parsing tools that generate a "lossless syntax tree".
Also, based on my experience with ANTLR v3 vs. v4, I ask the question why use a concrete syntax tree at all? Nobody answered that question in the comments. I don't understand why that is a good representation, other than the fact that you might not want to clutter your grammar with semantic actions ("pure declarative syntax").
To me the parse tree / CST seems to be resource-heavy while containing unnecessary information, and also lacking some crucial information like where there's whitespace and comments.
To summarize my article, I'm researching code representations in the wild for both style-preserving source translation (like go fix, lib2to3 in Python) and auto-formatting (like go fmt).
It's definitely possible I misunderstood something since my experience was relatively limited, but I have read a lot of the docs and bought the books.
Do you mean instead of an AST? I find the syntax tree better for non-compiler applications like translators.
http://www.oilshell.org/blog/2017/02/11.html
I researched "production" implementations, and found that they use something like a Lossless Syntax Tree (not an AST or CST):
https://github.com/oilshell/oil/wiki/Lossless-Syntax-Tree-Pa...
Examples: Clang, Microsoft's Roslyn platforms, RedBaron/lib2to3 for Python, scalameta, and Go. The defining property of the LST is that it can be round-tripped back to the source. This is called out in this C# design doc, along with some conventions for associating whitespace with syntax tree nodes:
https://github.com/dotnet/roslyn/wiki/Roslyn-Overview
What do you think of that claim? (If you prefer not to use this deep comment thread, feel free to contact me by e-mail instead at andychup@gmail.com.)
edit: of course there could be handwritten extensions, but I didn't dive that deep.
https://news.ycombinator.com/item?id=13041646
The "canonical" documentation of the Ruby parser is basically a 6000+ line Bison file where most of the linecount is taken up with handling exceptions... It's awful.
That sounds very like much like bash, where parse.y is 6227 lines. Almost of all of it is C code, it's not actually using very much of yacc...
It's actually worse these days it turns out parse.y for Ruby is 11348 lines [2], though for fairness it's worth noting that it's written in a quite "linefeed heavy" style, and this includes providing a Ruby API to the parser.
I think my bash parser is pretty complete, and it's about 4K lines in Python now, including the lexer. Changing it to the lossless syntax tree vs AST blew it up by a few hundred lines.
However, it does more than the 6K lines in bash's parse.y, because it parses in a single pass. Bash does more parsing in the 9700 line file subst.c, so it's hard to count.
But yeah I'm about to write my Oil parser now, as opposed to the OSH parser... I really want to avoid writing another 4K lines of code!!! That might be the most practical way though. :-(
Having worked in the industry for awhile, there are a lot of handwritten parsers out there, especially when you need to build an IDE with code completion. You are right that for small tool-based languages that don't need those things, a grammar-generated parser is completely sufficient, but if you go all in on a more general programming language, you are likely to wind up with a hand written parser eventually.
For example, here's a blurb from ANTLR creator Terence Parr[1]: "Programmers run into parsing problems all the time. Whether it’s a data format like JSON, a network protocol like SMTP, a server configuration file for Apache, _a PostScript/PDF file_, or a simple spreadsheet macro language"
No... using ANTLR is the wrong tool for writing a parser of "PDF files". Parsing real-world PDF files is very similar to parsing wild HTML. PDF files are often broken and noncompliant. Is that the fault of ANTLR?!? Not really, but consumers don't care.
With all the dozens of PDF helper libraries out there, this is probably the #1 bug report transcript:
- programmer/customer: "Your PDF library can't open RealWorldBroken.pdf -- please fix it"
- vendor customer support: "We looked at the pdf file you're trying to open and it's invalid"
- programmer/customer: "Yes but Adobe Acrobat Reader opens it just fine."
- <Vendor of library then adds code workaround to open that invalid file and make customer happy and thereby reinvents Adobe Acrobat's permissive parser -- one bug report at a time>
That last step inside the <angle brackets> is the kind of "industrial grade" parsing that ANTLR grammar files are not good at expressing. By mentioning PDF files, it's misleading people about what ANTLR is really good for. ANTLR is a better lex/yacc. It's not better than a hand-written parser for things like PDF files!
If you have a well-defined parsing problem in the classic "Dragon" book[2] sense and want to skip the tedium of hand writing a parser... yes ANTLR is wonderful for that.
>You really can't out perform a parser generator unless you live in an ivory tower"
You can easily outperform ANTLR if your parsing domain is "messy" and programmers (users of compilers) are looking for more than a binary result of accept/reject of a source file with "invalid token" on line 5 of a 1000 line program. They often want insightful error messages from the compiler that "guesses" the programmer's intentions. For example, the C++ parsing error messages in Clang that's superior to GCC cannot be expressed in a ANTLR grammar file.
[1] https://pragprog.com/book/tpantlr2/the-definitive-antlr-4-re...
[2] https://en.wikipedia.org/wiki/Compilers:_Principles,_Techniq...
ANTLR does have some generic error recovery capabilities such as single token insertion/deletion or re-sync recovery.
There is a whole chapter describing the algorithms in the ANTLR book: http://media.pragprog.com/titles/tpantlr/errors.pdf (unfortunately this link does not contain the full content of the chapter).
These kinds of generic heuristics is not something that would be simple to implement in most hand build parsers as they require full knowledge of the entire grammar and that information is often "hard coded" as source code not as a separate data structure.
Also many languages don't survive the prototyping phase into some kind of product, so why bother with premature optimizations?
LR has fewer constraints than LL(n), menhir's documentation is better and splitting lexing/parsing IMO adds clarity.
Also, there can be advantages to having a top-down parser for semantic actions.
The technical aspect of how the grammar is parsed, is a matter of taking the effort to design it in a LL (*) compatible way.
TParr's The Definitive ANTLR 4 Reference is quite good. And so's this mega tutorial.
https://pragprog.com/book/tpantlr2/the-definitive-antlr-4-re...
ANTLR is my goto tool for DSLs.
Plus, once you have an understanding of recursive descent parsing, it's a relatively small leap to recursive descent code generation. And once you're there, you have a pretty good high-level understanding of the entire compilation pipeline (minus optimization).
Then all of a sudden, compilers are a whole lot less impenetrable.
[0] https://pragprog.com/book/tpdsl/language-implementation-patt...
REs are level 3 https://en.wikipedia.org/wiki/Chomsky_hierarchy
The upside is that they are more expressive, but the downside is that it's no longer O(n) to parse a string.
Worst case for some regexes can be exponential, never run untrusted regexes on your server unless you use a safe implementation like re2 which was developed for Google Code Search.
Similar to, "What's Cascading about Cascading Style Sheets?"
If I were to make up history, I would say "oh, regular refers to finite state machines, so a regular language is a language recognizable by a finite state machine," but it appears my made-up history does not exactly match reality. This is not too far off, though, since in the paper he shows that regular events are exactly the kinds of events his nerve networks recognize, and there appears to be something about finite automata in the last section.
They could have been called "rational languages" instead, because of a connection with rational functions. In fact, there are manipulations of rational series in Kleene's paper to prove some things about the languages.
(I don't get the joke myself --- unless the joke is that you can't take the "regular" out of "regular language.")
You mean "context-free" not "regular" here, but either way, I believe that C is actually context-sensitive:
(a)(b);
Is this:1. Evaluating "b" and casting it to type "a"?
2. Evaluating the parenthesized expression "a" which yields a function, and then calling it, passing in "b"?
The only way to distinguish the two is by knowing whether "a" is a type or not.
You could argue that as long as the parser produces a suitably vague AST, it doesn't need to know the distinction and can pass it on to later phases. I think that might work because I don't know of cases where the syntax actually diverges based on the type of a symbol.
But in practice, I think most parsers want to produce a more precise AST that distinguishes cast expressions from function calls.
[0] http://eli.thegreenplace.net/2007/11/24/the-context-sensitiv...
I wasn't aware it supports JavaScript nowadays.
In any case, good selection of languages.
"Unlike most existing yacc/lex-style solutions Irony does not employ any scanner or parser code generation from grammar specifications written in a specialized meta-language. In Irony the target language grammar is coded directly in c# using operator overloading to express grammar constructs. "
> The most obvious is the lack of recursion: you can’t find a (regular) expression inside another one ...
PCRE has some recursion. Here is an example for parsing anything between { }, with counting of inner brackets:
'(?>\{(?:[^{}]|(?R))\})|\w+'
A C++11 constexpr can make hand coded parsers a lot more readible, allowing token names in case statements. For example , search on "str2int" in the following island parser: https://github.com/musesum/par
Perhaps the tutorials should start with the strengths of regular expressions, and how we can harness that for getting started with a lexer.
(Antlr4 is awesome :)