Skip to content

Instantly share code, notes, and snippets.

@jnwhiteh
Created June 3, 2017 15:53
Show Gist options
  • Save jnwhiteh/4b7297414f68b75e7f8d703736a92feb to your computer and use it in GitHub Desktop.
Save jnwhiteh/4b7297414f68b75e7f8d703736a92feb to your computer and use it in GitHub Desktop.
An LPEG grammar for the IRC protocol
require("luarocks.require")
require("lpeg")
local v = lpeg.V
local c = lpeg.C
local grammar = {
"message", -- Initial rule
message = (v"COLON" * c(v"prefix"))^-1 * c(v"command") * c(v"params") * v"CRLF",
params = v"SPACE" * ((v"COLON" * v"trailing") + (v"middle" * v"params"))^-1,
nospcrlfcl = lpeg.R("\001\009", "\011\012", "\014\031", "\033\057", "\059\255"),
middle = v"nospcrlfcl" * (v"COLON" + v"nospcrlfcl")^0,
trailing = (v"COLON" + v"SPACE" + v"nospcrlfcl")^0,
prefix = (c(v"servername") * v"SPACE") + ((c(v"nickname") * (v"BANG" * c(v"user"))^-1 * (v"AT" * c(v"host"))^-1) * v"SPACE"),
command = (v"LETTER" ^ 1) + (v"DIGIT" * v"DIGIT" * v"DIGIT"),
triple = v"DIGIT" * (v"DIGIT"^-1) * (v"DIGIT"^-1),
servername = v"hostname",
host = v"hostname" + v"hostaddr",
hostname = v"shortname" * (v"DOT" * v"shortname")^0,
shortname = (v"LETTER" + v"DIGIT") * (v"LETTER" + v"DIGIT" + v"DASH")^0 * (v"LETTER" + v"DIGIT")^0,
hostaddr = v"ip4addr",
ip4addr = v"triple" * v"DOT" * v"triple" * v"DOT" * v"triple" * v"DOT" * v"triple",
nickname = (v"LETTER" + v"SPECIAL") * (v"LETTER" + v"DIGIT" + v"SPECIAL" + v"DASH")^-8,
user = lpeg.R("\001\009", "\011\012", "\014\031", "\033\063", "\065\255")^1,
CRLF = lpeg.P("\r\n"),
DASH = lpeg.P("-"),
USCORE = lpeg.P("_"),
DOT = lpeg.P("."),
SPACE = lpeg.P(" "),
COLON = lpeg.P(":"),
BANG = lpeg.P("!"),
AT = lpeg.P("@"),
LETTER = lpeg.R("az", "AZ"),
DIGIT = lpeg.R("09"),
SPECIAL = lpeg.S(";[]\\`_^{|}"),
}
parser = lpeg.P(grammar)
local function test(entry, subject, expected)
subject = subject
local nt = {}
for k, v in pairs(grammar) do
nt[k] = v
end
nt[1] = entry
local pattern, err = pcall(lpeg.P, nt)
if not pattern then
error("Failed to compile test for entry: " .. entry .. "(" .. tostring(err) .. ")")
end
local match = lpeg.match(err, subject)
assert(match, "Failed test for " .. entry .. " with '" .. subject .. "'")
if match ~= expected then
error("Failed a complete match for " .. entry .. " got " .. match .. " for " .. #subject .. " string " .. subject)
end
io.stdout:write(".")
end
local function fail(entry, subject)
local succ,err = pcall(test, entry, subject)
assert(not succ, "Did not fail when expected: " .. entry .. " with " .. subject)
end
do
return
end
io.stdout:write("testing")
test("CRLF", "\r\n", 3)
test("COLON", ":", 2)
test("BANG", "!", 2)
test("AT", "@", 2)
test("LETTER", "Monkey", 2)
test("DIGIT", 31337, 2)
test("prefix", "MoNkEyS ", 9)
test("command", "PRIVMSG", 8)
test("nickname", "Cladhaire", 10)
test("nickname", "{clad|foo}", 10)
test("hostname", "wowforge.com", 13)
test("host", "wowforge.com", 13)
test("hostaddr", "128.256.232.122", 16)
test("command", "PING", 5)
test("params", " :anthony.freenode.net", 23)
test("message", "PING :anthony.freenode.net\r\n", 29)
test("nickname", "Aelo", 5)
test("user", "~Aeolbin", 9)
test("host", "81.170.1.150", 13)
test("prefix", "anthony.freenode.net ", 22)
test("prefix", "Aelo!~Aelobin@81.170.1.150 ", 28)
test("COLON", ":", 2)
test("SPACE", " ", 2)
test("nickname", "Aelo", 5)
test("BANG", "!", 2)
test("user", "~Aelobin", 9)
test("AT", "@", 2)
test("host", "81.170.1.150", 13)
test("command", "PRIVMSG", 8)
test("middle", "#wowui", 7)
test("params", " #wowui :rawr", 14)
test("message", ":Aelo!~Aelobin@81.170.1.150 PRIVMSG #wowui :rawr\r\n", 51)
-- Sanity
fail("LETTER", "1")
print(" complete!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment