Parsers for expressions with operator precedence.
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
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
Describes an operator as a parser.
They take a Parser a
as an input, which is used to parse subexpressions
recursively.
ℹ️ Both
Prefix
andInfix*
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)