334 lines · 10.2 KB
/**
 * A small evaluator for GitHub-Actions-style `${{ }}` expressions: literals, context paths,
 * comparison/logical operators and the common built-in functions.
 */

export type ExprValue = string | number | boolean | null | ExprValue[] | { [key: string]: ExprValue };

export type ExprContext = {
  contexts: Record<string, ExprValue>;
  jobStatus: "success" | "failure";
};

type Token =
  | { type: "number"; value: number }
  | { type: "string"; value: string }
  | { type: "ident"; value: string }
  | { type: "op"; value: string }
  | { type: "punct"; value: string };

const OPERATORS = ["==", "!=", "<=", ">=", "&&", "||", "<", ">", "!"];
const PUNCTUATION = ["(", ")", "[", "]", ",", "."];
const IDENT_START = /[A-Za-z_]/;
const IDENT_PART = /[A-Za-z0-9_-]/;
const DIGIT = /[0-9]/;
const MAX_EXPRESSION_DEPTH = 64;
const MAX_EXPRESSION_LENGTH = 8 * 1024;

function tokenize(source: string): Token[] {
  const tokens: Token[] = [];
  let index = 0;
  while (index < source.length) {
    const char = source[index];
    if (/\s/.test(char)) {
      index++;
      continue;
    }
    if (char === "'") {
      let value = "";
      index++;
      while (index < source.length) {