Skip to content

Instantly share code, notes, and snippets.

@LaKing
Last active April 6, 2021 19:12
Show Gist options
  • Save LaKing/fd0dadf24e2ad0cca1c3f085d29916f6 to your computer and use it in GitHub Desktop.
Save LaKing/fd0dadf24e2ad0cca1c3f085d29916f6 to your computer and use it in GitHub Desktop.
Accessor-string evaluation
// 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