From efe7b1b13578f4bee6921a0ae7034b7f890d0eb6 Mon Sep 17 00:00:00 2001 From: Ianko Leite Date: Fri, 17 Oct 2025 16:36:57 -0400 Subject: [PATCH 1/5] add elixir 1.19 and otp 28 --- .github/workflows/ci.yml | 1 + .tool-versions | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b72a8f..dc7a298 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: - { otp: "25", elixir: "1.14" } - { otp: "26", elixir: "1.16" } - { otp: "27", elixir: "1.18" } + - { otp: "28", elixir: "1.19" } env: MIX_ENV: test diff --git a/.tool-versions b/.tool-versions index fb36923..0c842a1 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -elixir 1.18.2-otp-27 -erlang 27.2.4 +elixir 1.19.0-otp-28 +erlang 28.1 From 81db41fc37fe8426cbaefa18e423f97d7154625d Mon Sep 17 00:00:00 2001 From: Ianko Leite Date: Fri, 17 Oct 2025 16:37:04 -0400 Subject: [PATCH 2/5] update dependencies --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index f22b6d2..54cf7ac 100644 --- a/mix.exs +++ b/mix.exs @@ -36,7 +36,7 @@ defmodule Xpeg.MixProject do defp deps do [ - {:ex_doc, "~> 0.14", only: [:dev], runtime: false} + {:ex_doc, "~> 0.38", only: [:dev], runtime: false} # {:exprof, "~> 0.2.0", only: [:dev, :test], runtime: false}, # {:poison, "~> 5.0", only: [:dev, :test], runtime: false}, # {:jason, "~> 1.2", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 9e74a0d..c4749c1 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, - "ex_doc": {:hex, :ex_doc, "0.37.3", "f7816881a443cd77872b7d6118e8a55f547f49903aef8747dbcb345a75b462f9", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "e6aebca7156e7c29b5da4daa17f6361205b2ae5f26e5c7d8ca0d3f7e18972233"}, + "ex_doc": {:hex, :ex_doc, "0.38.4", "ab48dff7a8af84226bf23baddcdda329f467255d924380a0cf0cee97bb9a9ede", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "f7b62346408a83911c2580154e35613eb314e0278aeea72ed7fedef9c1f165b2"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, From bab109d8869bcb9cd06803981eb89e7c8482d602 Mon Sep 17 00:00:00 2001 From: Ianko Leite Date: Fri, 17 Oct 2025 18:27:28 -0400 Subject: [PATCH 3/5] split code generation into separate branches at compile-time --- lib/codegen.ex | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/codegen.ex b/lib/codegen.ex index cdd73f8..8c09294 100644 --- a/lib/codegen.ex +++ b/lib/codegen.ex @@ -175,18 +175,23 @@ defmodule Xpeg.Codegen do end {:code, code} -> - quote location: :keep do - def parse(unquote(ip), s, si, ctx, back_stack, ret_stack, cap_stack, captures) do - {cap_stack, captures} = Xpeg.collect_captures(cap_stack, captures) - func = unquote(code) - - {captures, ctx} = - case unquote(options[:userdata]) do - true -> func.(captures, ctx) - _ -> {func.(captures), ctx} - end - - parse(unquote(ip + 1), s, si, ctx, back_stack, ret_stack, cap_stack, captures) + if options[:userdata] do + quote location: :keep do + def parse(unquote(ip), s, si, ctx, back_stack, ret_stack, cap_stack, captures) do + {cap_stack, captures} = Xpeg.collect_captures(cap_stack, captures) + func = unquote(code) + {captures, ctx} = func.(captures, ctx) + parse(unquote(ip + 1), s, si, ctx, back_stack, ret_stack, cap_stack, captures) + end + end + else + quote location: :keep do + def parse(unquote(ip), s, si, ctx, back_stack, ret_stack, cap_stack, captures) do + {cap_stack, captures} = Xpeg.collect_captures(cap_stack, captures) + func = unquote(code) + captures = func.(captures) + parse(unquote(ip + 1), s, si, ctx, back_stack, ret_stack, cap_stack, captures) + end end end From 69361f6ca1ad6103c6bdc05799ec2f4ccb44db43 Mon Sep 17 00:00:00 2001 From: Ianko Leite Date: Fri, 17 Oct 2025 18:32:25 -0400 Subject: [PATCH 4/5] use charlist sigil --- test/examples_test.exs | 32 +++++----- test/xpeg_test.exs | 136 ++++++++++++++++++++--------------------- 2 files changed, 84 insertions(+), 84 deletions(-) diff --git a/test/examples_test.exs b/test/examples_test.exs index ac1bb98..8efd08b 100644 --- a/test/examples_test.exs +++ b/test/examples_test.exs @@ -10,8 +10,8 @@ defmodule ExamplesTest do peg Dict do Dict <- Pair * star("," * Pair) * !1 Pair <- Word * "=" * Number * fn [a, b | cs] -> [{b, a} | cs] end - Word <- str(+{'a'..'z'}) - Number <- int(+{'0'..'9'}) + Word <- str(+{~c"a"..~c"z"}) + Number <- int(+{~c"0"..~c"9"}) end r = match(p, "grass=4,horse=1,star=2") @@ -26,9 +26,9 @@ defmodule ExamplesTest do Exp <- Term * star(Exp_op) Term <- Factor * star(Term_op) Factor <- Number | "(" * Exp * ")" - Number <- int(+{'0'..'9'}) - Term_op <- str({'*', '/'}) * Factor * fn [b, op, a | cs] -> [{op, a, b} | cs] end - Exp_op <- str({'+', '-'}) * Term * fn [b, op, a | cs] -> [{op, a, b} | cs] end + Number <- int(+{~c"0"..~c"9"}) + Term_op <- str({~c"*", ~c"/"}) * Factor * fn [b, op, a | cs] -> [{op, a, b} | cs] end + Exp_op <- str({~c"+", ~c"-"}) * Term * fn [b, op, a | cs] -> [{op, a, b} | cs] end end cs = match(p, "1+(2-3*4)/5").captures @@ -41,7 +41,7 @@ defmodule ExamplesTest do p = peg Json do # White space - S <- star({' ', '\t', '\r', '\n'}) + S <- star({" ", "\t", "\r", "\n"}) # Basic atoms true <- "true" * fn cs -> [true | cs] end @@ -49,24 +49,24 @@ defmodule ExamplesTest do Null <- "null" * fn cs -> [nil | cs] end # Strings - Xdigit <- {'0'..'9', 'a'..'f', 'A'..'F'} - Unicode_escape <- 'u' * Xdigit[4] - Escape <- '\\' * ({'"', '\\', '/', 'b', 'f', 'n', 'r', 't'} | Unicode_escape) - String_body <- star(Escape) * star(+({'\x20'..'\x7f'} - {'"'} - {'\\'}) * star(Escape)) - String <- '"' * str(String_body) * '"' + Xdigit <- {~c"0"..~c"9", ~c"a"..~c"f", ~c"A"..~c"F"} + Unicode_escape <- "u" * Xdigit[4] + Escape <- "\\" * ({~c"\"", ~c"\\", ~c"/", ~c"b", ~c"f", ~c"n", ~c"r", ~c"t"} | Unicode_escape) + String_body <- star(Escape) * star(+({~c" "..~c"\x7f"} - {~c"\""} - {~c"\\"}) * star(Escape)) + String <- "\"" * str(String_body) * "\"" # Numbers are converted to Elixir float - Minus <- '-' - Int_part <- '0' | {'1'..'9'} * star({'0'..'9'}) - Fract_part <- "." * +{'0'..'9'} - Exp_part <- {'e', 'E'} * opt({'+', '-'}) * +{'0'..'9'} + Minus <- "-" + Int_part <- "0" | {~c"1"..~c"9"} * star({~c"0"..~c"9"}) + Fract_part <- "." * +{~c"0"..~c"9"} + Exp_part <- {~c"e", ~c"E"} * opt({~c"+", ~c"-"}) * +{~c"0"..~c"9"} Number <- float(opt(Minus) * Int_part * opt(Fract_part) * opt(Exp_part)) # Objects are represented by an Elixir map Obj_pair <- S * String * S * ":" * Value * fn [v, k, obj | cs] -> [Map.put(obj, k, v) | cs] end - Object <- '{' * fn cs -> [%{} | cs] end * (Obj_pair * star("," * Obj_pair) | S) * "}" + Object <- "{" * fn cs -> [%{} | cs] end * (Obj_pair * star("," * Obj_pair) | S) * "}" # Arrays are represented by an Elixir list Array_elem <- Value * fn [v, a | cs] -> [[v | a] | cs] end diff --git a/test/xpeg_test.exs b/test/xpeg_test.exs index 80786af..a139ee9 100644 --- a/test/xpeg_test.exs +++ b/test/xpeg_test.exs @@ -20,7 +20,7 @@ defmodule XpegTest do run(patt("a"), "a") run(patt("a"), "b", :error) run(patt("abc"), "abc") - run(patt('abc'), "abc") + run(patt(~c"abc"), "abc") run(patt(~c"abc"), "abc") run(patt("abc"), "-bcd", :error) run(patt("abc"), "a-cd", :error) @@ -29,38 +29,38 @@ defmodule XpegTest do end test "set" do - run(patt({'a'}), "a") - run(patt({'b'}), "a", :error) - run(patt({'a', 'b'}), "a") - run(patt({'a', 'b'}), "b") - run(patt({'a', 'b'}), "c", :error) - run(patt({'a', 'b', 'c'}), "a") - run(patt({'a', 'b', 'c'}), "b") - run(patt({'a', 'b', 'c'}), "c") - run(patt({'a', 'b', 'c'}), "d", :error) - run(patt({'a'..'c'}), "a") - run(patt({'a'..'c'}), "b") - run(patt({'a'..'c'}), "c") - run(patt({'a'..'c'}), "d", :error) - run(patt({'a'..'c', 'd'}), "a") - run(patt({'a'..'c', 'd'}), "b") - run(patt({'a'..'c', 'd'}), "c") - run(patt({'a'..'c', 'd'}), "d") - run(patt({'a', 'b'..'d'}), "a") - run(patt({'a', 'b'..'d'}), "b") - run(patt({'a', 'b'..'d'}), "c") - run(patt({'a', 'b'..'d'}), "d") - run(patt({'a', 'b'..'c', 'd'}), "a") - run(patt({'a', 'b'..'c', 'd'}), "b") - run(patt({'a', 'b'..'c', 'd'}), "c") - run(patt({'a', 'b'..'c', 'd'}), "d") - run(patt({'a'..'c', 'e'..'g'}), "a") - run(patt({'a'..'c', 'e'..'g'}), "b") - run(patt({'a'..'c', 'e'..'g'}), "c") - run(patt({'a'..'c', 'e'..'g'}), "d", :error) - run(patt({'a'..'c', 'e'..'g'}), "e") - run(patt({'a'..'c', 'e'..'g'}), "f") - run(patt({'a'..'c', 'e'..'g'}), "g") + run(patt({~c"a"}), "a") + run(patt({~c"b"}), "a", :error) + run(patt({~c"a", ~c"b"}), "a") + run(patt({~c"a", ~c"b"}), "b") + run(patt({~c"a", ~c"b"}), "c", :error) + run(patt({~c"a", ~c"b", ~c"c"}), "a") + run(patt({~c"a", ~c"b", ~c"c"}), "b") + run(patt({~c"a", ~c"b", ~c"c"}), "c") + run(patt({~c"a", ~c"b", ~c"c"}), "d", :error) + run(patt({~c"a"..~c"c"}), "a") + run(patt({~c"a"..~c"c"}), "b") + run(patt({~c"a"..~c"c"}), "c") + run(patt({~c"a"..~c"c"}), "d", :error) + run(patt({~c"a"..~c"c", ~c"d"}), "a") + run(patt({~c"a"..~c"c", ~c"d"}), "b") + run(patt({~c"a"..~c"c", ~c"d"}), "c") + run(patt({~c"a"..~c"c", ~c"d"}), "d") + run(patt({~c"a", ~c"b"..~c"d"}), "a") + run(patt({~c"a", ~c"b"..~c"d"}), "b") + run(patt({~c"a", ~c"b"..~c"d"}), "c") + run(patt({~c"a", ~c"b"..~c"d"}), "d") + run(patt({~c"a", ~c"b"..~c"c", ~c"d"}), "a") + run(patt({~c"a", ~c"b"..~c"c", ~c"d"}), "b") + run(patt({~c"a", ~c"b"..~c"c", ~c"d"}), "c") + run(patt({~c"a", ~c"b"..~c"c", ~c"d"}), "d") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "a") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "b") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "c") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "d", :error) + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "e") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "f") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "g") end test "set using sigil" do @@ -90,13 +90,13 @@ defmodule XpegTest do run(patt({~c"a", ~c"b"..~c"c", ~c"d"}), "b") run(patt({~c"a", ~c"b"..~c"c", ~c"d"}), "c") run(patt({~c"a", ~c"b"..~c"c", ~c"d"}), "d") - run(patt({~c"a"..~c"c", 'e'..~c"g"}), "a") - run(patt({~c"a"..~c"c", 'e'..~c"g"}), "b") - run(patt({~c"a"..~c"c", 'e'..~c"g"}), "c") - run(patt({~c"a"..~c"c", 'e'..~c"g"}), "d", :error) - run(patt({~c"a"..~c"c", 'e'..~c"g"}), "e") - run(patt({~c"a"..~c"c", 'e'..~c"g"}), "f") - run(patt({~c"a"..~c"c", 'e'..~c"g"}), "g") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "a") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "b") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "c") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "d", :error) + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "e") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "f") + run(patt({~c"a"..~c"c", ~c"e"..~c"g"}), "g") end test "zero-or-one" do @@ -106,10 +106,10 @@ defmodule XpegTest do end test "zero-or-more" do - run(patt(star('a')), "aaaa") - run(patt(star('a') * 'b'), "aaaab") - run(patt(star('a') * 'b'), "bbbbb") - run(patt(star('a') * 'b'), "caaab", :error) + run(patt(star(~c"a")), "aaaa") + run(patt(star(~c"a") * ~c"b"), "aaaab") + run(patt(star(~c"a") * ~c"b"), "bbbbb") + run(patt(star(~c"a") * ~c"b"), "caaab", :error) end test "zero-or-more using sigils" do @@ -120,9 +120,9 @@ defmodule XpegTest do end test "one-or-more" do - run(patt(+'a' * 'b'), "aaaab") - run(patt(+'a' * 'b'), "ab") - run(patt(+'a' * 'b'), "b", :error) + run(patt(+~c"a" * ~c"b"), "aaaab") + run(patt(+~c"a" * ~c"b"), "ab") + run(patt(+~c"a" * ~c"b"), "b", :error) end test "one-or-more using sigils" do @@ -132,8 +132,8 @@ defmodule XpegTest do end test "not-predicate" do - run(patt('a' * !'b'), "ac") - run(patt('a' * !'b'), "ab", :error) + run(patt(~c"a" * !~c"b"), "ac") + run(patt(~c"a" * !~c"b"), "ab", :error) end test "not-predicate using sigils" do @@ -156,15 +156,15 @@ defmodule XpegTest do end test "[m..n]: count" do - run(patt('a'[2..4] * !1), "", :error) - run(patt('a'[2..4] * !1), "a", :error) - run(patt('a'[2..4] * !1), "aa") - run(patt('a'[2..4] * !1), "aaa") - run(patt('a'[2..4] * !1), "aaaa") - run(patt('a'[2..4] * !1), "aaaaa", :error) - run(patt('a'[0..1] * !1), "") - run(patt('a'[0..1] * !1), "a") - run(patt('a'[0..1] * !1), "aa", :error) + run(patt(~c"a"[2..4] * !1), "", :error) + run(patt(~c"a"[2..4] * !1), "a", :error) + run(patt(~c"a"[2..4] * !1), "aa") + run(patt(~c"a"[2..4] * !1), "aaa") + run(patt(~c"a"[2..4] * !1), "aaaa") + run(patt(~c"a"[2..4] * !1), "aaaaa", :error) + run(patt(~c"a"[0..1] * !1), "") + run(patt(~c"a"[0..1] * !1), "a") + run(patt(~c"a"[0..1] * !1), "aa", :error) end test "[m..n]: count using sigils" do @@ -194,24 +194,24 @@ defmodule XpegTest do test "-: difference" do run(patt("abcd" - "abcdef"), "abcdefgh", :error) run(patt("abcd" - "abcdf"), "abcdefgh") - run(patt({'a', 'b', 'c'} - {'a'}), "a", :error) + run(patt({~c"a", ~c"b", ~c"c"} - {~c"a"}), "a", :error) run(patt({~c"a", ~c"b", ~c"c"} - {~c"a"}), "a", :error) end test "Misc combos" do - run(patt('a' | 'b' * 'c'), "a") - run(patt('a' | 'b' * 'c' | 'd' * 'e' * 'f'), "a") - run(patt('a' | 'b' * 'c' | 'd' * 'e' * 'f'), "bc") - run(patt('a' | 'b' * 'c' | 'd' * 'e' * 'f'), "def") - run(patt({'a', 'b'} * 'c' | {'a', 'b'} * 'e'), "ac") - run(patt({'a', 'b'} * 'c' | {'a', 'b'} * 'e'), "ae") + run(patt(~c"a" | ~c"b" * ~c"c"), "a") + run(patt(~c"a" | ~c"b" * ~c"c" | ~c"d" * ~c"e" * ~c"f"), "a") + run(patt(~c"a" | ~c"b" * ~c"c" | ~c"d" * ~c"e" * ~c"f"), "bc") + run(patt(~c"a" | ~c"b" * ~c"c" | ~c"d" * ~c"e" * ~c"f"), "def") + run(patt({~c"a", ~c"b"} * ~c"c" | {~c"a", ~c"b"} * ~c"e"), "ac") + run(patt({~c"a", ~c"b"} * ~c"c" | {~c"a", ~c"b"} * ~c"e"), "ae") end test "Misc combos using sigils" do run(patt(~c"a" | ~c"b" * ~c"c"), "a") - run(patt(~c"a" | ~c"b" * ~c"c" | ~c"d" * ~c"e" * 'f'), "a") - run(patt(~c"a" | ~c"b" * ~c"c" | ~c"d" * ~c"e" * 'f'), "bc") - run(patt(~c"a" | ~c"b" * ~c"c" | ~c"d" * ~c"e" * 'f'), "def") + run(patt(~c"a" | ~c"b" * ~c"c" | ~c"d" * ~c"e" * ~c"f"), "a") + run(patt(~c"a" | ~c"b" * ~c"c" | ~c"d" * ~c"e" * ~c"f"), "bc") + run(patt(~c"a" | ~c"b" * ~c"c" | ~c"d" * ~c"e" * ~c"f"), "def") run(patt({~c"a", ~c"b"} * ~c"c" | {~c"a", ~c"b"} * ~c"e"), "ac") run(patt({~c"a", ~c"b"} * ~c"c" | {~c"a", ~c"b"} * ~c"e"), "ae") end From a8d0b0411d31f2c7d0d5dd31b0b9160103cd800c Mon Sep 17 00:00:00 2001 From: Ianko Leite Date: Fri, 17 Oct 2025 18:34:37 -0400 Subject: [PATCH 5/5] bump version --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 54cf7ac..6c2362a 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Xpeg.MixProject do def project do [ app: :xpeg, - version: "0.10.0", + version: "0.11.0", description: "Native Elixir PEG (Parsing Expression Grammars) string matching library", elixir: "~> 1.10", start_permanent: Mix.env() == :prod,