【问题标题】:Replace numbers fro string to object将数字从字符串替换为对象
【发布时间】:2017-11-19 09:48:23
【问题描述】:

我有两个变量

var sequence = '(1 AND 2) OR (2 AND 3)'
var filters =[{
        "name": "title",
        "opr": "=",
        "value": "My Post",
        "sid": "1"
    },
    {
        "name": "content",
        "opr": "like",
        "value": "Hello",
        "sid": "2"
    },
    {
        "name": "id",
        "opr": "gt",
        "value": "100",
        "sid": "3"
    }
]

现在我正在寻找替换序列并将其转换为类似的格式

 var output = {
        "OR": [{
                "AND": [{
                        "title": "My Post"
                    },
                    {
                        "content": {
                            "like": "Hello"
                        }
                    }
                ]
            },
            {
                "AND": [{
                        "content": {
                            "like": "Hello"
                        }
                    },
                    {
                        "id": {
                            "gt": "1000"
                        }
                    }
                ]
            }
        ]
    }
}

对于序列字符串,每个数字代表对象数组中的 sid 对于对象数组中的 opr 如果 opr 等于 "=" 它应该创建简单的键值对对象,如

{"title": "My Post"}

对于其他运算符,它应该创建类似

{"content": {"like": "Hello"}}

【问题讨论】:

标签: javascript node.js string object data-structures


【解决方案1】:

你想创建一个解析器。

您的用例可能会有所不同,并会改变您想要做出的权衡,但这里有一个简单的实现,可以完成您上面概述的内容。

const SEQUENCE = "(1 AND 2) OR (2 AND 3)";

const FILTERS = [{
    name: "title",
    opr: "=",
    value: "My Post",
    sid: "1"
  },
  {
    name: "content",
    opr: "like",
    value: "Hello",
    sid: "2"
  },
  {
    name: "id",
    opr: "gt",
    value: "1000",
    sid: "3"
  }
];

const result = compiler(SEQUENCE, FILTERS);

console.log(result);

function compiler(sequence, filters) {
  let tokens = _tokenizer(sequence);
  let ast = _parser(tokens);
  let newAst = _transformer(ast);
  let output = _generator(newAst, filters, {});
  return output;
}

//////////////////////////
function _tokenizer(input) {
  let current = 0;
  let tokens = [];
  let expr = /^[(].*[)]$/;
  let needsParens;
  let parenCounter;

  [].forEach.call(input, function(char, index) {
    if (char === '(') parenCounter = (parenCounter == null) ? 1 : parenCounter + 1;
    if (char === ')') --parenCounter;
    if (parenCounter === 0 && index != input.length - 1) needsParens = true;
  });

  if (!expr.test(input) || needsParens) input = "(" + input + ")";

  while (current < input.length) {
    let char = input[current];

    if (char === '(') {
      tokens.push({
        type: 'paren',
        value: '(',
      });
      current++;
      continue;
    }

    if (char === ')') {
      tokens.push({
        type: 'paren',
        value: ')',
      });
      current++;
      continue;
    }

    let WHITESPACE = /\s/;
    if (WHITESPACE.test(char)) {
      current++;
      continue;
    }

    let NUMBERS = /[0-9]/;
    if (NUMBERS.test(char)) {
      let value = '';

      while (NUMBERS.test(char)) {
        value += char;
        char = input[++current];
      }

      tokens.push({
        type: 'number',
        value
      });

      continue;
    }

    let LETTERS = /[A-Z]/i;
    if (LETTERS.test(char)) {
      let value = '';

      while (LETTERS.test(char)) {
        value += char;
        char = input[++current];
      }

      tokens.push({
        type: 'name',
        value
      });

      continue;
    }

    throw new TypeError('Unknown token: ' + char);
  }

  return tokens;
}
//////////////////////////

//////////////////////////
function _parser(tokens) {
  let current = 0;

  function walk() {
    let token = tokens[current];

    if (token.type === 'number') {
      current++;

      return {
        type: 'NumberLiteral',
        value: token.value,
      };
    }

    if (token.type === 'name') {
      current++;

      return {
        type: 'NameLiteral',
        value: token.value,
      };
    }

    if (token.type === 'paren' && token.value === '(') {
      token = tokens[++current];

      let node = {
        type: 'Expression',
        params: [],
      };

      while ((token.type !== 'paren') || (token.type === 'paren' && token.value !== ')')) {
        node.params.push(walk());
        token = tokens[current];
      }

      current++;

      return node;
    }

    throw new TypeError(token.type);
  }

  let ast = {
    type: 'Program',
    body: [],
  };

  while (current < tokens.length) {
    ast.body.push(walk());
  }

  return ast;
}
//////////////////////////

//////////////////////////
function _traverser(ast, visitor) {

  function _traverseArray(array, parent) {
    array.forEach(child => {
      _traverseNode(child, parent);
    });
  }

  function _traverseNode(node, parent) {
    let methods = visitor[node.type];

    if (methods && methods.enter) methods.enter(node, parent);

    switch (node.type) {
      case 'Program':
        _traverseArray(node.body, node);
        break;

      case 'Expression':
        _traverseArray(node.params, node);
        break;

      case 'NumberLiteral':
      case 'NameLiteral':
        break;

      default:
        throw new TypeError(node.type);
    }

    if (methods && methods.exit) methods.exit(node, parent);
  }

  _traverseNode(ast, null);
}
//////////////////////////

//////////////////////////
function _transformer(ast) {

  let newAst = {
    type: 'Program',
    body: [],
  };

  ast._context = newAst.body;

  _traverser(ast, {
    NumberLiteral: {
      enter(node, parent) {
        parent._context.push({
          type: 'NumberLiteral',
          value: node.value,
        });
      },
    },

    NameLiteral: {
      enter(node, parent) {
        parent._expression.bool = node.value;
      },
    },

    Expression: {
      enter(node, parent) {
        let expression = {
          type: 'Expression',
          arguments: [],
        };

        node._expression = expression;
        node._context = expression.arguments;

        parent._context.push(expression);
      },
    }
  });

  return newAst;
}
//////////////////////////

//////////////////////////
function _generator(node, filters, map) {
  if (filters) filters.forEach(filter => map[filter.sid] = filter);
  switch (node.type) {

    case 'Program':
      return JSON.parse(_generator(node.body[0], null, map));

    case 'Expression':
      return '{"' + node.bool + '":[' + node.arguments.map(arg => _generator(arg, null, map)) + ']}';

    case 'NumberLiteral':
      let filter = map[node.value];
      if (filter.opr === '=') return '{"' + filter.name + '":"' + filter.value + '"}';
      return '{"' + filter.name + '":{"' + filter.opr + '":"' + filter.value + '"}}';

    default:
      throw new TypeError(node.type);
  }
}
//////////////////////////

这是基于James Kyle's Super Tiny Compiler

为简洁起见,我删除了所有 cmets,并进行了大量编辑以满足您的要求。如果你想了解它是如何工作的或者有机会在未来调试或扩展它,你绝对应该看看那个 repo。

从高层次来看,这就是正在发生的事情。

  1. 标记器分隔特殊字符、空格和值。
  2. 解析器获取令牌并生成abstract syntax tree (AST)。
  3. 转换器抓取 AST 并针对我们的用例对其进行修改。
  4. 生成器使用 AST 和过滤数据编译所需的输入。

再一次,不能推荐James Kyle's Super Tiny Compiler

【讨论】:

    猜你喜欢
    • 2021-09-01
    • 2013-11-08
    • 1970-01-01
    • 1970-01-01
    • 2021-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多