Packages
surface
0.8.3
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/converter.ex
defmodule Surface.Compiler.Converter do
@moduledoc false
@type subject :: :expr | :tag_name | :attr_name
@callback opts() :: keyword()
@callback convert(subject :: subject(), text :: binary(), state :: map(), opts :: keyword()) ::
binary() | {binary(), state :: map()}
@callback after_convert_file(ext :: binary(), content :: binary()) :: binary()
alias Surface.Compiler.Tokenizer
def convert(text, opts) do
converter = Keyword.fetch!(opts, :converter)
metas =
text
|> Tokenizer.tokenize!()
|> extract_meta([], Keyword.merge(opts, converter.opts()))
state = %{tag_name: nil, tag_open_begin: nil}
scan_text(text, 1, 1, [], [], metas, state, opts)
end
defp scan_text(
"\r\n" <> rest,
line,
column,
buffer,
acc,
[{_type, %{line: line, column: column}} | _] = metas,
state,
opts
) do
acc = buffer_to_acc(buffer, acc)
scan_text(rest, line + 1, 1, ["\r\n"], acc, metas, state, opts)
end
defp scan_text(
"\r\n" <> rest,
line,
column,
buffer,
acc,
[{type, %{line_end: line, column_end: column}} | metas],
state,
opts
) do
{acc, state} = convert_buffer_to_acc(type, buffer, acc, state, opts)
scan_text(rest, line + 1, 1, ["\r\n"], acc, metas, state, opts)
end
defp scan_text("\r\n" <> rest, line, _column, buffer, acc, metas, state, opts) do
scan_text(rest, line + 1, 1, ["\r\n" | buffer], acc, metas, state, opts)
end
defp scan_text(
"\n" <> rest,
line,
column,
buffer,
acc,
[{_type, %{line: line, column: column}} | _] = metas,
state,
opts
) do
acc = buffer_to_acc(buffer, acc)
scan_text(rest, line + 1, 1, ["\n"], acc, metas, state, opts)
end
defp scan_text(
"\n" <> rest,
line,
column,
buffer,
acc,
[{type, %{line_end: line, column_end: column}} | metas],
state,
opts
) do
{acc, state} = convert_buffer_to_acc(type, buffer, acc, state, opts)
scan_text(rest, line + 1, 1, ["\n"], acc, metas, state, opts)
end
defp scan_text("\n" <> rest, line, _column, buffer, acc, metas, state, opts) do
scan_text(rest, line + 1, 1, ["\n" | buffer], acc, metas, state, opts)
end
defp scan_text(
<<c::utf8, rest::binary>>,
line,
column,
buffer,
acc,
[{_type, %{line: line, column: column}} | _] = metas,
state,
opts
) do
acc = buffer_to_acc(buffer, acc)
scan_text(rest, line, column + 1, [<<c::utf8>>], acc, metas, state, opts)
end
defp scan_text(
<<c::utf8, rest::binary>>,
line,
column,
buffer,
acc,
[{type, %{line_end: line, column_end: column}} | metas],
state,
opts
) do
{acc, state} = convert_buffer_to_acc(type, buffer, acc, state, opts)
scan_text(rest, line, column + 1, [<<c::utf8>>], acc, metas, state, opts)
end
defp scan_text(<<c::utf8, rest::binary>>, line, column, buffer, acc, metas, state, opts) do
scan_text(rest, line, column + 1, [<<c::utf8>> | buffer], acc, metas, state, opts)
end
defp scan_text(<<>>, _line, _column, buffer, acc, _metas, _state, _opts) do
buffer_to_acc(buffer, acc) |> Enum.reverse() |> to_string()
end
defp extract_meta([], acc, _opts) do
acc
end
defp extract_meta([node | nodes], acc, opts) do
acc = extract_meta(nodes, acc, opts)
extract_meta(node, acc, opts)
end
defp extract_meta({:expr, _value, meta}, acc, _opts) do
[{:expr, meta} | acc]
end
defp extract_meta({attr, value, meta}, acc, opts) when is_binary(attr) do
acc = extract_meta(value, acc, opts)
[{:attr_name, meta} | acc]
end
defp extract_meta({:tag_open, name, attrs, meta}, acc, opts) do
handle_full_node = Keyword.get(opts, :handle_full_node, [])
if name in handle_full_node do
meta_begin = %{
line: meta.line,
column: meta.column - 1,
line_end: meta.line_end,
column_end: meta.column_end
}
meta_end = %{
line: meta.line_end,
column: meta.column_end,
line_end: meta.node_line_end,
column_end: meta.node_column_end + if(meta[:self_close], do: 2, else: 1)
}
[{:tag_open_begin, meta_begin}, {:tag_open_end, meta_end} | acc]
else
acc = extract_meta(attrs, acc, opts)
[{:tag_open_name, meta} | acc]
end
end
defp extract_meta({:tag_close, name, meta}, acc, opts) do
handle_full_node = Keyword.get(opts, :handle_full_node, [])
node =
if name in handle_full_node do
{:tag_close, %{meta | column: meta.column - 2, column_end: meta.column_end + 1}}
else
{:tag_close_name, meta}
end
[node | acc]
end
defp extract_meta({:string, _name, %{delimiter: nil} = meta}, acc, _opts) do
[{:unquoted_string, meta} | acc]
end
defp extract_meta({:string, _name, %{delimiter: ?"} = meta}, acc, _opts) do
meta = %{meta | column: meta.column - 1, column_end: meta.column_end + 1}
[{:double_quoted_string, meta} | acc]
end
defp extract_meta(_node, acc, _opts) do
acc
end
defp buffer_to_acc([], acc), do: acc
defp buffer_to_acc(buffer, acc), do: [buffer_to_string(buffer) | acc]
defp convert_buffer_to_acc(_type, [], acc, _state, _opts), do: acc
defp convert_buffer_to_acc(type, buffer, acc, state, opts) do
converter = Keyword.fetch!(opts, :converter)
value = buffer_to_string(buffer)
{new_type, new_state} =
case type do
:tag_open_begin -> {:tag_open_begin, %{state | tag_open_begin: value}}
:tag_close -> {:tag_close, %{state | tag_open_begin: nil}}
:tag_open_name -> {:tag_name, %{state | tag_name: value}}
:tag_close_name -> {:tag_name, %{state | tag_name: nil}}
_ -> {type, state}
end
{converted_text, new_state} =
try do
case converter.convert(new_type, value, new_state, opts) do
{test, state} -> {test, state}
text -> {text, new_state}
end
rescue
exception ->
message = """
can't convert token of type #{inspect(new_type)}
Original type #{inspect(new_type)}
Value:
---
#{value}
---
Converter state:
#{inspect(state)}
Original error:
#{Exception.message(exception)}
"""
reraise RuntimeError, [message: message], __STACKTRACE__
end
{[converted_text | acc], new_state}
end
defp buffer_to_string(buffer) do
buffer
|> Enum.reverse()
|> IO.iodata_to_binary()
end
end