Каков алгоритм для парсинга выражений в инфиксной нотации?

Я хотел бы проанализировать булевы выражения в PHP. Как в:

A and B or C and (D or F or not G)

Условия можно считать простыми идентификаторами. У них будет немного структуры, но синтаксический анализатор не должен волноваться об этом. Это должно просто распознать ключевые слова and or not ( ). Все остальное - термин.

Я помню, что мы записали простые средства анализа арифметического выражения в школе, но я не помню, как она больше делалась. И при этом я не знаю что ключевые слова искать в Google/таким образом.

Готовая библиотека была бы хороша, но поскольку я помню, что алгоритм был довольно прост, таким образом, это могла бы быть забава и образовательный, чтобы повторно реализовать его сам.

9
задан Vilx- 19 January 2010 в 11:41
поделиться

7 ответов

Компания под названием Zynamics предлагает семантический дифференциальный инструмент двоичного уровня. Он использует мета-монтажный язык, называемый Reil для выполнения теоретического анализа графиков 2 версий двоичных и создает график с цветовой кодировкой для иллюстрации различий между ними. Я не уверен в цене, но я сомневаюсь, что это бесплатно.

-121--733185-

Я реализовал шунтирующий алгоритм двора, как предложено плинтусом. Тем не менее, этот алгоритм просто дает вам постфикс, ака обратного польского языка (RNP). Вы все еще должны оценить его, но это довольно легко, как только у вас есть выражение в RNP (описано, например здесь ).

Код ниже может быть не хороший стиль PHP, мои знания PHP несколько ограничены. Это должно быть достаточно, чтобы получить идею, хотя.

$operators = array("and", "or", "not");
$num_operands = array("and" => 2, "or" => 2, "not" => 1);
$parenthesis  = array("(", ")");

function is_operator($token) {
    global $operators;
    return in_array($token, $operators);
}

function is_right_parenthesis($token) {
    global $parenthesis;
    return $token == $parenthesis[1];
}

function is_left_parenthesis($token) {
    global $parenthesis;
    return $token == $parenthesis[0];
}

function is_parenthesis($token) {
    return is_right_parenthesis($token) || is_left_parenthesis($token);
}

// check whether the precedence if $a is less than or equal to that of $b
function is_precedence_less_or_equal($a, $b) {
    // "not" always comes first
    if ($b == "not")
        return true;

    if ($a == "not")
        return false;

    if ($a == "or" and $b == "and")
        return true;

    if ($a == "and" and $b == "or")
        return false;

    // otherwise they're equal
    return true;
}


function shunting_yard($input_tokens) {
    $stack = array();
    $output_queue = array();

    foreach ($input_tokens as $token) {
        if (is_operator($token)) {
            while (is_operator($stack[count($stack)-1]) && is_precedence_less_or_equal($token, $stack[count($stack)-1])) {
                    $o2 = array_pop($stack);
                    array_push($output_queue, $o2);
            }
            array_push($stack, $token);

        } else if (is_parenthesis($token)) {
            if (is_left_parenthesis($token)) {
                array_push($stack, $token);
            } else {
                while (!is_left_parenthesis($stack[count($stack)-1]) && count($stack) > 0) {
                    array_push($output_queue, array_pop($stack));
                }
                if (count($stack) == 0) {
                    echo ("parse error");
                    die();
                }
                $lp = array_pop($stack);
            }
        } else {
            array_push($output_queue, $token);  
        }
    }

    while (count($stack) > 0) {
        $op = array_pop($stack);
        if (is_parenthesis($op))
            die("mismatched parenthesis");
        array_push($output_queue, $op);
    }

    return $output_queue;
}

function str2bool($s) {
    if ($s == "true")
        return true;
    if ($s == "false")
        return false;
    die('$s doesn\'t contain valid boolean string: '.$s.'\n');
}

function apply_operator($operator, $a, $b) {
    if (is_string($a))
        $a = str2bool($a);
    if (!is_null($b) and is_string($b))
        $b = str2bool($b);

    if ($operator == "and")
        return $a and $b;
    else if ($operator == "or")
        return $a or $b;
    else if ($operator == "not")
        return ! $a;
    else die("unknown operator `$function'");
}

function get_num_operands($operator) {
    global $num_operands;
    return $num_operands[$operator];
}

function is_unary($operator) {
    return get_num_operands($operator) == 1;
}

function is_binary($operator) {
    return get_num_operands($operator) == 2;
}

function eval_rpn($tokens) {
    $stack = array();
    foreach ($tokens as $t) {
        if (is_operator($t)) {
            if (is_unary($t)) {
                $o1 = array_pop($stack);
                $r = apply_operator($t, $o1, null);
                array_push($stack, $r);
            } else { // binary
                $o1 = array_pop($stack);
                $o2 = array_pop($stack);
                $r = apply_operator($t, $o1, $o2);
                array_push($stack, $r);
            }
        } else { // operand
            array_push($stack, $t);
        }
    }

    if (count($stack) != 1)
        die("invalid token array");

    return $stack[0];
}

// $input = array("A", "and", "B", "or", "C", "and", "(", "D", "or", "F", "or", "not", "G", ")");
$input = array("false", "and", "true", "or", "true", "and", "(", "false", "or", "false", "or", "not", "true", ")");
$tokens = shunting_yard($input);
$result = eval_rpn($tokens);
foreach($input as $t)
    echo $t." ";
echo "==> ".($result ? "true" : "false")."\n";
2
ответ дан 4 December 2019 в 08:33
поделиться

Я бы пошел с Pratt Parser. Это почти похоже на рекурсивный спуск, но умнее :) Достойное объяснение Дуглас Крукфорд (из славы jslint) здесь .

2
ответ дан 4 December 2019 в 08:33
поделиться

Есть URL для HTTP-запросов, которые я хотел бы также спрятаться.

Если ваше приложение делает запрос, нет смысла скрывать это. Запуск приложения, как Fiddler, HTTP Analyzer, или одна из десятков других бесплатных и легко доступных методов, покажет весь трафик вашего приложения.

-121--1324850-

Самый простой способ - использовать Regexes, которые преобразуют ваше выражение в выражение в синтаксисе PHP, а затем используют Eval, как предложено SymcBean. Но я не уверен, что вы хотите использовать его в продукции.

Другой способ - кодировать свой собственный простой рекурсивный анализатор спуска . Это не так сильно, как это может звучать. Для простой грамматики такие ваши (булевые выражения), вы можете легко код с нуля. Вы также можете использовать генератор анализатора, аналогичный ANTLR для PHP, возможно, поиск генератора PHP-анализатора будет что-то.

0
ответ дан 4 December 2019 в 08:33
поделиться

Вы можете использовать анализатор LR для создания дерева разбора, а затем оценить дерево для получения результата. Подробное описание, в том числе примеры, можно найти в Википедии . Если вы не закодировали это самостоятельно, я напишу небольшой пример сегодня вечером.

0
ответ дан 4 December 2019 в 08:33
поделиться

Рекурсивные анализаторы спуска FULL для записи и легко читать . Первый шаг - написать свою грамматику.

Может быть, это грамматика, которую вы хотите.

expr        = and_expr ('or' and_expr)*
and_expr    = not_expr ('and' not_expr)*
not_expr    = simple_expr | 'not' not_expr
simple_expr = term | '(' expr ')'

Включение этого в рекурсивный анализатор спуска - супер легко. Просто напишите одну функцию на нетермину.

def expr():
    x = and_expr()
    while peek() == 'or':
        consume('or')
        y = and_expr()
        x = OR(x, y)
    return x

def and_expr():
    x = not_expr()
    while peek() == 'and':
        consume('and')
        y = not_expr()
        x = AND(x, y)
    return x

def not_expr():
    if peek() == 'not':
        consume('not')
        x = not_expr()
        return NOT(x)
    else:
        return simple_expr()

def simple_expr():
    t = peek()
    if t == '(':
        consume('(')
        result = expr()
        consume(')')
        return result
    elif is_term(t):
        consume(t)
        return TERM(t)
    else:
        raise SyntaxError("expected term or (")

Это не завершено. Вы должны предоставить немного больше кода:

  • входные функции. Потребляйте , , , а IS_Term - это функции, которые вы предоставляете. Они будут легко реализовать с использованием регулярных выражений. потребляют (ы) . Считывает следующий токен ввода и бросает ошибку, если она не совпадает S . PEEK () просто возвращает взгляд на следующий токен, не потребляя его. IS_Term (ы) Возвращает true, если - это термин.

  • Выходные функции. ИЛИ , , , , , , а , и Термин вызывается каждый раз, когда часть выражения успешно проанализируется. Они могут делать все, что вы хотите.

  • Функция обертки. вместо того, чтобы просто звонить EXPR напрямую, вы захотите написать небольшую функцию обертки, которая инициализирует переменные, используемые , потребляемым и PEEK , затем вызовы EXPR и, наконец, проверяет, чтобы убедиться, что нет вклада оставки, который не потреблял.

Даже со всем этим, это все еще крошечное количество кода. В Python полная программа составляет 84 строк , и это включает в себя несколько тестов.

15
ответ дан 4 December 2019 в 08:33
поделиться

Почему не Jsut не использует анализатор PHP?

 $terms=array('and','or','not','A','B','C','D'...);
 $values=array('*','+','!',1,1,0,0,1....);

 $expression="A and B or C and (D or F or not G)";
 $expression=preg_replace($terms, $values,$expression);
 $expression=preg_replace('^(+|-|!|1|0)','',$expression);
 $result=eval($expression);

на самом деле, что 2-е регулярное выражение не так (и требуется только Если вам нужно предотвратить инъекцию какой-либо кода) - но вы получите идею.

c.

4
ответ дан 4 December 2019 в 08:33
поделиться

Хотя разработчикам довольно часто приходится работать с одним и тем же файлом, вряд ли когда-либо возникала необходимость в выполнении обновления два раза подряд из-за того, что люди фиксировали изменения до того, как у вас была возможность завершить слияние.

И если это так, и вы должны были иметь 5 людей, работающих над одним и тем же файлом (сумасшедший по своему усмотрению), делая микрозамки каждые 5 минут, я бы сказал, что у вас есть другие проблемы, о которых вы должны беспокоиться и должны реструктурировать свой код, а также дать им (вашим сверстникам) право «соблазнительно».

-121--3206176-

См. Как изменить размер изображения на языке C # до определенного размера жесткого диска

-121--2357126-

Алгоритм маневровой станции Дайкстры является традиционным для перехода от инфикса к постфиксу/графу.

2
ответ дан 4 December 2019 в 08:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: