Quoted Elixir back into the term it stands for, when it stands for a term and nothing else.
Why not Code.eval_quoted/1
Because every caller here is holding something an author, or a package, or a database row supplied.
Evaluating it would run it. This walks it instead: a map, a tuple, a list and the three scalar
kinds resolve, and everything else — a call, a variable, an assign, an interpolation — answers
:error and is left as the source text it always was.
The two directions
term/1 is the exact inverse of Macro.escape/1, which is what makes data?/1 a one-liner rather
than a second allowlist kept in step with the first by hand. A value is data when escaping it and
unescaping it gives it back — and that property is the same one MishkaCmsCore.Builder.Command
relies on to print a term as source without an Inspect of its own.
Summary
Functions
Whether a term is data a page can carry — no function, no pid, no reference.
Whether a source string reads something only the running page has: an assign, the assigns
map, a field or a key of something (f[:email].value, item.title), or a function it calls by
name with parentheses or as a sigil (t("Hi"), ~p"/about").
The term a quoted expression stands for, or :error.
The term a source string stands for, or :error.
Functions
Whether a term is data a page can carry — no function, no pid, no reference.
The check a writer needs before printing a term as source, and the trap it is guarding against is
quiet: a three-element tuple like {:foo, [], nil} is a perfectly ordinary term AND a valid quoted
variable, so printing it unescaped writes foo into somebody's page. Escaping first is what makes
it print as a tuple; this says whether escaping it was enough.
iex> MishkaCmsCore.Builder.Literal.data?(%{"a" => [1, {:b, "c"}]})
true
iex> MishkaCmsCore.Builder.Literal.data?(&String.trim/1)
false
Whether a source string reads something only the running page has: an assign, the assigns
map, a field or a key of something (f[:email].value, item.title), or a function it calls by
name with parentheses or as a sigil (t("Hi"), ~p"/about").
The other side of value/1, and the question a property box has to ask of what was typed into
it, because typed words parse. E2E-0 is the alias E2E minus zero, my-page is my minus
page and hello world is the call hello(world); none of them reads anything the page has, and
each is what an author types as words. A bare name is a word for the same reason.
iex> MishkaCmsCore.Builder.Literal.expression?("f[:email].value")
true
iex> MishkaCmsCore.Builder.Literal.expression?("E2E-0")
false
The term a quoted expression stands for, or :error.
Quoted Elixir says what a thing is by its shape: a map is {:%{}, meta, pairs}, a tuple of any
size but two is {:{}, meta, items}, a two-tuple is itself — and every OTHER three-element tuple
is a call, a variable or an alias, which is precisely what must not resolve. Listing what is
allowed rather than what is not is the only way round that stays right when a syntax is added.
The term a source string stands for, or :error.
iex> MishkaCmsCore.Builder.Literal.value(~s|%{"kind" => :info}|)
{:ok, %{"kind" => :info}}
iex> MishkaCmsCore.Builder.Literal.value("@assign")
:error