mrsuh / php-bison-skeleton

PHP skeleton for Bison
https://mrsuh.com/articles/2023/php-skeleton-for-bison/
GNU General Public License v3.0
35 stars 1 forks source link

How to use %prec to increase priority? #6

Closed huanghantao closed 1 year ago

huanghantao commented 1 year ago

I'm implementing a negative number,the complete syntax is as follows:

%define api.parser.class {GeneratedParser}
%define api.namespace {App\Infrastructure\Core\Component\FormulaParser}
%code parser {
    private array $stmts = [];
    public function setStmts(array $stmts): void { $this->stmts = $stmts; }
    public function getStmts(): array { return $this->stmts; }
}

%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL
%left T_BOOLEAN_OR
%left T_BOOLEAN_AND
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
%left '&'
%left '+' '-'
%left '*' '/'
%left UMINUS
%left '(' ')'

%token T_COLUMN_ID
%token T_SHEET_ID
%token T_STRING
%token T_LNUMBER
%token T_IS_EQUAL               "'='"
%token T_IS_NOT_EQUAL           "'!='"
%token T_IS_SMALLER_OR_EQUAL    "'<='"
%token T_IS_GREATER_OR_EQUAL    "'>='"
%token T_BOOLEAN_OR             "'||'"
%token T_BOOLEAN_AND            "'&&'"

%%
start:
    top_statement_list  {
        self::setStmts($1);
    }
;

top_statement_list:
  top_statement_list statement  {
      $$[] = $2;
  }
  | %empty { $$ = []; }
;

statement:
    expr {
        $$ = new Node\Stmt\Expression($1);
    }
;

expr:
    expr '+' expr {
        $$ = new Node\Expr\BinaryOp\Plus($1, $3);
    }
    | expr '-' expr {
        $$ = new Node\Expr\BinaryOp\Minus($1, $3);
    }
    | '-' expr %prec UMINUS {
        $$ = new Node\Expr\BinaryOp\Minus(-$2);
    }
    | expr '*' expr {
        $$ = new Node\Expr\BinaryOp\Mul($1, $3);
    }
    | expr '/' expr {
        $$ = new Node\Expr\BinaryOp\Div($1, $3);
    }
    | expr '&' expr {
        $$ = new Node\Expr\BinaryOp\Concat($1, $3);
    }
    | expr T_IS_EQUAL expr {
        $$ = new Node\Expr\BinaryOp\Equal($1, $3);
    }
    | expr T_IS_NOT_EQUAL expr {
        $$ = new Node\Expr\BinaryOp\NotEqual($1, $3);
    }
    | expr '<' expr {
        $$ = new Node\Expr\BinaryOp\Smaller($1, $3);
    }
    | expr T_IS_SMALLER_OR_EQUAL expr {
        $$ = new Node\Expr\BinaryOp\SmallerOrEqual($1, $3);
    }
    | expr '>' expr {
        $$ = new Node\Expr\BinaryOp\Greater($1, $3);
    }
    | expr T_IS_GREATER_OR_EQUAL expr {
        $$ = new Node\Expr\BinaryOp\GreaterOrEqual($1, $3);
    }
    | expr T_BOOLEAN_AND expr {
        $$ = new Node\Expr\BinaryOp\BooleanAnd($1, $3);
    }
    | expr T_BOOLEAN_OR expr {
        $$ = new Node\Expr\BinaryOp\BooleanOr($1, $3);
    }
    | column_expr {
        $$ = $1;
    }
    | function_call_expr {
        $$ = $1;
    }
    | scalar {
        $$ = $1;
    }
;

column_expr:
    T_COLUMN_ID {
        $$ = new Node\Expr\ColumnId($1);
    }
    | T_SHEET_ID '.' T_COLUMN_ID {
        $$ = new Node\Expr\ColumnId($3, $1);
    }
;

function_call_expr:
    name argument_list {
        $$ = new Node\Expr\FuncCall($1, $2);
    }
;

argument_list:
    '(' ')' {
        $$ = [];
    }
    | '(' non_empty_argument_list ')' {
        $$ = $2;
    }
;

non_empty_argument_list:
    argument {
        $$ = [$1];
    }
    | non_empty_argument_list ',' argument {
        $$[] = $3;
    }
;

argument:
    expr {
        $$ = new Node\Arg($1);
    }
;

name:
    T_STRING {
        $$ = new Node\Name($1);
    }
;

scalar:
    T_LNUMBER {
        $$ = new Node\Scalar\LNumber($1);
    }
;

and output:

bison -S /xxx/vendor/mrsuh/php-bison-skeleton/src/php-skel.m4 -o /xxx/app/Infrastructure/Core/Component/FormulaParser/GeneratedParser.php /xxx/app/Infrastructure/Core/Component/FormulaParser/Grammar/grammar.y  -Wcounterexamples
/xxx/app/Infrastructure/Core/Component/FormulaParser/Grammar/grammar.y: warning: 1 shift/reduce conflict [-Wconflicts-sr]
/xxx/app/Infrastructure/Core/Component/FormulaParser/Grammar/grammar.y: warning: shift/reduce conflict on token '-' [-Wcounterexamples]
  Example: top_statement_list expr • '-' expr
  Shift derivation
    top_statement_list
    ↳ 2: top_statement_list statement
                            ↳ 4: expr
                                 ↳ 6: expr • '-' expr
  Reduce derivation
    top_statement_list
    ↳ 2: top_statement_list                  statement
         ↳ 2: top_statement_list statement   ↳ 4: expr
                                 ↳ 4: expr •      ↳ 7: '-' expr

It seems that %prec UMINUS is not effective.

Relevant environment:

bison --version
bison (GNU Bison) 3.8.2
Written by Robert Corbett and Richard Stallman.

Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
mrsuh commented 1 year ago

Hi I reproduced a simple calculator with %prec UMINUS and it works well.

%define api.parser.class {Parser}

%token T_NUMBER

%left '-' '+'
%left UMINUS

%%
start:
  expression                       { printf("%d\n", $1); }
;

expression:
  T_NUMBER                         { $$ = $1;       }
| expression '+' expression        { $$ = $1 + $3;  }
| expression '-' expression        { $$ = $1 - $3;  }
| '-' expression  %prec UMINUS     { $$ = -$2;      }
;

%%
class Lexer implements LexerInterface {

    private array $words;
    private int   $index = 0;
    private int   $value = 0;

    public function __construct($resource)
    {
        $this->words = explode(' ', trim(fgets($resource)));
    }

    public function yyerror(string $message): void
    {
        printf("%s\n", $message);
    }

    public function reportSyntaxError(Context $ctx): void
    {
        $this->yyerror('syntax error');
    }

    public function getLVal()
    {
        return $this->value;
    }

    public function yylex(): int
    {
        if ($this->index >= count($this->words)) {
            return LexerInterface::YYEOF;
        }

        $word = $this->words[$this->index++];
        if (is_numeric($word)) {
            $this->value = (int)$word;

            return LexerInterface::T_NUMBER;
        }

        return ord($word);
    }
}

$lexer  = new Lexer(STDIN);
$parser = new Parser($lexer);
if (!$parser->parse()) {
    exit(1);
}
bison -S ../../src/php-skel.m4 -o parser.php grammar.y
php parser.php <<< "1 + - 2"                          
-1

Feel free to ask questions here https://stackoverflow.com/search?q=bison+BNF as these warnings are unrelated to the php-bison-skeleton template.

You can suppress warnings:

%expect 2
huanghantao commented 1 year ago

Thank you! This was my problem and the conflict was resolved when I added semi to the statement:

%define api.parser.class {GeneratedParser}
%define api.namespace {App\Infrastructure\Core\Component\FormulaParser}
%code parser {
    private array $stmts = [];
    public function setStmts(array $stmts): void { $this->stmts = $stmts; }
    public function getStmts(): array { return $this->stmts; }
}

%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL
%left T_BOOLEAN_OR
%left T_BOOLEAN_AND
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
%left '&'
%left '+' '-'
%left '*' '/'
%left '(' ')'

%token T_COLUMN_ID
%token T_SHEET_ID
%token T_LABEL
%token T_LNUMBER
%token T_STRING
%token T_IS_EQUAL               "'='"
%token T_IS_NOT_EQUAL           "'!='"
%token T_IS_SMALLER_OR_EQUAL    "'<='"
%token T_IS_GREATER_OR_EQUAL    "'>='"
%token T_BOOLEAN_OR             "'||'"
%token T_BOOLEAN_AND            "'&&'"

%%
start:
    top_statement_list  {
        self::setStmts($1);
    }
;

top_statement_list:
  top_statement_list statement  {
      $$[] = $2;
  }
  | %empty { $$ = []; }
;

statement:
    expr semi {
        $$ = new Node\Stmt\Expression($1);
    }
;

expr:
    expr '+' expr {
        $$ = new Node\Expr\BinaryOp\Plus($1, $3);
    }
    | expr '-' expr {
        $$ = new Node\Expr\BinaryOp\Minus($1, $3);
    }
    | '-' expr {
        $$ = new Node\Expr\UnaryMinus($2);
    }
;

semi:
    ';' {
        $$ = $1;
    }
;