Packages
surface
0.12.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/css_parser.ex
defmodule Surface.Compiler.CSSParser do
@moduledoc false
alias Surface.Compiler.CSSTokenizer
@other_blocks ["(", "["]
@opening_symbol %{
"}" => "{",
")" => "(",
"]" => "["
}
def parse!(code, opts \\ []) do
code
|> CSSTokenizer.tokenize!(opts)
|> handle_token(opts)
end
defp handle_token(tokens, opts) do
state = %{
candidate?: false,
caller: opts[:caller] || __ENV__
}
handle_token(tokens, [[]], state)
end
defp handle_token([{:block_open, "{"} | rest], buffers, %{candidate?: true} = state) do
[buffer | buffers] = buffers
node = buffer_to_node(buffer, :selector_list)
buffers = push_node_to_current_buffer(node, buffers)
buffers = [[] | buffers]
handle_token(rest, buffers, %{state | candidate?: false})
end
defp handle_token([{:block_open, symbol} | rest], buffers, state) when symbol in @other_blocks do
buffers = [[] | buffers]
handle_token(rest, buffers, state)
end
defp handle_token([:semicolon = token | rest], buffers, %{candidate?: true} = state) do
[buffer | buffers] = buffers
node = buffer_to_node([token | buffer], :declaration)
buffers = push_node_to_current_buffer(node, buffers)
handle_token(rest, buffers, %{state | candidate?: false})
end
defp handle_token([{:block_close, "}", meta} | rest], buffers, %{candidate?: true} = state) do
# handle declaration
[buffer | buffers] = buffers
node = buffer_to_node(buffer, :declaration)
buffers = push_node_to_current_buffer(node, buffers)
# handle end of block
[buffer | buffers] = buffers
node = {:block, "{", Enum.reverse(buffer), convert_block_meta(meta)}
buffers = push_node_to_current_buffer(node, buffers)
handle_token(rest, buffers, %{state | candidate?: false})
end
defp handle_token([{:block_close, symbol, meta} | rest], buffers, state) do
[buffer | buffers] = buffers
node = {:block, @opening_symbol[symbol], Enum.reverse(buffer), convert_block_meta(meta)}
buffers = push_node_to_current_buffer(node, buffers)
handle_token(rest, buffers, state)
end
defp handle_token([{:text, _} = token | rest], buffers, %{candidate?: false} = state) do
buffers = [[token] | buffers]
handle_token(rest, buffers, %{state | candidate?: true})
end
defp handle_token([token | rest], buffers, state) do
buffers = push_node_to_current_buffer(token, buffers)
handle_token(rest, buffers, state)
end
defp handle_token([], [buffer], _state) do
Enum.reverse(buffer)
end
defp buffer_to_node(buffer, :selector_list) do
{reversed, items, list} =
Enum.reduce(buffer, {[], [], []}, fn
{:comma, _} = comma, {reversed, [{:ws, _} = space | items], list} ->
{[comma | reversed], [comma, space], [items | list]}
{:comma, _} = token, {reversed, items, list} ->
{[token | reversed], [token], [items | list]}
token, {reversed, items, list} ->
{[token | reversed], [token | items], list}
end)
list = [items | list]
case reversed do
[{:text, "@" <> _} | _] ->
{:at_rule, reversed}
_ ->
{:selector_list, list}
end
end
defp buffer_to_node(buffer, :declaration) do
case Enum.reverse(buffer) do
[{:text, "@" <> _} | _] = value ->
{:at_rule, value}
value ->
{:declaration, value}
end
end
defp push_node_to_current_buffer(node, buffers) do
[buffer | buffers] = buffers
buffer = [node | buffer]
[buffer | buffers]
end
defp convert_block_meta(meta) do
%{
line: meta.opening_line,
column: meta.opening_column,
line_end: meta.line,
column_end: meta.column
}
end
end