提问人:MarkokraM 提问时间:1/20/2017 最后编辑:MarkokraM 更新时间:1/21/2017 访问量:214
如何使用 PHP 从布尔表示法 (DSL) 创建数组树
How to create an array tree from boolean notation (DSL) with PHP
问:
我的输入很简单:
$input = '( ( "M" AND ( "(" OR "AND" ) ) OR "T" )';
其中 ( 在树上开始一个新节点,然后 ) 结束它。AND 和 OR 单词是为布尔运算保留的,因此除非它们不在 “” 标记内,否则它们具有特殊的含义。在我的 DSL 中,AND 和 OR 子句更改了 By 节点级别,因此在级别上只能有 AND 或 OR 子句。如果 AND 在 OR 之后,它将启动一个新的子节点。“”中的所有字符都应按原样看待。最后“可以像往常一样用\”逃脱。
在PHP中翻译句子的好方法是什么:
$output = array(array(array("M" , array("(", "AND")) , "T"), FALSE);
请注意,FALSE 是一个指示符,表示根级别具有 OR 关键字。如果输入为:
( ( "M" AND ( "(" OR "AND" ) ) AND "T" )
则输出为:
$output = array(array(array("M", array("(", "AND")), "T"), TRUE);
使用 replace('(', 'array('); 和 eval 代码很诱人,但转义字符和包装文字会成为一个问题。
目前我没有在 DSL 上实现 NOT 布尔运算符。
感谢您的帮助。JavaSript 代码也可以。
Python 示例:
在使用 PHP 和 Javascript 之前,我用 Python 做了一些测试。我所做的是:
- 使用正则表达式查找字符串文本
- 将文本替换为生成的键
- 将文字存储到 assoc 列表
- 通过括号将输入拆分为单级列表
- 查找根级布尔运算符
- 摆脱布尔运算符和空格
- 将文本键替换为存储值
它可能会起作用,但我相信一定有更复杂的方法可以做到这一点。
答:
这是我的副项目的库修改。它应该处理这些字符串 - 执行一些压力测试,如果它在某个地方损坏,请告诉我。
需要 tokenizer 类型类来提取和标记变量,因此它们不会干扰语法解析和标记化 parethesis,以便它们可以直接匹配(-evaluate 的内容不会捕获嵌套级别,并且会覆盖同一级别的所有上下文)。它还具有一些关键字语法(比需要的多一点,因为它只会针对根级别进行解析)。尝试使用错误的键访问变量注册表以及括号不匹配时引发。lazy
greedy
InvalidArgumentException
RuntimeException
class TokenizedInput
{
const VAR_REGEXP = '\"(?P<string>.*?)\"';
const BLOCK_OPEN_REGEXP = '\(';
const BLOCK_CLOSE_REGEXP = '\)';
const KEYWORD_REGEXP = '(?<keyword>OR|AND)';
// Token: <TOKEN_DELIM_LEFT><TYPE_TOKEN><ID_DELIM>$id<TOKEN_DELIM_RIGHT>
const TOKEN_DELIM_LEFT = '<';
const TOKEN_DELIM_RIGHT = '>';
const VAR_TOKEN = 'VAR';
const KEYWORD_TOKEN = 'KEYWORD';
const BLOCK_OPEN_TOKEN = 'BLOCK';
const BLOCK_CLOSE_TOKEN = 'ENDBLOCK';
const ID_DELIM = ':';
const ID_REGEXP = '[0-9]+';
private $original;
private $tokenized;
private $data = [];
private $blockLevel = 0;
private $varTokenId = 0;
protected $procedure = [
'varTokens' => self::VAR_REGEXP,
'keywordToken' => self::KEYWORD_REGEXP,
'blockTokens' => '(?P<open>' . self::BLOCK_OPEN_REGEXP . ')|(?P<close>' . self::BLOCK_CLOSE_REGEXP . ')'
];
private $tokenMatch;
public function __construct($input) {
$this->original = (string) $input;
}
public function string() {
isset($this->tokenized) or $this->tokenize();
return $this->tokenized;
}
public function variable($key) {
isset($this->tokenized) or $this->tokenize();
if (!isset($this->data[$key])) {
throw new InvalidArgumentException("Variable id:($key) does not exist.");
}
return $this->data[$key];
}
public function tokenSearchRegexp() {
if (!isset($this->tokenMatch)) {
$strings = $this->stringSearchRegexp();
$blocks = $this->blockSearchRegexp();
$this->tokenMatch = '#(?:' . $strings . '|' . $blocks . ')#';
}
return $this->tokenMatch;
}
public function stringSearchRegexp($id = null) {
$id = $id ?: self::ID_REGEXP;
return preg_quote(self::TOKEN_DELIM_LEFT . self::VAR_TOKEN . self::ID_DELIM)
. '(?P<id>' . $id . ')'
. preg_quote(self::TOKEN_DELIM_RIGHT);
}
public function blockSearchRegexp($level = null) {
$level = $level ?: self::ID_REGEXP;
$block_open = preg_quote(self::TOKEN_DELIM_LEFT . self::BLOCK_OPEN_TOKEN . self::ID_DELIM)
. '(?P<level>' . $level . ')'
. preg_quote(self::TOKEN_DELIM_RIGHT);
$block_close = preg_quote(self::TOKEN_DELIM_LEFT . self::BLOCK_CLOSE_TOKEN . self::ID_DELIM)
. '\k<level>'
. preg_quote(self::TOKEN_DELIM_RIGHT);
return $block_open . '(?P<contents>.*)' . $block_close;
}
public function keywordSearchRegexp($keyword = null) {
$keyword = $keyword ? '(?P<keyword>' . $keyword . ')' : self::KEYWORD_REGEXP;
return preg_quote(self::TOKEN_DELIM_LEFT . self::KEYWORD_TOKEN . self::ID_DELIM)
. $keyword
. preg_quote(self::TOKEN_DELIM_RIGHT);
}
private function tokenize() {
$current = $this->original;
foreach ($this->procedure as $method => $pattern) {
$current = preg_replace_callback('#(?:' . $pattern . ')#', [$this, $method], $current);
}
if ($this->blockLevel) {
throw new RuntimeException("Syntax error. Parenthesis mismatch." . $this->blockLevel);
}
$this->tokenized = $current;
}
protected function blockTokens($match) {
if (isset($match['close'])) {
$token = self::BLOCK_CLOSE_TOKEN . self::ID_DELIM . --$this->blockLevel;
} else {
$token = self::BLOCK_OPEN_TOKEN . self::ID_DELIM . $this->blockLevel++;
}
return $this->addDelimiters($token);
}
protected function varTokens($match) {
$this->data[$this->varTokenId] = $match[1];
return $this->addDelimiters(self::VAR_TOKEN . self::ID_DELIM . $this->varTokenId++);
}
protected function keywordToken($match) {
return $this->addDelimiters(self::KEYWORD_TOKEN . self::ID_DELIM . $match[1]);
}
private function addDelimiters($token) {
return self::TOKEN_DELIM_LEFT . $token . self::TOKEN_DELIM_RIGHT;
}
}
解析器类型类对标记化字符串执行匹配 - 提取已注册的变量,并通过克隆本身递归进入嵌套上下文。 运算符类型处理是不寻常的,这使得它更像是一个派生类,但无论如何,在解析器的世界里很难实现令人满意的抽象。
class ParsedInput
{
private $input;
private $result;
private $context;
public function __construct(TokenizedInput $input) {
$this->input = $input;
}
public function result() {
if (isset($this->result)) { return $this->result; }
$this->parse($this->input->string());
$this->addOperator();
return $this->result;
}
private function parse($string, $context = 'root') {
$this->context = $context;
preg_replace_callback(
$this->input->tokenSearchRegexp(),
[$this, 'buildStructure'],
$string
);
return $this->result;
}
protected function buildStructure($match) {
if (isset($match['contents'])) { $this->parseBlock($match['contents'], $match['level']); }
elseif (isset($match['id'])) { $this->parseVar($match['id']); }
}
protected function parseVar($id) {
$this->result[] = $this->input->variable((int) $id);
}
protected function parseBlock($contents, $level) {
$nested = clone $this;
$this->result[] = $nested->parse($contents, (int) $level);
}
protected function addOperator() {
$subBlocks = '#' . $this->input->blockSearchRegexp(1) . '#';
$rootLevel = preg_replace($subBlocks, '', $this->input->string());
$rootKeyword = '#' . $this->input->keywordSearchRegexp('AND') . '#';
return $this->result[] = (preg_match($rootKeyword, $rootLevel) === 1);
}
public function __clone() {
$this->result = [];
}
}
用法示例:
$input = '( ( "M" AND ( "(" OR "AND" ) ) AND "T" )';
$tokenized = new TokenizedInput($input);
$parsed = new ParsedInput($tokenized);
$result = $parsed->result();
我删除了命名空间/导入/intrefaces,因此您可以根据需要进行调整。也不想挖掘(现在可能无效)评论,所以也删除了它们。
评论
clone
blockSearchRegexp()
评论
OR
( "(" AND "AND" )
( "(" OR "AND" )