递归地将预格式化公式解析为数组

时间:2015-11-01 16:19:33

标签: php recursion

我正在尝试创建一个公式解析器,我目前仍然坚持如何递归地将代码转换为表达式数组。例如,采用以下公式:

$formula = "
    @VAR[a, 3];
    @IF[ (a <= 3) & (a = 3) ]:
        @VAR[a, a + 4];
        @IF[ a > 5 ]:
            @USE[a];
        @ENDIF
    @ELSEIF[ a > 4 ]:
        @VAR[a, 2];
    @ELSE:
        @VAR[a, 5];
    @ENDIF
    @VAR[a,5];
    @USE[a];
";

应输出:

{
    "0": "VAR[a, 3];",
    "IF[ (a <= 3) & (a = 3) ]:": {
        "0": "VAR[a, a + 4];",
        "1": "ENDIF",
        "IF[ a > 5 ]:": [
            "USE[a];"
        ]
    },
    "ELSEIF[ a > 4 ]:": [
        "VAR[a, 2];"
    ],
    "ELSE:": [
        "VAR[a, 5];"
    ],
    "1": "ENDIF",
    "2": "VAR[a,5];",
    "3": "USE[a];",
}

这样我就可以遍历每个项目并评估每个表达式。

我目前有以下代码,它不会输出预期的格式。

<?php

$formula = "
    @VAR[a, 3];
    @IF[ (a <= 3) & (a = 3) ]:
        @VAR[a, a + 4];
        @IF[ a > 5 ]:
            @USE[a];
        @ENDIF
    @ELSEIF[ a > 4 ]:
        @VAR[a, 2];
    @ELSE:
        @VAR[a, 5];
    @ENDIF
    @VAR[a,5];
    @USE[a];
";

$formulas = explode( "@", $formula );
$result = parse( $formulas );

echo json_encode( $result );
function parse( $lines  ){
    $exec_tree = array();
    foreach( $lines as $i => $block ){
        unset( $lines[$i] );
        $block = trim( str_replace( array(" ") , "" , preg_replace('/\s\s+/', ' ', $block) ) );
        if( trim( $block ) != "" ){

            // MATCH Variable assignments
            if( preg_match('/VAR\[(.*)\]\;?/', $block ) ){
                $exec_tree[] = $block;            
            }   
            // MATCH USE Statements
            if( preg_match('/USE\[(.*)\]\;?/', $block ) ){
                $exec_tree[] = $block;
            }      
            // MATCH IFs
            if( preg_match('/^IF\[(.*)\]\:/', $block ) ){
                $exec_tree[$block] = parse( $lines );
            }        
            // MATCH ELSEIFs
            if( preg_match('/^ELSEIF\[(.*)\]\:/', $block ) ){
                $exec_tree[$block] = parse( $lines );
            }    
            // MATCH ELSEs
            if( preg_match('/^ELSE:/', $block ) ){
                $exec_tree[$block] = parse( $lines );
            }      
            // MATCH ENDIFs
            if( preg_match('/^ENDIF/', $block ) ){
                break;
            }
        }
    }
    return $exec_tree;
}

代码本质上是递归的,但我认为我错过了递归终止的东西。它应该以ENDIF关键字结束。任何人都可以指出我正确的方向将非常感激。

现在是它的输出:( JSON格式化)

[
"VAR[a,3];",
[
    "IF[(a<=3)&(a=3)]:",
    [
        "VAR[a,a+4];",
        [
            "IF[a>5]:",
            [
                "USE[a];"
            ]
        ],
        "USE[a];"
    ]
],
"VAR[a,a+4];",
[
    "IF[a>5]:",
    [
        "USE[a];"
    ]
],
"USE[a];"

谢谢,

1 个答案:

答案 0 :(得分:1)

不可否认,这不是一个完全干净的解决方案,而是工作且易于重构:

<?php

$formula = "
    @VAR[a, 3];
    @IF[ (a <= 3) & (a = 3) ]:
        @VAR[a, a + 4];
        @IF[ a > 5 ]:
            @USE[a];
        @ENDIF
    @ELSEIF[ a > 4 ]:
        @VAR[a, 2];
    @ELSE:
        @VAR[a, 5];
    @ENDIF
    @VAR[a,5];
    @USE[a];
";

$formulas = explode( "@", $formula );
$rec = false;
$result   = parse( $formulas, $rec );

echo json_encode( $result, JSON_PRETTY_PRINT );

function parse( &$lines, &$rec ) {
    $exec_tree = array();
    while ( (bool) $lines === true ) {
        $block = array_shift( $lines );

        $block = trim( str_replace( array( " " ), "", preg_replace( '/\s\s+/', ' ', $block ) ) );
        if ( trim( $block ) != "" ) {

            // MATCH Variable assignments
            if ( preg_match( '/VAR\[(.*)\]\;?/', $block ) ) {
                $exec_tree[] = $block;
            } elseif ( preg_match( '/USE\[(.*)\]\;?/', $block ) ) {
                $exec_tree[] = $block;
            } elseif ( preg_match( '/^IF\[(.*)\]\:/', $block ) ) {
                $rec = true;
                $exec_tree[ $block ] = parse( $lines, $rec );
            } elseif ( preg_match( '/^ELSEIF\[(.*)\]\:/', $block ) ) {
                $rec = !$rec;
                if ( $rec === false ) {
                    array_unshift( $lines, $block );
                    break;
                } else {
                    $exec_tree[ $block ] = parse( $lines, $rec );
                }
            } elseif ( preg_match( '/^ELSE:/', $block ) ) {
                $rec = !$rec;
                if ( $rec === false ) {
                    array_unshift( $lines, $block );
                    break;
                } else {
                    $exec_tree[ $block ] = parse( $lines, $rec );
                }
            } elseif ( preg_match( '/^ENDIF/', $block ) ) {
                $rec = !$rec;
                if ( $rec === false ) {
                    array_unshift( $lines, $block );
                    break;
                } else {
                    $exec_tree[] = $block;
                }
            }
        }
    }

    return $exec_tree;
}

返回

{
    "0": "VAR[a,3];",
    "IF[(a<=3)&(a=3)]:": {
        "0": "VAR[a,a+4];",
        "IF[a>5]:": [
            "USE[a];"
        ],
        "1": "ENDIF"
    },
    "ELSEIF[a>4]:": [
        "VAR[a,2];"
    ],
    "ELSE:": [
        "VAR[a,5];"
    ],
    "1": "ENDIF",
    "2": "VAR[a,5];",
    "3": "USE[a];"
}

诀窍真的只是以某种方式跟踪你是否已经在一个区块中并且在其他区域,elseif和endif上突破,但仍然将这些值附加到最终结果。