Intuiting Pratt Parsing

(louis.co.nz)

38 points | by signa11 1 day ago

4 comments

  • logdahl 1 hour ago
    Love Pratt parsing! Not a compiler guy, but I've spent way too many hours reflecting on parsing. I remember trying to get though the dragon book so many times and reading all about formal grammar etc. Until I landed on; recursive descent parsing + Pratt for expressions. Super simple technique, and for me is sufficient. I'm sure it doesn't cover all cases, but just for toy languages it feels like we can usually do everything with 2-token lookahead.

    Not to step on anyone's toes, I just don't feel that formal grammar theory is that important in practice. :^)

    • signa11 19 minutes ago
      > Not to step on anyone's toes, I just don't feel that formal grammar theory is that important in practice. :^)

      exactly this ! a thousand times this !

      • ogogmad 12 minutes ago
        I think even the theory of Regular Languages is overdone: You can get the essence of what NFAs are without really needing NFAs. You can get O(n) string matching without formally implementing NFAs, or using any other formal model like regex-derivatives. In fact, thinking in terms of NFAs makes it harder to see how to implement negation (or "complement" if you prefer to call it that) efficiently. It's still only linear time!

        The need for NFA/DFA/derivative models is unnecessary because ultimately, REG is just DSPACE(O(1)). That's it. Thinking in any other way is confusing the map with the territory. Furthermore, REG is extremely robust, because we also have REG = DSPACE(o(log log n)) = NSPACE(o(log log n)) = 1-DSPACE(o(log n)).

    • randomNumber7 34 minutes ago
      It's not for toy languages. Most big compilers use recursive descent parsing.
    • gignico 53 minutes ago
      Until you need to do more than all-or-nothing parsing :) see tree-sitter for example, or any other efficient LSP implementation of incremental parsing.
    • ogogmad 38 minutes ago
      Quick other one: To parse infix expressions, every time you see "x·y | (z | w)", find the operator of least binding power: In my example, I've given "|" less binding power than "·". Anyway, this visually breaks the expression into two halves: "x·y" and "(z | w)". Recursively parse those two subexpressions. Essentially, that's it.

      The symbols "·" and "|" don't mean anything - I've chosen them to be visually intuitive: The "|" is supposed to look like a physical divider. Also, bracketed expressions "(...)" or "{...}" should be parsed first.

      Wikipedia mentions that a variant of this got used in FORTRAN I. You could also speed up my naive O(n^2) approach by using Cartesian trees, which you can build using something suspiciously resembling precedence climbing.

  • hyperhello 11 minutes ago
    You can either use the stack in an intuitive way, or you can change the tree directly in a somewhat less intuitive way without recursion. Essentially either DF or BF. I don’t see how it matters much anymore with stacks that grow automatically, but it’s good to understand.
  • randomNumber7 33 minutes ago
    I can recommend anyone reading pratts original paper. Its written in a very cool and badass style.

    https://dl.acm.org/doi/epdf/10.1145/512927.512931

  • priceishere 52 minutes ago
    An even simpler way imo, is explicit functions instead of a precedence table, then the code pretty much has the same structure as EBNF.

    Need to parse * before +? Begin at add, have it call parse_mul for its left and right sides, and so on.

      parse_mul() {
        left = parse_literal()
        while(is_mul_token()) { // left associative
          right = parse_literal()
          make_mul_node(left, right)
        }
      }
    
      parse_add() {
        left = parse_mul()
        while(is_add_token()) { // left associative
          right = parse_mul()
          make_add_node(left, right)
        }
      }
    
    Then just add more functions as you climb up the precedence levels.
    • kryptiskt 39 minutes ago
      You lose in versatility, then you can't add user-defined operators, which is pretty easy with a Pratt parser.