Predicator.Parser (predicator v3.4.0)
View SourceRecursive descent parser for predicator expressions.
The parser converts a stream of tokens from the lexer into an Abstract Syntax Tree (AST) with comprehensive error reporting including exact position information.
Grammar
The parser implements this grammar with proper operator precedence:
expression → logical_or
logical_or → logical_and ( "OR" | "||" logical_and )*
logical_and → logical_not ( "AND" | "&&" logical_not )*
logical_not → "NOT" | "!" logical_not | comparison
comparison → addition ( ( ">" | "<" | ">=" | "<=" | "=" | "==" | "!=" | "===" | "!==" | "in" | "contains" ) addition )?
addition → multiplication ( ( "+" | "-" ) multiplication )*
multiplication → unary ( ( "*" | "/" | "%" ) unary )*
unary → ( "-" | "!" ) unary | postfix
postfix → primary ( "[" expression "]" | "." IDENTIFIER )*
primary → NUMBER | FLOAT | STRING | BOOLEAN | DATE | DATETIME | IDENTIFIER | duration | relative_date | function_call | list | object | "(" expression ")"
function_call → FUNCTION_NAME "(" ( expression ( "," expression )* )? ")"
list → "[" ( expression ( "," expression )* )? "]"
object → "{" ( object_entry ( "," object_entry )* )? "}"
object_entry → object_key ":" expression
object_key → IDENTIFIER | STRING
duration → NUMBER UNIT+
relative_date → duration "ago" | duration "from" "now" | "next" duration | "last" duration
Examples
iex> {:ok, tokens} = Predicator.Lexer.tokenize("score > 85")
iex> Predicator.Parser.parse(tokens)
{:ok, {:comparison, :gt, {:identifier, "score"}, {:literal, 85}}}
iex> {:ok, tokens} = Predicator.Lexer.tokenize("(age >= 18)")
iex> Predicator.Parser.parse(tokens)
{:ok, {:comparison, :gte, {:identifier, "age"}, {:literal, 18}}}
iex> {:ok, tokens} = Predicator.Lexer.tokenize("score > 85 AND age >= 18")
iex> Predicator.Parser.parse(tokens)
{:ok, {:logical_and, {:comparison, :gt, {:identifier, "score"}, {:literal, 85}}, {:comparison, :gte, {:identifier, "age"}, {:literal, 18}}}}
Summary
Types
Arithmetic operators in the AST.
Abstract Syntax Tree node types.
Comparison operators in the AST.
Membership operators in the AST.
An object entry (key-value pair) in an object literal.
A key in an object literal - either an identifier or string literal.
Internal parser state for tracking position and tokens.
Relative date directions in the AST.
Parser result - either success with AST or error with details.
Unary operators in the AST.
A value that can appear in literals.
Functions
Parses a list of tokens into an Abstract Syntax Tree.
Types
@type arithmetic_op() :: :add | :subtract | :multiply | :divide | :modulo
Arithmetic operators in the AST.
@type ast() :: {:literal, value()} | {:string_literal, binary(), :double | :single} | {:identifier, binary()} | {:comparison, comparison_op(), ast(), ast()} | {:arithmetic, arithmetic_op(), ast(), ast()} | {:unary, unary_op(), ast()} | {:membership, membership_op(), ast(), ast()} | {:logical_and, ast(), ast()} | {:logical_or, ast(), ast()} | {:logical_not, ast()} | {:list, [ast()]} | {:object, [object_entry()]} | {:function_call, binary(), [ast()]} | {:bracket_access, ast(), ast()} | {:duration, [{integer(), binary()}]} | {:relative_date, ast(), relative_direction()}
Abstract Syntax Tree node types.
{:literal, value}
- A literal value (number, boolean, list, date, datetime, duration){:string_literal, value, quote_type}
- A string literal with quote type information{:identifier, name}
- A variable reference{:comparison, operator, left, right}
- A comparison expression (including equality){:arithmetic, operator, left, right}
- An arithmetic expression (+, -, *, /, %){:unary, operator, operand}
- A unary expression (-, !){:logical_and, left, right}
- A logical AND expression{:logical_or, left, right}
- A logical OR expression{:logical_not, operand}
- A logical NOT expression{:list, elements}
- A list literal{:object, entries}
- An object literal with key-value pairs{:membership, operator, left, right}
- A membership operation (in/contains){:function_call, name, arguments}
- A function call with arguments{:bracket_access, object, key}
- A bracket access expression (obj[key]){:duration, units}
- A duration literal (e.g., 3d8h){:relative_date, duration, direction}
- A relative date expression (e.g., 3d ago, next 2w)
@type comparison_op() ::
:gt | :lt | :gte | :lte | :eq | :equal_equal | :ne | :strict_eq | :strict_ne
Comparison operators in the AST.
@type membership_op() :: :in | :contains
Membership operators in the AST.
@type object_entry() :: {object_key(), ast()}
An object entry (key-value pair) in an object literal.
The key can be either an identifier or a string literal.
A key in an object literal - either an identifier or string literal.
@type parser_state() :: %{ tokens: [Predicator.Lexer.token()], position: non_neg_integer() }
Internal parser state for tracking position and tokens.
@type relative_direction() :: :ago | :future | :next | :last
Relative date directions in the AST.
@type result() :: {:ok, ast()} | {:error, binary(), pos_integer(), pos_integer()}
Parser result - either success with AST or error with details.
@type unary_op() :: :minus | :bang
Unary operators in the AST.
@type value() :: boolean() | integer() | binary() | [value()] | Date.t() | DateTime.t() | Predicator.Types.duration()
A value that can appear in literals.
Functions
@spec parse([Predicator.Lexer.token()]) :: result()
Parses a list of tokens into an Abstract Syntax Tree.
Parameters
tokens
- List of tokens from the lexer
Returns
{:ok, ast}
- Successfully parsed expression{:error, message, line, column}
- Parse error with position
Examples
iex> {:ok, tokens} = Predicator.Lexer.tokenize("score > 85")
iex> Predicator.Parser.parse(tokens)
{:ok, {:comparison, :gt, {:identifier, "score"}, {:literal, 85}}}
iex> {:ok, tokens} = Predicator.Lexer.tokenize("name = \"John\"")
iex> Predicator.Parser.parse(tokens)
{:ok, {:comparison, :eq, {:identifier, "name"}, {:string_literal, "John", :double}}}
iex> {:ok, tokens} = Predicator.Lexer.tokenize("active = true")
iex> Predicator.Parser.parse(tokens)
{:ok, {:comparison, :eq, {:identifier, "active"}, {:literal, true}}}