Skip to content

Commit c145f85

Browse files
authored
Merge pull request #5 from elixir-dbvisor/add-generated-lexer-and-parser
Add generated lexer and parser
2 parents 35d743c + 905b120 commit c145f85

23 files changed

+4578
-586
lines changed

.iex.exs

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Application.put_env(:sql, :ecto_repos, [SQL.Repo])
88
Application.put_env(:sql, SQL.Repo, username: "postgres", password: "postgres", hostname: "localhost", database: "sql_test#{System.get_env("MIX_TEST_PARTITION")}", pool: Ecto.Adapters.SQL.Sandbox, pool_size: 10)
99
Mix.Tasks.Ecto.Create.run(["-r", "SQL.Repo"])
1010
SQL.Repo.start_link()
11+
import SQL

lib/adapters/ansi.ex

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: 2025 DBVisor
3+
4+
defmodule SQL.Adapters.ANSI do
5+
@moduledoc """
6+
A SQL adapter for [ANSI](https://blog.ansi.org/sql-standard-iso-iec-9075-2023-ansi-x3-135/).
7+
"""
8+
@moduledoc since: "0.2.0"
9+
10+
use SQL.Token
11+
12+
def token_to_string(value, mod \\ __MODULE__)
13+
def token_to_string(value, mod) when is_struct(value) do
14+
to_string(%{value | module: mod})
15+
end
16+
17+
def token_to_string({tag, _, [{:parens, _, _} = value]}, mod) when tag in ~w[integer float update]a do
18+
"#{mod.token_to_string(tag)}#{mod.token_to_string(value)}"
19+
end
20+
def token_to_string({tag, _, value}, _mod) when tag in ~w[ident integer float]a do
21+
"#{value}"
22+
end
23+
def token_to_string({tag, _}, mod) do
24+
mod.token_to_string(tag)
25+
end
26+
def token_to_string({:comment, _, value}, _mod) do
27+
"-- #{value}"
28+
end
29+
def token_to_string({:comments, _, value}, _mod) do
30+
"\\* #{value} *\\"
31+
end
32+
def token_to_string({:double_quote, _, value}, _mod) do
33+
"\"#{value}\""
34+
end
35+
def token_to_string({:quote, _, value}, _mod) do
36+
"'#{value}'"
37+
end
38+
def token_to_string({:parens, _, value}, mod) do
39+
"(#{mod.token_to_string(value)})"
40+
end
41+
def token_to_string({:colon, _, value}, mod) do
42+
"; #{mod.token_to_string(value)}"
43+
end
44+
def token_to_string({:comma, _, value}, mod) do
45+
", #{mod.token_to_string(value)}"
46+
end
47+
def token_to_string({tag, _, []}, mod) do
48+
mod.token_to_string(tag)
49+
end
50+
def token_to_string({tag, _, [[_ | _] = left, right]}, mod) when tag in ~w[join]a do
51+
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)} #{mod.token_to_string(right)}"
52+
end
53+
def token_to_string({tag, _, [{:with = t, _, [left, right]}]}, mod) when tag in ~w[to]a do
54+
"#{mod.token_to_string(tag)} #{mod.token_to_string(left)} #{mod.token_to_string(t)} #{mod.token_to_string(right)}"
55+
end
56+
def token_to_string({tag, _, value}, mod) when tag in ~w[select from fetch limit where order offset group having with join by distinct create type drop insert alter table add into delete update start grant revoke set declare open close commit rollback references recursive]a do
57+
"#{mod.token_to_string(tag)} #{mod.token_to_string(value)}"
58+
end
59+
def token_to_string({:on = tag, _, [source, as, value]}, mod) do
60+
"#{mod.token_to_string(source)} #{mod.token_to_string(as)} #{mod.token_to_string(tag)} #{mod.token_to_string(value)}"
61+
end
62+
def token_to_string({tag, _, [left, [{:all = t, _, right}]]}, mod) when tag in ~w[union except intersect]a do
63+
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)} #{mod.token_to_string(t)} #{mod.token_to_string(right)}"
64+
end
65+
def token_to_string({:between = tag, _, [{:not = t, _, right}, left]}, mod) do
66+
"#{mod.token_to_string(right)} #{mod.token_to_string(t)} #{mod.token_to_string(tag)} #{mod.token_to_string(left)}"
67+
end
68+
def token_to_string({tag, _, [left, right]}, mod) when tag in ~w[:: [\] <> <= >= != || + - ^ * / % < > = like ilike as union except intersect between and or on is not in cursor for to]a do
69+
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)} #{mod.token_to_string(right)}"
70+
end
71+
def token_to_string({tag, _, [{:parens, _, _} = value]}, mod) when tag not in ~w[in on]a do
72+
"#{mod.token_to_string(tag)}#{mod.token_to_string(value)}"
73+
end
74+
def token_to_string({tag, _, values}, mod) when tag in ~w[not all between symmetric absolute relative forward backward on in for without]a do
75+
"#{mod.token_to_string(tag)} #{mod.token_to_string(values)}"
76+
end
77+
def token_to_string({tag, _, [left, right]}, mod) when tag in ~w[.]a do
78+
"#{mod.token_to_string(left)}.#{mod.token_to_string(right)}"
79+
end
80+
def token_to_string({tag, _, [left]}, mod) when tag in ~w[not]a do
81+
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)}"
82+
end
83+
def token_to_string({tag, _, [left]}, mod) when tag in ~w[asc desc isnull notnull]a do
84+
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)}"
85+
end
86+
def token_to_string({:binding, _, [idx]}, _mod) when is_integer(idx) do
87+
"?"
88+
end
89+
def token_to_string({:binding, _, value}, _mod) do
90+
"{{#{value}}}"
91+
end
92+
def token_to_string(:asterisk, _mod) do
93+
"*"
94+
end
95+
def token_to_string(value, _mod) when is_atom(value) do
96+
"#{value}"
97+
end
98+
def token_to_string(values, mod) when is_list(values) do
99+
Enum.reduce(values, "", fn
100+
token, "" -> mod.token_to_string(token)
101+
{:comma, _, _} = token, acc -> acc <> mod.token_to_string(token)
102+
token, acc -> acc <> " " <> mod.token_to_string(token)
103+
end)
104+
end
105+
end

lib/adapters/mysql.ex

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: 2025 DBVisor
3+
4+
defmodule SQL.Adapters.MySQL do
5+
@moduledoc """
6+
A SQL adapter for [MySQL](https://www.mysql.com).
7+
"""
8+
@moduledoc since: "0.2.0"
9+
10+
use SQL.Token
11+
12+
def token_to_string(value, mod \\ __MODULE__)
13+
def token_to_string(token, mod), do: SQL.Adapters.ANSI.token_to_string(token, mod)
14+
end

lib/adapters/postgres.ex

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: 2025 DBVisor
3+
4+
defmodule SQL.Adapters.Postgres do
5+
@moduledoc """
6+
A SQL adapter for [PostgreSQL](https://www.postgresql.org).
7+
"""
8+
@moduledoc since: "0.2.0"
9+
10+
use SQL.Token
11+
12+
def token_to_string(value, mod \\ __MODULE__)
13+
def token_to_string({:not, _, [left, {:in, _, [{:binding, _, _} = right]}]}, mod), do: "#{mod.token_to_string(left)} != ANY(#{mod.token_to_string(right)})"
14+
def token_to_string({:in, _, [left, {:binding, _, _} = right]}, mod), do: "#{mod.token_to_string(left)} = ANY(#{mod.token_to_string(right)})"
15+
def token_to_string({:binding, _, [idx]}, _mod) when is_integer(idx), do: "$#{idx}"
16+
def token_to_string(token, mod), do: SQL.Adapters.ANSI.token_to_string(token, mod)
17+
end

lib/adapters/tds.ex

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: 2025 DBVisor
3+
4+
defmodule SQL.Adapters.TDS do
5+
@moduledoc """
6+
A SQL adapter for [TDS](https://www.microsoft.com/en-ca/sql-server).
7+
"""
8+
@moduledoc since: "0.2.0"
9+
10+
use SQL.Token
11+
12+
def token_to_string(value, mod \\ __MODULE__)
13+
def token_to_string({:binding, _, [idx]}, _mod) when is_integer(idx), do: "@#{idx}"
14+
def token_to_string(token, mod), do: SQL.Adapters.ANSI.token_to_string(token, mod)
15+
end

lib/bnf.ex

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: 2025 DBVisor
3+
# https://standards.iso.org/iso-iec/9075/-2/ed-6/en/
4+
# https://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_IEC_9075-1_2023_ed_6_-_id_76583_Publication_PDF_(en).zip
5+
# 0. \w(?![^<]*>)
6+
# 1. <[^>]*>\.{3} repeat non-terminal rule
7+
# 2. ({.+}...) repeat group
8+
# 3. <[^>]*> - non-terminal
9+
# 4. \[[^\]]*] - optional
10+
# 5. \|(?![^\[]*\]) - or
11+
12+
defmodule SQL.BNF do
13+
@moduledoc false
14+
15+
def parse() do
16+
File.cwd!()
17+
|> Path.join("standard/ISO_IEC_9075-2(E)_Foundation.bnf.txt")
18+
|> File.read!()
19+
|> parse()
20+
end
21+
22+
def parse(binary) do
23+
Map.new(parse(binary, :symbol, [], [], [], [], []))
24+
end
25+
26+
defp parse(<<>>, _type, data, acc, symbol, expr, rules) do
27+
merge(rules, symbol, expr ++ merge(acc, data))
28+
end
29+
defp parse(<<?*, rest::binary>>, :symbol = type, symbol, _acc, _data, _expr, rules) do
30+
parse(rest, type, [], [], symbol, [], rules)
31+
end
32+
defp parse(<<?\n, ?\n, ?<, rest::binary>>, _type, data, acc, symbol, expr, rules) do
33+
parse(<<?<, rest::binary>>, :symbol, [], [], [], [], merge(rules, symbol, expr ++ merge(acc, data)))
34+
end
35+
defp parse(<<?:, ?:, ?=, rest::binary>>, _type, data, acc, symbol, expr, rules) do
36+
parse(rest, :expr, [], [], String.trim("#{data}"), [], merge(rules, symbol, expr ++ acc))
37+
end
38+
defp parse(<<?., rest::binary>>, type, [?!, ?! | _] = data, acc, symbol, expr, rules) do
39+
parse(rest, type, [], merge(acc, "#{data ++ [?.]}"), symbol, expr, rules)
40+
end
41+
defp parse(<<?., ?., ?., rest::binary>>, type, data, acc, symbol, expr, rules) do
42+
parse(rest, type, data ++ [?., ?., ?.], acc, symbol, expr, rules)
43+
end
44+
defp parse(<<?|, rest::binary>>, type, data, acc, symbol, expr, rules) do
45+
parse(rest, type, data ++ [?|], acc, symbol, expr, rules)
46+
end
47+
defp parse(<<b, rest::binary>>, type, [] = data, acc, symbol, expr, rules) when b in [?\s, ?\t, ?\r, ?\n, ?\f] do
48+
parse(rest, type, data, acc, symbol, expr, rules)
49+
end
50+
defp parse(<<b, rest::binary>>, type, data, acc, symbol, expr, rules) when b in [?\n] do
51+
parse(rest, type, data, acc, symbol, expr, rules)
52+
end
53+
defp parse(<<b, rest::binary>>, type, data, acc, symbol, expr, rules) do
54+
parse(rest, type, data ++ [b], acc, symbol, expr, rules)
55+
end
56+
57+
defp merge([], []), do: []
58+
defp merge(rules, []), do: rules
59+
defp merge(rules, data), do: rules ++ [data]
60+
defp merge(rules, [], []), do: rules
61+
defp merge(rules, rule, expr) when is_list(rule), do: merge(rules, "#{rule}", expr)
62+
defp merge(rules, rule, expr) when is_list(expr), do: merge(rules, rule, "#{expr}")
63+
defp merge(rules, "<space>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, ["\u0020"]}] # 32 \u0020
64+
defp merge(rules, "<identifier start>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}] # "Lu", "Ll", "Lt", "Lm", "Lo", or "Nl" Unicode.Set.match?(<<b::utf8>>, "[[:Lu:], [:Ll:], [:Lt:], [:Lm:], [:Lo:], [:Nl:]]")
65+
defp merge(rules, "<identifier extend>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}] # 183 \u00B7 or "Mn", "Mc", "Nd", "Pc", or "Cf" Unicode.Set.match?(<<b::utf8>>, "[[:Mn:], [:Mc:], [:Nd:], [:Pc:], [:Cf:]]")
66+
defp merge(rules, "<Unicode escape character>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, ["\\u"]}]
67+
defp merge(rules, "<non-double quote character>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
68+
defp merge(rules, "<whitespace>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, ["\u0009", "\u000D", "\u00A0", "\u00A0", "\u1680", "\u2000", "\u2001", "\u2002", "\u2003", "\u2004", "\u2005", "\u2006", "\u2007", "\u2008", "\u2009", "\u200A", "\u202F", "\u205F", "\u3000", "\u180E", "\u200B", "\u200C", "\u200D", "\u2060", "\uFEFF"]}]
69+
defp merge(rules, "<truncating whitespace>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
70+
defp merge(rules, "<bracketed comment contents>" = symbol, _expr), do: rules ++ [{symbol, [:ignore]}]
71+
defp merge(rules, "<newline>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, ["\u000A", "\u000B", "\u000C", "\u000D", "\u0085", "\u2028", "\u2029"]}]
72+
defp merge(rules, "<non-quote character>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
73+
defp merge(rules, "<non-escaped character>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
74+
defp merge(rules, "<escaped character>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
75+
defp merge(rules, "<JSON path literal>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
76+
defp merge(rules, "<JSON path string literal>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
77+
defp merge(rules, "<JSON path numeric literal>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
78+
defp merge(rules, "<JSON path identifier>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
79+
defp merge(rules, "<JSON path key name>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
80+
defp merge(rules, "<implementation-defined JSON representation option>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
81+
defp merge(rules, "<preparable implementation-defined statement>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
82+
defp merge(rules, "<SQLSTATE class code>" = symbol, _expr), do: rules ++ [{symbol, [:ignore]}]
83+
defp merge(rules, "<SQLSTATE subclass code>" = symbol, _expr), do: rules ++ [{symbol, [:ignore]}]
84+
defp merge(rules, "<host label identifier>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
85+
defp merge(rules, "<host PL/I label variable>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
86+
defp merge(rules, "<embedded SQL Ada program>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
87+
defp merge(rules, "<Ada host identifier>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
88+
defp merge(rules, "<embedded SQL C program>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
89+
defp merge(rules, "<C host identifier>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
90+
defp merge(rules, "<embedded SQL COBOL program>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
91+
defp merge(rules, "<COBOL host identifier>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
92+
defp merge(rules, "<embedded SQL Fortran program>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
93+
defp merge(rules, "<Fortran host identifier>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
94+
defp merge(rules, "<embedded SQL MUMPS program>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
95+
defp merge(rules, "<MUMPS host identifier>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
96+
defp merge(rules, "<embedded SQL Pascal program>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
97+
defp merge(rules, "<Pascal host identifier>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
98+
defp merge(rules, "<embedded SQL PL/I program>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
99+
defp merge(rules, "<PL/I host identifier>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
100+
defp merge(rules, "<direct implementation-defined statement>" = symbol, "!! See the Syntax Rules."), do: rules ++ [{symbol, [:ignore]}]
101+
defp merge(_rules, symbol, "!! See the Syntax Rules."), do: raise "Please apply rules for #{symbol} by referencing the PDF or https://github.com/ronsavage/SQL/blob/master/Syntax.rules.txt"
102+
defp merge(rules, symbol, expr), do: rules ++ [{symbol, expr}]
103+
end

lib/compiler.ex

-50
This file was deleted.

0 commit comments

Comments
 (0)