【问题标题】:Javascript Parse Nested String Functions with parametersJavascript 使用参数解析嵌套字符串函数
【发布时间】:2015-04-22 19:25:18
【问题描述】:

我正在尝试将此字符串解析为一组有组织的函数:

var str = "a(b, c(e, f(h,i,j), g(k,l,m(o,p,q)) ), d(r,s,t))"

理想情况下,我想把它变成这样的对象:

var obj = {
    func:'a',
    params:[
        {p:'b'},
        {p: {
            func:'c',
            params:[
                {
                    p:'e',
                    p:{
                        func:'f',
                        params:[
                            {p:'h'},
                            {p:'i'},
                            {p:'j'}
                        ]
                    },
                    p:'g',
                    params:[
                        {p:'k'},
                        {p:'l'},
                        {p:{
                            func:'m',
                            params:[
                                {p:'o'},
                                {p:'p'},
                                {p:'q'}
                            ]
                        }}
                    ]
                }
            ]
        }},
        {
            p:'d',
            params:[
                {p:'r'},
                {p:'s'},
                {p:'t'}
            ]
        }
    ]
}

我已经尝试了大约 8 小时的混合 str.replace() str.substring() 和 str.indexOf() 并且没有任何运气。

任何关于如何实现我的目标的帮助都会受到欢迎。

注意:函数可以采用任意数量的参数,并且不设置为 3

更新——我不再尝试进行字符串操作,而是逐个字符地处理它。创建所需的输出:

var str = "a(b, c(e, f(h,i,j), g(k,l,m(o,p,q)) ), d(r,s,t))";
str = str.replace('/ /g,""');
var strArr = str.split('');
var firstPass = "";
var final;
var buildObj = function(){
for(var i = 0; i < strArr.length; i++){
    var letters = /^[0-9a-zA-Z]+$/;

    if(strArr[i].match(letters)){
        if(strArr[i + 1] == '('){
            firstPass += '},{"func":' + '"' + strArr[i] + '"';
        } else {
            firstPass += '"' + strArr[i] + '"';
        }

    }
    if(strArr[i] == '('){
        firstPass += ',"params":[{"p":';
    }
    if(strArr[i] == ')'){
        firstPass += '}],';
    }
    if(strArr[i] == ','){
        firstPass += '},{"p":';
    }

    //console.log(job + '}')
}

var secondPass = firstPass;
secondPass += '}'
secondPass = secondPass.replace(/,{"p":}/g,'');
secondPass = secondPass.replace('},','');
secondPass = secondPass.replace(/],}/g,']}');
final = secondPass
console.log(final)
console.log(JSON.parse(final))

};

【问题讨论】:

  • 您将不得不编写一个更复杂的解析器来解析它。通过常规语法并不能真正处理它。计算括号嵌套是主要问题。
  • 是的,这是我遇到的主要问题。由于处理未知数量的参数,它变得更加困难。如果有任何想法,我会全力以赴。
  • 解析是一个很大的话题,就像你在高级计算机科学中学习的一门或多门课程一样。这不是超级复杂,但涉及的东西很多。
  • 你考虑过jison吗?会简单很多。

标签: javascript regex string parsing


【解决方案1】:

正则表达式和字符串破解是行不通的;正则表达式不能(直接)处理任何具有嵌套结构的文本(人们一直在学习这个......)。切换到单个字符并没有改善任何东西。

通常您需要的是一个生成标记(语言元素)的词法分析器和一个解析器(检查元素组织是否正确)。

实际上,您可以将这些组合成一个连贯的结构,用于简单的语言,例如对 OP 感兴趣的语言。轻松查看this SO answer on how to build a recursive descent parser;遵循那个告诉如何构建树的答案(本质上,如何构建您想要的结果结构)。

【讨论】:

    【解决方案2】:

    我看到了这个问题,并认为尝试一下可能会很有趣。我将介绍我的思考过程,希望对您有所帮助。

    我制作的对象并不完全映射到你的,但很容易。无需额外工作即可轻松完成我制作的对象,而不会被诸如将事物放入数组之类的“无关”细节分心。

    1.) 我假设空格是无用的。第一步是用空替换所有空格。

    function processStatement(statement) {
        return statement.replace(/[ ]/g, '');
    }
    // Output: a(b,c(e,f(h,i,j),g(k,l,m(o,p,q))),d(r,s,t))
    

    2.) 我继续创建树状对象的目标,其参数不会导致更多函数作为死胡同。我需要一种解析“根”的方法,代码应该解释更多:

    function produceNodeFromStatement(statement) {
        var regex = new RegExp('([a-zA-Z])\\((.+)\\)', 'g');
        var results = regex.exec(statement);
        // This regex matches for the widest pattern: identifier(stuff-inside)
        // Running the previous output would result in matching groups of:
        // identifier: a
        // stuff-inside: b,c(e,f(h,i,j),g(k,l,m(o,p,q))),d(r,s,t)
    
        var root = {}
        // We need a way to split the stuff-inside by commas that are not enclosed in parenthesis.
        // We want to turn stuff-inside into the following array:
        // [ 'b', 'c(e,f(h,i,j),g(k,l,m(o,p,q)))', 'd(r,s,t)' ]
        // Since my regex-fu is bad, I wrote a function to do this, explained in the next step.
        var parameters = splitStatementByExternalCommas(results[2]);
    
        var node = {};
        parameters.forEach(function (parameter) {
            if (parameter.indexOf('(') == -1) {
                node[parameter] = null;
            } else {
                // Recursion. This function produces an anonymous wrapper object around a node.
                // I will need to unwrap my result.
                var wrappedNode = deconstructStatement(parameter);
                var key;
                for (key in wrappedNode) {
                    node[key] = wrappedNode[key];
                }
            }
        });
    
        // Assign node to the node's identifier
        root[results[1]] = node;
        return root;
    }
    

    3.) 公式中唯一缺少的部分是仅用外部逗号分割字符串的函数 - 因为我无法找出正则表达式,这里是 splitStatementByExternalCommas

    function splitStatementByExternalCommas(statement) {
        statement += ','; // so I don't have to handle the "last, leftover parameter"
        var results = [];
        var chars = statement.split('');
        var level = 0; // levels of parenthesis, used to track how deep I am in ().
        var index = 0; // determines which parameter am I currently on.
        var temp = '';
    
        // this is somewhat like your implementation in the edits, I walk through the characters one by one, and do something extra if it's a special character.
        chars.forEach(function (char) {
            switch (char) {
                case '(':
                    temp += char;
                    level++;
                    break;
                case ')':
                    temp += char;
                    level--;
                    break;
                case ',':
                    // if the comma is between a set of parenthesis, ignore.
                    if (level !== 0) { temp += char; }
                    // if the comma is external, split the string.
                    else { results[index] = temp; temp = ''; index++; }
                    break;
                default:
                    temp += char;
                    break;
            }
        });
        return results;
    }
    

    4.) 将所有内容放在一起,将您的字符串语句转换为中间对象:

    var str = "a(b, c(e, f(h,i,j), g(k,l,m(o,p,q)) ), d(r,s,t))";
    str = processStatement(str);
    var root = produceNodeFromStatement(str);
    // Output is something like:
    
    { 
      a: { 
           b: null,
           c: {
              e: null,
              f: { h: null, i: null, j: null },
              g: {
                  k: null, l: null,
                  m: { o: null, p: null, q: null }
              }
           },
           d: { r: null, s: null, t: null }
      }
    }
    

    5.) 我假设从现在开始将此中间对象映射到您的预期目标很简单?

    【讨论】:

    • 假设字符串是有效的。如果不是,所有的赌注都没有了。
    【解决方案3】:

    您不能对 3 个值使用相同的属性名称,因此您不能这样做。

            func:'c',
            params:[
                {
                    p:'e',
                    p:{
                        func:'f',
                        params:[
                            {p:'h'},
                            {p:'i'},
                            {p:'j'}
                        ]
                    },
                    p:'g',
    

    如果我们删除这部分并修复您示例中其他不一致的部分(至少尝试编写一个本身不是失败的示例),使用 eval 将您的代码转换为 javascript 相对容易:

    解析器:

    var parse = function (str) {
    
        var compiled = str.replace(/(\w+)\s*(\W)/g, function (match, name, token) {
            if (token == "(")
                return "q(\"" + name + "\",";
            else
                return "p(\"" + name + "\")" + token;
        }).replace(/,\s*\)/g, ")");
    
        function q(name) {
            return {
                p: {
                    func: name,
                    params: Array.prototype.slice.call(arguments, 1)
                }
            };
        }
    
        function p(name) {
            return {
                p: name
            };
        }
    
        var f = eval("(function (){return " + compiled + ";})");
    
        return f().p;
    };
    

    测试:

    describe("x", function () {
    
        it("y", function () {
    
            var str = "a(b, c(e), d(r,s,t))";
    
    
            var obj = {
                func: 'a',
                params: [
                    {p: "b"},
                    {
                        p: {
                            func: 'c',
                            params: [
                                {
                                    p: 'e'
                                }
                            ]
                        }
                    },
                    {
                        p: {
                            func: 'd',
                            params: [
                                {p: 'r'},
                                {p: 's'},
                                {p: 't'}
                            ]
                        }
                    }
                ]
            };
    
            expect(parse(str)).toEqual(obj);
    
        });
    
    });
    

    注意:

    我同意 Ira Baxter 的观点,您必须阅读更多关于如何做到这一点的信息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-07
      相关资源
      最近更新 更多