defmodule Solid.Tag.If do
@moduledoc """
If and Unless tags
"""
import NimbleParsec
alias Solid.Parser.{Argument, BaseTag, Literal}
alias Solid.Expression
@behaviour Solid.Tag
space = Literal.whitespace(min: 0)
operator =
choice([
string("=="),
string("!="),
string(">="),
string("<="),
string(">"),
string("<"),
string("contains")
])
|> map({:erlang, :binary_to_atom, [:utf8]})
argument_filter =
tag(Argument.argument(), :argument)
|> tag(
repeat(
lookahead_not(choice([operator, string("and"), string("or")]))
|> concat(Argument.filter())
),
:filters
)
defcombinator(:__argument_filter__, argument_filter)
boolean_operation =
tag(parsec(:__argument_filter__), :arg1)
|> ignore(space)
|> tag(operator, :op)
|> ignore(space)
|> tag(parsec(:__argument_filter__), :arg2)
|> wrap()
expression =
ignore(space)
|> choice([boolean_operation, wrap(parsec(:__argument_filter__))])
|> ignore(space)
bool_and =
string("and")
|> replace(:bool_and)
bool_or =
string("or")
|> replace(:bool_or)
boolean_expression =
expression
|> repeat(choice([bool_and, bool_or]) |> concat(expression))
defcombinator(:__boolean_expression__, boolean_expression)
@impl true
def spec(parser) do
space = Literal.whitespace(min: 0)
if_tag =
ignore(BaseTag.opening_tag())
|> ignore(string("if"))
|> tag(parsec({__MODULE__, :__boolean_expression__}), :expression)
|> ignore(BaseTag.closing_tag())
|> tag(parsec({parser, :liquid_entry}), :result)
elsif_tag =
ignore(BaseTag.opening_tag())
|> ignore(string("elsif"))
|> tag(parsec({__MODULE__, :__boolean_expression__}), :expression)
|> ignore(BaseTag.closing_tag())
|> tag(parsec({parser, :liquid_entry}), :result)
|> tag(:elsif_exp)
unless_tag =
ignore(BaseTag.opening_tag())
|> ignore(string("unless"))
|> tag(parsec({__MODULE__, :__boolean_expression__}), :expression)
|> ignore(space)
|> ignore(BaseTag.closing_tag())
|> tag(parsec({parser, :liquid_entry}), :result)
cond_if_tag =
tag(if_tag, :if_exp)
|> tag(times(elsif_tag, min: 0), :elsif_exps)
|> optional(tag(BaseTag.else_tag(parser), :else_exp))
|> ignore(BaseTag.opening_tag())
|> ignore(string("endif"))
|> ignore(BaseTag.closing_tag())
cond_unless_tag =
tag(unless_tag, :unless_exp)
|> tag(times(elsif_tag, min: 0), :elsif_exps)
|> optional(tag(BaseTag.else_tag(parser), :else_exp))
|> ignore(BaseTag.opening_tag())
|> ignore(string("endunless"))
|> ignore(BaseTag.closing_tag())
choice([cond_if_tag, cond_unless_tag])
end
@impl true
def render([{:if_exp, exp} | _] = tag, context, options) do
{result, context} = eval_expression(exp[:expression], context, options)
if result, do: throw({:result, exp, context})
context = eval_elsif_exps(tag[:elsif_exps], context, options)
else_exp = tag[:else_exp]
if else_exp, do: throw({:result, else_exp, context})
{nil, context}
catch
{:result, result, context} -> {result[:result], context}
end
def render([{:unless_exp, exp} | _] = tag, context, options) do
{result, context} = eval_expression(exp[:expression], context, options)
unless result, do: throw({:result, exp, context})
context = eval_elsif_exps(tag[:elsif_exps], context, options)
else_exp = tag[:else_exp]
if else_exp, do: throw({:result, else_exp, context})
{nil, context}
catch
{:result, result, context} -> {result[:result], context}
end
defp eval_elsif_exps(nil, context, _options), do: context
defp eval_elsif_exps(elsif_exps, context, options) do
{result, context} = eval_elsifs(elsif_exps, context, options)
if result, do: throw({:result, elem(result, 1), context})
context
end
defp eval_elsifs(elsif_exps, context, options) do
Enum.reduce_while(elsif_exps, {nil, context}, fn {:elsif_exp, elsif_exp}, {nil, context} ->
{result, context} = eval_expression(elsif_exp[:expression], context, options)
if result do
{:halt, {{:elsif_exp, elsif_exp}, context}}
else
{:cont, {nil, context}}
end
end)
end
defp eval_expression(exps, context, options), do: Expression.eval(exps, context, options)
end