-4

我刚刚完成了我的第一个(玩具)转译器的“版本 0”。有用。它将一串“伪 JavaScript”(具有附加功能的 JavaScript)转换为一串可运行的 JavaScript。现在,我想改进它。

其他 SO 用户可能最感兴趣的工作领域是:编译后的代码(即我的转译器的输出)不注意编码风格建议,如对一些较早的 SO 问题的接受答案中给出的。如果我手头有第二个转译器,该编码风格建议得到重视,我可以就哪个分支更有希望继续开发做出明智的决定——我想比较这两个分支的性能、开发时间需要,错误的数量等,并据此决定。

让我告诉你我的转译器处理的“附加 JS 功能”:“嵌套返回”。考虑像这样的闭包/嵌套函数

function myOuterFunc(){
    ... code ...
    function innerFunc(){
        ... code ...
    }
    ... code ...
}

(请注意,上面的 '...code...' 应该包括所有可能的 JS 代码,包括更多的嵌套函数声明,因此myOuterFunc不一定是 的直接父级innerFunc

在上述情况下,假设您希望从myOuterFunc内部某处返回结果- 不一定直接在内部 -innerFunc

实现“嵌套返回”后,您可以简单地编写

return.myOuterFunc result

这是使用此功能并做一些有意义的事情的(不可运行的)函数的示例

function multiDimensionalFind(isNeedle, haystack) {
    // haystack is an array of arrays
    // loop (recursively) through all ways of picking one element from each array in haystack
    // feed the picked elements as array to isNeedle and return immediately when isNeedle gives true
    // with those picked elements being the result, i.e. the 'found needle'
    var LEVEL = haystack.length;
    function inner(stack) {
        var level = stack.length;
        if (level >= LEVEL) {
            if (isNeedle(stack)) return.multiDimensionalFind stack;
        } else {
            var arr = haystack[level];
            for (var i = 0; i < arr.length; i++) {
                inner(stack.concat([arr[i]]));
            }
        }
    }
    inner([]);
    return 'not found'
}

这是我的转译器自动生成的(可运行的)代码(显然,注释是稍后添加/删除的),然后是一些代码测试该函数是否完成了它声称的工作(并且确实如此,你可以说服自己。 )

///////////// the function /////////////////
function multiDimensionalFind(isNeedle, haystack) {
    try {
        var LEVEL = haystack.length;
        function inner(stack) {
            var level = stack.length;
            if (level >= LEVEL) {
                if (isNeedle(stack)) throw stack;
            } else {
                var arr = haystack[level];
                for (var i = 0; i < arr.length; i++) {
                    inner(stack.concat([arr[i]]));
                }
            }
        }
        inner([]);
        return 'not found'
    } catch(e){
        // make sure "genuine" errors don't get destroyed or mishandled
        if (e instanceof Error) throw e; else return e;
    }
}
////////////////// test it //////////////////
content = document.getElementById('content');
function log2console(){
  var digits = [0,1];
  var haystack = [digits,digits,digits,digits,digits];
  var str = '';
  function isNeedle(stack){
    str = str + ', ' + stack.join('')
    return false;
  }
  multiDimensionalFind(isNeedle, haystack);
  content.textContent = str;
}
function find71529(){ // second button
  var digits = [0,1,2,3,4,5,6,7,8,9]
  var haystack = [digits,digits,digits,digits,digits]
  function isNeedle(stack){
    return stack.reduce(function(b,i){ return 10*b+i; }, 0) === 71529
    // returns true iff the stack contains [7,1,5,2,9]
  }
  content.textContent = multiDimensionalFind(
    isNeedle, haystack
  ).join('_')
}
<button onclick='log2console()'>print binary numbers with 5 digits</button>
<br>
<button onclick='find71529()'>find something is 5d space</button>
<div id='content'></div>

你可以在这里玩我的这个小提琴中的转译器。我正在使用esprima 库esprima之上的 escodegen.js 库,这是我自己的一个正在进行中的微小的抽象语法树生成库(请参阅小提琴中的脚本标签)。不是库的代码,也不是 UI 代码,即转译器的“真肉”只有不到 100 行(见函数transpile)。所以这可能没有你想象的那么复杂。

我不记得我在哪里看到过风格推荐,但我可以肯定它实际上在多个地方。如果您知道或遇到这样一个问题,我邀请您将链接放入问题下方的评论中,我将标记为有用。到目前为止,只有一个链接,谢谢 Barmar。

你可能会问为什么我什至费心先写一个“不合规”的转译器,而不是马上去写“合规”的版本。这与估计的工作量有关。我估计“合规”版本的工作量要大得多。如此之多,以至于开始这样的努力似乎并不值得。我很想知道这种对工作量的评估是正确的还是错误的。因此问题。请不要暗示问题的修辞,甚至是不诚实的动机;不管对某些人来说听起来多么奇怪,我确实希望被证明是错误的,所以请不要以为我出于任何原因“只是这么说”,你会对我做一个不公正。这是,到目前为止,我投入最多的工作。而且,如果你问我,这是迄今为止我在这里问过的最好的问题。

除了有人帮助我编写转译器的“合规”版本之外,我还对任何客观可证明的东西感兴趣(尽管程度较低)有机会说服我“不合规”的方式是错误的方法。速度测试(带有 jsperf 的链接)、可重现的错误报告,诸如此类。

我应该提到到目前为止我自己进行的速度测试:

第一次测试第二次测试

松散相关的问题

4

4 回答 4

2

更好的方法是使用return(如果你不能完全重构塔)。它清楚地说明了您在做什么以及何时从中间函数返回值;您可以通过查看可能提供结果的那些函数来判断。相反,throw在那些中间层中使用是不可见的;它以一种为错误条件设计的方式神奇地绕过它们。

如果您不想这样做,我认为您除了throw. 我想知道沿着生成器函数或类似函数的路线走下去,但我认为这只会使事情变得更加复杂,而不是更少。

使用return不会使示例代码明显复杂化,并且(在我看来)确实使正在发生的事情以及可能预期结果的时间更加清晰。

function wrapper(){
    function first(){
        function second(){
            function third(){
                doStuff4();
                some loop {
                    var result = ...
                    if (something) return result;
                }
            }
            doStuff2();
            let result = third();
            if (result) {
                return result;
            }
            doStuff3();
            return third();
        }
        doStuff1();
        return second();
    }
    return first() || "not found";
}

(在上面,result经过真实性测试;如果合适,请替换其他内容。)

于 2020-07-25T13:55:42.960 回答
1

好的,这是使用 JavaScript 的所有异步功能的另一种方法......所以基本上我已经重新创建了您的嵌套函数,但使用了 Promise/await 技术。您只会得到一次结果,这是您第一次从任何级别的嵌套函数中解析它。其他一切都将是GC。一探究竟:

// Create async function
(async () => {
  const fnStack = (val) => {
    return new Promise((resolve, reject) => {
      // Worker functions.
      // Could be async!
      const doStuff1 = (val) => val + 1;
      const doStuff2 = (val) => val * 2;
      const doStuff3 = (val) => val * -1; // This will not affect result
      const doStuff4 = (val) => val + 1000;
      
      // Nested hell
      function first() {
        function second() {
          function third() {
            val = doStuff4(val);
            // Some loop
            for(let i = 0; i < 1000; i++) {
              if(i === 500) {
                // Here we got our result
                // Resolve it!
                resolve(val);
              }
            }
          }
          val = doStuff2(val);
          third();
          // Below code will not affect
          // resolved result in third() above
          val = doStuff3(val);
          third();
        }
        val = doStuff1(val);
        second();
      }
      
      //
      first();
    });
  }
  
  // Run and get value
  const val = await fnStack(5);
  
  // We get our result ones
  console.log(val);
})();

于 2020-07-28T21:49:18.980 回答
0

我认为你应该使用数组;

const funcs = [first, second, third];
for(let i = 0; i < funcs.length; ++i){
 const result = funcs[i]();
 if (result) break;
}

您应该return只使用具有结果的函数。

于 2020-07-25T15:14:53.653 回答
-2

稍后,当我开始处理它时,我将在此处添加说明,说明如何使用我用于我的转译器的抽象语法树生成库,甚至可能从另一个版本开始,也许更详细地解释什么是事情是让我认为这更多的工作。

旧版本如下(即将删除)

如果该功能被多次使用,我们将需要这样的东西:

function NestedThrowee(funcName, value){
    this.funcName = funcName;
    this.value = value;
}

return.someFunctionName someReturnValue(编译前)会给出(编译后)类似的东西

var toBeThrown = new NestedThrowee("someFunctionName", someReturnValue);

在生成的catch块内

if (e instanceof Error){
    throw e;   // re-throw "genuine" Error
} else {
    if (e instance of NestedThrowee){
    if (e.funcName === ... the name of the function to which
                       this catch block here belongs ...) return e.value;
    throw new Error('something happened which mathheadinclouds deemed impossible');
}

在一般情况下,有必要像上面显示的那样包装结果(或'throwee'),因为可能有多个嵌套的“嵌套返回”,我们必须注意catch短语和捕获的类型NestedThrowee匹配的对象(按函数名)。

于 2020-07-25T14:14:33.613 回答