Packages
surface
0.6.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.1
0.1.0
0.1.0-alpha.2
0.1.0-alpha.1
0.1.0-alpha.0
A component based library for Phoenix LiveView
Current section
Files
Jump to
Current section
Files
lib/surface/compiler/parse_tree_translator.ex
defmodule Surface.Compiler.ParseTreeTranslator do
@behaviour Surface.Compiler.NodeTranslator
alias Surface.IOHelper
alias Surface.Compiler.Helpers
def handle_init(state), do: state
def handle_expression(expression, meta, state) do
{{:expr, expression, to_meta(meta)}, state}
end
def handle_tagged_expression("^", expression, meta, state) do
{{:ast, expression, to_meta(meta)}, state}
end
def handle_comment(comment, meta, state) do
{{:comment, comment, meta}, state}
end
def handle_node(<<first, _::binary>> = name, attributes, body, meta, state, _context)
when first == ?. or first in ?A..?Z do
decomposed_tag = Helpers.decompose_component_tag(name, state.caller)
meta = Map.put(meta, :decomposed_tag, decomposed_tag)
{{name, attributes, body, to_meta(meta)}, state}
end
def handle_node(name, attributes, body, meta, state, _context) do
{{name, attributes, body, to_meta(meta)}, state}
end
def handle_block(name, [], _body, meta, _state, _context) do
message = "missing expression for block {\##{name} ...}"
IOHelper.compile_error(message, meta.file, meta.line)
end
def handle_block(name, expr, body, meta, state, _context) do
{{:block, name, expr, body, to_meta(meta)}, state}
end
def handle_subblock(:default, expr, children, meta, state, %{parent_block: "case"}) do
if !Helpers.blank?(children) do
message = "cannot have content between {#case ...} and {#match ...}"
IOHelper.compile_error(message, meta.file, meta.line)
end
{{:block, :default, expr, [], to_meta(meta)}, state}
end
def handle_subblock(:default, expr, children, meta, state, _context) do
{{:block, :default, expr, children, to_meta(meta)}, state}
end
def handle_subblock(name, expr, children, meta, state, _context) do
{{:block, name, expr, children, to_meta(meta)}, state}
end
def handle_text(text, state) do
{text, state}
end
# TODO: Update these after accepting the expression directly instead of the :root attribute
def handle_block_expression(_block_name, nil, _state, _context) do
[]
end
def handle_block_expression(_block_name, {:expr, expr, expr_meta}, _state, _context) do
meta = to_meta(expr_meta)
[{:root, {:attribute_expr, expr, meta}, meta}]
end
def handle_attribute(
:root,
{:tagged_expr, "...", expr, _marker_meta},
attr_meta,
_state,
context
) do
{:expr, value, expr_meta} = expr
%{tag_name: tag_name} = context
directive =
case tag_name do
"#slot" ->
message = """
cannot pass dynamic attributes to <#slot>.
Slots only accept `name`, `index`, `:args`, `:if` and `:for`.
"""
IOHelper.compile_error(message, attr_meta.file, attr_meta.line)
<<first, _::binary>> when first == ?. or first in ?A..?Z ->
":props"
_ ->
":attrs"
end
{directive, {:attribute_expr, value, to_meta(expr_meta)}, to_meta(attr_meta)}
end
def handle_attribute(
name,
{:tagged_expr, "...", expr, marker_meta},
_attr_meta,
_state,
_context
) do
{:expr, value, _expr_meta} = expr
message = """
cannot assign `{...#{value}}` to attribute `#{name}`. \
The tagged expression `{... }` can only be used on a root attribute/property.
Example: <div {...@attrs}>
"""
IOHelper.compile_error(message, marker_meta.file, marker_meta.line)
end
def handle_attribute(
:root,
{:tagged_expr, "=", expr, _marker_meta},
attr_meta,
_state,
context
) do
{:expr, value, expr_meta} = expr
%{tag_name: tag_name} = context
original_name = strip_name_from_tagged_expr_equals!(value, expr_meta)
name =
case tag_name do
<<first, _::binary>> when first == ?. or first in ?A..?Z ->
original_name
_ ->
String.replace(original_name, "_", "-")
end
{name, {:attribute_expr, value, to_meta(expr_meta)}, to_meta(attr_meta)}
end
def handle_attribute(
name,
{:tagged_expr, "=", expr, marker_meta},
_attr_meta,
_state,
_context
) do
{:expr, value, _expr_meta} = expr
message = """
cannot assign `{=#{value}}` to attribute `#{name}`. \
The tagged expression `{= }` can only be used on a root attribute/property.
Example: <div {=@class}>
"""
IOHelper.compile_error(message, marker_meta.file, marker_meta.line)
end
def handle_attribute(
name,
{:tagged_expr, "^", {:expr, value, expr_meta}, _marker_meta},
attr_meta,
_state,
_context
) do
{name, {:ast, value, to_meta(expr_meta)}, to_meta(attr_meta)}
end
def handle_attribute(name, {:expr, expr, expr_meta}, attr_meta, _state, _context) do
{name, {:attribute_expr, expr, to_meta(expr_meta)}, to_meta(attr_meta)}
end
def handle_attribute(name, value, attr_meta, _state, _context) do
{name, value, to_meta(attr_meta)}
end
def context_for_node(name, _meta, _state) do
%{tag_name: name}
end
def context_for_subblock(name, _meta, _state, parent_context) do
%{sub_block: name, parent_block: Map.get(parent_context, :block_name)}
end
def context_for_block(name, _meta, _state) do
%{block_name: name}
end
def to_meta(%{void_tag?: true} = meta) do
drop_common_keys(meta)
end
def to_meta(meta) do
meta
|> Map.drop([:void_tag?])
|> drop_common_keys()
end
defp drop_common_keys(meta) do
Map.drop(meta, [
:self_close,
:line_end,
:column_end,
:node_line_end,
:node_column_end,
:macro?,
:ignored_body?
])
end
defp strip_name_from_tagged_expr_equals!(value, expr_meta) do
case Code.string_to_quoted(value) do
{:ok, {:@, _, [{name, _, _}]}} when is_atom(name) ->
to_string(name)
{:ok, {name, _, _}} when is_atom(name) ->
to_string(name)
_ ->
message = """
invalid value for tagged expression `{=#{value}}`. \
The expression must be either an assign or a variable.
Examples: `<div {=@class}>` or `<div {=class}>`
"""
IOHelper.compile_error(message, expr_meta.file, expr_meta.line)
end
end
end