Javascript使用参数解析嵌套字符串函数

时间:2015-02-21 00:17:55

标签: javascript regex string parsing

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

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

UPDATE - 我停止尝试进行字符串操作并逐个字符地接近它。要创建所需的输出:

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))

};

3 个答案:

答案 0 :(得分:2)

正则表达式和字符串黑客行为不起作用;正则表达式无法处理(直接)具有嵌套结构的任何文本(人们不断学习...)。切换到单个字符不会改善任何事情。

经典你想要的是一个产生标记(语言元素)的词法分析器和一个解析器(检查元素是否正确组织)。

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

答案 1 :(得分:0)

我看到了这个问题,并认为尝试可能会很有趣。我将完成我的思考过程并希望能帮到你。

我制作的对象并不完全映射到你的对象,但可能很容易。在没有额外工作的情况下最终得到我生成的对象更容易,而不会被“无关”的细节分散注意力,比如将数据放入数组中。

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.)我将假设从这里开始将这个中间对象映射到你想要的目标是直截了当的?

答案 2 :(得分:0)

您不能对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,你必须阅读更多关于如何做到这一点的事情。