about summary refs log tree commit diff
path: root/users/wpcarro/scratch
diff options
context:
space:
mode:
authorWilliam Carroll <wpcarro@gmail.com>2023-01-19T18·09-0800
committerclbot <clbot@tvl.fyi>2023-01-19T18·12+0000
commit509e356bb8fcba2264368ca1e973e270ab614f98 (patch)
tree24f141931b5c1708e522701de9907cc5803fd774 /users/wpcarro/scratch
parent0dfe460fbb8cda0831fbcf4d9e42948c2bb88afa (diff)
feat(wpcarro/slx.js): Support JavaScript simple-select impl r/5704
See README.md

Change-Id: I6a50e34398c42aabe3cceba160be006f1867eca4
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7874
Reviewed-by: wpcarro <wpcarro@gmail.com>
Autosubmit: wpcarro <wpcarro@gmail.com>
Tested-by: BuildkiteCI
Diffstat (limited to 'users/wpcarro/scratch')
-rw-r--r--users/wpcarro/scratch/simple-select/index.html9
-rw-r--r--users/wpcarro/scratch/simple-select/index.js372
2 files changed, 0 insertions, 381 deletions
diff --git a/users/wpcarro/scratch/simple-select/index.html b/users/wpcarro/scratch/simple-select/index.html
deleted file mode 100644
index f7d4576f79a1..000000000000
--- a/users/wpcarro/scratch/simple-select/index.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-  </head>
-  <body>
-    <script src="./index.js"></script>
-  </body>
-</html>
diff --git a/users/wpcarro/scratch/simple-select/index.js b/users/wpcarro/scratch/simple-select/index.js
deleted file mode 100644
index 15a35ab74334..000000000000
--- a/users/wpcarro/scratch/simple-select/index.js
+++ /dev/null
@@ -1,372 +0,0 @@
-const state = {
-    // Match values case sensitively when filtering.
-    caseSensitive: false,
-    // Coerce values into regular expressions (instead of strings) when they're defined as atoms.
-    preferRegex: true,
-    // The key in the JS object that hosts the Date type against which we filter.
-    dateKey: 'Date',
-};
-
-// TODO(wpcarro): Support filtering by date (before, after).
-// TODO(wpcarro): Support grouping with parentheses.
-
-function select(query, xs) {
-    const predicate = compile(parse(query));
-    return xs.filter(predicate);
-}
-
-function compile(ast) {
-    if (ast.type === 'CONJUNCTION') {
-        const lhs = compile(ast.lhs);
-        const rhs = compile(ast.rhs);
-
-        if (ast.joint === 'AND') {
-            return function(x) {
-                return lhs(x) && rhs(x);
-            };
-        }
-        if (ast.joint === 'OR') {
-            return function(x) {
-                return lhs(x) || rhs(x);
-            };
-        }
-    }
-    if (ast.type === 'DATE_SELECTION') {
-        if (ast.key === 'before') {
-            return function(row) {
-                let t = new Date();
-                if (ast.val === 'yesterday') {
-                    t.setDate(t.getDate() - 1);
-                    console.log(t);
-                } 
-                // MM/DD/YYYY
-                else {
-                    t = new Date(ast.val);
-                }
-                return row[state.dateKey] < t;
-            };
-        }
-        if (ast.key === 'after') {
-            return function(row) {
-                let t = new Date();
-                if (ast.val === 'yesterday') {
-                    t.setDate(t.getDate() - 1);
-                    console.log(t);
-                } 
-                // MM/DD/YYYY
-                else {
-                    t = new Date(ast.val);
-                }
-                return row[state.dateKey] > t;
-            };
-        }
-    }
-    if (ast.type === 'SELECTION') {
-        const f = compile(ast.val);
-        return function(row) {
-            return ast.negate ? !f(row[ast.key]) : f(row[ast.key]);
-        };
-    }
-    if (ast.type === 'MATCH_ALL') {
-        if (ast.matchType === 'STRING') {
-            return function(row) {
-                return Object.values(row).some(x => {
-                    if (state.caseSensitive) {
-                        return x === ast.val;
-                    } else {
-                        return x.toLowerCase() === ast.val.toLowerCase();
-                    }
-                })
-            };
-        }
-        if (ast.matchType === 'REGEX') {
-            return function(row) {
-                return Object.values(row).some(x => ast.val.test(x));
-            };
-        }
-    }
-    if (ast.type === 'STRING') {
-        return function(x) {
-            if (state.caseSensitive) {
-                return x === ast.val;
-            } else {
-                return x.toLowerCase() === ast.val.toLowerCase();
-            }
-        };
-    }
-    if (ast.type === 'REGEX') {
-        return function(x) {
-            return ast.val.test(x);
-        };
-    }
-}
-
-// A "selection" without a "$column:" prefix should fuzzy-search all columns.
-//
-// conjunction -> selection ( ( "AND" | "OR" )? selection )* ;
-// selection   -> "-"? COLUMN ":" ( regex | string ) | regex ;
-// regex       -> [_-a-zA-Z0-9] | "/" [ _-a-zA-Z0-9] "/" | string ;
-// string      -> "\"" [ _-a-zA-Z0-9] "\"" ;
-
-// Whatever characters are valid for a JS regex.
-const ATOM_REGEX = /[-_.\[\]a-zA-Z0-9*+^$]/;
-
-function tokenize(x) {
-    const result = [];
-    let i = 0;
-    while (i < x.length) {
-        if (x[i] === ' ') {
-            i += 1;
-            while (i < x.length && x[i] === ' ') {
-                i += 1;
-            }
-            result.push(['WHITESPACE', null]);
-            continue;
-        }
-        if (x[i] === '-') {
-            result.push(['NEGATE', null]);
-            i += 1;
-            continue;
-        }
-        if (ATOM_REGEX.test(x[i])) {
-            let curr = x[i];
-            i += 1;
-            while (i < x.length && ATOM_REGEX.test(x[i])) {
-                curr += x[i];
-                i += 1;
-            }
-            result.push(['ATOM', curr]);
-            continue;
-        }
-        if (x[i] === ':') {
-            result.push(['COLON', null]);
-            i += 1;
-            continue;
-        }
-        if (x[i] === '(') {
-            result.push(['LPAREN', null]);
-            i += 1;
-            continue;
-        }
-        if (x[i] === ')') {
-            result.push(['RPAREN', null]);
-            i += 1;
-            continue;
-        }
-        if (x[i] === '/') {
-            let start = i;
-            let curr = '';
-            i += 1;
-            while (i < x.length && x[i] !== '/') {
-                curr += x[i];
-                i += 1;
-            }
-            // error
-            if (i >= x.length) {
-                throw `Tokenize Error: EOL while attempting to tokenize the regex beginning at column: ${start}`;
-            }
-            if (x[i] === '/') {
-                result.push(['REGEX', curr]);
-                i += 1;
-            }
-            continue;
-        }
-        if (x[i] === '"') {
-            let start = i;
-            let curr = '';
-            i += 1;
-            while (i < x.length && x[i] !== '"') {
-                // continue on \"
-                if (x[i] === '\\' && x[i + 1] === '"') {
-                    curr += '\"';
-                    i += 2;
-                } else {
-                    curr += x[i];
-                    i += 1;
-                }
-            }
-            if (i >= x.length) {
-                throw `Tokenize Error: EOL while attempting to tokenize the string starting at column: ${start}`;
-            }
-            if (x[i] === '"') {
-                result.push(['STRING', curr]);
-                i += 1;
-            }
-            continue;
-        }
-        else {
-            i += 1;
-        }
-    }
-    return result;
-}
-
-function expect(f, expectation, p) {
-    const [type, val] = p.tokens[p.i];
-    if (f(type, val)) {
-        p.i += 1;
-    } else {
-        throw `Parse Error: expected ${expectation}, but got ${p.tokens[p.i]}; ${JSON.stringify(p)}`
-    }
-}
-
-function matches(f, p) {
-    const [type, val] = p.tokens[p.i];
-    if (f(type, val)) {
-        return true;
-    }
-    return false;
-}
-
-function match(f, expectation, p) {
-    const [type, val] = p.tokens[p.i];
-    if (f(type, val)) {
-        p.i += 1;
-        return val;
-    }
-    throw `Parse Error: expected ${expectation}, but got: ${p.tokens[p.i]}; ${JSON.stringify(p)}`;
-}
-
-function skipWhitespace(p) {
-    while (p.i < p.tokens.length && matches((type, _) => type === 'WHITESPACE', p)) {
-        p.i += 1;
-    }
-}
-
-function parser(tokens) {
-    return { i: 0, tokens };
-}
-
-function parse(x) {
-    const tokens = tokenize(x);
-    const p = parser(tokens);
-    return conjunction(p);
-}
-
-function conjunction(p) {
-    skipWhitespace(p);
-
-    const lhs = selection(p);
-    skipWhitespace(p);
-
-    if (p.i >= p.tokens.length) {
-        return lhs;
-    }
-
-    let joint = 'AND';
-    if (matches((type, val) => type === 'ATOM' && val === 'AND', p)) {
-        joint = 'AND';
-        p.i += 1;
-    } else if (matches((type, val) => type === 'ATOM' && val === 'OR', p)) {
-        joint = 'OR';
-        p.i += 1;
-    }
-    skipWhitespace(p);
-    let rhs = conjunction(p);
-
-    return {
-        type: 'CONJUNCTION',
-        joint,
-        lhs,
-        rhs,
-    };
-}
-
-function peekType(n, p) {
-    if (p.i + n < p.tokens.length) {
-        return p.tokens[p.i + n][0];
-    }
-    return null;
-}
-
-function selection(p) {
-    // column:value OR -column:value
-    if ((peekType(0, p) === 'ATOM' && peekType(1, p) === 'COLON') ||
-        (peekType(0, p) === 'NEGATE' && peekType(1, p) === 'ATOM' && peekType(2, p) === 'COLON')) {
-
-        let negate = false;
-        if (p.tokens[p.i][0] === 'NEGATE') {
-            negate = true;
-            p.i += 1;
-        }
-
-        const key = match((type, _) => type === 'ATOM', 'a column label', p);
-        expect((type, val) => type === 'COLON', 'a colon', p);
-
-        if (key === 'before' || key === 'after') {
-            const val = date(p);
-            return {
-                type: 'DATE_SELECTION',
-                key,
-                val,
-            };
-        } else {
-            const val = value(p);
-            return {
-                type: 'SELECTION',
-                negate,
-                key,
-                val,
-            };
-        }
-    } else {
-        return matchAll(p);
-    }
-}
-
-function matchAll(p) {
-    const [type, val] = p.tokens[p.i];
-
-    // Cast atoms into strings or regexes depending on the current state.
-    if (type === 'ATOM') {
-        p.i += 1;
-        if (state.preferRegex) {
-            const regex = state.caseSensitive ? new RegExp(val) : new RegExp(val, "i");
-            return { type: 'MATCH_ALL', matchType: 'REGEX', val: regex };
-        } else {
-            return { type: 'MATCH_ALL', matchType: 'STRING', val }
-        }
-    }
-    if (type === 'STRING') {
-        p.i += 1;
-        return { type: 'MATCH_ALL', matchType: 'STRING', val };
-    }
-    if (type === 'REGEX') {
-        p.i += 1;
-        const regex = state.caseSensitive ? new RegExp(val) : new RegExp(val, "i");
-        return { type: 'MATCH_ALL', matchType: 'REGEX', val: regex };
-    }
-    throw `Parse Error: Expected a regular expression or a string, but got: ${p.tokens[p.i]}; ${JSON.stringify(p)}`;
-}
-
-function value(p) {
-    const [type, val] = p.tokens[p.i];
-
-    // Cast atoms into strings or regexes depending on the current state.
-    if (type === 'ATOM') {
-        p.i += 1;
-        if (state.preferRegex) {
-            const regex = state.caseSensitive ? new RegExp(val) : new RegExp(val, "i");
-            return { type: 'REGEX', val: regex };
-        } else {
-            return { type: 'STRING', val }
-        }
-    }
-    if (type === 'STRING') {
-        p.i += 1;
-        return { type, val };
-    }
-    if (type === 'REGEX') {
-        p.i += 1;
-        const regex = state.caseSensitive ? new RegExp(val) : new RegExp(val, "i");
-        return { type, val: regex };
-    }
-    throw `Parse Error: Expected a regular expression or a string, but got: ${p.tokens[p.i]}; ${JSON.stringify(p)}`;
-}
-
-function date(p) {
-    const [type, val] = p.tokens[p.i];
-    p.i += 1;
-
-    return val;
-}