expressionsubExpressionBuild a parser based on the given configuration that can
be combined with other
elm/parser
parsers.
The top level expression is parsed with a precedence of 0,
see subExpression.
oneOf:
A list of parsers used at the start of an expression (or sub-expression), or after an operator. They will be tried successively by the parser using
Parser.oneOfwith the current parserConfigpassed as their argument.
Examples: parsers for literals, constants, prefix expressions or a sub-expression between parentheses.
At least one of them is required and one must succeed, else the whole expression parser would not be able to parse an
expr.
andThenOneOf:
A list of parsers using the result of a previous parser. They need to provide their
Intprecedence as only the ones that have a precedence above the current one (0by default) will be tried by the parser usingParser.oneOf, with the parserConfigand the current left expression (the expression returned by the previous parser) arguments.
Examples: parsers for prefix and postfix expressions.
spaces:
A parser called before and after each previously configured parser, typically used to consume whitespaces.
If a more specific behavior is needed, this parser can be ignored by using
succeed ()and a custom behavior added insideoneOfandandThenOneOfparsers.
Notes:
spaces one are parameterized by a
Config to be able to call subExpression,
which is the main building block for configuration helpers. This Config
will be automatically passed by the parser.Parser.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 ((|.), (|=), Parser, end, float, keyword, run, succeed, symbol)
import Pratt exposing (infixLeft, infixRight, literal, prefix)
mathExpression : Parser Float
mathExpression =
Pratt.expression
{ oneOf =
[ literal float
, prefix 3 (symbol "-") negate
, parenthesizedExpression
]
, andThenOneOf =
[ infixLeft 1 (symbol "+") (+)
, infixLeft 1 (symbol "-") (-)
, infixLeft 2 (symbol "*") (*)
, infixLeft 2 (symbol "/") (/)
, infixRight 4 (symbol "^") (^)
]
, spaces = Parser.spaces
}
parenthesizedExpression : Config Float -> Parser Float
parenthesizedExpression config =
succeed identity
|. symbol "("
|= subExpression 0 config
|. symbol ")"
parser : Parser Float
parser =
succeed identity
|= mathExpression
|. 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 3Build an expression parser based on the precedence and configuration arguments.
This is the core function of the parser.
The expression function is actually implemented using
subExpression with a precedence of 0.
subExpression is used to make configuration helpers.
For example prefix can be implemented like this:
prefix : Int -> Parser () -> (expr -> expr) -> Config expr -> Parser expr
prefix precedence operator apply config =
succeed apply
|. operator
|= subExpression precedence configA parser for sub-expressions between parentheses like this:
parenthesizedExpression : Config Expr -> Parser Expr
parenthesizedExpression config =
succeed identity
|. symbol "("
|= subExpression 0 config
|. symbol ")"Algorithm:
subExpression uses the following algorithm:
Int precedence argument as the current precedence.spaces parser.oneOf parsers successively in order using
Parser.oneOf
until one is chosen (i.e. until one of them starts chomping characters).
This parser may call subExpression recursively with different
precedence values.
If no parser succeeds, the whole parser fails, else the expression parsed is
used as the left expression in the next steps.spaces parser.andThenOneOf parsers, keeping only those that have a
precedence above the current one, then try them successively using
Parser.oneOf with the left expression argument until one is chosen.
This parser may also call
subExpression recursively with different precedence values.Build a parser for a literal.
The Config argument is passed automatically by the parser.
import Parser exposing (Parser, number, run)
import Pratt exposing (literal)
type Expr
= Int Int
| Float Float
digits : Parser Expr
digits =
number
{ int = Just Int
, hex = Just Int
, octal = Nothing
, binary = Nothing
, float = Just Float
}
expression : Parser Expr
expression =
Pratt.expression
{ oneOf = [ literal digits ]
, andThenOneOf = []
, spaces = Parser.spaces
}
run expression "1234" --> Ok (Int 1234)
run expression "0x1b" --> Ok (Int 27)
run expression "3.14159" --> Ok (Float 3.14159)Note: if you want to be able to handle expressions like 3---4 == 3-(-(-4)),
you could have a negation prefix parser like prefix 3 (-) Neg declared
before the literal digits and let digits only handle positive numbers.
Build a parser for a constant.
The Config argument is passed automatically by the parser.
import Parser exposing (Parser, keyword, run)
import Pratt exposing (constant)
expression : Parser Float
expression =
Pratt.expression
{ oneOf = [ constant (keyword "pi") pi ]
, andThenOneOf = []
, spaces = Parser.spaces
}
run expression "pi" --> Ok piBuild a parser for a prefix expression with a given precedence.
The Config argument is passed automatically by the parser.
import Parser exposing (Parser, int, run, symbol)
import Pratt exposing (literal, prefix)
expression : Parser Int
expression =
Pratt.expression
{ oneOf =
[ literal int
, prefix 3 (symbol "-") negate
, prefix 3 (symbol "+") identity
]
, andThenOneOf = []
, spaces = Parser.spaces
}
run expression "-1" --> Ok -1
run expression "--1" --> Ok 1
run expression "+1" --> Ok 1
run expression "+-+-1" --> Ok 1prefix can also be used to build more complex helpers, for example:
type Expr
= IfThenElse Expr Expr Expr
ifThenElse : Config Expr -> Parser Expr
ifThenElse config =
succeed IfThenElse
|= prefix 0 (keyword "if") identity config
|= prefix 0 (keyword "then") identity config
|= prefix 0 (keyword "else") identity configBuild a parser for an infix expression with a left-associative operator and a given precedence.
The Config argument is passed automatically by the parser.
import Parser exposing (Parser, float, run, symbol)
import Pratt exposing (literal, prefix)
expression : Parser Float
expression =
Pratt.expression
{ oneOf = [ literal float ]
, andThenOneOf =
[ infixLeft 1 (symbol "+") (+)
, infixLeft 1 (symbol "-") (-)
, infixLeft 2 (symbol "*") (*)
, infixLeft 2 (symbol "/") (/)
]
, spaces = Parser.spaces
}
run expression "5+4-3*2/1" --> Ok (5+4-(3*2/1))
run expression "5+4-3*2/1" --> Ok 3Build a parser for an infix expression with a right-associative operator and a given precedence.
The Config argument is passed automatically by the parser.
import Parser exposing (Parser, float, run, symbol)
import Pratt exposing (literal, infixRight)
expression : Parser Float
expression =
Pratt.expression
{ oneOf = [ literal float ]
, andThenOneOf = [ infixRight 4 (symbol "^") (^) ]
, spaces = Parser.spaces
}
run expression "2^2^3" --> Ok (2^(2^3))
run expression "2^2^3" --> Ok 256Note: As usual in Pratt parsers, right-associativity is achieved by parsing the right expression with the precedence of the infix operator minus 1.
Build a parser for a postfix expression with a given precedence.
The Config argument is passed automatically by the parser.
import Parser exposing (Parser, float, run, symbol)
import Pratt exposing (literal, postfix)
expression : Parser Float
expression =
Pratt.expression
{ oneOf = [ literal float ]
, andThenOneOf = [ postfix 6 (symbol "°") degrees ]
, spaces = Parser.spaces
}
run expression "180°" --> Ok pi
run expression "360°" --> Ok (2*pi)
An opaque type based on
Pratt.Advanced.Configholding the parser configuration.