Last active
April 6, 2021 19:12
-
-
Save LaKing/fd0dadf24e2ad0cca1c3f085d29916f6 to your computer and use it in GitHub Desktop.
Accessor-string evaluation
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
// https://gist.github.com/LaKing/fd0dadf24e2ad0cca1c3f085d29916f6#file-accessor-demo-js | |
/* @DOC Accessor-string implementation | |
// An accessor is a js-like syntax string that can be evaluated (without eval) | |
// set the env object: where keys are symbols for the string, and values are from our scope | |
// the string then has access to these values, and can perform dot notation, bracket notation, function evaluation with arguments. | |
// I wrote this for dynamic access to stuff in vuex store. (2021) | |
*/ | |
// Demo Object | |
const o = { | |
a: { | |
b: { | |
"c/d": function getter(ga, ea, eb, ec) { | |
console.log(ga, ea, eb, ec); | |
return { | |
x: function (xa) { | |
return ga + " " + xa; | |
}, | |
}; | |
}, | |
}, | |
}, | |
alfa: { | |
beta: { | |
gamma: "Gamma-string", | |
}, | |
delta: { | |
one: 1, | |
six: 6 | |
} | |
}, | |
}; | |
// demo string | |
const str = "$.a.b[c/d]($.alfa.beta.gamma, y('extra-argument'), $.alfa.delta.one, '$.alfa.delta.six').x(y(111))"; | |
// demo env | |
const env = { $: o, y: (v) => "result is " + v + " as " + typeof v }; | |
const result = evaluate(str, {}, env); | |
console.log(result); | |
// Gamma-string result is extra-argument as string 1 $.alfa.delta.six | |
// Gamma-string result is 111 as number | |
//export default | |
function evaluate(instr, defpointer = {}, env = {}) { | |
if (typeof instr !== "string") return instr; | |
// extend our accessr language with keys and values from javascript Object | |
// vuetify tends to like arrays instead of objects | |
if (env.keys === undefined) env.keys = Object.keys; | |
if (env.values === undefined) env.values = Object.values; | |
// a function might have comma seperated arguments | |
function argumentParser(str) { | |
if (str.length < 1) return null; | |
let simple_string = false; | |
let double_string = false; | |
let results = []; | |
let sx = 0; | |
// we will check the string and split it to [start-index, end-index] arrays based on the commas | |
for (let i = 0; i < str.length; i++) { | |
const char = str[i]; | |
if (char === `'`) simple_string = !simple_string; | |
if (char === `"`) double_string = !double_string; | |
if (char === `,` && !simple_string && !double_string) { | |
results.push([sx, i]); | |
sx = i+1; | |
} | |
} | |
results.push([sx, str.length]); | |
return results.map(a => str.substring(a[0], a[1]).trim()); | |
} | |
// here we look for functions | |
function splitfn(str, pointer = {}) { | |
// we do recursive stuff, so check for strings that we in our accessor-language consider to be primitives | |
// primitive single string | |
if (str.startsWith("'") && str.endsWith("'")) return str.slice(1, -1); | |
if (str.startsWith('"') && str.endsWith('"')) return str.slice(1, -1); | |
// primitive single boolean | |
const lcs = str.toLowerCase(); | |
if (lcs === "true") return true; | |
if (lcs === "false") return false; | |
// primitive single number | |
if (str.match(/^-?\d+$/)) return Number(str); | |
// bracets count | |
let bc = 0; | |
// start and end index | |
let bsx = null; | |
let bex = null; | |
//check the string we have .. is it a function? | |
for (let i = 0; i < str.length; i++) { | |
const char = str[i]; | |
// Find the first ( | |
if (char === "(") { | |
if (bc === 0) if (bsx === null) bsx = i; | |
bc++; | |
} | |
// and the matching ) | |
if (char === ")") { | |
bc--; | |
if (bc === 0) if (bex === null) bex = i + 1; | |
} | |
} | |
if (bex === null) { | |
// no, it's not a function | |
if (bsx !== null) return console.log("[vuetiform-accessor] Invalid function call in " + str); | |
return access(str, pointer); | |
} | |
// so the string has now three parts | |
const pre = str.slice(0, bsx); | |
const arg = str.slice(bsx + 1, bex - 1); | |
const res = str.slice(bex); | |
let fn = function () {}; | |
// check if the pre string is something from our env | |
if (Object.keys(env).includes(pre)) fn = env[pre]; | |
// if not try to resolve it | |
else fn = access(pre, pointer); | |
// the argument itself might be a function too | |
const args = argumentParser(arg).map(a => splitfn(a)); | |
// we accessed something, that should be a function | |
if (typeof fn !== "function") return console.log("[vuetiform-accessor] '" + str + "' Invalid function " + pre + " got: " + fn); | |
// it is a function, so we call it | |
const value = fn(...args); | |
// and process further on .... | |
return splitfn(res, value); | |
} | |
// where we parse dot and bracket notation | |
function access(accessor, pointer = {}) { | |
// construct teh list of symbols we will dive into | |
// unify dot and bracket notation | |
const wordlist = accessor | |
.replace(/\./g, "×") // for dot notation | |
.replace(/\[(\"|\')?/g, "×") // for bracket notation [ | |
.replace(/(\"|\')?\]/g, "") // for bracket notation ] | |
.split("×"); | |
// no go through the sequence | |
for (let w of wordlist) { | |
// empty strings can be skipped, eg something started with a dot, that's ok | |
if (w.length < 1) continue; | |
// we can encounter a symbol that is in our env - especially at the beginning of a sequence | |
if (Object.keys(env).includes(w)) { | |
pointer = env[w]; | |
continue; | |
} | |
// if we hit something undefined, then there is an error in our accessor definition / althought it might happen on purpose | |
if (pointer[w] === undefined) { | |
//if (Object.keys(pointer).length > 0) console.log("[vuetiform-accessor] Invalid accessor " + accessor + " has no " + w + " it has keys: " + Object.keys(pointer).join('|')); | |
return; | |
} | |
pointer = pointer[w]; | |
} | |
// our accessor part is resolved | |
return pointer; | |
} | |
// the whole evaluation starts with finding the first function | |
return splitfn(instr, defpointer); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment