Skip to content

Commit 10c9b77

Browse files
committed
Add generated lexer and parser
1 parent 35d743c commit 10c9b77

22 files changed

+4381
-583
lines changed

lib/adapters/ansi.ex

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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)
15+
end
16+
def token_to_string({tag, _, value}, _mod) when tag in ~w[ident integer float]a do
17+
"#{value}"
18+
end
19+
def token_to_string({tag, _}, mod) do
20+
mod.token_to_string(tag)
21+
end
22+
def token_to_string({:double_quote, _, value}, _mod) do
23+
"\"#{value}\""
24+
end
25+
def token_to_string({:quote, _, value}, _mod) do
26+
"'#{value}'"
27+
end
28+
def token_to_string({:parens, _, value}, mod) do
29+
"(#{mod.token_to_string(value)})"
30+
end
31+
def token_to_string({:colon, _, value}, mod) do
32+
"; #{mod.token_to_string(value)}"
33+
end
34+
def token_to_string({tag, _, []}, mod) do
35+
mod.token_to_string(tag)
36+
end
37+
def token_to_string({tag, _, [[_ | _] = left, right]}, mod) when tag in ~w[join]a do
38+
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)} #{mod.token_to_string(right)}"
39+
end
40+
def token_to_string({tag, _, value}, mod) when tag in ~w[select from fetch limit where order offset group having with join by distinct]a do
41+
"#{mod.token_to_string(tag)} #{mod.token_to_string(value)}"
42+
end
43+
def token_to_string({:recursive = tag, _, values}, mod) do
44+
"#{mod.token_to_string(tag)} #{mod.token_to_string(values)}"
45+
end
46+
def token_to_string({:on = tag, _, [{:parens, _, _} = value]}, mod) do
47+
"#{mod.token_to_string(tag)} #{mod.token_to_string(value)}"
48+
end
49+
def token_to_string({:on = tag, _, [source, as, value]}, mod) do
50+
"#{mod.token_to_string(source)} #{mod.token_to_string(as)} #{mod.token_to_string(tag)} #{mod.token_to_string(value)}"
51+
end
52+
def token_to_string({tag, _, [{:parens, _, _} = value]}, mod) do
53+
"#{mod.token_to_string(tag)}#{mod.token_to_string(value)}"
54+
end
55+
def token_to_string({:between = tag, _, [{:not = t, _, right}, left]}, mod) do
56+
"#{mod.token_to_string(right)} #{mod.token_to_string(t)} #{mod.token_to_string(tag)} #{mod.token_to_string(left)}"
57+
end
58+
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]a do
59+
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)} #{mod.token_to_string(right)}"
60+
end
61+
def token_to_string({tag, _, values}, mod) when tag in ~w[not all between symmetric absolute relative forward backward on in]a do
62+
"#{mod.token_to_string(tag)} #{mod.token_to_string(values)}"
63+
end
64+
def token_to_string({tag, _, [left, right]}, mod) when tag in ~w[.]a do
65+
"#{mod.token_to_string(left)}.#{mod.token_to_string(right)}"
66+
end
67+
def token_to_string({tag, _, [left]}, mod) when tag in ~w[not]a do
68+
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)}"
69+
end
70+
def token_to_string({tag, _, [left]}, mod) when tag in ~w[asc desc isnull notnull]a do
71+
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)}"
72+
end
73+
def token_to_string({:binding, _, [idx]}, _mod) when is_integer(idx) do
74+
"?"
75+
end
76+
def token_to_string({:binding, _, value}, _mod) do
77+
"{{#{value}}}"
78+
end
79+
def token_to_string(:asterisk, _mod) do
80+
"*"
81+
end
82+
def token_to_string(value, _mod) when is_atom(value) do
83+
"#{value}"
84+
end
85+
def token_to_string(values, mod) when is_list(values) do
86+
Enum.reduce(values, "", fn
87+
token, "" -> mod.token_to_string(token)
88+
{:comma, _, _} = token, acc -> acc <> mod.token_to_string(token)
89+
token, acc -> acc <> " " <> mod.token_to_string(token)
90+
end)
91+
end
92+
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)