Skip to content

Commit 1d0d2dc

Browse files
committed
Richer parsing
1 parent c249300 commit 1d0d2dc

File tree

4 files changed

+103
-5
lines changed

4 files changed

+103
-5
lines changed

apps/components_guide_web/lib/components_guide_web/controllers/universal_modules_controller.ex

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,53 @@ defmodule ComponentsGuideWeb.UniversalModulesController do
1414
const verbose = false;
1515
const alwaysNull = null;
1616
17+
const debug = verbose;
18+
1719
export const dateFormat = "YYYY/MM/DD";
1820
21+
const exampleDotOrg = new URL("https://example.org");
22+
1923
const array = [1, 2, 3];
2024
const arrayMultiline = [
2125
4,
2226
5,
2327
6
2428
];
2529
export const flavors = ["vanilla", "chocolate", "caramel", "raspberry"];
30+
export const flavorsSet = new Set(["vanilla", "chocolate", "caramel", "raspberry"]);
2631
2732
const object = { "key": "value" };
2833
2934
function hello() {}
30-
function* gen() {}
35+
function* gen() {
36+
const a = 1;
37+
yield [1, 2, 3];
38+
}
3139
"""
3240
# decoded = decode_module(source)
3341
decoded = Parser.decode(source)
42+
identifiers = ComponentsGuideWeb.UniversalModulesInspector.list_identifiers(elem(decoded, 1))
3443

3544
render(conn, "index.html",
3645
page_title: "Universal Modules",
3746
source: source,
38-
decoded: inspect(decoded)
47+
decoded: inspect(decoded),
48+
identifiers: inspect(identifiers),
3949
)
4050
end
4151
end
4252

53+
defmodule ComponentsGuideWeb.UniversalModulesInspector do
54+
def is_identifier({:const, _, _}), do: true
55+
def is_identifier({:function, _, _, _}), do: true
56+
def is_identifier({:generator_function, _, _, _}), do: true
57+
def is_identifier(_), do: false
58+
59+
def list_identifiers(module_body) do
60+
for statement <- module_body, is_identifier(statement), do: statement
61+
end
62+
end
63+
4364
defmodule ComponentsGuideWeb.UniversalModulesParser do
4465
def compose(submodule, input) do
4566
mod = Module.concat(__MODULE__, submodule)
@@ -110,6 +131,8 @@ defmodule ComponentsGuideWeb.UniversalModulesParser do
110131
end
111132

112133
defmodule Function do
134+
defdelegate compose(submodule, input), to: ComponentsGuideWeb.UniversalModulesParser
135+
113136
def decode(<<"function", rest::bitstring>>),
114137
do: decode(%{generator_mark: nil, name: nil, args: nil, body: nil}, rest)
115138

@@ -136,10 +159,33 @@ defmodule ComponentsGuideWeb.UniversalModulesParser do
136159
defp decode(%{args: {:closed, args}, name: name, body: {:open, body_items}} = context, <<"}", rest::bitstring>>) do
137160
case context.generator_mark do
138161
true ->
139-
{:ok, {:generator_function, name, args, body_items}, rest}
162+
{:ok, {:generator_function, name, args, Enum.reverse(body_items)}, rest}
140163

141164
nil ->
142-
{:ok, {:function, name, args, body_items}, rest}
165+
{:ok, {:function, name, args, Enum.reverse(body_items)}, rest}
166+
end
167+
end
168+
169+
defp decode(%{body: {:open, _}} = context, <<char::utf8, rest::bitstring>>) when char in [?\n, ?\t, ?;],
170+
do: decode(context, rest)
171+
172+
defp decode(%{body: {:open, body_items}} = context, <<"const ", _::bitstring>> = input) do
173+
case compose(Const, input) do
174+
{:ok, term, rest} ->
175+
decode(%{context | body: {:open, [term | body_items]}}, rest)
176+
177+
{:error, reason} ->
178+
{:error, {reason, body_items}}
179+
end
180+
end
181+
182+
defp decode(%{body: {:open, body_items}} = context, <<"yield ", _::bitstring>> = input) do
183+
case compose(Yield, input) do
184+
{:ok, term, rest} ->
185+
decode(%{context | body: {:open, [term | body_items]}}, rest)
186+
187+
{:error, reason} ->
188+
{:error, {reason, body_items}}
143189
end
144190
end
145191

@@ -168,6 +214,9 @@ defmodule ComponentsGuideWeb.UniversalModulesParser do
168214
def decode(<<"const ", rest::bitstring>>),
169215
do: decode({:expect_identifier, []}, rest)
170216

217+
def decode(<<_::bitstring>>),
218+
do: {:error, :expected_const}
219+
171220
defp decode({:expect_identifier, _} = context, <<" ", rest::bitstring>>),
172221
do: decode(context, rest)
173222

@@ -199,16 +248,47 @@ defmodule ComponentsGuideWeb.UniversalModulesParser do
199248
end
200249

201250
defmodule Expression do
251+
import Unicode.Guards
252+
202253
def decode(input), do: decode([], input)
203254

255+
defp finalize([{:found_identifier, reverse_identifier} | context_rest]) do
256+
identifier = reverse_identifier |> Enum.reverse() |> :binary.list_to_bin()
257+
[{:ref, identifier} | context_rest]
258+
end
259+
260+
defp finalize(expression), do: expression
261+
204262
defp decode(expression, <<";", rest::bitstring>>),
205-
do: {:ok, expression, rest}
263+
do: {:ok, finalize(expression), rest}
206264

207265
defp decode([] = context, <<" ", rest::bitstring>>), do: decode(context, rest)
208266
defp decode([], <<"true", rest::bitstring>>), do: decode(true, rest)
209267
defp decode([], <<"false", rest::bitstring>>), do: decode(false, rest)
210268
defp decode([], <<"null", rest::bitstring>>), do: decode(nil, rest)
211269

270+
defp decode([], <<"new URL(", rest::bitstring>>) do
271+
[encoded_json, rest] = String.split(rest, ");\n", parts: 2)
272+
case Jason.decode(encoded_json) do
273+
{:ok, value} ->
274+
{:ok, {:url, value}, rest}
275+
276+
{:error, error} ->
277+
{:error, error}
278+
end
279+
end
280+
281+
defp decode([], <<"new Set(", rest::bitstring>>) do
282+
[encoded_json, rest] = String.split(rest, ");\n", parts: 2)
283+
case Jason.decode(encoded_json) do
284+
{:ok, value} ->
285+
{:ok, {:set, value}, rest}
286+
287+
{:error, error} ->
288+
{:error, error}
289+
end
290+
end
291+
212292
# TODO: parse JSON by finding the end character followed by a semicolon + newline.
213293
# JSON strings cannoc contain literal newlines (it’s considered to be a control character),
214294
# so instead it must be encoded as "\n". So we can use this fast to know an actual newline is
@@ -245,6 +325,14 @@ defmodule ComponentsGuideWeb.UniversalModulesParser do
245325
decode(f, rest)
246326
end
247327
end
328+
329+
defp decode([], <<char::utf8, rest::bitstring>>) when is_lower(char) or is_upper(char),
330+
do: decode([{:found_identifier, [char]}], rest)
331+
332+
defp decode([{:found_identifier, reverse_identifier} | context_rest], <<char::utf8, rest::bitstring>>)
333+
when is_lower(char) or is_upper(char) or is_digit(char) do
334+
decode([{:found_identifier, [char | reverse_identifier]} | context_rest], rest)
335+
end
248336
end
249337

250338
defmodule KnownIdentifier do

apps/components_guide_web/lib/components_guide_web/templates/universal_modules/index.html.eex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,9 @@
55
<pre class="whitespace-pre-line">
66
<%= @decoded %>
77
</pre>
8+
9+
<h2>Identifiers</h2>
10+
11+
<pre class="whitespace-pre-line">
12+
<%= @identifiers %>
13+
</pre>

apps/components_guide_web/mix.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ defmodule ComponentsGuideWeb.MixProject do
4747
{:telemetry_metrics, "~> 0.4"},
4848
{:telemetry_poller, "~> 0.4"},
4949
{:gettext, "~> 0.11"},
50+
{:unicode_guards, "~> 1.0"},
5051
{:components_guide, in_umbrella: true},
5152
{:jason, "~> 1.0"},
5253
{:plug_cowboy, "~> 2.0"},

mix.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454
"telemetry_metrics": {:hex, :telemetry_metrics, "0.5.0", "1b796e74add83abf844e808564275dfb342bcc930b04c7577ab780e262b0d998", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31225e6ce7a37a421a0a96ec55244386aec1c190b22578bd245188a4a33298fd"},
5555
"telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"},
5656
"tesla": {:hex, :tesla, "1.3.3", "26ae98627af5c406584aa6755ab5fc96315d70d69a24dd7f8369cfcb75094a45", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2648f1c276102f9250299e0b7b57f3071c67827349d9173f34c281756a1b124c"},
57+
"unicode": {:hex, :unicode, "1.13.1", "4ebb6dc60e91f04e0dd46dca93576416473bc22bae69484122c11682af48e8df", [:mix], [], "hexpm", "fe4fcc8e15f444cf07bfbca30ad726be99c12d45da06d70b0e4f0c49b0d7d5ce"},
58+
"unicode_guards": {:hex, :unicode_guards, "1.0.0", "e0a71517486de1ac2a3bdc516da48d6f149f61873732d7a3d882e8951c7649f2", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:unicode_set, "~> 1.0", [hex: :unicode_set, repo: "hexpm", optional: false]}], "hexpm", "d8c91bf78ff9bd6a9053d4ad43f0ec4ff3ed15c36917fe36bb19dbf36650096c"},
59+
"unicode_set": {:hex, :unicode_set, "1.1.0", "32971b9b8061f2b2f0c607ba588ad9a6202e78a0c577555df4da899fd4434f23", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:unicode, "~> 1.13", [hex: :unicode, repo: "hexpm", optional: false]}], "hexpm", "b416e7b18d1297bbdc25c05e56506291e8fd3f339b8386d4e1f6ddb57c047918"},
5760
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
5861
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
5962
}

0 commit comments

Comments
 (0)