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

瑞安·莱利(Ryan Riley)

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

科尔坦

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

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

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


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

const OPERATIONS = {};

现在,让我们设置评估器功能。显然,它必须接收将要计算的表达式,而且必须接收可以由操作修改的上下文。

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

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

  if (!(expression instanceof Array)) {
    return expression;
  }

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

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

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

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

哦,很好,我们知道此操作!现在,我们应该怎么称呼它?如果它是那些令人讨厌的有状态操作之一,则它显然需要接收其参数以及上下文。另外,正如我们在Mapbox中看到的那样let,操作可以创建新的上下文!

我提出以下签名,尽管您可以根据自己的喜好和用例进行更改:

第一个参数:当前上下文

第二个参数:所有操作参数的数组。如果该操作是可变参数,则可以轻松进行迭代,并且更简单的操作仍然可以使用解构来获得“固定”签名。我们将传递未经评估的参数“ raw”,以便该操作可以对它们执行任何它想做的邪恶的事情。

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

  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]) => {

是的,我们有一个上下文,但是我们不想在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无关紧要,只是一些口红使其显得更像样。

// Here we will later define all functions available for the expression language
const OPERATIONS = {};

// Now, let's set up the evaluator function.
// It obviously must receive the expression it will evaluate,
// but also a context that can be modified by operations.
const evaluate = (expression, context = {}) => {
  // First, we deal with literals by evaluating them as themselves
  if (!(expression instanceof Array)) {
    return expression;
  }

  // Right, now to the real deal:
  // let's find out what operation to run and its parameters.
  const [operationKey, ...rawParameters] = expression;
  const operation = OPERATIONS[operationKey];

  // We handle unknown operations by panicking! AAAH!
  if (operation == null) {
    throw new Error(`Unknown operation ${operationKey}!`);
  }

  // Oh nice, we know this operation! Now, how should we call it?
  // It obviously needs to receive its parameters, as well as the context,
  // in case it is one of those pesky stateful operations. Plus, as we
  // have seen with Mapbox's `let`, operations can create new contexts!
  //
  // I propose the following signature, though you can change it for your
  // particular preference and use-cases:
  //
  // First parameter:
  //      Current context
  // Second parameter:
  //      Array of all of the operation's parameters. This makes for
  //      easy iteration if the operation is variadic, and simpler stuff
  //      can still just use deconstruction to have a "fixed" signature.
  //      We will pass the parameters "raw", not evaluated, so that the
  //      operation can do whatever evil things it wants to do to them.
  // Return value:
  //      Whatever the operation wants to evaluate to!
  return operation(context, rawParameters);
};

// Right, right, we have set up the evaluator, but how do we actually use it?
// We need some operations, let's start with the easy ones to wet our feet:
// Remember how I said above that the parameters array comes in raw?
// We'll need to evaluate them manually inside our operation functions.
OPERATIONS["-"] = (context, [a, b]) => evaluate(a, context) - evaluate(b, context);
OPERATIONS["+"] = (context, [a, b]) => evaluate(a, context) + evaluate(b, context);

// Okay, that was easy, but what if we want
// to accept an arbitrary amount of arguments?
OPERATIONS["*"] = (context, parameters) => parameters
  .map(p => evaluate(p, context))
  .reduce((accumulator, x) => accumulator * x);
  
// Right, now let's implement those arrays we spoke of.
// The solution is simple, have an operation that
// creates the array from its parameters!
OPERATIONS["array"] = (context, parameters) => parameters
  .map(p => evaluate(p, context));

// Cool, cool, but what about the evil spawns of Satan himself? Let and Var?

// Let's start with the lesser of them:
// Easy, we just read whatever was stored in the context for that variable name!
OPERATIONS["var"] = (context, [variable]) => context[variable];

// Now, the "tricky" one, Let, which is both variadic AND changes the context!
// I'll pull out my braces here  because it's gonna be a bit bigger than the
// previous beautiful one-line operations!
OPERATIONS["let"] = (context, [...definitions]) => {
  // Right, we have A context, but we don't want to pollute it outside
  // the Let block! So let's copy it to a new temporary one:
  const innerContext = { ...context
  };

  // Now we need to loop the definitions, remember, they are 2 elements each:
  // A variable name, and its value expression! But first, we need to pick
  // out the last argument which is the expression to be executed in the
  // resulting context:
  const body = definitions.pop()

  // Let's get the obvious stuff out of the way, if we have an odd number of
  // things in our definitions, the user is wrong! Let's throw it on their
  // ugly face! Let's use a cryptic error message just to be evil...
  if (definitions.length % 2 === 1) {
    throw new Error("Unmatched definitions!");
  }

  // Cool, now we get to do the cool stuff which is create those variables:
  for (let i = 0; i < definitions.length - 1; i += 2) {
    const name = definitions[i];
    const value = definitions[i + 1];

    // Here I made the choice that variables in the same block can depend
    // on previous variables, if that's not to your liking, use the parent
    // context instead of the one we're modifying at the moment.
    innerContext[name] = evaluate(value, innerContext);
  }

  // Variables are DONE, now let's evaluate the body!
  return evaluate(body, innerContext);
};

// Bonus points for reading the snippet code:
// Remember that we are not limited to numeric values,
// anything that JSON accepts we accept too!
// So here's some simple string manipulation.
OPERATIONS["join"] = (context, [separator, things]) => evaluate(things, context)
  .flat()
  .join(separator);

// And we're done! That is the basic of evaluating a syntax tree!







// Not really relevant to the question itself, just a quick and dirty REPL

(() => {
  const input = document.getElementById("input");
  const output = document.getElementById("output");

  const runSnippet = () => {
    let expression;

    try {
      expression = JSON.parse(input.value);
    } catch (e) {
      // Let the user type at peace by not spamming errors on partial JSON
      return;
    }

    const result = evaluate(expression);
    output.innerText = JSON.stringify(result, null, 2);
  }

  input.addEventListener("input", runSnippet);

  runSnippet();
})();
html {
  display: flex;
  align-items: stretch;
  justify-content: stretch;
  height: 100vh;
  background: beige;
}

body {
  flex: 1;
  display: grid;
  grid-template-rows: 1fr auto;
  grid-gap: 1em;
}

textarea {
  padding: 0.5em;
  border: none;
  background: rgba(0, 0, 0, 0.8);
  color: white;
  resize: none;
}
<textarea id="input">
[
  "let",
  "pi", 3.14159,
  "radius", 5,
  [
    "join",
    " ",
    [
      "array",
      "a circle with radius",
      ["var", "radius"],
      "has a perimeter of",
      [
        "*",
        2,
        ["var", "pi"],
        ["var", "radius"]
      ]
    ]
  ]
]

</textarea>
<pre id="output">
</pre>

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

Webpack:是否可以在编译时评估javascript表达式?

来自分类Dev

在评估表达式的类型时,是否可以指示ghci将具体类型用于约束?

来自分类Dev

是否可以使用 xquery (BaseX) 评估存储在变量中的“复杂”xpath 表达式?

来自分类Dev

是否可以重载lambda表达式?

来自分类Dev

是否可以重载lambda表达式?

来自分类Dev

javascript:检查表达式是否可评估?

来自分类Dev

表达式的部分评估

来自分类Dev

Python表达式评估

来自分类Dev

如何评估表达式?

来自分类Dev

评估数学表达式

来自分类Dev

后缀表达式评估

来自分类Dev

评估表达式树

来自分类Dev

评估表达式 CLISP

来自分类Dev

Mapbox gl 表达式

来自分类Dev

直接在“选择”语句中评估存储在“ Varchar列”中的表达式

来自分类Dev

是否可以在以下条件中找到哪个表达式评估为true,在此基础上我可以找到a的值

来自分类Dev

vim的表达式评估可以与缓冲区中的现有表达式一起使用吗?

来自分类Dev

正则表达式:是否可以在正则表达式中进行子匹配?

来自分类Dev

是否可以将车把表达式值传递给子表达式?

来自分类Dev

编译器是否必须评估表达式是否取决于模板参数?

来自分类Dev

是否可以动态设置Polymer表达式?

来自分类Dev

是否可以在launchd的ProgramArguments数组中计算表达式?

来自分类Dev

是否可以访问转换表达式的索引?

来自分类Dev

是否可以仅使用lambda表达式实现堆栈?

来自分类Dev

是否可以在iframe中更新angularjs表达式?

来自分类Dev

是否可以在FXML中使用算术表达式?

来自分类Dev

抛出或删除表达式是否可以依赖?

来自分类Dev

是否可以在lambda表达式中定位EventHandler?

来自分类Dev

是否可以对SSRS中的表达式列求和

Related 相关文章

  1. 1

    Webpack:是否可以在编译时评估javascript表达式?

  2. 2

    在评估表达式的类型时,是否可以指示ghci将具体类型用于约束?

  3. 3

    是否可以使用 xquery (BaseX) 评估存储在变量中的“复杂”xpath 表达式?

  4. 4

    是否可以重载lambda表达式?

  5. 5

    是否可以重载lambda表达式?

  6. 6

    javascript:检查表达式是否可评估?

  7. 7

    表达式的部分评估

  8. 8

    Python表达式评估

  9. 9

    如何评估表达式?

  10. 10

    评估数学表达式

  11. 11

    后缀表达式评估

  12. 12

    评估表达式树

  13. 13

    评估表达式 CLISP

  14. 14

    Mapbox gl 表达式

  15. 15

    直接在“选择”语句中评估存储在“ Varchar列”中的表达式

  16. 16

    是否可以在以下条件中找到哪个表达式评估为true,在此基础上我可以找到a的值

  17. 17

    vim的表达式评估可以与缓冲区中的现有表达式一起使用吗?

  18. 18

    正则表达式:是否可以在正则表达式中进行子匹配?

  19. 19

    是否可以将车把表达式值传递给子表达式?

  20. 20

    编译器是否必须评估表达式是否取决于模板参数?

  21. 21

    是否可以动态设置Polymer表达式?

  22. 22

    是否可以在launchd的ProgramArguments数组中计算表达式?

  23. 23

    是否可以访问转换表达式的索引?

  24. 24

    是否可以仅使用lambda表达式实现堆栈?

  25. 25

    是否可以在iframe中更新angularjs表达式?

  26. 26

    是否可以在FXML中使用算术表达式?

  27. 27

    抛出或删除表达式是否可以依赖?

  28. 28

    是否可以在lambda表达式中定位EventHandler?

  29. 29

    是否可以对SSRS中的表达式列求和

热门标签

归档