davidcavazos / parser / Parser.Expression

Parsers for expressions with operator precedence.

Basic usage

expression : List (List (Operator a)) -> Parser a

Parses an expression using the provided operators with operator precedence. There can be multiple operators sharing the same precedence like + and -, or * and /.

import Parser exposing (Parser, andThen, drop, parse, succeed, take)
import Parser.Char exposing (char)
import Parser.Common exposing (number)

factorial : Float -> Float
factorial n =
    List.product (List.range 1 (floor n) |> List.map toFloat)

calculate : Parser Float
calculate =
    expression
        [ [ prefix identity (char '+')
          , prefix (\x -> -x) (char '-')
          , suffix factorial (char '!')
          ]
        , [ fromRight (^) (char '^')
          ]
        , [ fromLeft (*) (char '*')
          , fromLeft (/) (char '/')
          ]
        , [ fromLeft (+) (char '+')
          , fromLeft (-) (char '-')
          ]
        , [ inbetween identity (char '(') (char ')')
          , term identity number
          ]
        ]

-- Terms and parentheses.
parse "1" calculate   --> Ok 1
parse "(1)" calculate --> Ok 1

-- Prefix operators.
parse "-1" calculate  --> Ok -1  --   -1
parse "+-1" calculate --> Ok -1  -- +(-1)
parse "-+1" calculate --> Ok -1  -- -(+1)
parse "--1" calculate --> Ok 1   -- -(-1)

-- Suffix operators.
parse "5!" calculate  --> Ok 120  -- 5!
parse "3!!" calculate --> Ok 720  -- (3!)!

-- Left-associative binary operators.
parse "1+2+3" calculate   --> Ok 6    -- (1 + 2) + 3
parse "1-2-3" calculate   --> Ok -4   -- (1 - 2) - 3
parse "1-(2-3)" calculate --> Ok 2    -- 1 - (2 - 3)
parse "2*3*4" calculate   --> Ok 24   -- (2 * 3) * 4
parse "2/4/5" calculate   --> Ok 0.1  -- (2 / 4) / 5
parse "2/(4/5)" calculate --> Ok 2.5  -- 2 / (4 / 5)

-- Right-associative binary operators.
parse "2^3^2" calculate   --> Ok 512  -- 2 ^ (3 ^ 2)
parse "(2^3)^2" calculate --> Ok 64   -- (2 ^ 3) ^ 2)

-- Operator precedence.
parse "1+-2" calculate  --> Ok -1  -- 1 + (-2)
parse "1+2*3" calculate --> Ok 7   -- 1 + (2 * 3)
parse "1*2+3" calculate --> Ok 5   -- (1 * 2) + 3
parse "1*2^3" calculate --> Ok 8   -- 1 * (2 ^ 3)
parse "1^2*3" calculate --> Ok 3   -- (1 ^ 2) * 3

Operators

term : (a -> b) -> Parser a -> Operator b

Defines an individual term. This is usually a number, or a variable name, or anything not containing an operator.

import Parser exposing (Parser, parse)
import Parser.Common exposing (number)

expr : Parser Float
expr =
    expression [ [ term identity number ] ]

parse "5" expr --> Ok 5

-- It does not trim spaces beforehand.
parse " 5" expr |> Result.toMaybe --> Nothing

prefix : (a -> a) -> Parser op -> Operator a

Defines a unary prefix operator.

import Parser exposing (Parser, parse)
import Parser.Char exposing (char)
import Parser.Common exposing (number)

expr : Parser Float
expr =
    expression
        [ [ prefix (\x -> -x) (char '-') ]
        , [ term identity number ]
        ]

parse "-5" expr --> Ok -5

-- It does not trim spaces beforehand.
parse " -5" expr |> Result.toMaybe --> Nothing

-- But it does afterwards.
parse "- 5" expr --> Ok -5

suffix : (a -> a) -> Parser op -> Operator a

Defines a unary suffix operator.

import Parser exposing (Parser, parse)
import Parser.Char exposing (char)
import Parser.Common exposing (number)

factorial : Float -> Float
factorial n =
    List.product (List.range 1 (floor n) |> List.map toFloat)

expr : Parser Float
expr =
    expression
        [ [ suffix factorial (char '!') ]
        , [ term identity number ]
        ]

parse "5!" expr --> Ok 120

-- It trims spaces beforehand.
parse "5 !" expr --> Ok 120

inbetween : (a -> a) -> Parser open -> Parser close -> Operator a

Defines an expression surrounded by an open and close operator.

import Parser exposing (Parser, parse)
import Parser.Char exposing (char)
import Parser.Common exposing (number)

expr : Parser Float
expr =
    expression
        [ [ inbetween identity (char '(') (char ')') ]
        , [ term identity number ]
        ]

parse "(5)" expr --> Ok 5
parse "(5" expr |> Result.toMaybe --> Nothing
parse "()" expr |> Result.toMaybe --> Nothing

-- It does not trim spaces beforehand.
parse " (5)" expr |> Result.toMaybe --> Nothing

-- But it does inbetween.
parse "( 5 )" expr --> Ok 5

fromLeft : (a -> a -> a) -> Parser op -> Operator a

Defines a binary left-associative operator.

import Parser exposing (Parser, parse)
import Parser.Char exposing (char)
import Parser.Common exposing (number)

expr : Parser Float
expr =
    expression
        [ [ fromLeft (+) (char '+')
          , fromLeft (-) (char '-')
          ]
        , [ term identity number ]
        ]

parse "1+2" expr   --> Ok 3
parse "1+2+3" expr --> Ok 6
parse "1-2-3" expr --> Ok -4

-- It does not trim spaces beforehand.
parse " 1+2" expr |> Result.toMaybe --> Nothing

-- But it does inbetween.
parse "1 + 2" expr --> Ok 3

fromRight : (a -> a -> a) -> Parser op -> Operator a

Defines a binary right-associative operator.

import Parser exposing (Parser, parse)
import Parser.Char exposing (char)
import Parser.Common exposing (number)

expr : Parser Float
expr =
    expression
        [ [ fromRight (^) (char '^') ]
        , [ term identity number ]
        ]

parse "2^3" expr   --> Ok 8
parse "2^3^2" expr --> Ok 512 -- 2 ^ (3 ^ 2)

-- It does not trim spaces beforehand.
parse " 2^3" expr |> Result.toMaybe --> Nothing

-- But it does inbetween.
parse "2 ^ 3" expr --> Ok 8

Custom operators


type Operator a
    = Prefix (Parser a -> Parser a)
    | InfixFromLeft (Parser a -> Parser (a -> a))
    | InfixFromRight (Parser a -> Parser (a -> a))

Describes an operator as a parser. They take a Parser a as an input, which is used to parse subexpressions recursively.

ℹ️ Both Prefix and Infix* operators must start by parsing something other than a subexpression, otherwise there will be a stack overflow.

import Parser exposing (Parser, drop, parse, succeed, take)
import Parser.Char exposing (char)
import Parser.Common exposing (number)

-- Prefix operators simply parse and apply the operation.
neg : Operator Float
neg =
    Prefix
        (\expr ->
            succeed (\x -> -x)
                |> drop (char '-')
                |> take expr
        )

-- Infix operators parse only the right side of the subexpression.
-- The Parser returns a function that takes the left side of the
-- subexpression as an input, and applies the operation.
-- This is for a left-associative infix operator.
add : Operator Float
add =
    InfixFromLeft
        (\expr ->
            succeed (\right left -> left + right)
                |> drop (char '+')
                |> take expr
        )

-- You can also define a right-associative infix operator.
pow : Operator Float
pow =
    InfixFromLeft
        (\expr ->
            succeed (\right left -> left ^ right)
                |> drop (char '^')
                |> take expr
        )

-- A term can be achieved by simply parsing a token but not recursing.
num : Operator Float
num =
    Prefix (\_ -> number)

calculate : Parser Float
calculate =
    expression
        [ [ neg ] -- -1
        , [ pow ] -- 1 ^ 2
        , [ add ] -- 1 + 2
        , [ num ] -- 1
        ]

parse "1+-2^3" calculate --> Ok -7 -- 1 + ((-2) ^ 3)