From 5517162a93ac030f55ab80f504e04e7553447355 Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Mon, 11 Jan 2021 19:02:51 +0900 Subject: [PATCH] Add json module --- modules/json.krk | 174 +++++++++++++++++++++++++++++++++ test/testJsonModule.krk | 121 +++++++++++++++++++++++ test/testJsonModule.krk.expect | 2 + 3 files changed, 297 insertions(+) create mode 100644 modules/json.krk create mode 100644 test/testJsonModule.krk create mode 100644 test/testJsonModule.krk.expect diff --git a/modules/json.krk b/modules/json.krk new file mode 100644 index 0000000..04ee42a --- /dev/null +++ b/modules/json.krk @@ -0,0 +1,174 @@ +""" +JSON parser +""" + +def loads(s): + let i = 0 + let value # okay dumb scoping thing, but we'll assign this to _value later... + def peek(): + return s[i] if i < len(s) else None + def advance(): + i++ + def whitespace(): + while peek() in ' \r\n\t': advance() + def string(): + if peek() != '"': raise ValueError("String should start with \" but got " + peek()) + advance() + let out = "" + let c = peek() + while c != '"': + if c is None: raise ValueError("Unterminated string") + if c == '\\': + advance() + c = peek() + if c == '"': out += '"' + elif c == '\\': out += '\\' + elif c == '/': out += '/' + elif c == 'b': out += '\x08' # Backspace + elif c == 'f': out += '\x0c' # Formfeed + elif c == 'n': out += '\n' + elif c == 'r': out += '\r' + elif c == 't': out += '\t' + elif c == 'u': + advance() + let sub = [0,0,0,0] + sub[0] = peek() + advance() + sub[1] = peek() + advance() + sub[2] = peek() + advance() + sub[3] = peek() + if not all([x in '0123456789abcdefABCDEF' for x in sub]): + raise ValueError('Invalid codepoint in JSON string') + raise NotImplementedError('TODO: unicode escapes') + else: + raise ValueError('Invalid escape sequence in JSON string') + else: + out += c + advance() + c = peek() + if peek() != '"': + raise ValueError("That was not expected.") + advance() + return out + def obj(): + if peek() != '{': raise ValueError("Object should start with {?") + advance() + let out = {} + whitespace() + if peek() == '}': return out + while True: + whitespace() + let k = string() + whitespace() + if peek() != ':': raise ValueError("Expected : after key, got " + peek()) + advance() + let v = value() + out[k] = v + if peek() == '}': + advance() + return out + if peek() != ',': + raise ValueError('Expected , after value, or } for end of object.') + advance() + def array(): + if peek() != '[': raise ValueError("Array should start with [?") + advance() + let out = [] + whitespace() + if peek() == ']': return out + while True: + whitespace() + out.append(value()) + if peek() == ']': + advance() + return out + if peek() != ',': + raise ValueError("Expected , after value, or ] for end of array.") + advance() + def number(): + let value = 0 + let sign = 1 + if peek() == '-': + sign = -1 + advance() + if peek() == '0': + advance() + elif peek() in '0123456789': + value = int(peek()) + advance() + while peek() in '0123456789': + value = value * 10 + value = value + int(peek()) + advance() + else: + raise ValueError("Expected digit") + if peek() == '.': + advance() + let mult = 0.1 + if peek() not in '0123456789': + raise ValueError("Expected digit") + while peek() in '0123456789': + value = value + multiplier * int(peek()) + multiplier = multiplier * 0.1 + advance() + if peek() in 'eE': + raise NotImplementedError("Exponent values are not implemented.") + return value + def boolean(): + let inv = ValueError("Invalid literal while parsing bool") + if peek() == 't': + advance() + if peek() != 'r': raise inv + advance() + if peek() != 'u': raise inv + advance() + if peek() != 'e': raise inv + advance() + return True + elif peek() == 'f': + advance() + if peek() != 'a': raise inv + advance() + if peek() != 'l': raise inv + advance() + if peek() != 's': raise inv + advance() + if peek() != 'e': raise inv + advance() + return False + raise inv + def null(): + let inv = ValueError("Invalid literal while parsing null") + if peek() != 'n': raise inv + advance() + if peek() != 'u': raise inv + advance() + if peek() != 'l': raise inv + advance() + if peek() != 'l': raise inv + advance() + return None + def _value(): + whitespace() + let c = peek() + let out = None + if c== '"': + out = string() + elif c == '{': + out = obj() + elif c == '[': + out = array() + elif c == '-' or c in '0123456789': + out = number() + elif c == 't' or c == 'f': + out = boolean() + elif c == 'n': + out = null() + else: + raise ValueError("Unexpected character") + whitespace() + return out + value = _value + return value() diff --git a/test/testJsonModule.krk b/test/testJsonModule.krk new file mode 100644 index 0000000..a6c615f --- /dev/null +++ b/test/testJsonModule.krk @@ -0,0 +1,121 @@ +import json + +let data = ''' +{ + "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" + } + } + } + } +} +''' + +print(json.loads(data)) + +data = ''' +{"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"}}} +''' + +print(json.loads(data)) diff --git a/test/testJsonModule.krk.expect b/test/testJsonModule.krk.expect new file mode 100644 index 0000000..fa14089 --- /dev/null +++ b/test/testJsonModule.krk.expect @@ -0,0 +1,2 @@ +{'glossary': {'title': 'example glossary', 'GlossDiv': {'title': 'S', 'GlossList': {'GlossEntry': {'SortAs': 'SGML', 'Abbrev': 'ISO 8879:1986', 'Acronym': 'SGML', 'GlossTerm': 'Standard Generalized Markup Language', 'GlossSee': 'markup', 'ID': 'SGML', 'GlossDef': {'para': 'A meta-markup language, used to create markup languages such as DocBook.', 'GlossSeeAlso': ['GML', 'XML']}}}}}} +{'web-app': {'taglib': {'taglib-uri': 'cofax.tld', 'taglib-location': '/WEB-INF/tlds/cofax.tld'}, 'servlet': [{'servlet-class': 'org.cofax.cds.CDSServlet', 'init-param': {'templateProcessorClass': 'org.cofax.WysiwygTemplate', 'cachePagesStore': 100, 'searchEngineListTemplate': 'forSearchEnginesList.htm', 'searchEngineFileTemplate': 'forSearchEngines.htm', 'cachePackageTagsStore': 200, 'dataStoreUser': 'sa', 'dataStoreInitConns': 10, 'configGlossary:installationAt': 'Philadelphia, PA', 'cachePagesTrack': 200, 'dataStoreLogFile': '/usr/local/tomcat/logs/datastore.log', 'templateOverridePath': '', 'redirectionClass': 'org.cofax.SqlRedirection', 'cachePackageTagsTrack': 200, 'useDataStore': True, 'maxUrlLength': 500, 'dataStorePassword': 'dataStoreTestQuery', 'defaultFileTemplate': 'articleTemplate.htm', 'defaultListTemplate': 'listTemplate.htm', 'dataStoreConnUsageLimit': 100, 'templatePath': 'templates', 'useJSP': False, 'configGlossary:poweredBy': 'Cofax', 'dataStoreClass': 'org.cofax.SqlDataStore', 'dataStoreName': 'cofax', 'cacheTemplatesRefresh': 15, 'dataStoreDriver': 'com.microsoft.jdbc.sqlserver.SQLServerDriver', 'cachePagesDirtyRead': 10, 'configGlossary:adminEmail': 'ksm@pobox.com', 'dataStoreTestQuery': 'SET NOCOUNT ON;select test=\'test\';', 'cacheTemplatesStore': 50, 'templateLoaderClass': 'org.cofax.FilesTemplateLoader', 'configGlossary:staticPath': '/content/static', 'searchEngineRobotsDb': 'WEB-INF/robots.db', 'cacheTemplatesTrack': 100, 'dataStoreLogLevel': 'debug', 'dataStoreUrl': 'jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon', 'cachePagesRefresh': 10, 'configGlossary:poweredByIcon': '/images/cofax.gif', 'dataStoreMaxConns': 100, 'jspFileTemplate': 'articleTemplate.jsp', 'cachePackageTagsRefresh': 60, 'jspListTemplate': 'listTemplate.jsp'}, 'servlet-name': 'cofaxCDS'}, {'servlet-class': 'org.cofax.cds.EmailServlet', 'init-param': {'mailHostOverride': 'mail2', 'mailHost': 'mail1'}, 'servlet-name': 'cofaxEmail'}, {'servlet-class': 'org.cofax.cds.AdminServlet', 'servlet-name': 'cofaxAdmin'}, {'servlet-class': 'org.cofax.cds.FileServlet', 'servlet-name': 'fileServlet'}, {'servlet-class': 'org.cofax.cms.CofaxToolsServlet', 'init-param': {'logMaxSize': '', 'log': 1, 'removeTemplateCache': '/content/admin/remove?cache=templates&id=', 'dataLogMaxSize': '', 'lookInContext': 1, 'adminGroupID': 4, 'removePageCache': '/content/admin/remove?cache=pages&id=', 'dataLogLocation': '/usr/local/tomcat/logs/dataLog.log', 'betaServer': True, 'fileTransferFolder': '/usr/local/tomcat/webapps/content/fileTransferFolder', 'logLocation': '/usr/local/tomcat/logs/CofaxTools.log', 'dataLog': 1, 'templatePath': 'toolstemplates/'}, 'servlet-name': 'cofaxTools'}], 'servlet-mapping': {'cofaxCDS': '/', 'fileServlet': '/static/*', 'cofaxEmail': '/cofaxutil/aemail/*', 'cofaxTools': '/tools/*', 'cofaxAdmin': '/admin/*'}}}