是否可以直接评估Mapbox表达式?

我正在寻找一种 JavaScript 表达式语法来指定 JSON 中的操作。Mapbox 的表达式正是我正在寻找的,但我找不到任何关于这些表达式是否可以在 Mapbox 之外使用的文档。那可能吗?如果是这样,你会怎么做?

回答

它们“只是”抽象语法树的 JSON 形式,因此您可以编写自己的执行程序。特别是,根据他们自己的文档,他们似乎遵循以下约定:

  1. 数组是表达式,而所有其他 JSON 类型都是文字(奇怪的是,这意味着没有直接使用数组文字!我稍后会详细说明)
  2. 数组的第一项是要执行的函数,其余项是该函数的参数。
  3. 根对象不一定与表达式语法相关,只是在它们发生的地方使用它。
  4. 唯一的“有状态”的是let/var函数,它允许您创建变量,范围限定为封闭let表达式,这表明它们可以通过某种方式将上下文传递给函数。

所以,让我们建立一个!我将尝试逐行查看下面的代码,但如果您更喜欢那里的格式,您也可以查看问题末尾的代码段源。


在这里,我们稍后将定义可用于表达式语言的所有函数

const OPERATIONS = {};
const OPERATIONS = {};

现在,让我们设置评估器功能。它显然必须接收它将评估的表达式,而且还必须接

const evaluate = (expression, context = {}) => {

收可以通过操作修改的上下文。

const evaluate = (expression,
  if (!(expression instanceof Array)) {
    return expression;
  }
  if (!(expression instanceof Array)) {
    return expression;
  }

context = {}) => {

首先,我们通过将它们作为它们本身来评估它们来处理文字

是的,现在是真正的交易:让我们找出要运行的操作及其参数。

  const [operationKey, ...rawParameters] = expression;
  const operation = OPERATIONS[operationKey];

我们通过恐慌来处理未知的操作!天啊!

  if (operation == null) {
    throw new Error(`Unknown operation ${operationKey}!`);
  }

哦,太好了,我们知道这个操作!现在,我们应该如何称呼它?它显然需要接收其参数以及上下文,以防它是那些讨厌的有状态操作之一。另外,正如我们在 Mapbox 中看到的let,操作可以创建新的上下文!

我建议使用以下签名,但您可以根据自己的特定偏好和用例进行更改:

第一个参数:
当前上下文

第二个参数:
所有操作参数的数组。如果操作是可变的,这使得迭代变得容易,并且更简单的东西仍然可以使用解构来获得“固定”签名。我们将传递参数“原始”,而不是评估,以便操作可以对它们做任何它想做的坏事。

返回值:
无论操作想要评估什么!

  return operation(context, rawParameters);
};

对对对,我们已经设置了评估器,但是我们如何实际使用它呢?

我们需要一些操作,让我们从容易弄湿我们的脚开始:还记得我上面说过参数数组是原始的吗?我们需要在我们的操作函数中手动评估它们。

OPERATIONS["-"] = (context, [a, b]) => evaluate(a, context) - evaluate(b, context);
OPERATIONS["+"] = (context, [a, b]) => evaluate(a, context) + evaluate(b, context);

好的,这很容易,但是如果我们想接受任意数量的参数怎么办?

OPERATIONS["*"] = (context, parameters) => parameters
  .map(p => evaluate(p, context))
  .reduce((accumulator, x) => accumulator * x);

好的,现在让我们实现我们所说的那些数组。解决方案很简单,有一个操作,从它的参数创建数组!

OPERATIONS["array"] = (context, parameters) => parameters
  .map(p => evaluate(p, context));

很酷,很酷,但是撒旦本人的邪恶后代呢?letvar

让我们从其中较小的开始:简单,我们只是读取存储在上下文中的变量名!

OPERATIONS["var"] = (context, [variable]) => context[variable];

现在,“棘手”的 ,let既是可变参数又改变了上下文!

我会在这里拔掉我的牙套,因为它会比以前漂亮的单线操作大一点!

OPERATIONS["let"] = (context, [...definitions]) => {

对,我们有 A 上下文,但我们不想在let块外污染它!因此,让我们将其复制到一个新的临时文件中:

  const innerContext = { ...context };

现在我们需要循环定义,记住,每个定义有 2 个元素:一个变量名,和它的值表达式!但首先,我们需要挑选最后一个参数,即要在结果上下文中执行的表达式:

  const body = definitions.pop()

让我们把明显的东西放在一边,如果我们的定义中有奇数个东西,那么用户就错了!让我们把它扔在他们丑陋的脸上!让我们使用一个神秘的错误信息只是为了邪恶......

  if (definitions.length % 2 === 1) {
    throw new Error("Unmatched definitions!");
  }

很酷,现在我们要做一些很酷的事情,即创建这些变量:

  for (let i = 0; i < definitions.length - 1; i += 2) {
    const name = definitions[i];
    const value = definitions[i + 1];

在这里,我选择了同一块中的变量可以依赖于以前的变量,如果这不符合您的喜好,请使用父上下文而不是我们目前正在修改的上下文。

    innerContext[name] = evaluate(value, innerContext);
  }

变量已完成,现在让我们评估主体!

  return evaluate(body, innerContext);
};

我们完成了!这是评估语法树的基础!

您现在可能想要继续添加您自己的特定于域的操作。

我制作了这个片段来演示这最终是如何工作的,如果这是您的风格,则使用代码注释而不是文字编码。HTML 和 CSS 无关紧要,只是涂了点口红让它看起来更像样。

 

 


    以上是是否可以直接评估Mapbox表达式?的全部内容。
    THE END
    分享
    二维码
    < <上一篇
    下一篇>>