spec/regex_subset_spec.cr @ trunk
spec
scale = 0.75 fontscale = 1.500 box wid 1.380 ht 0.340 with .nw at (0.035, -0.035) fill 0xCBD5E1 color 0xCBD5E1 box wid 1.380 ht 0.340 with .nw at (0.000, 0.000) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.022) fill 0xffffff color 0xffffff line from (0.140, 0.000) to (0.140, -0.340) color white line from (1.240, 0.000) to (1.240, -0.340) color white text "require 'spec'" color white at (0.690, -0.170)→ /pikchrshow
crystal source
require "spec"
json block
{
"kind": "require",
"name": "spec",
"doc": null,
"children": []
}
json
scale = 0.75 fontscale = 1.500 box wid 1.330 ht 0.340 with .nw at (0.035, -0.455) fill 0xCBD5E1 color 0xCBD5E1 box wid 1.330 ht 0.340 with .nw at (0.000, -0.420) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.443) fill 0xffffff color 0xffffff line from (0.140, -0.420) to (0.140, -0.760) color white line from (1.190, -0.420) to (1.190, -0.760) color white text "require 'json'" color white at (0.665, -0.590)→ /pikchrshow
crystal source
require "json"
json block
{
"kind": "require",
"name": "json",
"doc": null,
"children": []
}
../src/gpcr/regex_subset
scale = 0.75 fontscale = 1.500 box wid 2.894 ht 0.340 with .nw at (0.035, -0.875) fill 0xCBD5E1 color 0xCBD5E1 box wid 2.894 ht 0.340 with .nw at (0.000, -0.840) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.863) fill 0xffffff color 0xffffff line from (0.140, -0.840) to (0.140, -1.180) color white line from (2.754, -0.840) to (2.754, -1.180) color white text "require '../src/gpcr/regex_subset'" color white at (1.447, -1.010)→ /pikchrshow
crystal source
require "../src/gpcr/regex_subset"
json block
{
"kind": "require",
"name": "../src/gpcr/regex_subset",
"doc": null,
"children": []
}
../examples/robots_grammar
scale = 0.75 fontscale = 1.500 box wid 3.258 ht 0.340 with .nw at (0.035, -1.295) fill 0xCBD5E1 color 0xCBD5E1 box wid 3.258 ht 0.340 with .nw at (0.000, -1.260) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -1.283) fill 0xffffff color 0xffffff line from (0.140, -1.260) to (0.140, -1.600) color white line from (3.118, -1.260) to (3.118, -1.600) color white text "require '../examples/robots_grammar'" color white at (1.629, -1.430)→ /pikchrshow
crystal source
require "../examples/robots_grammar"
json block
{
"kind": "require",
"name": "../examples/robots_grammar",
"doc": null,
"children": []
}
EXPECTED_SOURCE = {
"main_head" => "๐โฎ๐โฏ๐โ",
"def_head" => "๐๐ฅ(โฎ(๐ฅ(๏ผ๐ฅ)*)?โฏ)?โ",
"global" => "๐โฎ๐ฅ๏ผ[โ๐๐๐ฅ]โฏโ",
"literal" => "[๐ข๐]",
"call0" => "โ",
"call" => "๐ฅโฎ([โ๐๐๐ฅ](๏ผ[โ๐๐๐ฅ])*)?โฏ",
"call1" => "โโฎ[โ๐๐๐ฅ]โฏ",
"call2" => "โโฎ[โ๐๐๐ฅ]๏ผ[โ๐๐๐ฅ]โฏ",
"paren" => "โฎ[โ๐๐๐ฅ]โฏ",
"neg" => "(?<![โ๐๐๐ฅโฏ])โ[โ๐๐๐ฅ]",
"mul" => "(?<![โ๏ผโ๏ผ
])[โ๐๐๐ฅ][โ๏ผโ๏ผ
][โ๐๐๐ฅ]",
"add" => "(?<![โ๏ผโ๏ผ
โโ])[โ๐๐๐ฅ][โโ][โ๐๐๐ฅ](?![โ๏ผโ๏ผ
])",
"cmp" => "(?<![โ๏ผโ๏ผ
โโโบโปโผโฝ])[โ๐๐๐ฅ][โบโปโผโฝ][โ๐๐๐ฅ](?![โ๏ผโ๏ผ
โโ])",
"eq" => "(?<![โ๏ผโ๏ผ
โโโบโปโผโฝโโ ])[โ๐๐๐ฅ][โโ ][โ๐๐๐ฅ](?![โ๏ผโ๏ผ
โโโบโปโผโฝ])",
"and" => "(?<![โ๏ผโ๏ผ
โโโบโปโผโฝโโ โง])[โ๐๐๐ฅ][โง][โ๐๐๐ฅ](?![โ๏ผโ๏ผ
โโโบโปโผโฝโโ ])",
"or" => "(?<![โ๏ผโ๏ผ
โโโบโปโผโฝโโ โงโจโป])[โ๐๐๐ฅ][โจโป][โ๐๐๐ฅ](?![โ๏ผโ๏ผ
โโโบโปโผโฝโโ โง])",
"command1" => "โ[โ๐๐๐ฅ](?=[โโฏ๏ผ])",
"command2" => "โ[โ๐๐๐ฅ]๏ผ[โ๐๐๐ฅ](?=[โโฏ๏ผ])",
"assign" => "๐ฅ๏ผ[โ๐๐๐ฅ](?=[โโฏ๏ผ])",
"opassign" => "๐ฅ[โโโโ][โ๐๐๐ฅ](?=[โโฏ๏ผ])",
"if_head" => "๐[โ๐๐๐ฅ]โ",
"elsif_head" => "๐[โ๐๐๐ฅ]โ",
"while_head" => "๐ฃ[โ๐๐๐ฅ]โ",
"until_head" => "๐[โ๐๐๐ฅ]โ",
"case_head" => "๐[โ๐๐๐ฅ]โ",
"when_head" => "๐ถ[โ๐๐๐ฅ]โ",
"return" => "โฉ[โ๐๐๐ฅ]?โ",
"break" => "๐โ",
"exprstmt" => "[โ๐๐๐ฅ]โ",
"if" => "๐
ธโข*(๐
ดโข*)*(๐โโข*)?๐โ",
"while" => "๐โข*๐โ",
"until" => "๐โข*๐โ",
"case" => "๐
ฒ(๐โข*)+(๐โโข*)?๐โ",
"def" => "๐
ณโข*๐โ",
"main" => "๐
ผโข*๐โ",
"program" => "\\Aโข+\\z",
}
describe "GPCR::RegexSubset against crystal-robots' real GRAMMAR table" do
it "ROBOTS_GRAMMAR covers every rule (36, verified against parser.cr 2026-09-12)" do
ROBOTS_GRAMMAR.size.should eq(36)
ROBOTS_GRAMMAR.map(&.name).to_set.should eq(EXPECTED_SOURCE.keys.to_set)
end
ROBOTS_GRAMMAR.each do |rule|
expected = EXPECTED_SOURCE[rule.name]
it "#{rule.name}: to_source matches the hand-written regex literal exactly" do
GPCR::RegexSubset.to_source(rule.pattern).should eq(expected)
end
it "#{rule.name}: emitted source builds a working Regex" do
GPCR::RegexSubset.to_regex(rule.pattern).should be_a(Regex)
end
it "#{rule.name}: round-trips through JSON with identical source" do
json = GPCR::RegexSubset.pattern_to_json(rule.pattern)
restored = GPCR::RegexSubset.pattern_from_json(JSON.parse(json))
GPCR::RegexSubset.to_source(restored).should eq(expected)
end
end
describe "behavioral spot checks on the trickiest rules" do
it "mul (infix, no higher guard) matches a bare multiplication, not addition" do
re = GPCR::RegexSubset.to_regex(ROBOTS_GRAMMAR.find! { |r| r.name == "mul" }.pattern)
"โโโ".should match(re)
"โโโ".should_not match(re)
end
it "add (infix, guarded by mul) matches a plain add but not one still claimed by a mul" do
re = GPCR::RegexSubset.to_regex(ROBOTS_GRAMMAR.find! { |r| r.name == "add" }.pattern)
"๐โโ".should match(re)
# the left operand of "โ" here is still adjacent to an unreduced "โ" โ
# the behind-guard must block the whole match, forcing mul to reduce
# โ โ ๐ into one value first. No match anywhere in the string proves
# the guard is doing its job, not matching a different substring.
"โโ๐โโ".match(re).should be_nil
end
it "if (block rule) matches a minimal if/end with no elsif or else" do
re = GPCR::RegexSubset.to_regex(ROBOTS_GRAMMAR.find! { |r| r.name == "if" }.pattern)
"๐
ธโข๐โ".should match(re)
end
it "if (block rule) matches with elsif and else branches present" do
re = GPCR::RegexSubset.to_regex(ROBOTS_GRAMMAR.find! { |r| r.name == "if" }.pattern)
"๐
ธโข๐
ดโข๐โโข๐โ".should match(re)
end
it "case (block rule) requires at least one when branch" do
re = GPCR::RegexSubset.to_regex(ROBOTS_GRAMMAR.find! { |r| r.name == "case" }.pattern)
"๐
ฒ๐โ".should_not match(re)
"๐
ฒ๐โข๐โ".should match(re)
end
end
end