Expression.V2 (expression v2.33.1)

A second attempt at the parser, hopefully a little easier to read & maintain.

parse/1 parsed an Expression into AST. eval/3 evaluates the given AST using the context supplied.

For details on how this is done please read Expression.V2.Parser and Expression.V2.Compile.

This parser & evaluator supports the following:

  • strings either double or single quoted.
  • integers such as 1, 2, 40, 55
  • floats such as 3.141592653589793
  • booleans which can be written in any mixed case such as tRue or TRUE, False etc
  • Range.t such as 1..10, also with steps 1..10//2
  • Date.t such as 2022-01-01 which is parsed into ~D[2022-01-01]
  • Time.t such as 10:30 which is parsed into ~T[10:30:00]
  • ISO formatted DateTime.t such as 2022-05-24T00:00:00 which is parsed into ~U[2022-05-24 00:00:00.0Z]
  • US formatted DateTime.t such as 01-02-2020 23:23:23 which is parsed into ~U[2020-02-01T23:23:23Z]
  • Lists of any of the above, such as [1, 2, 3] or [1, 1.234, "john"]
  • Reading properties off of nested objects such as maps with a full stop, such as contact.name returning "Doe" from %{"contact" => %{"name" => "Doe"}}
  • Reading attributes off of maps, such as contact[the_key] which returns "Doe" from %{"contact" => %{"name" => "Doe"}, "the_key" => "name"}
  • Anonymous functions with & and &1 as capture operators, &(&1 + 1) is an anonymous function that increments the input by 1.

The result of a call to eval/3 is a list of typed evaluated items. It is up to the integrating library to determine how best to convert these into a final end user representation.

Examples

iex> alias Expression.V2
iex> V2.eval("the date is @date(2022, 2, 20)")
["the date is ", ~D[2022-02-20]]
iex> V2.eval("the answer is @true")
["the answer is ", true]
iex> V2.eval("22 divided by 7 is @(22 / 7)")
["22 divided by 7 is ", 3.142857142857143]
iex> V2.eval(
...>   "Hello @proper(contact.name)! Looking forward to meet you @date(2023, 2, 20)",
...>   V2.Context.new(%{"contact" => %{"name" => "mary"}})
...> )
["Hello ", "Mary", "! Looking forward to meet you ", ~D[2023-02-20]]
iex> V2.eval("@map(1..3, &date(2023, 1, &1))")
[[~D[2023-01-01], ~D[2023-01-02], ~D[2023-01-03]]]
iex> V2.eval(
...>   "Here is the multiplication table of @number: @(map(1..10, &(&1 * number)))",
...>   V2.Context.new(%{"number" => 5})
...> )
[
  "Here is the multiplication table of ",
  5,
  ": ",
  [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
]

Summary

Functions

Return the code generated for the Abstract Syntax tree or Expression string provided.

Return the default value for a potentially complex value.

Evaluate a string with expressions against a given context

Evaluate an expression and cast all items to strings before joining the full result into a single string value to be returned.

Evaluate a parsed Expression against a given context

Evaluate a string with an expression block against a context

Evaluate the given AST against a given context

Parse a string with expressions into AST for the compile step

Parse a string with an expression block into AST for the compile step

This function is referenced by Expression.V2.Compile to make access to values in Maps or Lists easier

Functions

Link to this function

compile(expression)

@spec compile(expression :: String.t()) :: [term()]
Link to this function

compile_block(final)

Link to this function

debug(expression)

@spec debug(String.t() | [term()]) :: String.t()

Return the code generated for the Abstract Syntax tree or Expression string provided.

Link to this function

default_value(val, context \\ nil)

Return the default value for a potentially complex value.

Complex values can be Maps that have a __value__ key, if that's returned then we can to use the __value__ value when eval'ing against operators or functions.

Link to this function

escape(expression)

@spec escape(String.t()) :: String.t()
Link to this function

eval(expression, context \\ Context.new())

@spec eval(expression :: String.t(), context :: Expression.V2.Context.t()) :: [term()]

Evaluate a string with expressions against a given context

Link to this function

eval_as_string(expression, context \\ Context.new())

@spec eval_as_string(String.t(), Expression.V2.Context.t()) :: String.t()

Evaluate an expression and cast all items to strings before joining the full result into a single string value to be returned.

This calls eval/2 internally, maps the results with default_value/2 followed by stringify/1 and then joins them.

Link to this function

eval_ast(parsed_parts, context \\ Context.new())

@spec eval_ast([term()], context :: Expression.V2.Context.t()) :: [term()]

Evaluate a parsed Expression against a given context

Link to this function

eval_block(expression_block, context \\ Context.new())

@spec eval_block(String.t(), context :: Expression.V2.Context.t()) ::
  term() | {:error, reason :: String.t(), bad_parts :: String.t()}

Evaluate a string with an expression block against a context

Link to this function

eval_block_ast(ast, context)

@spec eval_block_ast([term()], context :: Expression.V2.Context.t()) :: [term()]

Evaluate the given AST against a given context

Link to this function

parse(expression)

@spec parse(String.t()) ::
  {:ok, [term()]} | {:error, reason :: String.t(), bad_parts :: String.t()}

Parse a string with expressions into AST for the compile step

Link to this function

parse_block(expression_block)

@spec parse_block(String.t()) ::
  {:ok, [term()]} | {:error, reason :: String.t(), bad_parts :: String.t()}

Parse a string with an expression block into AST for the compile step

Link to this function

read_attribute(map, item)

@spec read_attribute(map() | list(), binary() | integer()) :: term()

This function is referenced by Expression.V2.Compile to make access to values in Maps or Lists easier

Link to this function

stringify(items)

@spec stringify(term()) :: String.t()