{"id":13776459,"url":"https://github.com/dmy/elm-pratt-parser","last_synced_at":"2025-07-10T11:09:29.493Z","repository":{"id":57674973,"uuid":"174662407","full_name":"dmy/elm-pratt-parser","owner":"dmy","description":"Pratt / Top-Down Operator Precedence parsing for elm/parser","archived":false,"fork":false,"pushed_at":"2020-04-22T10:57:27.000Z","size":47,"stargazers_count":23,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-05-18T20:48:12.405Z","etag":null,"topics":["down","elm","expression","operator","parser","parsing","pratt","precedence","tdop","top","top-down"],"latest_commit_sha":null,"homepage":"https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest","language":"Elm","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-09T07:26:01.000Z","updated_at":"2023-09-30T11:10:01.000Z","dependencies_parsed_at":"2022-09-02T14:25:25.919Z","dependency_job_id":null,"html_url":"https://github.com/dmy/elm-pratt-parser","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmy%2Felm-pratt-parser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmy%2Felm-pratt-parser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmy%2Felm-pratt-parser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmy%2Felm-pratt-parser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmy","download_url":"https://codeload.github.com/dmy/elm-pratt-parser/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248540731,"owners_count":21121406,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["down","elm","expression","operator","parser","parsing","pratt","precedence","tdop","top","top-down"],"created_at":"2024-08-03T18:00:26.515Z","updated_at":"2025-04-12T08:34:08.549Z","avatar_url":"https://github.com/dmy.png","language":"Elm","readme":"# Pratt Parser\n\n**Top-Down Operator Precedence Parsing**\n\nFace complex precedence and associativity rules without fear using\n[`elm/parser`](https://package.elm-lang.org/packages/elm/parser/1.1.0/).\n\n```sh\nelm install elm/parser\nelm install dmy/elm-pratt-parser\n```\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Getting Started](#getting-started)\n    - [Calculator Example](#calculator-example)\n    - [Step By Step](#step-by-step)\n- [About Pratt Parsers](#about-pratt-parsers)\n- [Terminology](#terminology)\n- [Design and Implementation Considerations](#design-and-implementation-considerations)\n- [References](#references)\n- [License](#license)\n\n# Overview\n\n\nWriting parsers using\n[`elm/parser`](https://package.elm-lang.org/packages/elm/parser/1.1.0/)\nis usually simple and fun, but handling complex operators precedence and\nassociativity rules in an expression parser\n[can be tricky](https://github.com/elm/parser/blob/1.1.0/examples/Math.elm#L144),\nor even hard and frustrating for more complex cases.\n\nThis library goal is to fix this by adding a single \n[`expression`](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#expression)\nparser to `elm/parser`:\n\n```\nexpression :\n    { oneOf : List (Config expr -\u003e Parser expr)\n    , andThenOneOf : List (Config expr -\u003e ( Int, expr -\u003e Parser expr ))\n    , spaces : Parser ()\n    }\n    -\u003e Parser expr\n```\n\nThis functions is configured with smaller standard parsers, precedence values\nand associativity rules, thanks to a minimalist flexible API, and handles the\nwhole expression parsing complexity using a simple but powerful algorithm\ninherited from the one described by **Vaughan Pratt** in his 1973 paper **\"Top\nDown Operator Precedence\"** [[1]](#references).\n \nHelpers are provided for\n[literals](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#literal),\n[constants](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#constant),\n[prefix](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#prefix),\n[infix](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#infixLeft)\nand [postfix](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#postfix)\nexpressions but custom ones can be defined when needed.\n\nThe library is small, has a test suite, benefits from tail-call elimination\nfor left-associative operations, never uses\n[`backtrackable`](https://package.elm-lang.org/packages/elm/parser/1.1.0/Parser#backtrackable)\nby itself and allows to produce helpful error messages using\n[`Parser.Advanced`](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt.Advanced)\nif wanted.\n\n\n# Getting Started\n\n## Calculator Example\n\nHere is a quite complete calculator.\n\nIt evaluates the result during parsing, without generating an explicit\nintermediate abstract syntax tree (AST), so it directly uses `Float` as the\n`expr` type.\n\n```elm\nimport Parser exposing ((|.), (|=), Parser)\nimport Pratt\n\n\nmathExpression : Parser Float\nmathExpression =\n    Pratt.expression\n        { oneOf =\n            [ Pratt.literal Parser.float\n            , Pratt.constant (Parser.keyword \"pi\") pi\n            , Pratt.prefix 3 (Parser.symbol \"-\") negate\n            , Pratt.prefix 5 (Parser.keyword \"cos\") cos\n            , parenthesizedExpression\n            ]\n        , andThenOneOf =\n            [ Pratt.infixLeft 1 (Parser.symbol \"+\") (+)\n            , Pratt.infixLeft 1 (Parser.symbol \"-\") (-)\n            , Pratt.infixLeft 2 (Parser.symbol \"*\") (*)\n            , Pratt.infixLeft 2 (Parser.symbol \"/\") (/)\n            , Pratt.infixRight 4 (Parser.symbol \"^\") (^)\n            , Pratt.postfix 6 (Parser.symbol \"°\") degrees\n            ]\n        , spaces = Parser.spaces\n        }\n\n\nparenthesizedExpression : Pratt.Config Float -\u003e Parser Float\nparenthesizedExpression config =\n    Parser.succeed identity\n        |. Parser.symbol \"(\"\n        |= Pratt.subExpression 0 config\n        |. Parser.symbol \")\"\n\n\nmath : Parser Float\nmath =\n    Parser.succeed identity\n        |= mathExpression\n        -- string must end after an expression:\n        |. Parser.end\n\n\nParser.run math \"-2^2^3\" --\u003e Ok -(2^(2^3))\nParser.run math \"-2^2^3\" --\u003e Ok -256\n\nParser.run math \"3--4\" --\u003e Ok (3-(-4))\nParser.run math \"3--4\" --\u003e Ok 7\n\nParser.run math \"cos (2*pi)\" --\u003e Ok 1\nParser.run math \"cos (2*180°)\" --\u003e Ok 1\nParser.run math \"1 - cos 360°\" --\u003e Ok 0\n```\n\n## Step by Step\n\nLet's describe step by step each part of the example.\n\n**1.**\nFirst we configure the parsers used at the start of an expression or after an\noperator. The expression parser cannot work without at least one of these\nparsers succeeding as it would not be able to parse an `expr` value.\nThese parsers include among others: *literals*, *constants*, *prefix* \nexpressions or sub-expressions parsers.\n\n\n    mathExpression : Parser Float\n    mathExpression =\n        Pratt.expression\n            { oneOf =\n                [ Pratt.literal Parser.float\n                , Pratt.constant (Parser.keyword \"pi\") pi\n                , Pratt.prefix 3 (Parser.symbol \"-\") negate\n                , Pratt.prefix 5 (Parser.keyword \"cos\") cos\n                , parenthesizedExpression\n                ]\n\n\nNote that\n[`literal`](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#literal),\n[`constant`](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#constant)\nand [`prefix`](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#prefix)\nhelpers all use a `Parser` argument, like `float`,\n`keyword pi` or `symbol \"-\"` here, so you have full control on parsing and\nproduced error messages.\n\nThe [`prefix`](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#prefix)\nhelper first needs an `Int` argument, the\n***precedence***. The higher it is, the higher the operator *precedence* is.\n\nAll parsers last parameter is a `Config expr`, passed automatically by the\n`expression` parser, that allows to call recursively\n[`subExpression`](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#subExpression)\nwith a custom *precedence*.  \nThis is used here in the parser for sub-expressions between parentheses and\nalso inside `prefix` and `infix` helpers.  \nThis is why the type of each `oneOf` parser is\n[`Config expr -\u003e Parser expr`](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#expression).\n\n\n    parenthesizedExpression : Pratt.Config Float -\u003e Parser Float\n    parenthesizedExpression config =\n        Parser.succeed identity\n            |. Parser.symbol \"(\"\n            |= Pratt.subExpression 0 config\n            |. Parser.symbol \")\"\n\n\nNote that `expression` is equivalent to `subExpression 0`, so the\n[`expression`](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#expression)\nparser starts parsing the expression with the lowest *precedence*.\n\n**2.**\nThen we configure the parsers that use the result of the previous parser.\nAs they use the previously parsed expression, they have an `expr` parameter.\nThey typically include *infix* and *postfix* expressions parsers:\n\n\n            , andThenOneOf =\n                [ Pratt.infixLeft 1 (Parser.symbol \"+\") (+)\n                , Pratt.infixLeft 1 (Parser.symbol \"-\") (-)\n                , Pratt.infixLeft 2 (Parser.symbol \"*\") (*)\n                , Pratt.infixLeft 2 (Parser.symbol \"/\") (/)\n                , Pratt.infixRight 4 (Parser.symbol \"^\") (^)\n                , postfix 6 (Parser.symbol \"°\") degrees\n                ]\n\n \nLike configuration `oneOf` parsers, they receive a `Config expr`, but return\ninstead a tuple  `(Int, expr -\u003e Parser expr)` because they need to provide their\n*precedence* to the algorithm, and a `expr -\u003e Parser expr` parser that will\nbe called with the preceding expression (the *left expression*).  \n\nSee [`subExpression`](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#subExpression)\ndocumentation to better understand the algorithm.\n\n**3.** We can then complete the configuration by adding a `spaces : Parser ()`\nparser that will be used between each previously configured parser.  \n[`Parser.spaces`](https://package.elm-lang.org/packages/elm/parser/1.1.0/Parser#spaces)\nis used here, but `succeed ()` could have been used if a general parser was not\nwanted.\n\n\n            , spaces = Parser.spaces\n            }\n\n\n**4.** At last we require the end of string after our expression by including our\nparser into a top-level `elm/parser` one:\n\n\n    math : Parser Float\n    math =\n        Parser.succeed identity\n            |= mathExpression\n            -- string must end after an expression:\n            |. Parser.end\n\n\n**That's it!  \nAnd we have used all the functions exposed by the `Pratt` module.**\n\n    \nA more complete example is included in the package source code, see\n`examples/Calc.elm`.\n\nFor a similar example but with an AST using a custom\ntype, see `examples/Math.elm` instead.\n\nOf course, you can define parsers for any expression, not only mathematical\nones. For example have a look at `examples/Boole.elm` for a start with Boole's\nAlgebra and an example of `if then else` expressions.\n\n# About Pratt Parsers\n\nThe parsers configured by this library use a variant of the algorithm described\nby **Vaughan Pratt** in his 1973 paper \"Top Down Operator Precedence\"\n[[1]](#references). Such parsers are usuall called *\"Pratt parsers\"*,\n*\"Top-Down Operator Precedence parsers\"*, or *\"TDOP\"* parsers.\n\nThis algorithm is used in this library because my experience comparing\nalternatives confirmed for the most part what Vaughan Pratt claimed:\n\u003e *\"The approach described [...] is very simple to understand, trivial to\n\u003e implement, easy to use, extremely efficient in practice if not in theory,\n\u003e yet flexible enough to meet most reasonable syntactic needs of users [...].\n\u003e Moreover, it deals nicely with error detection.\"*\n\nNote that the later *\"precedence climbing\"* algorithm is now often considered\nas a special case of the earlier Pratt parsing algorithm.\n\nSee [[2]](#references) to read more about them.\n\nAt last, Douglas Crockford, who implemented a Javascript parser using a Pratt\nparser for JSLint [[4]](#references), said:\n\u003e \"Another explanation is that his technique is most effective when used in a\n\u003e dynamic, functional programming language.\"\n\nI believe that the `dynamic` part has already been proven wrong\n[[5]](#references)[[6]](#references)[[7]](#references), and I hope that this\nimplementation will contribute confirming it.\n\n\n# Terminology\n\nThe terminology used in Vaughan R. Pratt's paper is not really intuitive\nnor mainstream so it has been changed in this library:\n* Configuration `oneOf` parsers are called in the paper `nud`,\nfor \"**Nu**ll **D**enotation\". Here is how they are defined in the original\npaper:\n\u003e *\"We will call the code denoted by a token without a preceding expression its\nnull denotation or nud\"*\n* Configuration `andThenOneOf` parsers are called `led`, for \"**Le**ft\n**D**enotation\". Here is how they are defined in the original paper:\n\u003e *\"We will call the code denoted by a token with a preceding expression its\nleft denotation or led\"*\n* The *precedence* is called *binding power* in the paper. It is used for\noperators precedence, and also allows to achieve right-associative operations.\n\nSee [[3]](#references) and the algorithm described in\n[`subExpression`](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt#subExpression)\ndocumentation for more details.\n\n# Design and Implementation Considerations\n\nThe main differences with Pratt's design and implementation, that reflects on\nthis library API, are:\n\n1. *Tokens* are reduced to their minimal form:\n    - just a parser for `nud` tokens\n    - a *precedence* (alias *binding power*) and a parser\n    (with an expression parameter) for `led` tokens\n\n   There is therefore no actual *token* anymore and it is not necessary to\n   tokenize the expression before parsing it.\n\n2. `nuds` and `leds` are not stored in a `Dict` using the operator as the key,\ninstead they are just two lists of parsers used with\n[`Parser.oneOf`](https://package.elm-lang.org/packages/elm/parser/1.1.0/Parser#oneOf).\n\n# References\n\n1. Vaughan R. Pratt, [\"Top Down Operator Precedence\"](https://tdop.github.io/), 1973.\n2. Andy Chu, [Pratt Parsing Index and Updates](https://www.oilshell.org/blog/2017/03/31.html), 2017 (updated regularly)\n3. Eli Bendersky, [Top-Down operator precedence parsing](https://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing), 2010\n4. Douglas Crockford, [Top Down Operator Precedence Douglas Crockford](http://crockford.com/javascript/tdop/tdop.html), 2007\n5. Andy Chu, [Pratt Parsers Can Be Statically Typed](https://www.oilshell.org/blog/2016/11/05.html), 2016\n6. Matthew Manela, [A Monadic Pratt Parser](https://matthewmanela.com/blog/a-monadic-pratt-parser/), 2011\n7. Julian Hall, [Pratt Parsing in Parsec. Perfect.](http://kindlang.blogspot.com/2016/08/pratt-parsing-in-parsec-perfect.html), [parsec-pratt](https://hackage.haskell.org/package/parsec-pratt), 2016\n\n# License\n\nBSD-3-Clause\n\n","funding_links":[],"categories":["Packages"],"sub_categories":["Parsing"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmy%2Felm-pratt-parser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmy%2Felm-pratt-parser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmy%2Felm-pratt-parser/lists"}