dmy/elm-pratt-parser/1.0.0

NOTE: the latest version is 2.0.0

Pratt

expression : Config expr -> Parser expr

Build a parser based on the given configuration that can be combined with other elm/parser parsers.

The top level expression is parsed with a binding power of 0, see subExpression.

For example a parser that, given a Config, requires the end of the string after having parsed successfully an expression, can be written like this:

import Parser exposing (..)
import Pratt exposing (..)


parser : Config expr -> Parser expr
parser config =
    succeed identity
        |= expression config
        |. end

Let's test it with a simple integer NUD parser:

nuds : List (Config Int -> Parser Int)
nuds =
    [ always int ]


conf : Config Int
conf =
    configure
        { nuds = nuds
        , leds = []
        , spaces = succeed ()
        }


run (parser conf) "1" --> Ok 1

Our spaces parser is set to succeed () and therefore does nothing, so trailing spaces are forbidden:

run (parser conf) "1 "
--> Err [{ row = 1, col = 2, problem = ExpectingEnd }]

Without Parser.end, the parser will succeed, consuming only the first expression:

run (expression conf) "1 2" --> Ok 1

Configuration

type alias Config expr =
Config Never Problem expr

An opaque type based on Pratt.Advanced.Config representing the parser configuration. A Config is created using configure.

{ nuds : List (Config expr -> Parser expr)
, leds : List (Config expr -> ( Int, expr -> Parser expr ))
, spaces : Parser ()
}
-> Config expr

Build a Config from NUD, LED and spaces parsers.

nuds:

A list of "Null Denotation" parsers that do not take an expression on the left. They will be tried successively by the parser using Parser.oneOf and the parser Config.

Examples: parsers for constants, literals, prefix expressions, function calls or a sub-expression between parentheses.

leds:

A list of "Left Denotation" parsers that do take an expression on the left and have an Int binding power. The ones that have a binding power above the current one (0 by default) will be tried by the parser using Parser.oneOf, the parser Config and the current left expression (the expression returned by the previous parser).

Examples: parsers for prefix and postfix expressions.

spaces:

A parser called before and after each nud and led parser, usually used to consume whitespaces.

If a more specific behavior is needed, this parser can be ignored by using succeed () and a custom behavior added inside nuds and leds parsers.

Notes:

  • The NUD and LED parsers are parameterized by a Config to be able to call expression and subExpression, which are the main building blocks for NUD and LED helpers. This Config will be automatically passed by the parser.
  • The parser will not use backtrackable, so it is up to you to correctly setup your parsers to avoid having failed parsers consuming characters.

For example, a basic calculator could be configured like this:

import Parser exposing (..)
import Pratt exposing (..)


nuds : List (Config Float -> Parser Float)
nuds =
    [ always float
    , prefix 3 (symbol "-") negate
    , \config ->
        succeed identity
            |. symbol "("
            |= expression config
            |. symbol ")"
    ]


leds : List (Config Float -> ( Int, Float -> Parser Float ))
leds =
    [ infixLeft 1 (symbol "+") (+)
    , infixLeft 1 (symbol "-") (-)
    , infixLeft 2 (symbol "*") (*)
    , infixLeft 2 (symbol "/") (/)
    , infixRight 4 (symbol "^") (^)
    ]


conf : Config Float
conf =
    configure
        { nuds = nuds
        , leds = leds
        , spaces = spaces
        }


parser : Parser Float
parser =
    succeed identity
        |= expression  conf
        |. end




run parser "-1*3--5+4/2^2" --> Ok ((-1*3)-(-5)+(4/(2^2)))
run parser "-1*3--5+4/2^2" --> Ok 3
run parser "((-1*3)-(-5)+(4/(2^2)))" --> Ok 3

Configuration Helpers

subExpression : Int -> Config expr -> Parser expr

Build an expression parser based on the given binding power and configuration.

This is the core function of the parser. The expression function is actually implemented using subExpression with a binding power of 0, like this:

expression : Config expr -> Parser expr
expression config =
    subExpression 0 config

subExpression and expression are also used to make LED and NUD helpers.

For example prefix can be implemented like this:

prefix : Int -> Parser () -> (e -> e) -> Config expr -> Parser expr
prefix bindingPower operator apply config =
    succeed apply
        |. operator
        |= subExpression bindingPower config

Or a parser for sub-expressions between parentheses like this:

parens : Config Expr -> Parser Expr
parens config =
    succeed identity
        |. symbol "("
        |= expression config
        |. symbol ")"

Or using prefix:

parens : Config Expr -> Parser Expr
parens config =
    prefix 0 (symbol "(") identity config
        |. symbol ")"

Algorithm:

subExpression uses the following algorithm:

  1. Use the Int binding power argument as the current binding power.

  2. Run the spaces parser.

  3. Try nuds parsers successively in order using Parser.oneOf until one is chosen (i.e. until one of them consumes characters). This parser may call expression or subExpression recursively with different binding power values. If no parser succeeds, the whole parser fails, else the expression returned by the nud parser is used as the left expression in the next steps.

  4. Run the spaces parser.

  5. Select leds parsers that have a binding power above the current one, try them successively using Parser.oneOf with the left expression argument until one is chosen. This parser may also call expression or subExpressionrecursively with different binding power values.

  6. If no led parser succeeds, return the left expression. Else, loop from 4. using the expression just parsed by the led parser as the new left expression.

NUD Helpers

constant : Parser () -> expr -> Config expr -> Parser expr

Build a NUD parser for a constant.

The Config argument is passed automatically by the parser.

import Parser exposing (..)
import Pratt exposing (..)


nuds : List (Config Float -> Parser Float)
nuds =
    [ constant (keyword "pi") pi ]


conf : Config Float
conf =
    configure
        { nuds = nuds, leds = [], spaces = spaces }


run (expression conf) "pi" --> Ok pi
prefix : Int -> Parser () -> (expr -> expr) -> Config expr -> Parser expr

Build a NUD parser for a prefix expression with a given binding power.

The Config argument is passed automatically by the parser.

import Parser exposing (..)
import Pratt exposing (..)


nuds : List (Config Int -> Parser Int)
nuds =
    [ always int
    , prefix 3 (symbol "-") negate
    , prefix 3 (symbol "+") identity
    ]


conf : Config Int
conf =
    configure
        { nuds = nuds, leds = [], spaces = spaces }


run (expression conf) "-1" --> Ok -1
run (expression conf) "--1" --> Ok 1
run (expression conf) "+1" --> Ok 1
run (expression conf) "+-+-1" --> Ok 1

LED Helpers

Int
-> Parser ()
-> (expr -> expr -> expr)
-> Config expr
-> ( Int, expr -> Parser expr )

Build a LED parser for an infix expression with a left-associative operator and a given binding power.

The Config argument is passed automatically by the parser.

import Parser exposing (..)
import Pratt exposing (..)


nuds : List (Config Float -> Parser Float)
nuds =
    [ always float ]


leds : List (Config Float -> (Int, Float -> Parser Float))
leds =
    [ infixLeft 1 (symbol "+") (+)
    , infixLeft 1 (symbol "-") (-)
    , infixLeft 2 (symbol "*") (*)
    , infixLeft 2 (symbol "/") (/)
    ]


conf : Config Float
conf =
    configure
        { nuds = nuds, leds = leds, spaces = spaces }


run (expression conf) "5+4-3*2/1" --> Ok (5+4-(3*2/1))
run (expression conf) "5+4-3*2/1" --> Ok 3
Int
-> Parser ()
-> (expr -> expr -> expr)
-> Config expr
-> ( Int, expr -> Parser expr )

Build a LED parser for an infix expression with a right-associative operator and a given binding power.

The Config argument is passed automatically by the parser.

import Parser exposing (..)
import Pratt exposing (..)


nuds : List (Config Float -> Parser Float)
nuds =
    [ always float ]


leds : List (Config Float -> (Int, Float -> Parser Float))
leds =
    [ infixRight 4 (symbol "^") (^) ]


conf : Config Float
conf =
    configure
        { nuds = nuds, leds = leds, spaces = spaces }


run (expression conf) "2^2^3" --> Ok (2^(2^3))
run (expression conf) "2^2^3" --> Ok 256

Note: As usual in Pratt parsers, right-associativity is achieved by parsing the right expression with the binding power of the infix operator minus 1.

Int
-> Parser ()
-> (expr -> expr)
-> Config expr
-> ( Int, expr -> Parser expr )

Build a LED parser for a postfix expression with a given binding power.

The Config argument is passed automatically by the parser.

import Parser exposing (..)
import Pratt exposing (..)


nuds : List (Config Float -> Parser Float)
nuds =
    [ always float ]




leds : List (Config Float -> (Int, Float -> Parser Float))
leds =
    [ postfix 6 (symbol "°") degrees ]


conf : Config Float
conf =
    configure
        { nuds = nuds, leds = leds, spaces = spaces }


run (expression conf) "180°" --> Ok pi
run (expression conf) "360°" --> Ok (2*pi)