[Dart翻译]将任意字符串作为Dart代码运行

893 阅读4分钟

本文由 简悦SimpRead 转码,原文地址 iiro.dev

Javascript有eval(),用于将字符串作为代码运行。尽管你很可能永远都不应该使用它,但som......。

(注意: 这只在JIT模式下工作。只有当你正在构建一个使用Dart VM运行的Dart应用程序,并且不是AOT编译时,这才是一个可行的解决方案。你不能在Flutter中使用这种方法。或者你可以,但它在发布模式下停止工作)。

在Javascript中,可以把一个字符串作为代码来评估。

const code = 'console.log("hey!")';

// Execute "code" as Javascript
eval(code);

Javascript有eval(),这是一个特殊的函数,它接收一个字符串并将其作为Javascript代码进行评估。上面的例子在控制台中打印了一个友好的 "嘿!"

"但是李罗,你为什么不直接调用console.log()?"

首先,我的名字是 Iiro 。回答这个问题,你是完全正确的。事实上,你会这样做。

下面是同样的事情,但代码更少。

console.log("Hey!")

如果两个例子的结果都是一样的,那么我们为什么首先要eval()

在这种情况下,我们不会。那段代码只是**的例子,而不是一个实际的用例。使用eval()的真正用例是很难想出来的。

那么--我什么时候需要eval()

大多数时候,你根本不应该使用eval() 。机会是你在应用开发中永远不会需要它。

在非常罕见的情况下,你可能真的需要它。最有可能的是在构建开发者工具时--毕竟,没有eval就没有REPL

今天我发现自己在Dart中构建东西时需要eval()。你猜怎么着,这是在建立一个开发工具的背景下。

final functionName = 'applyMagicSauce';

这里的 "functionName "是在运行时黑掉的。它是通过在Dart项目中搜索特定抽象类的实现者而完成的。

这些抽象类是我写的,但实现者来自第三方库。可能有多个functionName需要调用。

我需要把那个functionName拿出来,做一些类似的事情。

final functionName = 'applyMagicSauce';
final input = 'some arbitrary string';

// Construct a call to "applyMagicSauce()"
// with "some arbitrary string" as input.
final result = eval('$functionName("$input")');

调用applyMagicSauce(..)并传递给它一个字符串,返回的字符串略有不同。我需要找到一种方法来调用它。

你把你的eval()藏在哪里了,Dart?

Dart 没有eval() 函数。这真是个坏消息。

如果你在网络浏览器中运行Dart,你可以通过从Dart中调用Javascript来实现,就像这样。

void main() {
  final result = js.context.callMethod("eval", /* ... */);
}

但是,当在虚拟机上运行Dart时,这不会起作用

我也很确定,如果我试图将一些Dart发送到Javascript的eval()函数,我不会得到任何有用的回报。

经过一番调查,我发现了GitHub问题上的这个评论

你可以创建一个带有主函数的新库,将源代码编码为数据。URI,并将其生成为一个新的隔离区,但这并不允许你在当前的隔离区中创建新的函数。你必须使用隔离区端口通信与新代码进行通信。

我决定就这样尝试一下。有一个名为Isolate.spoonUri的函数,似乎正是我所需要的。

它接收一个Uri--除其他外,可以通过调用Uri.dataFromString从一个字符串中构建。

void main() async {
  final uri = Uri.dataFromString(
    '''
    void main() {
      print("Hellooooooo from the other side!");
    }
    ''',
    mimeType: 'application/dart',
  );
  await Isolate.spawnUri(uri, [], null);
}

你猜怎么着?这该死的东西起作用了。

ironman$ dart lib/eval.dart

Hellooooooo from the other side!

将任意字符串作为Dart代码执行是个不错的选择! 发回一些东西呢?

void main() async {
  final name = 'Eval Knievel';
  final uri = Uri.dataFromString(
    '''
    import "dart:isolate";

    void main(_, SendPort port) {
      port.send("Nice to meet you, $name!");
    }
    ''',
    mimeType: 'application/dart',
  );

  final port = ReceivePort();
  await Isolate.spawnUri(uri, [], port.sendPort);

  final String? response = await (port.first as FutureOr<String?>);
  print(response);
}

(Dart中的Isolates是相当...隔离的。他们不共享内存。为了传递数据,我们需要使用_ports--这可能看起来有点麻烦。另一方面,我们不会遇到同步问题,所以这一点。)

这个也成功了。

ironman$ dart lib/eval.dart

Nice to meet you, Eval Knievel!

还记得之前的 "applyMagicSauce() "吗?让我们想象一下,我在一个叫做packageUri的变量里有完整的package:包含文件的URI。因为我确实有。

我还知道我想调用的函数的名字是applyMagicSauce。它有一个参数,是一个字符串。

现在是时候把一些字符串拼接起来了。

void main() async {
  // I filled out the content of these variables here
  // for clarity. In a real scenario, you'd probably
  // parse these from somewhere.
  final packageUri = 'package:magic/magic_sauce.dart';
  final functionName = 'applyMagicSauce';
  final input = 'Hellooooooo from the other side!';
  final uri = Uri.dataFromString(
    '''
    import "dart:isolate";
    import '$packageUri';

    void main(_, SendPort port) {
      port.send($functionName("$input"));
    }
    ''',
    mimeType: 'application/dart',
  );

  final port = ReceivePort();
  final isolate =
  await Isolate.spawnUri(uri, [], port.sendPort);
  final String? response = await (port.first as FutureOr<String?>);

  port.close();
  isolate.kill();

  print(response);
}

运行后,applyMagicSauce的结果将出现在response变量中。

如果magic sauce函数是在洗刷一个字符串,我们会在控制台得到类似这样的打印结果。

ironman$ dart lib/eval.dart

lor meetei ooo sdel fthooh!oHoor

最后的话

在Javascript中,使用eval的第一条规则是不使用它。这也适用于Dart。

它通常很笨拙,而且会有更好的方法。如果你只是在构建Flutter应用程序,你可能永远不会需要它。事实上,如果你正在构建一个Flutter应用程序,这种方法将不适合你

第二条规则是:eval可以是邪恶的。如果你真的用Dart VM运行它,你需要认真考虑,确保你不会意外地在最终用户的电脑上运行一些恶意的东西。


www.deepl.com 翻译