Expression.V2 (expression v2.41.4)

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

compile(expression)

@spec compile(expression :: String.t()) :: [term()]

compile_block(final)

debug(expression)

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

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

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.

escape(expression)

@spec escape(String.t()) :: String.t()

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

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.

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

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

eval_block_ast(ast, context)

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

Evaluate the given AST against a given context

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

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

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

stringify(items)

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