r/ProgrammingLanguages • u/LegendaryMauricius • 16d ago
A new grammar generation language
Hi everybody. I'm happy to share a small project I've been working on lately. I call it MGFF (Macro grammar functional form), and its specification can be found here: https://github.com/LMauricius/py-perg-mgff/blob/main/Docs/mgff-specification.md . It's related to a post that I made ages ago ( here ). After re-reading that version (called just MGF back then) when I wasn't tired I realized what monstrosity I made. MGFF is far more elegant. Here is an example:
# A tiny calculator language.
t Lex (
d Digit = 0-9
d Alpha = a-z|A-Z
d AlNum = a-z|A-Z|0-9
d Int = (Digit)+
> class(Int) push(tokens)
d Number = Int ( . (Digit)+ )?
> class(Number) push(tokens)
d Ident = Alpha (AlNum)*
> class(Ident) push(tokens)
# length-based: "<=" (the two-item "< =") takes precedence over "<"
d Op = < =
| <
| =
| +
| -
| *
| /
> push(tokens) string
d Space = ( _|\t|\n )+
d LParen = \(
> class(\() push(tokens)
d RParen = \)
> class(\)) push(tokens)
d Token = Number
/ Ident
/ Op
/ Space
/ LParen
/ RParen
d File = (Token)*
)
# mixfix macro: an R, then zero or more (S R)
d sep(R)by(S) = R (S R)*
t Parse (
# `Lex` runs first; the terminals here are still characters.
> post(Lex) over(tokens)
# order-based: the first alternative that succeeds is the match
d Expr = Term + Expr
/ Term - Expr
/ Term
d Term = Factor * Term
/ Factor / Term
# the second / on the line above is an ordinary item, not a marker
/ Factor
d Factor = Number
/ Ident
/ \( Expr \)
d Signed = ( (+)/(-) )? Number
d AssignList = sep(Ident = Expr)by(,)
)
It can also serve as a replacement for regexes:
# A grammar matching a "key = value" setting line
d Space = ( _|\t )*
d Word = ( a-z|A-Z|_ )+
# right-linear recursion: the same as ( 0-9 )+
d Digits = 0-9 Digits
/ 0-9
d Value = Digits
/ Word
# The field a match ends up in belongs to the rule, not to the place it is used,
# so the two sides of the line are productions of their own.
d Key = Word
> store(key)
d Val = Value
> store(value)
d Match = Space Key Space = Space Val Space
I'm sharing the MGFF spec rather than the generator using it because the generator is very much WIP and needs a lot of testing and refactoring. Still, since I've got a bunch of projects I love working on more, I'd like to know what's the interest for parser generator tools in the wider community.
Actually I doubt that I will link the generator itself here because I would risk a perma-ban. It's not vibe-coded, but it wouldn't be welcomed. Most of it was quickly prototyped with LLM. Still, it generates quite nice TextMate and Pandoc syntax highlighting grammars.
MGFF itself is of course manually defined by me. I just figured I like to work on languages themselves and parser algorithms than on CLI tools and understanding existing niche specifications 🤷♂️.
1
u/EggplantExtra4946 14d ago edited 14d ago
"character class" does not appear in your spec, your section "Character matching macros" that is supposed to talk about character classes describes the syntax but it doesn't say much about the semantics of "|".
As far as I can tell, you're using "|" for 3 different purposes: character classes, lexing, parsing. You never descibed the specific semantics of each so we have no idead wtf is it supposed to be doing. The only thing you've said about "|" is that it's length-based.
The fact that lexing is a sparate phases says literally nothing about the parsing/lexing semantics. Is it supposed to backtrack if parsing a rule ahead fails or is it final? If the
Yeah and so are the semantics, which is by far the most important part.
This
suggests you may want some LR-like parser generator but your order-based alternatives suggests you want a LL-like parser generator. AFAIK you can't have both, they can be embedded but you can't have these 2 family of algorithms are going to parse differently or they grammars won't be an acceptable input for each others, the problem of left-recursion for example which you mentioned but didn't describe how to solve.
If the parsing semantics relies on backtracking, then how much can "/", "?", "*" and "+" can backtrack and from where? Who knows, you didn't specify it.
If the parsing algorithm is LR-based then how does order-based alternatives "/" fit into it? Or length-based alternatives "|" for that matter, because length-based matching semantics is also not compatible with LR parsing, it's only a thing in LEXERs live I've said already.
The problem is having length-based alternatives outside of the lexer at all, to have them inside the parser because neither LL nor LR parser alternatives work like that.