{"id":2515,"date":"2019-08-26T15:10:09","date_gmt":"2019-08-26T14:10:09","guid":{"rendered":"https:\/\/solidt.eu\/site\/?p=2515"},"modified":"2022-07-25T08:35:43","modified_gmt":"2022-07-25T07:35:43","slug":"simple-javascript-parser","status":"publish","type":"post","link":"https:\/\/solidt.eu\/site\/simple-javascript-parser\/","title":{"rendered":"Simple javascript parser"},"content":{"rendered":"\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">function moveNext(scope, t) {\n    if (t == \"\\n\") {\n        scope.currentLine++;\n        scope.currentChar = 1;\n    } else {\n        scope.currentChar++;\n    }\n}\nfunction match(d, t) {\n    return d.regex ? d.t.test(t) : d.t.startsWith(t);\n}\nfunction hasMatch(scope, m) {\n    const def = m.def;\n    m.line = scope.currentLine;\n    m.char = scope.currentChar;\n    if (def.filter) return;\n\n    if (def.startScope) {\n        var p = scope.current;\n        var n = [];\n        n.parent = p;\n        n.scope = def.startScope;\n        n.push(m);\n        p.push(n);\n        scope.current = n;\n    } else if (def.endScope &amp;&amp; scope.current.scope === def.endScope) {\n        scope.current.push(m);\n        if (scope.current.parent) {\n            scope.current = scope.current.parent;\n        }\n    } else {\n        scope.current.push(m);\n    }\n}\nfunction noMatch(scope, t) {\n    scope.current.push({\n        def: { type: 'UNKNOWN' },\n        value: t,\n        line: scope.currentLine,\n        char: scope.currentChar\n    });\n}\n\/\/ t = current text, c = next character\nfunction parseToken(scope, t, c, tc) {\n    moveNext(scope, t);\n    let currentMatch = null;\n    for (const d of scope.defs) {\n        \/\/ next character matches also? token not complete: return\n        if (c !== '' &amp;&amp; match(d, tc)) {\n            return;\n        }\n        \/\/ test if token matches definition\n        if (match(d, t)) {\n            currentMatch = { def: d, value: t };\n            break;\n        }\n    }\n    if (currentMatch) {\n        hasMatch(scope, currentMatch);\n    } else {\n        noMatch(scope, t);\n    }\n    scope.break = true;\n}\nfunction createScope(gram) {\n    let scope = {\n        defs: [],\n        root: [],\n        break: false,\n        currentLine: 1,\n        currentChar: 1\n    };\n    scope.current = scope.root;\n    gram.defs.forEach((def) => {\n        let t = def.t;\n        if (!t) return;\n        if (!Array.isArray(t)) { t = [t]; }\n        for (let i = 0; i &lt; t.length; i += 1) {\n            let token = def.regex ? new RegExp('^' + t[i] + '$') : t[i];\n            scope.defs.push({ type: def.type, regex: def.regex, t: token, filter: def.filter, startScope: def.startScope, endScope: def.endScope });\n        }\n    });\n    return scope;\n}\nfunction lex(scope, input) {\n    let l = 0, r = 1;\n    while (r &lt;= input.length) {\n        let t = input.slice(l, r);\n        let c = input.slice(r, r + 1);\n        parseToken(scope, t, c, t + c);\n        if (scope.break) {\n            scope.break = false;\n            l = r;\n        }\n        r += 1;\n    }\n    return scope.root;\n}\nconst grammar = {\n    name: 'mangoGrammar',\n    defs: [\n        {\n            type: 'brace-start',\n            t: '(',\n            startScope: 'brace'\n        },\n        {\n            type: 'brace-end',\n            t: ')',\n            endScope: 'brace'\n        },\n        {\n            type: 'name',\n            t: '[_a-zA-Z][_a-zA-Z0-9]*',\n            regex: true\n        },\n        {\n            type: 'compare-operator',\n            t: ['==', '!=']\n        },\n        {\n            type: 'combine-operator',\n            t: ['&amp;&amp;', '||']\n        },\n        {\n            type: 'number',\n            t: '[0-9]+(\\\\.?[0-9]*)',\n            regex: true\n        },\n        {\n            type: 'whitespace',\n            t: '[^\\\\S\\\\r\\\\n]+',\n            regex: true,\n            filter: true\n        },\n        {\n            type: 'string',\n            \/\/t: '\"([^\"]*)\"?',\n            t: '\"([^\"\\\\\\\\]|\\\\\\\\.|\\\\\\\\)*\"?',\n            regex: true\n        }\n    ]\n};\nconst input = \"(category == 1.456123 &amp;&amp; name = \\\"a b \\\\\\\"c\\\\\\\" \\\\r\\\\n d\\\\\\\"\\\" &amp;&amp; bookNumber != 1) || bookNumber == 66\";\nconst scope = createScope(grammar);\nconst result = lex(scope, input);\nconst mapVal = (m) => {\n    if (Array.isArray(m)) {\n        return m.map(mapVal);\n    }\n    return [m.line, m.char, m.value, m.def.type];\n};\nconsole.log(input);\nconsole.log(result.map(mapVal));\n\/\/ [ [ [ 1, 2, '(', 'brace-start' ],\n\/\/     [ 1, 10, 'category', 'name' ],\n\/\/     [ 1, 13, '==', 'compare-operator' ]\n\/\/     [ 1, 22, '1.456123', 'number' ],\n\/\/     [ 1, 25, '&amp;&amp;', 'combine-operator' ]\n\/\/     [ 1, 30, 'name', 'name' ],\n\/\/     [ 1, 32, '=', 'compare-operator' ],\n\/\/     [ 1, 42, '\"a b c d\"', 'string' ],\n\/\/     [ 1, 45, '&amp;&amp;', 'combine-operator' ]\n\/\/     [ 1, 56, 'bookNumber', 'name' ],\n\/\/     [ 1, 59, '!=', 'compare-operator' ]\n\/\/     [ 1, 61, '1', 'number' ],\n\/\/     [ 1, 62, ')', 'brace-end' ] ],\n\/\/   [ 1, 65, '||', 'combine-operator' ],\n\/\/   [ 1, 76, 'bookNumber', 'name' ],\n\/\/   [ 1, 79, '==', 'compare-operator' ],\n\/\/   [ 1, 82, '66', 'number' ] ]<\/pre>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[5,4,1],"tags":[],"class_list":["post-2515","post","type-post","status-publish","format-standard","hentry","category-javascript","category-programming","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/2515","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/comments?post=2515"}],"version-history":[{"count":6,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/2515\/revisions"}],"predecessor-version":[{"id":3828,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/2515\/revisions\/3828"}],"wp:attachment":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/media?parent=2515"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/categories?post=2515"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/tags?post=2515"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}