简体   繁体   English

递归连接由抽象语法树状结构形成的数组时遇到问题

[英]Trouble with recursively joining an array formed from an abstract syntax tree-like structure

I have a rough AST-like structure;我有一个类似 AST 的粗略结构; an array containing either a string, or an object, where the object has an id and arguments, where the arguments are an array of either a string or an object, as above. an array containing either a string, or an object, where the object has an id and arguments, where the arguments are an array of either a string or an object, as above.

The goal is to output a string from the joined strings and objects' strings, recursively built(due to the nature of the structure).目标是 output 从连接的字符串和对象的字符串中递归构建(由于结构的性质)。

A further complication is that the objects are really function calls, with differing argument consumption.更复杂的是,这些对象实际上是 function 调用,具有不同的参数消耗。 However, the output is always a string.但是,output 始终是一个字符串。

I've tried varied methods of translating this AST-like structure into a string, without success;我尝试了多种方法将这种类似 AST 的结构转换为字符串,但均未成功; below is my current attempt, but it doesn't work as aside from the obvious output clashes, I'm having trouble figuring out the exact logic I need to achieve the goal.下面是我目前的尝试,但除了明显的 output 冲突之外,它不起作用,我无法弄清楚实现目标所需的确切逻辑。

 const argLists = [ [ "one", { id: "$id1", args: [["two", "three"], "4", "3", "$1-"] }, "four", "five", { id: "$id1", args: [["six", "seven"], "3", "4", "$1-"] } ], ["(", "$+", "$1", "$+", "text", "$+", "$2", "$+", ")", "$3-"], [ { id: "$id1", args: [ [ "one", "$+", { id: "$+", args: ["two", { id: "$chr", args: ["44"] }, "three"] }, "$+", "four" ], "4", "5", "$1-" ] } ] ] function joinArgs(args = [], output) { for (let i = 0; i < args.length; i++) { let arg = args[i]; if (typeof arg === "string") { i--; let tempOutput = "", prev = args[i] || "", join = "", next = args[i + 2] || ""; if (typeof prev === "string" && typeof next === "string") { join += arg === "$+"? "": `${arg}`; tempOutput = `${prev}${join}${next}`; } else if (typeof prev === "string" && typeof next === "object") { tempOutput = `${prev}${join}${joinArgs( next.args, output )}`; } else if (typeof prev === "object" && typeof next === "string") { tempOutput = `${joinArgs( prev.args, output )}${join}${next}`; } i += 3; output += tempOutput; } else if (typeof arg === "object") { if (Array.isArray(arg)) { output += joinArgs(arg, output); } else { if (arg.id === "$+") { output += joinArgs(arg.args, output); } else if (arg.id === "$chr") { output += String.fromCharCode(arg.args[0]); } else if (arg.id === "$id1") { const id1Args = []; for ( let id1ArgIdx = 0; id1ArgIdx < arg.args.length; id1ArgIdx++ ) { let id1Arg = arg.args[id1ArgIdx]; if (Array.isArray(id1Arg)) { id1Arg = joinArgs(id1Arg, output).trim(); } id1Args.push(id1Arg); } output += " " // This function is irrelevant to the problem; but it does return a string //id1( //...id1Args.slice(0, id1Args.length - 1), // id1Args[id1Args.length - 1] //); } } } } return output; } argLists.forEach(arg => { console.log(joinArgs(arg, "")); });

The data for the test cases:测试用例的数据:

 const argLists = [ [ "one", { id: "$id1", args: [["two", "three"], "4", "3", "$1-"] }, "four", "five", { id: "$id1", args: [["six", "seven"], "3", "4", "$1-"] } ], ["(", "$+", "$1", "$+", "text", "$+", "$2", "$+", ")", "$3-"], [ { id: "$id1", args: [ [ "one", "$+", { id: "$+", args: ["two", { id: "$chr", args: ["44"] }, "three"] }, "$+", "four" ], "4", "5", "$1-" ] } ] ];

The expected output:预期的 output:

 const output = [ "one (two three 4 3 $1-) four five (six seven 3 4 $1-)", "($1text$2) $3-", "(onetwo,threefour 4 5 $1-)" ];

The output should be concatenated according to these rules: output 应根据以下规则连接:

  1. If the argument is a string and is a $+ , concatenate the previous and next values without a space between them( prevnext ).如果参数是字符串并且是$+ ,则连接前一个值和下一个值,它们之间没有空格( prevnext )。
  2. If the argument is a string and is not a $+ , concatenate the previous, current, and next value with a space between them( prev current next ).如果参数是字符串而不是$+ ,则连接上一个、当前和下一个值,它们之间有一个空格( prev current next )。
  3. If the argument is an object, get its arguments from the args property and recurse as necessary, however:如果参数是 object,则从args属性中获取其 arguments 并根据需要进行递归,但是:
    1. If the object id is $+ , join all arguments without spaces, recursing as necessary per argument.如果 object id 是$+ ,则加入所有不带空格的 arguments ,根据需要根据参数递归。
    2. If the object id is $chr , return String.fromCharCode() with the only argument supplied to the object given to that static method.如果 object id 是$chr ,则返回String.fromCharCode() ,并将提供给 object 的唯一参数提供给该 static 方法。
    3. If the object id is $id1 , treat the first argument as an array according to the rules above(if it is an array), and join all remaining arguments with a space and wrap everything in parenthesis.如果 object id 为$id1 ,则根据上述规则将第一个参数视为数组(如果是数组),并将所有剩余的 arguments 用空格连接起来,并将所有内容括在括号中。
    4. It is technically possible that an object's id may not be any of the prior three possibilities, but that eventuality isn't an issue, since the function the object calls is always going to return a string.从技术上讲,对象的 id 可能不是前三种可能性中的任何一种,但这种可能性不是问题,因为 function object 调用总是会返回一个字符串。

This works for all your test cases provided.这适用于您提供的所有测试用例。

Whenever $+ appears just leave it in the string and then replace all occurrences of it at the end.每当出现$+时,只需将其留在字符串中,然后在末尾替换所有出现的它。 This saves having to deal with doing any 'prev' or 'next' stuff which imo really adds complexity.这样就不必处理任何“上一个”或“下一个”的事情,这确实增加了复杂性。 In the case of the code snippet below, it removes them every time right before the recursive function returns.对于下面的代码片段,它每次都会在递归 function 返回之前删除它们。

The key is to remember with recursive functions is the deepest recursive function call returns first, and in this case keeps building a bigger string up and up the stack.使用递归函数的关键是要记住最深的递归 function 调用首先返回,并且在这种情况下不断构建更大的字符串并向上堆栈。 This line of code,这行代码,

outputString += "(" + joinArgs(current_arg.args) + ") ";

I really think helps understand how the code below works.我真的认为有助于理解下面的代码是如何工作的。 Note that there is a trailing whitespace character after the closing parenthesis and this is trimmed if the closing parenthesis is the last character in the final string.请注意,右括号后面有一个尾随空白字符,如果右括号是最终字符串中的最后一个字符,则会将其修剪掉。

 const argLists = [ [ "one", { id: "$id1", args: [["two", "three"], "4", "3", "$1-"] }, "four", "five", { id: "$id1", args: [["six", "seven"], "3", "4", "$1-"] } ], ["(", "$+", "$1", "$+", "text", "$+", "$2", "$+", ")", "$3-"], [ { id: "$id1", args: [ [ "one", "$+", { id: "$+", args: ["two", { id: "$chr", args: ["44"] }, "three"] }, "$+", "four" ], "4", "5", "$1-" ] } ] ]; function joinArgs(args = []) { outputString = ""; for (let i = 0; i < args.length; i++) { var current_arg = args[i]; //check for string condition if (typeof current_arg === 'string') { outputString += current_arg + " "; } //check for object condition if (typeof current_arg === "object") { if (current_arg.id == '$id1'){ //if object has $id1 id, call joinArgs with the object's args as args parameter //surround object's returned output with parenthesis outputString += "(" + joinArgs(current_arg.args) + ") "; } //no recursive call needed here if (current_arg.id == '$chr') { outputString += "$+ " + String.fromCharCode(current_arg.args); } //call joinArgs with object args as args parameter if (current_arg.id == '$+') { outputString += joinArgs(current_arg.args); } // if object is array call joinArgs with the array as args paramater if (Array.isArray(current_arg)) { outputString += joinArgs(current_arg); } } } //clean up outputString to remove unwanted " $+ " outputString = outputString.replace(/ \$\+ /g, ""); //remove whitespace before closing parenthesis outputString = outputString.replace(/ \)/g, "\)"); return outputString; } argLists.forEach(arg => { //trim leading and trailing whitespace astString = joinArgs(arg).trim(); console.log(astString); });

This version separates the processing of a generic value into handling for arrays, plain objects, and other values (which are just returned.) The main joinArg function calls joinObject and joinArray , each of which can recursively call back to joinArg .此版本将通用值的处理分离为对 arrays、普通对象和其他值(刚刚返回)的处理。主joinArg function 调用joinObjectjoinArray ,每个都可以递归回调joinArg

I think the breakdown in joinObject makes it clear enough how to add other cases if the need arises.我认为joinObject中的细分足以清楚地说明如何在需要时添加其他情况。 It also has a place for default handling, which you alluded to but didn't specify.它还有一个默认处理的位置,您提到但没有指定。

 const joinObject = ({id, args}) => (({ '$+': (ns) => ns.map (joinArgs).join (''), '$chr': ([n]) => String.fromCharCode (n), '$id1': ([ns, ...more]) => `(${joinArgs (ns)} ${more.map (joinArgs).join (' ')})`, }) [id] || ((args) => args.map (joinArgs).join (' '))) (args) // `----------------------------------------' // `--- default. Not sure what belongs here const joinArray = (args) => args.reduce( ({str, skip}, arg) => ({ str: str + (skip || arg == '$+'? '': ' ') + (arg == '$+'? '': joinArgs(arg)), skip: arg == '$+' }), {str: '', skip: true} ).str const joinArgs = (args) => Array.isArray (args)? joinArray (args): Object (args) === args? joinObject (args): args const argLists = [["one", {id: "$id1", args: [["two", "three"], "4", "3", "$1-"]}, "four", "five", {id: "$id1", args: [["six", "seven"], "3", "4", "$1-"]}], ["(", "$+", "$1", "$+", "text", "$+", "$2", "$+", ")", "$3-"], [{id: "$id1", args: [["one", "$+", {id: "$+", args: ["two", {id: "$chr", args: ["44"]}, "three"]}, "$+", "four"], "4", "5", "$1-"]}]] argLists.forEach(args => console.log(joinArgs(args)))

This handles the samples you supplied.这将处理您提供的样本。 I'm curious to see if it captures your full needs or if it still would need to be tweaked for other cases.我很想知道它是否满足您的全部需求,或者是否仍需要针对其他情况进行调整。

The big trick here is the mutual recursion.这里的大技巧是相互递归。 It can well simplify the code you write in circumstances like this.它可以很好地简化您在这种情况下编写的代码。 Another useful technique is the object holding the handlers to run for the different Object id values.另一个有用的技术是 object 持有为不同的 Object id值运行的处理程序。 I find this very useful for separating out the main parts of the logic from what glues them together.我发现这对于将逻辑的主要部分与将它们粘合在一起的部分分离非常有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM