Last active
December 19, 2018 21:37
-
-
Save hmenke/4536dda27095634b4563a1a9d854a040 to your computer and use it in GitHub Desktop.
Very simple JSON parser and serializer with LPEG
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local lpeg = assert(require("lpeg")) | |
local C, Cf, Cg, Ct, P, R, S, V = | |
lpeg.C, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.P, lpeg.R, lpeg.S, lpeg.V | |
-- number parsing | |
local digit = R"09" | |
local dot = P"." | |
local eE = S"eE" | |
local sign = S"+-"^-1 | |
local mantissa = digit^1 * dot * digit^0 + dot * digit^1 + digit^1 | |
local exponent = (eE * sign * digit^1)^-1 | |
local real = sign * mantissa * exponent / tonumber | |
-- optional whitespace | |
local ws = S" \t\n\r"^0 | |
-- match a literal string surrounded by whitespace | |
local lit = function(str) | |
return ws * P(str) * ws | |
end | |
-- match a literal string and synthesize an attribute | |
local attr = function(str,attr) | |
return ws * P(str) / function() return attr end * ws | |
end | |
-- JSON grammar | |
local json = P{ | |
"object", | |
value = | |
V"null_value" + | |
V"bool_value" + | |
V"string_value" + | |
V"real_value" + | |
V"array" + | |
V"object", | |
null_value = | |
attr("null", nil), | |
bool_value = | |
attr("true", true) + attr("false", false), | |
string_value = | |
ws * P'"' * C((P'\\"' + 1 - P'"')^0) * P'"' * ws, | |
real_value = | |
ws * real * ws, | |
array = | |
lit"[" * Ct((V"value" * lit","^-1)^0) * lit"]", | |
member_pair = | |
Cg(V"string_value" * lit":" * V"value") * lit","^-1, | |
object = | |
lit"{" * Cf(Ct"" * V"member_pair"^0, rawset) * lit"}" | |
} | |
local serialize | |
serialize = function(tab, indent) | |
local str = "" | |
-- indentation for nested values | |
indent = indent or 0 | |
local s = string.rep(" ", indent) | |
-- count the number of entries in the table | |
local n = 0 | |
for _ in pairs(tab) do n = n + 1 end | |
-- check if the table is an array | |
local isarray = tab[1] ~= nil and #tab == n | |
local iterator = isarray and ipairs or pairs | |
-- opening delimiter | |
str = str .. (isarray and "[\n" or "{\n") | |
local i = 0 | |
for k,v in iterator(tab) do | |
-- common prefix with indent | |
local p | |
if isarray then | |
p = s | |
else | |
p = s .. '"' .. k .. '": ' | |
end | |
if type(v) == "table" then | |
-- serialize a table recursively | |
str = str .. (p) | |
str = str .. serialize(v, indent + 2) | |
elseif type(v) == "string" then | |
-- serialize string quoted | |
str = str .. (p .. '"' .. v .. '"') | |
else | |
-- convert everything else just to string | |
str = str .. (p .. tostring(v) .. '') | |
end | |
-- add a comma in the end unless it's the last element | |
i = i + 1 | |
str = str .. (i == n and "\n" or ",\n") | |
end | |
-- closing delimiter with unindent | |
str = str .. (string.rep(" ", indent - 2) .. (isarray and "]" or "}")) | |
return str | |
end | |
--[[ | |
Examples from https://www.json.org/example.html | |
--]] | |
local example = [[ | |
{ | |
"glossary": { | |
"title": "example glossary", | |
"GlossDiv": { | |
"title": "S", | |
"GlossList": { | |
"GlossEntry": { | |
"ID": "SGML", | |
"SortAs": "SGML", | |
"GlossTerm": "Standard Generalized Markup Language", | |
"Acronym": "SGML", | |
"Abbrev": "ISO 8879:1986", | |
"GlossDef": { | |
"para": "A meta-markup language, used to create markup languages such as DocBook.", | |
"GlossSeeAlso": ["GML", "XML"] | |
}, | |
"GlossSee": "markup" | |
} | |
} | |
} | |
} | |
} | |
]] | |
local m = json:match(example) | |
print(serialize(m)) | |
print(string.rep("=",20)) | |
local example = [[ | |
{"menu": { | |
"id": "file", | |
"value": "File", | |
"popup": { | |
"menuitem": [ | |
{"value": "New", "onclick": "CreateNewDoc()"}, | |
{"value": "Open", "onclick": "OpenDoc()"}, | |
{"value": "Close", "onclick": "CloseDoc()"} | |
] | |
} | |
}} | |
]] | |
local m = json:match(example) | |
print(serialize(m)) | |
print(string.rep("=",20)) | |
local example = [[ | |
{"widget": { | |
"debug": "on", | |
"window": { | |
"title": "Sample Konfabulator Widget", | |
"name": "main_window", | |
"width": 500, | |
"height": 500 | |
}, | |
"image": { | |
"src": "Images/Sun.png", | |
"name": "sun1", | |
"hOffset": 250, | |
"vOffset": 250, | |
"alignment": "center" | |
}, | |
"text": { | |
"data": "Click Here", | |
"size": 36, | |
"style": "bold", | |
"name": "text1", | |
"hOffset": 250, | |
"vOffset": 100, | |
"alignment": "center", | |
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" | |
} | |
}} | |
]] | |
local m = json:match(example) | |
print(serialize(m)) | |
print(string.rep("=",20)) | |
local example = [[ | |
{"web-app": { | |
"servlet": [ | |
{ | |
"servlet-name": "cofaxCDS", | |
"servlet-class": "org.cofax.cds.CDSServlet", | |
"init-param": { | |
"configGlossary:installationAt": "Philadelphia, PA", | |
"configGlossary:adminEmail": "ksm@pobox.com", | |
"configGlossary:poweredBy": "Cofax", | |
"configGlossary:poweredByIcon": "/images/cofax.gif", | |
"configGlossary:staticPath": "/content/static", | |
"templateProcessorClass": "org.cofax.WysiwygTemplate", | |
"templateLoaderClass": "org.cofax.FilesTemplateLoader", | |
"templatePath": "templates", | |
"templateOverridePath": "", | |
"defaultListTemplate": "listTemplate.htm", | |
"defaultFileTemplate": "articleTemplate.htm", | |
"useJSP": false, | |
"jspListTemplate": "listTemplate.jsp", | |
"jspFileTemplate": "articleTemplate.jsp", | |
"cachePackageTagsTrack": 200, | |
"cachePackageTagsStore": 200, | |
"cachePackageTagsRefresh": 60, | |
"cacheTemplatesTrack": 100, | |
"cacheTemplatesStore": 50, | |
"cacheTemplatesRefresh": 15, | |
"cachePagesTrack": 200, | |
"cachePagesStore": 100, | |
"cachePagesRefresh": 10, | |
"cachePagesDirtyRead": 10, | |
"searchEngineListTemplate": "forSearchEnginesList.htm", | |
"searchEngineFileTemplate": "forSearchEngines.htm", | |
"searchEngineRobotsDb": "WEB-INF/robots.db", | |
"useDataStore": true, | |
"dataStoreClass": "org.cofax.SqlDataStore", | |
"redirectionClass": "org.cofax.SqlRedirection", | |
"dataStoreName": "cofax", | |
"dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", | |
"dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", | |
"dataStoreUser": "sa", | |
"dataStorePassword": "dataStoreTestQuery", | |
"dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", | |
"dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", | |
"dataStoreInitConns": 10, | |
"dataStoreMaxConns": 100, | |
"dataStoreConnUsageLimit": 100, | |
"dataStoreLogLevel": "debug", | |
"maxUrlLength": 500}}, | |
{ | |
"servlet-name": "cofaxEmail", | |
"servlet-class": "org.cofax.cds.EmailServlet", | |
"init-param": { | |
"mailHost": "mail1", | |
"mailHostOverride": "mail2"}}, | |
{ | |
"servlet-name": "cofaxAdmin", | |
"servlet-class": "org.cofax.cds.AdminServlet"}, | |
{ | |
"servlet-name": "fileServlet", | |
"servlet-class": "org.cofax.cds.FileServlet"}, | |
{ | |
"servlet-name": "cofaxTools", | |
"servlet-class": "org.cofax.cms.CofaxToolsServlet", | |
"init-param": { | |
"templatePath": "toolstemplates/", | |
"log": 1, | |
"logLocation": "/usr/local/tomcat/logs/CofaxTools.log", | |
"logMaxSize": "", | |
"dataLog": 1, | |
"dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", | |
"dataLogMaxSize": "", | |
"removePageCache": "/content/admin/remove?cache=pages&id=", | |
"removeTemplateCache": "/content/admin/remove?cache=templates&id=", | |
"fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", | |
"lookInContext": 1, | |
"adminGroupID": 4, | |
"betaServer": true}}], | |
"servlet-mapping": { | |
"cofaxCDS": "/", | |
"cofaxEmail": "/cofaxutil/aemail/*", | |
"cofaxAdmin": "/admin/*", | |
"fileServlet": "/static/*", | |
"cofaxTools": "/tools/*"}, | |
"taglib": { | |
"taglib-uri": "cofax.tld", | |
"taglib-location": "/WEB-INF/tlds/cofax.tld"}}} | |
]] | |
local m = json:match(example) | |
print(serialize(m)) | |
print(string.rep("=",20)) | |
local example = [[ | |
{"menu": { | |
"header": "SVG Viewer", | |
"items": [ | |
{"id": "Open"}, | |
{"id": "OpenNew", "label": "Open New"}, | |
null, | |
{"id": "ZoomIn", "label": "Zoom In"}, | |
{"id": "ZoomOut", "label": "Zoom Out"}, | |
{"id": "OriginalView", "label": "Original View"}, | |
null, | |
{"id": "Quality"}, | |
{"id": "Pause"}, | |
{"id": "Mute"}, | |
null, | |
{"id": "Find", "label": "Find..."}, | |
{"id": "FindAgain", "label": "Find Again"}, | |
{"id": "Copy"}, | |
{"id": "CopyAgain", "label": "Copy Again"}, | |
{"id": "CopySVG", "label": "Copy SVG"}, | |
{"id": "ViewSVG", "label": "View SVG"}, | |
{"id": "ViewSource", "label": "View Source"}, | |
{"id": "SaveAs", "label": "Save As"}, | |
null, | |
{"id": "Help"}, | |
{"id": "About", "label": "About Adobe CVG Viewer..."} | |
] | |
}} | |
]] | |
local m = json:match(example) | |
print(serialize(m)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment