├── .github └── workflows │ └── php.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── bin └── phpyacc ├── composer.json ├── composer.lock ├── examples ├── 00-basic-usage │ ├── grammar.y │ ├── parser.diff │ ├── parser.kmyacc.php │ ├── parser.phpyacc.php │ ├── parser.template.php │ ├── y.diff │ ├── y.kmyacc.output │ └── y.phpyacc.output ├── 01-expression-support │ ├── grammar.y │ ├── parser.diff │ ├── parser.kmyacc.php │ ├── parser.phpyacc.php │ ├── parser.template.php │ ├── y.diff │ ├── y.kmyacc.output │ └── y.phpyacc.output ├── 02-complex-expression-support │ ├── grammar.y │ ├── parser.diff │ ├── parser.kmyacc.php │ ├── parser.phpyacc.php │ ├── parser.template.php │ ├── y.diff │ ├── y.kmyacc.output │ └── y.phpyacc.output ├── 10-php7 │ ├── grammar.y │ ├── parser.diff │ ├── parser.kmyacc.php │ ├── parser.phpyacc.php │ ├── parser.template.php │ ├── y.diff │ ├── y.kmyacc.output │ └── y.phpyacc.output ├── 20-custom-parser │ ├── grammar.y │ ├── parser.diff │ ├── parser.kmyacc.php │ ├── parser.phpyacc.php │ ├── parser.template.php │ ├── y.diff │ ├── y.kmyacc.output │ └── y.phpyacc.output └── rebuild.php └── lib ├── CodeGen ├── Language.php ├── Language │ └── PHP.php └── Template.php ├── Compress ├── Auxiliary.php ├── Compress.php ├── CompressResult.php ├── Preimage.php ├── TRow.php └── functions.php ├── Exception ├── LexingException.php ├── LogicException.php ├── ParseException.php ├── PhpYaccException.php └── TemplateException.php ├── Generator.php ├── Grammar ├── Context.php ├── State.php └── Symbol.php ├── Lalr ├── ArrayBitset.php ├── Bitset.php ├── Conflict.php ├── Conflict │ ├── ReduceReduce.php │ └── ShiftReduce.php ├── Generator.php ├── Item.php ├── LalrResult.php ├── Lr1.php ├── Reduce.php ├── StringBitset.php └── functions.php ├── Macro.php ├── Yacc ├── Lexer.php ├── LexerTest.php ├── Macro │ └── DollarExpansion.php ├── MacroAbstract.php ├── MacroSet.php ├── Parser.php ├── ParserTest.php ├── Production.php ├── ProductionTest.php ├── Token.php └── TokenTest.php └── functions.php /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | name: Run tests on ${{ matrix.php }} 6 | runs-on: ubuntu-latest 7 | 8 | strategy: 9 | matrix: 10 | php: [ '8.1', '8.2', '8.3', '8.4' ] 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | # Docs: https://github.com/shivammathur/setup-php 17 | - name: Setup PHP 18 | uses: shivammathur/setup-php@v2 19 | with: 20 | php-version: ${{ matrix.php }} 21 | 22 | - name: Setup problem matchers for PHPUnit 23 | run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 24 | 25 | - name: Install Composer dependencies 26 | run: composer install --no-progress 27 | 28 | - name: Run PHPUnit 29 | run: composer test 30 | 31 | - name: Check Formatting 32 | run: vendor/bin/php-cs-fixer fix -v --dry-run --stop-on-violation --using-cache=no ./lib 33 | if: ${{ matrix.php == '8.3' }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .idea/ 3 | .phpunit.result.cache 4 | .php-cs-fixer.cache 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | ============== 3 | 4 | _Version 2.0, January 2004_ 5 | _<>_ 6 | 7 | ### Terms and Conditions for use, reproduction, and distribution 8 | 9 | #### 1. Definitions 10 | 11 | “License” shall mean the terms and conditions for use, reproduction, and 12 | distribution as defined by Sections 1 through 9 of this document. 13 | 14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright 15 | owner that is granting the License. 16 | 17 | “Legal Entity” shall mean the union of the acting entity and all other entities 18 | that control, are controlled by, or are under common control with that entity. 19 | For the purposes of this definition, “control” means **(i)** the power, direct or 20 | indirect, to cause the direction or management of such entity, whether by 21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the 22 | outstanding shares, or **(iii)** beneficial ownership of such entity. 23 | 24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising 25 | permissions granted by this License. 26 | 27 | “Source” form shall mean the preferred form for making modifications, including 28 | but not limited to software source code, documentation source, and configuration 29 | files. 30 | 31 | “Object” form shall mean any form resulting from mechanical transformation or 32 | translation of a Source form, including but not limited to compiled object code, 33 | generated documentation, and conversions to other media types. 34 | 35 | “Work” shall mean the work of authorship, whether in Source or Object form, made 36 | available under the License, as indicated by a copyright notice that is included 37 | in or attached to the work (an example is provided in the Appendix below). 38 | 39 | “Derivative Works” shall mean any work, whether in Source or Object form, that 40 | is based on (or derived from) the Work and for which the editorial revisions, 41 | annotations, elaborations, or other modifications represent, as a whole, an 42 | original work of authorship. For the purposes of this License, Derivative Works 43 | shall not include works that remain separable from, or merely link (or bind by 44 | name) to the interfaces of, the Work and Derivative Works thereof. 45 | 46 | “Contribution” shall mean any work of authorship, including the original version 47 | of the Work and any modifications or additions to that Work or Derivative Works 48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 49 | by the copyright owner or by an individual or Legal Entity authorized to submit 50 | on behalf of the copyright owner. For the purposes of this definition, 51 | “submitted” means any form of electronic, verbal, or written communication sent 52 | to the Licensor or its representatives, including but not limited to 53 | communication on electronic mailing lists, source code control systems, and 54 | issue tracking systems that are managed by, or on behalf of, the Licensor for 55 | the purpose of discussing and improving the Work, but excluding communication 56 | that is conspicuously marked or otherwise designated in writing by the copyright 57 | owner as “Not a Contribution.” 58 | 59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf 60 | of whom a Contribution has been received by Licensor and subsequently 61 | incorporated within the Work. 62 | 63 | #### 2. Grant of Copyright License 64 | 65 | Subject to the terms and conditions of this License, each Contributor hereby 66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 67 | irrevocable copyright license to reproduce, prepare Derivative Works of, 68 | publicly display, publicly perform, sublicense, and distribute the Work and such 69 | Derivative Works in Source or Object form. 70 | 71 | #### 3. Grant of Patent License 72 | 73 | Subject to the terms and conditions of this License, each Contributor hereby 74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 75 | irrevocable (except as stated in this section) patent license to make, have 76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 77 | such license applies only to those patent claims licensable by such Contributor 78 | that are necessarily infringed by their Contribution(s) alone or by combination 79 | of their Contribution(s) with the Work to which such Contribution(s) was 80 | submitted. If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this License 84 | for that Work shall terminate as of the date such litigation is filed. 85 | 86 | #### 4. Redistribution 87 | 88 | You may reproduce and distribute copies of the Work or Derivative Works thereof 89 | in any medium, with or without modifications, and in Source or Object form, 90 | provided that You meet the following conditions: 91 | 92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of 93 | this License; and 94 | * **(b)** You must cause any modified files to carry prominent notices stating that You 95 | changed the files; and 96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, 97 | all copyright, patent, trademark, and attribution notices from the Source form 98 | of the Work, excluding those notices that do not pertain to any part of the 99 | Derivative Works; and 100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any 101 | Derivative Works that You distribute must include a readable copy of the 102 | attribution notices contained within such NOTICE file, excluding those notices 103 | that do not pertain to any part of the Derivative Works, in at least one of the 104 | following places: within a NOTICE text file distributed as part of the 105 | Derivative Works; within the Source form or documentation, if provided along 106 | with the Derivative Works; or, within a display generated by the Derivative 107 | Works, if and wherever such third-party notices normally appear. The contents of 108 | the NOTICE file are for informational purposes only and do not modify the 109 | License. You may add Your own attribution notices within Derivative Works that 110 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 111 | provided that such additional attribution notices cannot be construed as 112 | modifying the License. 113 | 114 | You may add Your own copyright statement to Your modifications and may provide 115 | additional or different license terms and conditions for use, reproduction, or 116 | distribution of Your modifications, or for any such Derivative Works as a whole, 117 | provided Your use, reproduction, and distribution of the Work otherwise complies 118 | with the conditions stated in this License. 119 | 120 | #### 5. Submission of Contributions 121 | 122 | Unless You explicitly state otherwise, any Contribution intentionally submitted 123 | for inclusion in the Work by You to the Licensor shall be under the terms and 124 | conditions of this License, without any additional terms or conditions. 125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 126 | any separate license agreement you may have executed with Licensor regarding 127 | such Contributions. 128 | 129 | #### 6. Trademarks 130 | 131 | This License does not grant permission to use the trade names, trademarks, 132 | service marks, or product names of the Licensor, except as required for 133 | reasonable and customary use in describing the origin of the Work and 134 | reproducing the content of the NOTICE file. 135 | 136 | #### 7. Disclaimer of Warranty 137 | 138 | Unless required by applicable law or agreed to in writing, Licensor provides the 139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, 140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 141 | including, without limitation, any warranties or conditions of TITLE, 142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 143 | solely responsible for determining the appropriateness of using or 144 | redistributing the Work and assume any risks associated with Your exercise of 145 | permissions under this License. 146 | 147 | #### 8. Limitation of Liability 148 | 149 | In no event and under no legal theory, whether in tort (including negligence), 150 | contract, or otherwise, unless required by applicable law (such as deliberate 151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 152 | liable to You for damages, including any direct, indirect, special, incidental, 153 | or consequential damages of any character arising as a result of this License or 154 | out of the use or inability to use the Work (including but not limited to 155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 156 | any and all other commercial damages or losses), even if such Contributor has 157 | been advised of the possibility of such damages. 158 | 159 | #### 9. Accepting Warranty or Additional Liability 160 | 161 | While redistributing the Work or Derivative Works thereof, You may choose to 162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 163 | other liability obligations and/or rights consistent with this License. However, 164 | in accepting such obligations, You may act only on Your own behalf and on Your 165 | sole responsibility, not on behalf of any other Contributor, and only if You 166 | agree to indemnify, defend, and hold each Contributor harmless for any liability 167 | incurred by, or claims asserted against, such Contributor by reason of your 168 | accepting any such warranty or additional liability. 169 | 170 | _END OF TERMS AND CONDITIONS_ 171 | 172 | ### APPENDIX: How to apply the Apache License to your work 173 | 174 | To apply the Apache License to your work, attach the following boilerplate 175 | notice, with the fields enclosed by brackets `[]` replaced with your own 176 | identifying information. (Don't include the brackets!) The text should be 177 | enclosed in the appropriate comment syntax for the file format. We also 178 | recommend that a file or class name and description of purpose be included on 179 | the same “printed page” as the copyright notice for easier identification within 180 | third-party archives. 181 | 182 | Copyright [2017] [Anthony Ferrara] 183 | 184 | Licensed under the Apache License, Version 2.0 (the "License"); 185 | you may not use this file except in compliance with the License. 186 | You may obtain a copy of the License at 187 | 188 | http://www.apache.org/licenses/LICENSE-2.0 189 | 190 | Unless required by applicable law or agreed to in writing, software 191 | distributed under the License is distributed on an "AS IS" BASIS, 192 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 193 | See the License for the specific language governing permissions and 194 | limitations under the License. 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP-Yacc 2 | 3 | This is a port of [`kmyacc`](https://github.com/moriyoshi/kmyacc-forked) into PHP. It is a parser-generator, meaning it takes a YACC grammar file and generates a parser file. 4 | 5 | ## A Direct Port (For Now) 6 | 7 | Right now, this is a direct port. Meaning that it works exactly like `kmyacc`. Looking in the examples, you can see that this means that you must supply a "parser template" in addition to the grammar. 8 | 9 | Longer term, we want to add simplifying functionality. We will always support providing a template, but we will offer a series of default templates for common use-cases. 10 | 11 | ## What can I do with this? 12 | 13 | You can parse most structured and unstructured grammars. There are some gotchas to [LALR(1) parsers](https://en.wikipedia.org/wiki/LALR_parser) that you need to be aware of (for example, Shift/Shift conflicts and Shift/Reduce conflicts). But those are beyond this simple intro. 14 | 15 | ## How does it work? 16 | 17 | I don't know. I just ported the code until it worked correctly. 18 | 19 | ## YACC Grammar 20 | 21 | That's way beyond the scope of this documentation, but checkout [The YACC page here](http://dinosaur.compilertools.net/yacc/) for some info. 22 | 23 | Over time we will document the grammar more... 24 | 25 | ## How do I use it? 26 | 27 | For now, check out the examples folder. The current state of the CLI tool will change, so any usage today should please provide feedback and use-cases so that we can better design the tooling support. 28 | 29 | ## Why did you do this? 30 | 31 | Many projects have the need for parsers (and therefore parser-generators). Nikita's [PHP-Parser](https://github.com/nikic/PHP-Parser) is one tool that uses kmyacc to generate its parser. There are many other projects out there that either use hand-written parsers, or use kmyacc or another parser-generator. 32 | 33 | Unfortunately, not many parser-generators exist for PHP. And those that do exist I have found to be rigid or not powerful enough to parse PHP itself. 34 | 35 | This project is an aim to resolve that. 36 | 37 | ## Performance 38 | 39 | There's a TON of performance optimizations possible here. The original code was a direct port, so some structures are definitely sub-optimal. Over time we will improve the performance. 40 | 41 | However, this will always be at least a slightly-slow process. Generating a parser requires a lot of resources, so should never happen inside of a web request. 42 | 43 | Using the generated parser however should be quite fast (the generated parser is fairly well optimized already). 44 | 45 | ## What's left to do? 46 | 47 | A bunch of things. Here's the wishlist: 48 | 49 | * Refactor to make conventions consistent (some parts currently use camelCase, some parts use snake_case, etc). 50 | * Performance tuning 51 | * Unit test as much as possible 52 | * Document as much as possible (It's a complicated series of algorithms with no source documentation in either project). 53 | * Redesign the CLI binary and how it operates 54 | * Decide whether multi-language support is worth while, or if we should just move to only PHP codegen support. 55 | * Add default templates and parser implementations 56 | * At least one of which generates an "AST" by default, similar to Ruby's [Treetop library](https://github.com/nathansobo/treetop) 57 | * Build a reasonably performant lexer-generator (very likely as a separate project) 58 | * A lot of debugging (though we don't know of any bugs, they are there) 59 | * Building out of features we didn't need for the initial go (for example, support for `%union`, etc). 60 | 61 | And a lot more. 62 | 63 | ## Contributing 64 | 65 | -------------------------------------------------------------------------------- /bin/phpyacc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | pspref = $argv[++$i]; 57 | break; 58 | case '-x': 59 | $context->verboseDebug = true; 60 | case '-v': 61 | $context->debugFile = fopen(getcwd() . "/y.output", 'w' ); 62 | break; 63 | case '-t': 64 | $context->tflag = true; 65 | break; 66 | case '-a': 67 | $context->aflag = true; 68 | break; 69 | case '-n': 70 | $context->allowSemanticValueReferenceByName = true; 71 | break; 72 | case '-m': 73 | if ($i === $argc - 2) { 74 | error("Skeleton file required for option -m"); 75 | } 76 | $skeleton = file_get_contents($argv[++$i]); 77 | break; 78 | default: 79 | error("Unexpected argument/flag {$argv[$i]}"); 80 | 81 | } 82 | } 83 | 84 | (new Generator)->generate($context, file_get_contents($grammarFile), $skeleton, $resultFile); 85 | 86 | 87 | function help() 88 | { 89 | echo << The name of the class to generate 96 | -x Enable extended debug mode 97 | -v Generate y.output file 98 | -t Set the T flag for templates (inclusion of debug information) 99 | -a Set the A flag for templates (unused) 100 | -m Path to the skeleton file to use 101 | 102 | 103 | EOH; 104 | } 105 | 106 | function error(string $message) 107 | { 108 | echo $message . "\n"; 109 | help(); 110 | exit(2); 111 | } 112 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ircmaxell/php-yacc", 3 | "description": "A PHP YACC Parser-Generator library", 4 | "license": "Apache-2.0", 5 | "authors": [ 6 | { 7 | "name": "Anthony Ferrara", 8 | "email": "ircmaxell@php.net" 9 | } 10 | ], 11 | "autoload": { 12 | "psr-4": { 13 | "PhpYacc\\": "lib/" 14 | }, 15 | "files": [ 16 | "lib/functions.php" 17 | ] 18 | }, 19 | "bin": ["bin/phpyacc"], 20 | "require": { 21 | "php": ">=8.1" 22 | }, 23 | "require-dev": { 24 | "friendsofphp/php-cs-fixer": "^3.65", 25 | "phpstan/phpstan": "^2.0", 26 | "phpunit/phpunit": "^10.5" 27 | }, 28 | "scripts": { 29 | "analyze": "phpstan analyze lib", 30 | "build": [ 31 | "@cs-fix", 32 | "@build-examples" 33 | ], 34 | "build-examples": "php examples/rebuild.php", 35 | "cs-fix": "php-cs-fixer fix -v ./lib", 36 | "test": "phpunit lib" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/00-basic-usage/grammar.y: -------------------------------------------------------------------------------- 1 | 2 | %% 3 | 4 | expr: 5 | '1' { $$ = 1; } 6 | ; 7 | 8 | %% 9 | -------------------------------------------------------------------------------- /examples/00-basic-usage/parser.diff: -------------------------------------------------------------------------------- 1 | 103c103 2 | < "start : expr", 3 | --- 4 | > "\$start : expr", 5 | -------------------------------------------------------------------------------- /examples/00-basic-usage/parser.kmyacc.php: -------------------------------------------------------------------------------- 1 | reduceCallbacks = [ 109 | 0 => function ($stackPos) { 110 | $this->semValue = $this->semStack[$stackPos]; 111 | }, 112 | 1 => function ($stackPos) { 113 | $this->semValue = 1; 114 | }, 115 | ]; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /examples/00-basic-usage/parser.phpyacc.php: -------------------------------------------------------------------------------- 1 | reduceCallbacks = [ 109 | 0 => function ($stackPos) { 110 | $this->semValue = $this->semStack[$stackPos]; 111 | }, 112 | 1 => function ($stackPos) { 113 | $this->semValue = 1; 114 | }, 115 | ]; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /examples/00-basic-usage/parser.template.php: -------------------------------------------------------------------------------- 1 | semValue 4 | #semval($,%t) $this->semValue 5 | #semval(%n) $stackPos-(%l-%n) 6 | #semval(%n,%t) $stackPos-(%l-%n) 7 | 8 | namespace PhpParser\Parser; 9 | 10 | use PhpParser\Error; 11 | use PhpParser\Node; 12 | use PhpParser\Node\Expr; 13 | use PhpParser\Node\Name; 14 | use PhpParser\Node\Scalar; 15 | use PhpParser\Node\Stmt; 16 | #include; 17 | 18 | /** 19 | * This is an automatically GENERATED file, which should not be manually edited. 20 | */ 21 | class Parser extends \PhpParser\ParserAbstract 22 | { 23 | protected $tokenToSymbolMapSize = #(YYMAXLEX); 24 | protected $actionTableSize = #(YYLAST); 25 | protected $gotoTableSize = #(YYGLAST); 26 | 27 | protected $invalidSymbol = #(YYBADCH); 28 | protected $errorSymbol = #(YYINTERRTOK); 29 | protected $defaultAction = #(YYDEFAULT); 30 | protected $unexpectedTokenRule = #(YYUNEXPECTED); 31 | 32 | protected $YY2TBLSTATE = #(YY2TBLSTATE); 33 | protected $YYNLSTATES = #(YYNLSTATES); 34 | 35 | protected $symbolToName = array( 36 | #listvar terminals 37 | ); 38 | 39 | protected $tokenToSymbol = array( 40 | #listvar yytranslate 41 | ); 42 | 43 | protected $action = array( 44 | #listvar yyaction 45 | ); 46 | 47 | protected $actionCheck = array( 48 | #listvar yycheck 49 | ); 50 | 51 | protected $actionBase = array( 52 | #listvar yybase 53 | ); 54 | 55 | protected $actionDefault = array( 56 | #listvar yydefault 57 | ); 58 | 59 | protected $goto = array( 60 | #listvar yygoto 61 | ); 62 | 63 | protected $gotoCheck = array( 64 | #listvar yygcheck 65 | ); 66 | 67 | protected $gotoBase = array( 68 | #listvar yygbase 69 | ); 70 | 71 | protected $gotoDefault = array( 72 | #listvar yygdefault 73 | ); 74 | 75 | protected $ruleToNonTerminal = array( 76 | #listvar yylhs 77 | ); 78 | 79 | protected $ruleToLength = array( 80 | #listvar yylen 81 | ); 82 | #if -t 83 | 84 | protected $productions = array( 85 | #production-strings; 86 | ); 87 | #endif 88 | 89 | protected function initReduceCallbacks() { 90 | $this->reduceCallbacks = [ 91 | #reduce 92 | %n => function ($stackPos) { 93 | %b 94 | }, 95 | #noact 96 | %n => function ($stackPos) { 97 | $this->semValue = $this->semStack[$stackPos]; 98 | }, 99 | #endreduce 100 | ]; 101 | } 102 | } 103 | #tailcode; -------------------------------------------------------------------------------- /examples/00-basic-usage/y.diff: -------------------------------------------------------------------------------- 1 | 3c3 2 | < start [ '1' ] 3 | --- 4 | > $start [ '1' ] 5 | 6c6 6 | < (0) start : . expr 7 | --- 8 | > (0) $start : . expr 9 | 12c12 10 | < (0) start : expr . 11 | --- 12 | > (0) $start : expr . 13 | 15c15 14 | < (0) start : . expr 15 | --- 16 | > (0) $start : . expr 17 | 22c22 18 | < (0) start : expr . 19 | --- 20 | > (0) $start : expr . 21 | 39d38 22 | < 3 items 23 | 42,43d40 24 | < 1304 bytes used 25 | < 26 | -------------------------------------------------------------------------------- /examples/00-basic-usage/y.kmyacc.output: -------------------------------------------------------------------------------- 1 | EMPTY nonterminals: 2 | First: 3 | start [ '1' ] 4 | expr [ '1' ] 5 | state unknown: 6 | (0) start : . expr 7 | [ EOF ] 8 | state unknown: 9 | (1) expr : '1' . 10 | [ EOF ] 11 | state unknown: 12 | (0) start : expr . 13 | [ EOF ] 14 | state 0 15 | (0) start : . expr 16 | 17 | '1' shift 2 and reduce (1) 18 | expr goto 1 19 | . error 20 | 21 | state 1 22 | (0) start : expr . 23 | 24 | EOF accept 25 | . error 26 | 27 | state 2 28 | (1) expr : '1' . 29 | 30 | . reduce (1) 31 | 32 | 33 | Statistics for grammar.y: 34 | 3 terminal symbols 35 | 2 nonterminal symbols 36 | 2 productions 37 | 3 states 38 | 0 shift/reduce, 0 reduce/reduce conflicts 39 | 3 items 40 | 3 lookahead sets used 41 | 2+4=6 action entries 42 | 1304 bytes used 43 | 44 | State=>class: 45 | 46 | 0=>0 1=>1 47 | 48 | Terminal action: 49 | T\S 0 1 50 | EOF . 0 51 | '1' 2 . 52 | 53 | Nonterminal GOTO table: 54 | T\S 0 1 55 | expr 1 . 56 | 57 | Nonterminal GOTO table: 58 | T\S default 0 1 59 | expr 1 = . 60 | 61 | Candidates of aux table: 62 | Used aux table: 63 | state 0 (class 0) 64 | state 1 (class 1) 65 | Order: 66 | 1,0, 67 | Order: 68 | 0,1, 69 | -------------------------------------------------------------------------------- /examples/00-basic-usage/y.phpyacc.output: -------------------------------------------------------------------------------- 1 | EMPTY nonterminals: 2 | First: 3 | $start [ '1' ] 4 | expr [ '1' ] 5 | state unknown: 6 | (0) $start : . expr 7 | [ EOF ] 8 | state unknown: 9 | (1) expr : '1' . 10 | [ EOF ] 11 | state unknown: 12 | (0) $start : expr . 13 | [ EOF ] 14 | state 0 15 | (0) $start : . expr 16 | 17 | '1' shift 2 and reduce (1) 18 | expr goto 1 19 | . error 20 | 21 | state 1 22 | (0) $start : expr . 23 | 24 | EOF accept 25 | . error 26 | 27 | state 2 28 | (1) expr : '1' . 29 | 30 | . reduce (1) 31 | 32 | 33 | Statistics for grammar.y: 34 | 3 terminal symbols 35 | 2 nonterminal symbols 36 | 2 productions 37 | 3 states 38 | 0 shift/reduce, 0 reduce/reduce conflicts 39 | 3 lookahead sets used 40 | 2+4=6 action entries 41 | State=>class: 42 | 43 | 0=>0 1=>1 44 | 45 | Terminal action: 46 | T\S 0 1 47 | EOF . 0 48 | '1' 2 . 49 | 50 | Nonterminal GOTO table: 51 | T\S 0 1 52 | expr 1 . 53 | 54 | Nonterminal GOTO table: 55 | T\S default 0 1 56 | expr 1 = . 57 | 58 | Candidates of aux table: 59 | Used aux table: 60 | state 0 (class 0) 61 | state 1 (class 1) 62 | Order: 63 | 1,0, 64 | Order: 65 | 0,1, 66 | -------------------------------------------------------------------------------- /examples/01-expression-support/grammar.y: -------------------------------------------------------------------------------- 1 | 2 | %left '+' 3 | 4 | %% 5 | 6 | expr: 7 | expr '+' expr { $$ = $1 + $3; } 8 | | '1' { $$ = 1; } 9 | ; 10 | 11 | %% 12 | -------------------------------------------------------------------------------- /examples/01-expression-support/parser.diff: -------------------------------------------------------------------------------- 1 | 105c105 2 | < "start : expr", 3 | --- 4 | > "\$start : expr", 5 | -------------------------------------------------------------------------------- /examples/01-expression-support/parser.kmyacc.php: -------------------------------------------------------------------------------- 1 | reduceCallbacks = [ 112 | 0 => function ($stackPos) { 113 | $this->semValue = $this->semStack[$stackPos]; 114 | }, 115 | 1 => function ($stackPos) { 116 | $this->semValue = $stackPos-(3-1) + $stackPos-(3-3); 117 | }, 118 | 2 => function ($stackPos) { 119 | $this->semValue = 1; 120 | }, 121 | ]; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /examples/01-expression-support/parser.phpyacc.php: -------------------------------------------------------------------------------- 1 | reduceCallbacks = [ 112 | 0 => function ($stackPos) { 113 | $this->semValue = $this->semStack[$stackPos]; 114 | }, 115 | 1 => function ($stackPos) { 116 | $this->semValue = $stackPos-(3-1) + $stackPos-(3-3); 117 | }, 118 | 2 => function ($stackPos) { 119 | $this->semValue = 1; 120 | }, 121 | ]; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /examples/01-expression-support/parser.template.php: -------------------------------------------------------------------------------- 1 | semValue 4 | #semval($,%t) $this->semValue 5 | #semval(%n) $stackPos-(%l-%n) 6 | #semval(%n,%t) $stackPos-(%l-%n) 7 | 8 | namespace PhpParser\Parser; 9 | 10 | use PhpParser\Error; 11 | use PhpParser\Node; 12 | use PhpParser\Node\Expr; 13 | use PhpParser\Node\Name; 14 | use PhpParser\Node\Scalar; 15 | use PhpParser\Node\Stmt; 16 | #include; 17 | 18 | /* This is an automatically GENERATED file, which should not be manually edited. 19 | */ 20 | class Parser extends \PhpParser\ParserAbstract 21 | { 22 | protected $tokenToSymbolMapSize = #(YYMAXLEX); 23 | protected $actionTableSize = #(YYLAST); 24 | protected $gotoTableSize = #(YYGLAST); 25 | 26 | protected $invalidSymbol = #(YYBADCH); 27 | protected $errorSymbol = #(YYINTERRTOK); 28 | protected $defaultAction = #(YYDEFAULT); 29 | protected $unexpectedTokenRule = #(YYUNEXPECTED); 30 | 31 | protected $YY2TBLSTATE = #(YY2TBLSTATE); 32 | protected $YYNLSTATES = #(YYNLSTATES); 33 | 34 | protected $symbolToName = array( 35 | #listvar terminals 36 | ); 37 | 38 | protected $tokenToSymbol = array( 39 | #listvar yytranslate 40 | ); 41 | 42 | protected $action = array( 43 | #listvar yyaction 44 | ); 45 | 46 | protected $actionCheck = array( 47 | #listvar yycheck 48 | ); 49 | 50 | protected $actionBase = array( 51 | #listvar yybase 52 | ); 53 | 54 | protected $actionDefault = array( 55 | #listvar yydefault 56 | ); 57 | 58 | protected $goto = array( 59 | #listvar yygoto 60 | ); 61 | 62 | protected $gotoCheck = array( 63 | #listvar yygcheck 64 | ); 65 | 66 | protected $gotoBase = array( 67 | #listvar yygbase 68 | ); 69 | 70 | protected $gotoDefault = array( 71 | #listvar yygdefault 72 | ); 73 | 74 | protected $ruleToNonTerminal = array( 75 | #listvar yylhs 76 | ); 77 | 78 | protected $ruleToLength = array( 79 | #listvar yylen 80 | ); 81 | #if -t 82 | 83 | protected $productions = array( 84 | #production-strings; 85 | ); 86 | #endif 87 | 88 | protected function initReduceCallbacks() { 89 | $this->reduceCallbacks = [ 90 | #reduce 91 | %n => function ($stackPos) { 92 | %b 93 | }, 94 | #noact 95 | %n => function ($stackPos) { 96 | $this->semValue = $this->semStack[$stackPos]; 97 | }, 98 | #endreduce 99 | ]; 100 | } 101 | } 102 | #tailcode; -------------------------------------------------------------------------------- /examples/01-expression-support/y.diff: -------------------------------------------------------------------------------- 1 | 3c3 2 | < start [ '1' ] 3 | --- 4 | > $start [ '1' ] 5 | 6c6 6 | < (0) start : . expr 7 | --- 8 | > (0) $start : . expr 9 | 12c12 10 | < (0) start : expr . 11 | --- 12 | > (0) $start : expr . 13 | 25c25 14 | < (0) start : . expr 15 | --- 16 | > (0) $start : . expr 17 | 32c32 18 | < (0) start : expr . 19 | --- 20 | > (0) $start : expr . 21 | 64d63 22 | < 8 items 23 | 67,68d65 24 | < 1920 bytes used 25 | < 26 | -------------------------------------------------------------------------------- /examples/01-expression-support/y.kmyacc.output: -------------------------------------------------------------------------------- 1 | EMPTY nonterminals: 2 | First: 3 | start [ '1' ] 4 | expr [ '1' ] 5 | state unknown: 6 | (0) start : . expr 7 | [ EOF ] 8 | state unknown: 9 | (2) expr : '1' . 10 | [ EOF '+' ] 11 | state unknown: 12 | (0) start : expr . 13 | [ EOF ] 14 | (1) expr : expr . '+' expr 15 | [ EOF '+' ] 16 | state unknown: 17 | (1) expr : expr '+' . expr 18 | [ EOF '+' ] 19 | state unknown: 20 | (1) expr : expr . '+' expr 21 | [ EOF '+' ] 22 | (1) expr : expr '+' expr . 23 | [ EOF '+' ] 24 | state 0 25 | (0) start : . expr 26 | 27 | '1' shift 3 and reduce (2) 28 | expr goto 1 29 | . error 30 | 31 | state 1 32 | (0) start : expr . 33 | (1) expr : expr . '+' expr 34 | 35 | EOF accept 36 | '+' shift 2 37 | . error 38 | 39 | state 2 40 | (1) expr : expr '+' . expr 41 | 42 | '1' shift 3 and reduce (2) 43 | expr goto 4 and reduce (1) 44 | . error 45 | 46 | state 3 47 | (2) expr : '1' . 48 | 49 | . reduce (2) 50 | 51 | state 4 52 | (1) expr : expr . '+' expr 53 | (1) expr : expr '+' expr . 54 | 55 | . reduce (1) 56 | 57 | 58 | Statistics for grammar.y: 59 | 4 terminal symbols 60 | 2 nonterminal symbols 61 | 3 productions 62 | 5 states 63 | 0 shift/reduce, 0 reduce/reduce conflicts 64 | 8 items 65 | 7 lookahead sets used 66 | 6+6=12 action entries 67 | 1920 bytes used 68 | 69 | State=>class: 70 | 71 | 0=>0 1=>1 2=>0 72 | 73 | Terminal action: 74 | T\S 0 1 75 | EOF . 0 76 | '+' . 2 77 | '1' 3 . 78 | 79 | Nonterminal GOTO table: 80 | T\S 0 1 2 81 | expr 1 . 4 82 | 83 | Nonterminal GOTO table: 84 | T\S default 0 1 2 85 | expr 1 = . 4 86 | 87 | Candidates of aux table: 88 | Used aux table: 89 | state 0 (class 0) 90 | state 1 (class 1) 91 | state 2 (class 0) 92 | Order: 93 | 1,0, 94 | Order: 95 | 1,0, 96 | -------------------------------------------------------------------------------- /examples/01-expression-support/y.phpyacc.output: -------------------------------------------------------------------------------- 1 | EMPTY nonterminals: 2 | First: 3 | $start [ '1' ] 4 | expr [ '1' ] 5 | state unknown: 6 | (0) $start : . expr 7 | [ EOF ] 8 | state unknown: 9 | (2) expr : '1' . 10 | [ EOF '+' ] 11 | state unknown: 12 | (0) $start : expr . 13 | [ EOF ] 14 | (1) expr : expr . '+' expr 15 | [ EOF '+' ] 16 | state unknown: 17 | (1) expr : expr '+' . expr 18 | [ EOF '+' ] 19 | state unknown: 20 | (1) expr : expr . '+' expr 21 | [ EOF '+' ] 22 | (1) expr : expr '+' expr . 23 | [ EOF '+' ] 24 | state 0 25 | (0) $start : . expr 26 | 27 | '1' shift 3 and reduce (2) 28 | expr goto 1 29 | . error 30 | 31 | state 1 32 | (0) $start : expr . 33 | (1) expr : expr . '+' expr 34 | 35 | EOF accept 36 | '+' shift 2 37 | . error 38 | 39 | state 2 40 | (1) expr : expr '+' . expr 41 | 42 | '1' shift 3 and reduce (2) 43 | expr goto 4 and reduce (1) 44 | . error 45 | 46 | state 3 47 | (2) expr : '1' . 48 | 49 | . reduce (2) 50 | 51 | state 4 52 | (1) expr : expr . '+' expr 53 | (1) expr : expr '+' expr . 54 | 55 | . reduce (1) 56 | 57 | 58 | Statistics for grammar.y: 59 | 4 terminal symbols 60 | 2 nonterminal symbols 61 | 3 productions 62 | 5 states 63 | 0 shift/reduce, 0 reduce/reduce conflicts 64 | 7 lookahead sets used 65 | 6+6=12 action entries 66 | State=>class: 67 | 68 | 0=>0 1=>1 2=>0 69 | 70 | Terminal action: 71 | T\S 0 1 72 | EOF . 0 73 | '+' . 2 74 | '1' 3 . 75 | 76 | Nonterminal GOTO table: 77 | T\S 0 1 2 78 | expr 1 . 4 79 | 80 | Nonterminal GOTO table: 81 | T\S default 0 1 2 82 | expr 1 = . 4 83 | 84 | Candidates of aux table: 85 | Used aux table: 86 | state 0 (class 0) 87 | state 1 (class 1) 88 | state 2 (class 0) 89 | Order: 90 | 1,0, 91 | Order: 92 | 1,0, 93 | -------------------------------------------------------------------------------- /examples/02-complex-expression-support/grammar.y: -------------------------------------------------------------------------------- 1 | 2 | %left '+' T_FOO 3 | 4 | %% 5 | 6 | expr: 7 | expr '+' expr { $$ = $1 + $3; } 8 | | '1' { $$ = 1; } 9 | | T_FOO expr { $$ = $2; } 10 | ; 11 | 12 | %% 13 | -------------------------------------------------------------------------------- /examples/02-complex-expression-support/parser.diff: -------------------------------------------------------------------------------- 1 | 106c106 2 | < "start : expr", 3 | --- 4 | > "\$start : expr", 5 | -------------------------------------------------------------------------------- /examples/02-complex-expression-support/parser.kmyacc.php: -------------------------------------------------------------------------------- 1 | reduceCallbacks = [ 114 | 0 => function ($stackPos) { 115 | $this->semValue = $this->semStack[$stackPos]; 116 | }, 117 | 1 => function ($stackPos) { 118 | $this->semValue = $stackPos-(3-1) + $stackPos-(3-3); 119 | }, 120 | 2 => function ($stackPos) { 121 | $this->semValue = 1; 122 | }, 123 | 3 => function ($stackPos) { 124 | $this->semValue = $stackPos-(2-2); 125 | }, 126 | ]; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /examples/02-complex-expression-support/parser.phpyacc.php: -------------------------------------------------------------------------------- 1 | reduceCallbacks = [ 114 | 0 => function ($stackPos) { 115 | $this->semValue = $this->semStack[$stackPos]; 116 | }, 117 | 1 => function ($stackPos) { 118 | $this->semValue = $stackPos-(3-1) + $stackPos-(3-3); 119 | }, 120 | 2 => function ($stackPos) { 121 | $this->semValue = 1; 122 | }, 123 | 3 => function ($stackPos) { 124 | $this->semValue = $stackPos-(2-2); 125 | }, 126 | ]; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /examples/02-complex-expression-support/parser.template.php: -------------------------------------------------------------------------------- 1 | semValue 4 | #semval($,%t) $this->semValue 5 | #semval(%n) $stackPos-(%l-%n) 6 | #semval(%n,%t) $stackPos-(%l-%n) 7 | 8 | namespace PhpParser\Parser; 9 | 10 | use PhpParser\Error; 11 | use PhpParser\Node; 12 | use PhpParser\Node\Expr; 13 | use PhpParser\Node\Name; 14 | use PhpParser\Node\Scalar; 15 | use PhpParser\Node\Stmt; 16 | #include; 17 | 18 | /* This is an automatically GENERATED file, which should not be manually edited. 19 | */ 20 | class Parser extends \PhpParser\ParserAbstract 21 | { 22 | protected $tokenToSymbolMapSize = #(YYMAXLEX); 23 | protected $actionTableSize = #(YYLAST); 24 | protected $gotoTableSize = #(YYGLAST); 25 | 26 | protected $invalidSymbol = #(YYBADCH); 27 | protected $errorSymbol = #(YYINTERRTOK); 28 | protected $defaultAction = #(YYDEFAULT); 29 | protected $unexpectedTokenRule = #(YYUNEXPECTED); 30 | 31 | protected $YY2TBLSTATE = #(YY2TBLSTATE); 32 | protected $YYNLSTATES = #(YYNLSTATES); 33 | 34 | protected $symbolToName = array( 35 | #listvar terminals 36 | ); 37 | 38 | protected $tokenToSymbol = array( 39 | #listvar yytranslate 40 | ); 41 | 42 | protected $action = array( 43 | #listvar yyaction 44 | ); 45 | 46 | protected $actionCheck = array( 47 | #listvar yycheck 48 | ); 49 | 50 | protected $actionBase = array( 51 | #listvar yybase 52 | ); 53 | 54 | protected $actionDefault = array( 55 | #listvar yydefault 56 | ); 57 | 58 | protected $goto = array( 59 | #listvar yygoto 60 | ); 61 | 62 | protected $gotoCheck = array( 63 | #listvar yygcheck 64 | ); 65 | 66 | protected $gotoBase = array( 67 | #listvar yygbase 68 | ); 69 | 70 | protected $gotoDefault = array( 71 | #listvar yygdefault 72 | ); 73 | 74 | protected $ruleToNonTerminal = array( 75 | #listvar yylhs 76 | ); 77 | 78 | protected $ruleToLength = array( 79 | #listvar yylen 80 | ); 81 | #if -t 82 | 83 | protected $productions = array( 84 | #production-strings; 85 | ); 86 | #endif 87 | 88 | protected function initReduceCallbacks() { 89 | $this->reduceCallbacks = [ 90 | #reduce 91 | %n => function ($stackPos) { 92 | %b 93 | }, 94 | #noact 95 | %n => function ($stackPos) { 96 | $this->semValue = $this->semStack[$stackPos]; 97 | }, 98 | #endreduce 99 | ]; 100 | } 101 | } 102 | #tailcode; -------------------------------------------------------------------------------- /examples/02-complex-expression-support/y.diff: -------------------------------------------------------------------------------- 1 | 3c3 2 | < start [ T_FOO '1' ] 3 | --- 4 | > $start [ T_FOO '1' ] 5 | 6c6 6 | < (0) start : . expr 7 | --- 8 | > (0) $start : . expr 9 | 15c15 10 | < (0) start : expr . 11 | --- 12 | > (0) $start : expr . 13 | 33c33 14 | < (0) start : . expr 15 | --- 16 | > (0) $start : . expr 17 | 57c57 18 | < (0) start : expr . 19 | --- 20 | > (0) $start : expr . 21 | 88d87 22 | < 12 items 23 | 91,92d89 24 | < 2432 bytes used 25 | < 26 | -------------------------------------------------------------------------------- /examples/02-complex-expression-support/y.kmyacc.output: -------------------------------------------------------------------------------- 1 | EMPTY nonterminals: 2 | First: 3 | start [ T_FOO '1' ] 4 | expr [ T_FOO '1' ] 5 | state unknown: 6 | (0) start : . expr 7 | [ EOF ] 8 | state unknown: 9 | (3) expr : T_FOO . expr 10 | [ EOF '+' ] 11 | state unknown: 12 | (2) expr : '1' . 13 | [ EOF '+' ] 14 | state unknown: 15 | (0) start : expr . 16 | [ EOF ] 17 | (1) expr : expr . '+' expr 18 | [ EOF '+' ] 19 | state unknown: 20 | (1) expr : expr . '+' expr 21 | [ EOF '+' ] 22 | (3) expr : T_FOO expr . 23 | [ EOF '+' ] 24 | state unknown: 25 | (1) expr : expr '+' . expr 26 | [ EOF '+' ] 27 | state unknown: 28 | (1) expr : expr . '+' expr 29 | [ EOF '+' ] 30 | (1) expr : expr '+' expr . 31 | [ EOF '+' ] 32 | state 0 33 | (0) start : . expr 34 | 35 | T_FOO shift 1 36 | '1' shift 4 and reduce (2) 37 | expr goto 3 38 | . error 39 | 40 | state 1 41 | (3) expr : T_FOO . expr 42 | 43 | T_FOO shift 1 44 | '1' shift 4 and reduce (2) 45 | expr goto 5 and reduce (3) 46 | . error 47 | 48 | state 2 49 | (1) expr : expr '+' . expr 50 | 51 | T_FOO shift 1 52 | '1' shift 4 and reduce (2) 53 | expr goto 6 and reduce (1) 54 | . error 55 | 56 | state 3 57 | (0) start : expr . 58 | (1) expr : expr . '+' expr 59 | 60 | EOF accept 61 | '+' shift 2 62 | . error 63 | 64 | state 4 65 | (2) expr : '1' . 66 | 67 | . reduce (2) 68 | 69 | state 5 70 | (1) expr : expr . '+' expr 71 | (3) expr : T_FOO expr . 72 | 73 | . reduce (3) 74 | 75 | state 6 76 | (1) expr : expr . '+' expr 77 | (1) expr : expr '+' expr . 78 | 79 | . reduce (1) 80 | 81 | 82 | Statistics for grammar.y: 83 | 5 terminal symbols 84 | 2 nonterminal symbols 85 | 4 productions 86 | 7 states 87 | 0 shift/reduce, 0 reduce/reduce conflicts 88 | 12 items 89 | 10 lookahead sets used 90 | 12+8=20 action entries 91 | 2432 bytes used 92 | 93 | State=>class: 94 | 95 | 0=>0 1=>0 2=>0 3=>1 96 | 97 | Terminal action: 98 | T\S 0 1 99 | EOF . 0 100 | '+' . 2 101 | T_FOO 1 . 102 | '1' 4 . 103 | 104 | Nonterminal GOTO table: 105 | T\S 0 1 2 3 106 | expr 3 5 6 . 107 | 108 | Nonterminal GOTO table: 109 | T\S default 0 1 2 3 110 | expr 3 = 5 6 . 111 | 112 | Candidates of aux table: 113 | Used aux table: 114 | state 0 (class 0) 115 | state 1 (class 0) 116 | state 2 (class 0) 117 | state 3 (class 1) 118 | Order: 119 | 1,0, 120 | Order: 121 | 1,0, 122 | -------------------------------------------------------------------------------- /examples/02-complex-expression-support/y.phpyacc.output: -------------------------------------------------------------------------------- 1 | EMPTY nonterminals: 2 | First: 3 | $start [ T_FOO '1' ] 4 | expr [ T_FOO '1' ] 5 | state unknown: 6 | (0) $start : . expr 7 | [ EOF ] 8 | state unknown: 9 | (3) expr : T_FOO . expr 10 | [ EOF '+' ] 11 | state unknown: 12 | (2) expr : '1' . 13 | [ EOF '+' ] 14 | state unknown: 15 | (0) $start : expr . 16 | [ EOF ] 17 | (1) expr : expr . '+' expr 18 | [ EOF '+' ] 19 | state unknown: 20 | (1) expr : expr . '+' expr 21 | [ EOF '+' ] 22 | (3) expr : T_FOO expr . 23 | [ EOF '+' ] 24 | state unknown: 25 | (1) expr : expr '+' . expr 26 | [ EOF '+' ] 27 | state unknown: 28 | (1) expr : expr . '+' expr 29 | [ EOF '+' ] 30 | (1) expr : expr '+' expr . 31 | [ EOF '+' ] 32 | state 0 33 | (0) $start : . expr 34 | 35 | T_FOO shift 1 36 | '1' shift 4 and reduce (2) 37 | expr goto 3 38 | . error 39 | 40 | state 1 41 | (3) expr : T_FOO . expr 42 | 43 | T_FOO shift 1 44 | '1' shift 4 and reduce (2) 45 | expr goto 5 and reduce (3) 46 | . error 47 | 48 | state 2 49 | (1) expr : expr '+' . expr 50 | 51 | T_FOO shift 1 52 | '1' shift 4 and reduce (2) 53 | expr goto 6 and reduce (1) 54 | . error 55 | 56 | state 3 57 | (0) $start : expr . 58 | (1) expr : expr . '+' expr 59 | 60 | EOF accept 61 | '+' shift 2 62 | . error 63 | 64 | state 4 65 | (2) expr : '1' . 66 | 67 | . reduce (2) 68 | 69 | state 5 70 | (1) expr : expr . '+' expr 71 | (3) expr : T_FOO expr . 72 | 73 | . reduce (3) 74 | 75 | state 6 76 | (1) expr : expr . '+' expr 77 | (1) expr : expr '+' expr . 78 | 79 | . reduce (1) 80 | 81 | 82 | Statistics for grammar.y: 83 | 5 terminal symbols 84 | 2 nonterminal symbols 85 | 4 productions 86 | 7 states 87 | 0 shift/reduce, 0 reduce/reduce conflicts 88 | 10 lookahead sets used 89 | 12+8=20 action entries 90 | State=>class: 91 | 92 | 0=>0 1=>0 2=>0 3=>1 93 | 94 | Terminal action: 95 | T\S 0 1 96 | EOF . 0 97 | '+' . 2 98 | T_FOO 1 . 99 | '1' 4 . 100 | 101 | Nonterminal GOTO table: 102 | T\S 0 1 2 3 103 | expr 3 5 6 . 104 | 105 | Nonterminal GOTO table: 106 | T\S default 0 1 2 3 107 | expr 3 = 5 6 . 108 | 109 | Candidates of aux table: 110 | Used aux table: 111 | state 0 (class 0) 112 | state 1 (class 0) 113 | state 2 (class 0) 114 | state 3 (class 1) 115 | Order: 116 | 1,0, 117 | Order: 118 | 1,0, 119 | -------------------------------------------------------------------------------- /examples/10-php7/parser.diff: -------------------------------------------------------------------------------- 1 | 809c809 2 | < "start : start", 3 | --- 4 | > "\$start : start", 5 | -------------------------------------------------------------------------------- /examples/10-php7/parser.template.php: -------------------------------------------------------------------------------- 1 | semValue 4 | #semval($,%t) $this->semValue 5 | #semval(%n) $stackPos-(%l-%n) 6 | #semval(%n,%t) $stackPos-(%l-%n) 7 | 8 | namespace PhpParser\Parser; 9 | 10 | use PhpParser\Error; 11 | use PhpParser\Node; 12 | use PhpParser\Node\Expr; 13 | use PhpParser\Node\Name; 14 | use PhpParser\Node\Scalar; 15 | use PhpParser\Node\Stmt; 16 | #include; 17 | 18 | /* This is an automatically GENERATED file, which should not be manually edited. 19 | */ 20 | class Parser extends \PhpParser\ParserAbstract 21 | { 22 | protected $tokenToSymbolMapSize = #(YYMAXLEX); 23 | protected $actionTableSize = #(YYLAST); 24 | protected $gotoTableSize = #(YYGLAST); 25 | 26 | protected $invalidSymbol = #(YYBADCH); 27 | protected $errorSymbol = #(YYINTERRTOK); 28 | protected $defaultAction = #(YYDEFAULT); 29 | protected $unexpectedTokenRule = #(YYUNEXPECTED); 30 | 31 | protected $YY2TBLSTATE = #(YY2TBLSTATE); 32 | protected $YYNLSTATES = #(YYNLSTATES); 33 | 34 | protected $symbolToName = array( 35 | #listvar terminals 36 | ); 37 | 38 | protected $tokenToSymbol = array( 39 | #listvar yytranslate 40 | ); 41 | 42 | protected $action = array( 43 | #listvar yyaction 44 | ); 45 | 46 | protected $actionCheck = array( 47 | #listvar yycheck 48 | ); 49 | 50 | protected $actionBase = array( 51 | #listvar yybase 52 | ); 53 | 54 | protected $actionDefault = array( 55 | #listvar yydefault 56 | ); 57 | 58 | protected $goto = array( 59 | #listvar yygoto 60 | ); 61 | 62 | protected $gotoCheck = array( 63 | #listvar yygcheck 64 | ); 65 | 66 | protected $gotoBase = array( 67 | #listvar yygbase 68 | ); 69 | 70 | protected $gotoDefault = array( 71 | #listvar yygdefault 72 | ); 73 | 74 | protected $ruleToNonTerminal = array( 75 | #listvar yylhs 76 | ); 77 | 78 | protected $ruleToLength = array( 79 | #listvar yylen 80 | ); 81 | #if -t 82 | 83 | protected $productions = array( 84 | #production-strings; 85 | ); 86 | #endif 87 | 88 | protected function initReduceCallbacks() { 89 | $this->reduceCallbacks = [ 90 | #reduce 91 | %n => function ($stackPos) { 92 | %b 93 | }, 94 | #noact 95 | %n => function ($stackPos) { 96 | $this->semValue = $this->semStack[$stackPos]; 97 | }, 98 | #endreduce 99 | ]; 100 | } 101 | } 102 | #tailcode; -------------------------------------------------------------------------------- /examples/10-php7/y.diff: -------------------------------------------------------------------------------- 1 | 1c1 2 | < EMPTY nonterminals: start start top_statement_list top_statement_list_ex no_comma optional_comma inner_statement_list_ex inner_statement_list elseif_list else_single new_elseif_list new_else_single for_expr optional_expr catches optional_finally optional_ref optional_ellipsis parameter_list optional_return_type extends_from implements_list class_statement_list interface_extends_list case_list non_empty_parameter_list parameter optional_param_type class_statement method_modifiers trait_adaptation_list exit_expr backticks_expr lexical_vars ctor_arguments lexical_var_list non_empty_lexical_var_list lexical_var array_pair_list list_expr_elements list_expr_element inner_array_pair_list array_pair 3 | --- 4 | > EMPTY nonterminals: $start start top_statement_list top_statement_list_ex no_comma optional_comma inner_statement_list_ex inner_statement_list elseif_list else_single new_elseif_list new_else_single for_expr optional_expr catches optional_finally optional_ref optional_ellipsis parameter_list optional_return_type extends_from implements_list class_statement_list interface_extends_list case_list non_empty_parameter_list parameter optional_param_type class_statement method_modifiers trait_adaptation_list exit_expr backticks_expr lexical_vars ctor_arguments lexical_var_list non_empty_lexical_var_list lexical_var array_pair_list list_expr_elements list_expr_element inner_array_pair_list array_pair 5 | 3c3 6 | < start [ error T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE T_PRINT T_YIELD T_YIELD_FROM '+' '-' '!' '~' T_INC T_DEC T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@' '[' T_NEW T_CLONE T_EXIT T_IF T_LNUMBER T_DNUMBER T_STRING T_VARIABLE T_INLINE_HTML T_CONSTANT_ENCAPSED_STRING T_ECHO T_DO T_WHILE T_FOR T_FOREACH T_DECLARE T_SWITCH T_BREAK T_CONTINUE T_GOTO T_FUNCTION T_CONST T_RETURN T_TRY T_THROW T_USE T_GLOBAL T_STATIC T_ABSTRACT T_FINAL T_UNSET T_ISSET T_EMPTY T_HALT_COMPILER T_CLASS T_TRAIT T_INTERFACE T_LIST T_ARRAY T_CLASS_C T_TRAIT_C T_METHOD_C T_FUNC_C T_LINE T_FILE T_START_HEREDOC T_NAMESPACE T_NS_C T_DIR T_NS_SEPARATOR ';' '{' '(' '`' '"' '$' @ ] 7 | --- 8 | > $start [ error T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE T_PRINT T_YIELD T_YIELD_FROM '+' '-' '!' '~' T_INC T_DEC T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@' '[' T_NEW T_CLONE T_EXIT T_IF T_LNUMBER T_DNUMBER T_STRING T_VARIABLE T_INLINE_HTML T_CONSTANT_ENCAPSED_STRING T_ECHO T_DO T_WHILE T_FOR T_FOREACH T_DECLARE T_SWITCH T_BREAK T_CONTINUE T_GOTO T_FUNCTION T_CONST T_RETURN T_TRY T_THROW T_USE T_GLOBAL T_STATIC T_ABSTRACT T_FINAL T_UNSET T_ISSET T_EMPTY T_HALT_COMPILER T_CLASS T_TRAIT T_INTERFACE T_LIST T_ARRAY T_CLASS_C T_TRAIT_C T_METHOD_C T_FUNC_C T_LINE T_FILE T_START_HEREDOC T_NAMESPACE T_NS_C T_DIR T_NS_SEPARATOR ';' '{' '(' '`' '"' '$' @ ] 9 | 147c147 10 | < (0) start : . start 11 | --- 12 | > (0) $start : . start 13 | 152c152 14 | < (0) start : start . 15 | --- 16 | > (0) $start : start . 17 | 9628c9628 18 | < (0) start : . start 19 | --- 20 | > (0) $start : . start 21 | 27346c27346 22 | < (0) start : start . 23 | --- 24 | > (0) $start : start . 25 | 30675a30676 26 | > grammar.y: there are 2 shift/reduce conflicts 27 | 30683d30683 28 | < 4416 items 29 | 30686,30687d30685 30 | < 440128 bytes used 31 | < 32 | -------------------------------------------------------------------------------- /examples/20-custom-parser/grammar.y: -------------------------------------------------------------------------------- 1 | 2 | 3 | %token NUMBER 4 | %left '+' '-' 5 | %left '*' '/' 6 | %right '^' 7 | %left NEG 8 | 9 | %% 10 | 11 | statement 12 | : /* empty */ { exit(0); } 13 | | expression { printf("= %f\n", $1); } 14 | ; 15 | 16 | expression 17 | : factor { $$ = $1; } 18 | | expression '*' expression { $$ = $1 * $3; } 19 | | expression '/' expression { $$ = $1 / $3; } 20 | | expression '+' expression { $$ = $1 + $3; } 21 | | expression '-' expression { $$ = $1 - $3; } 22 | | expression '^' expression { $$ = pow($1, $3); } 23 | | '-' expression %prec NEG { $$ = -$2; } 24 | ; 25 | 26 | factor 27 | : NUMBER { $$ = $1; } 28 | | '(' expression ')' { $$ = $2; } 29 | ; 30 | 31 | %% 32 | -------------------------------------------------------------------------------- /examples/20-custom-parser/parser.phpyacc.php: -------------------------------------------------------------------------------- 1 | = 0 269 | && $yyn < YYLAST && $yycheck[$yyn] == $yychar 270 | || ($yystate < YY2TBLSTATE 271 | && ($yyn = $yybase[$yystate + YYNLSTATES] + $yychar) >= 0 272 | && $yyn < YYLAST && $yycheck[$yyn] == $yychar)) 273 | && ($yyn = $yyaction[$yyn]) != YYDEFAULT) { 274 | /* 275 | * >= YYNLSTATE: shift and reduce 276 | * > 0: shift 277 | * = 0: accept 278 | * < 0: reduce 279 | * = -YYUNEXPECTED: error 280 | */ 281 | if ($yyn > 0) { 282 | /* shift */ 283 | YYTRACE_SHIFT($yychar); 284 | $yysp++; 285 | 286 | $yysstk[$yysp] = $yystate = $yyn; 287 | $yyastk[$yysp] = $yylval; 288 | $yychar = -1; 289 | 290 | if ($yyerrflag > 0) 291 | $yyerrflag--; 292 | if ($yyn < YYNLSTATES) 293 | continue; 294 | 295 | /* $yyn >= YYNLSTATES means shift-and-reduce */ 296 | $yyn -= YYNLSTATES; 297 | } else 298 | $yyn = -$yyn; 299 | } else 300 | $yyn = $yydefault[$yystate]; 301 | } 302 | 303 | while (true) { 304 | /* reduce/error */ 305 | if ($yyn == 0) { 306 | /* accept */ 307 | YYTRACE_ACCEPT(); 308 | yyflush(); 309 | return 0; 310 | } 311 | else if ($yyn != YYUNEXPECTED) { 312 | /* reduce */ 313 | $yyl = $yylen[$yyn]; 314 | $n = $yysp-$yyl+1; 315 | $yyval = isset($yyastk[$n]) ? $yyastk[$n] : null; 316 | YYTRACE_REDUCE($yyn); 317 | /* Following line will be replaced by reduce actions */ 318 | switch($yyn) { 319 | case 1: 320 | { exit(0); } break; 321 | case 2: 322 | { printf("= %f\n", $yyastk[$yysp-(1-1)]); } break; 323 | case 3: 324 | { $yyval = $yyastk[$yysp-(1-1)]; } break; 325 | case 4: 326 | { $yyval = $yyastk[$yysp-(3-1)] * $yyastk[$yysp-(3-3)]; } break; 327 | case 5: 328 | { $yyval = $yyastk[$yysp-(3-1)] $yyastk[$yysp-(3-3)]; } break; 329 | case 6: 330 | { $yyval = $yyastk[$yysp-(3-1)] + $yyastk[$yysp-(3-3)]; } break; 331 | case 7: 332 | { $yyval = $yyastk[$yysp-(3-1)] - $yyastk[$yysp-(3-3)]; } break; 333 | case 8: 334 | { $yyval = pow($yyastk[$yysp-(3-1)], $yyastk[$yysp-(3-3)]); } break; 335 | case 9: 336 | { $yyval = -$yyastk[$yysp-(2-2)]; } break; 337 | case 10: 338 | { $yyval = $yyastk[$yysp-(1-1)]; } break; 339 | case 11: 340 | { $yyval = $yyastk[$yysp-(3-2)]; } break; 341 | } 342 | /* Goto - shift nonterminal */ 343 | $yysp -= $yyl; 344 | $yyn = $yylhs[$yyn]; 345 | if (($yyp = $yygbase[$yyn] + $yysstk[$yysp]) >= 0 && $yyp < YYGLAST 346 | && $yygcheck[$yyp] == $yyn) 347 | $yystate = $yygoto[$yyp]; 348 | else 349 | $yystate = $yygdefault[$yyn]; 350 | 351 | $yysp++; 352 | 353 | $yysstk[$yysp] = $yystate; 354 | $yyastk[$yysp] = $yyval; 355 | } 356 | else { 357 | /* error */ 358 | switch ($yyerrflag) { 359 | case 0: 360 | yyerror("syntax error"); 361 | case 1: 362 | case 2: 363 | $yyerrflag = 3; 364 | /* Pop until error-expecting state uncovered */ 365 | 366 | while (!(($yyn = $yybase[$yystate] + YYINTERRTOK) >= 0 367 | && $yyn < YYLAST && $yycheck[$yyn] == YYINTERRTOK 368 | || ($yystate < YY2TBLSTATE 369 | && ($yyn = $yybase[$yystate + YYNLSTATES] + YYINTERRTOK) >= 0 370 | && $yyn < YYLAST && $yycheck[$yyn] == YYINTERRTOK))) { 371 | if ($yysp <= 0) { 372 | yyflush(); 373 | return 1; 374 | } 375 | $yystate = $yysstk[--$yysp]; 376 | YYTRACE_POP($yystate); 377 | } 378 | $yyn = $yyaction[$yyn]; 379 | YYTRACE_SHIFT(YYINTERRTOK); 380 | $yysstk[++$yysp] = $yystate = $yyn; 381 | break; 382 | 383 | case 3: 384 | YYTRACE_DISCARD($yychar); 385 | if ($yychar == 0) { 386 | yyflush(); 387 | return 1; 388 | } 389 | $yychar = -1; 390 | break; 391 | } 392 | } 393 | 394 | if ($yystate < YYNLSTATES) 395 | break; 396 | /* >= YYNLSTATES means shift-and-reduce */ 397 | $yyn = $yystate - YYNLSTATES; 398 | } 399 | } 400 | } 401 | 402 | 403 | -------------------------------------------------------------------------------- /examples/20-custom-parser/parser.template.php: -------------------------------------------------------------------------------- 1 | = 0 221 | && $yyn < YYLAST && $yycheck[$yyn] == $yychar 222 | || ($yystate < YY2TBLSTATE 223 | && ($yyn = $yybase[$yystate + YYNLSTATES] + $yychar) >= 0 224 | && $yyn < YYLAST && $yycheck[$yyn] == $yychar)) 225 | && ($yyn = $yyaction[$yyn]) != YYDEFAULT) { 226 | /* 227 | * >= YYNLSTATE: shift and reduce 228 | * > 0: shift 229 | * = 0: accept 230 | * < 0: reduce 231 | * = -YYUNEXPECTED: error 232 | */ 233 | if ($yyn > 0) { 234 | /* shift */ 235 | @if -t 236 | YYTRACE_SHIFT($yychar); 237 | @endif 238 | $yysp++; 239 | 240 | $yysstk[$yysp] = $yystate = $yyn; 241 | $yyastk[$yysp] = $yylval; 242 | $yychar = -1; 243 | 244 | if ($yyerrflag > 0) 245 | $yyerrflag--; 246 | if ($yyn < YYNLSTATES) 247 | continue; 248 | 249 | /* $yyn >= YYNLSTATES means shift-and-reduce */ 250 | $yyn -= YYNLSTATES; 251 | } else 252 | $yyn = -$yyn; 253 | } else 254 | $yyn = $yydefault[$yystate]; 255 | } 256 | 257 | while (true) { 258 | /* reduce/error */ 259 | if ($yyn == 0) { 260 | /* accept */ 261 | @if -t 262 | YYTRACE_ACCEPT(); 263 | @endif 264 | yyflush(); 265 | return 0; 266 | } 267 | else if ($yyn != YYUNEXPECTED) { 268 | /* reduce */ 269 | $yyl = $yylen[$yyn]; 270 | $n = $yysp-$yyl+1; 271 | $yyval = isset($yyastk[$n]) ? $yyastk[$n] : null; 272 | @if -t 273 | YYTRACE_REDUCE($yyn); 274 | @endif 275 | /* Following line will be replaced by reduce actions */ 276 | switch($yyn) { 277 | @reduce 278 | case %n: 279 | {%b} break; 280 | @endreduce 281 | } 282 | /* Goto - shift nonterminal */ 283 | $yysp -= $yyl; 284 | $yyn = $yylhs[$yyn]; 285 | if (($yyp = $yygbase[$yyn] + $yysstk[$yysp]) >= 0 && $yyp < YYGLAST 286 | && $yygcheck[$yyp] == $yyn) 287 | $yystate = $yygoto[$yyp]; 288 | else 289 | $yystate = $yygdefault[$yyn]; 290 | 291 | $yysp++; 292 | 293 | $yysstk[$yysp] = $yystate; 294 | $yyastk[$yysp] = $yyval; 295 | } 296 | else { 297 | /* error */ 298 | switch ($yyerrflag) { 299 | case 0: 300 | yyerror("syntax error"); 301 | case 1: 302 | case 2: 303 | $yyerrflag = 3; 304 | /* Pop until error-expecting state uncovered */ 305 | 306 | while (!(($yyn = $yybase[$yystate] + YYINTERRTOK) >= 0 307 | && $yyn < YYLAST && $yycheck[$yyn] == YYINTERRTOK 308 | || ($yystate < YY2TBLSTATE 309 | && ($yyn = $yybase[$yystate + YYNLSTATES] + YYINTERRTOK) >= 0 310 | && $yyn < YYLAST && $yycheck[$yyn] == YYINTERRTOK))) { 311 | if ($yysp <= 0) { 312 | yyflush(); 313 | return 1; 314 | } 315 | $yystate = $yysstk[--$yysp]; 316 | @if -t 317 | YYTRACE_POP($yystate); 318 | @endif 319 | } 320 | $yyn = $yyaction[$yyn]; 321 | @if -t 322 | YYTRACE_SHIFT(YYINTERRTOK); 323 | @endif 324 | $yysstk[++$yysp] = $yystate = $yyn; 325 | break; 326 | 327 | case 3: 328 | @if -t 329 | YYTRACE_DISCARD($yychar); 330 | @endif 331 | if ($yychar == 0) { 332 | yyflush(); 333 | return 1; 334 | } 335 | $yychar = -1; 336 | break; 337 | } 338 | } 339 | 340 | if ($yystate < YYNLSTATES) 341 | break; 342 | /* >= YYNLSTATES means shift-and-reduce */ 343 | $yyn = $yystate - YYNLSTATES; 344 | } 345 | } 346 | } 347 | 348 | @tailcode; -------------------------------------------------------------------------------- /examples/20-custom-parser/y.diff: -------------------------------------------------------------------------------- 1 | 1c1 2 | < EMPTY nonterminals: start statement 3 | --- 4 | > EMPTY nonterminals: $start statement 5 | 3c3 6 | < start [ NUMBER '-' '(' @ ] 7 | --- 8 | > $start [ NUMBER '-' '(' @ ] 9 | 8c8 10 | < (0) start : . statement 11 | --- 12 | > (0) $start : . statement 13 | 22c22 14 | < (0) start : statement . 15 | --- 16 | > (0) $start : statement . 17 | 150c150 18 | < (0) start : . statement 19 | --- 20 | > (0) $start : . statement 21 | 289c289 22 | < (0) start : statement . 23 | --- 24 | > (0) $start : statement . 25 | 359d358 26 | < 66 items 27 | 362,363d360 28 | < 7544 bytes used 29 | < 30 | -------------------------------------------------------------------------------- /examples/20-custom-parser/y.kmyacc.output: -------------------------------------------------------------------------------- 1 | EMPTY nonterminals: start statement 2 | First: 3 | start [ NUMBER '-' '(' @ ] 4 | statement [ NUMBER '-' '(' @ ] 5 | expression [ NUMBER '-' '(' ] 6 | factor [ NUMBER '(' ] 7 | state unknown: 8 | (0) start : . statement 9 | [ EOF ] 10 | (1) statement : . 11 | [ EOF ] 12 | state unknown: 13 | (10) factor : NUMBER . 14 | [ EOF '+' '-' '*' '/' '^' ')' ] 15 | state unknown: 16 | (9) expression : '-' . expression 17 | [ EOF '+' '-' '*' '/' '^' ')' ] 18 | state unknown: 19 | (11) factor : '(' . expression ')' 20 | [ EOF '+' '-' '*' '/' '^' ')' ] 21 | state unknown: 22 | (0) start : statement . 23 | [ EOF ] 24 | state unknown: 25 | (2) statement : expression . 26 | [ EOF ] 27 | (4) expression : expression . '*' expression 28 | [ EOF '+' '-' '*' '/' '^' ] 29 | (5) expression : expression . '/' expression 30 | [ EOF '+' '-' '*' '/' '^' ] 31 | (6) expression : expression . '+' expression 32 | [ EOF '+' '-' '*' '/' '^' ] 33 | (7) expression : expression . '-' expression 34 | [ EOF '+' '-' '*' '/' '^' ] 35 | (8) expression : expression . '^' expression 36 | [ EOF '+' '-' '*' '/' '^' ] 37 | state unknown: 38 | (3) expression : factor . 39 | [ EOF '+' '-' '*' '/' '^' ')' ] 40 | state unknown: 41 | (4) expression : expression . '*' expression 42 | [ EOF '+' '-' '*' '/' '^' ')' ] 43 | (5) expression : expression . '/' expression 44 | [ EOF '+' '-' '*' '/' '^' ')' ] 45 | (6) expression : expression . '+' expression 46 | [ EOF '+' '-' '*' '/' '^' ')' ] 47 | (7) expression : expression . '-' expression 48 | [ EOF '+' '-' '*' '/' '^' ')' ] 49 | (8) expression : expression . '^' expression 50 | [ EOF '+' '-' '*' '/' '^' ')' ] 51 | (9) expression : '-' expression . 52 | [ EOF '+' '-' '*' '/' '^' ')' ] 53 | state unknown: 54 | (4) expression : expression . '*' expression 55 | [ '+' '-' '*' '/' '^' ')' ] 56 | (5) expression : expression . '/' expression 57 | [ '+' '-' '*' '/' '^' ')' ] 58 | (6) expression : expression . '+' expression 59 | [ '+' '-' '*' '/' '^' ')' ] 60 | (7) expression : expression . '-' expression 61 | [ '+' '-' '*' '/' '^' ')' ] 62 | (8) expression : expression . '^' expression 63 | [ '+' '-' '*' '/' '^' ')' ] 64 | (11) factor : '(' expression . ')' 65 | [ EOF '+' '-' '*' '/' '^' ')' ] 66 | state unknown: 67 | (6) expression : expression '+' . expression 68 | [ EOF '+' '-' '*' '/' '^' ')' ] 69 | state unknown: 70 | (7) expression : expression '-' . expression 71 | [ EOF '+' '-' '*' '/' '^' ')' ] 72 | state unknown: 73 | (4) expression : expression '*' . expression 74 | [ EOF '+' '-' '*' '/' '^' ')' ] 75 | state unknown: 76 | (5) expression : expression '/' . expression 77 | [ EOF '+' '-' '*' '/' '^' ')' ] 78 | state unknown: 79 | (8) expression : expression '^' . expression 80 | [ EOF '+' '-' '*' '/' '^' ')' ] 81 | state unknown: 82 | (11) factor : '(' expression ')' . 83 | [ EOF '+' '-' '*' '/' '^' ')' ] 84 | state unknown: 85 | (4) expression : expression . '*' expression 86 | [ EOF '+' '-' '*' '/' '^' ')' ] 87 | (5) expression : expression . '/' expression 88 | [ EOF '+' '-' '*' '/' '^' ')' ] 89 | (6) expression : expression . '+' expression 90 | [ EOF '+' '-' '*' '/' '^' ')' ] 91 | (6) expression : expression '+' expression . 92 | [ EOF '+' '-' '*' '/' '^' ')' ] 93 | (7) expression : expression . '-' expression 94 | [ EOF '+' '-' '*' '/' '^' ')' ] 95 | (8) expression : expression . '^' expression 96 | [ EOF '+' '-' '*' '/' '^' ')' ] 97 | state unknown: 98 | (4) expression : expression . '*' expression 99 | [ EOF '+' '-' '*' '/' '^' ')' ] 100 | (5) expression : expression . '/' expression 101 | [ EOF '+' '-' '*' '/' '^' ')' ] 102 | (6) expression : expression . '+' expression 103 | [ EOF '+' '-' '*' '/' '^' ')' ] 104 | (7) expression : expression . '-' expression 105 | [ EOF '+' '-' '*' '/' '^' ')' ] 106 | (7) expression : expression '-' expression . 107 | [ EOF '+' '-' '*' '/' '^' ')' ] 108 | (8) expression : expression . '^' expression 109 | [ EOF '+' '-' '*' '/' '^' ')' ] 110 | state unknown: 111 | (4) expression : expression . '*' expression 112 | [ EOF '+' '-' '*' '/' '^' ')' ] 113 | (4) expression : expression '*' expression . 114 | [ EOF '+' '-' '*' '/' '^' ')' ] 115 | (5) expression : expression . '/' expression 116 | [ EOF '+' '-' '*' '/' '^' ')' ] 117 | (6) expression : expression . '+' expression 118 | [ EOF '+' '-' '*' '/' '^' ')' ] 119 | (7) expression : expression . '-' expression 120 | [ EOF '+' '-' '*' '/' '^' ')' ] 121 | (8) expression : expression . '^' expression 122 | [ EOF '+' '-' '*' '/' '^' ')' ] 123 | state unknown: 124 | (4) expression : expression . '*' expression 125 | [ EOF '+' '-' '*' '/' '^' ')' ] 126 | (5) expression : expression . '/' expression 127 | [ EOF '+' '-' '*' '/' '^' ')' ] 128 | (5) expression : expression '/' expression . 129 | [ EOF '+' '-' '*' '/' '^' ')' ] 130 | (6) expression : expression . '+' expression 131 | [ EOF '+' '-' '*' '/' '^' ')' ] 132 | (7) expression : expression . '-' expression 133 | [ EOF '+' '-' '*' '/' '^' ')' ] 134 | (8) expression : expression . '^' expression 135 | [ EOF '+' '-' '*' '/' '^' ')' ] 136 | state unknown: 137 | (4) expression : expression . '*' expression 138 | [ EOF '+' '-' '*' '/' '^' ')' ] 139 | (5) expression : expression . '/' expression 140 | [ EOF '+' '-' '*' '/' '^' ')' ] 141 | (6) expression : expression . '+' expression 142 | [ EOF '+' '-' '*' '/' '^' ')' ] 143 | (7) expression : expression . '-' expression 144 | [ EOF '+' '-' '*' '/' '^' ')' ] 145 | (8) expression : expression . '^' expression 146 | [ EOF '+' '-' '*' '/' '^' ')' ] 147 | (8) expression : expression '^' expression . 148 | [ EOF '+' '-' '*' '/' '^' ')' ] 149 | state 0 150 | (0) start : . statement 151 | (1) statement : . 152 | 153 | NUMBER shift 16 and reduce (10) 154 | '-' shift 3 155 | '(' shift 4 156 | statement goto 12 157 | expression goto 2 158 | factor goto 17 and reduce (3) 159 | . reduce (1) 160 | 161 | state 1 162 | (4) expression : expression . '*' expression 163 | (5) expression : expression . '/' expression 164 | (6) expression : expression . '+' expression 165 | (7) expression : expression . '-' expression 166 | (8) expression : expression . '^' expression 167 | (11) factor : '(' expression . ')' 168 | 169 | '+' shift 5 170 | '-' shift 6 171 | '*' shift 7 172 | '/' shift 8 173 | '^' shift 9 174 | ')' shift 19 and reduce (11) 175 | . error 176 | 177 | state 2 178 | (2) statement : expression . 179 | (4) expression : expression . '*' expression 180 | (5) expression : expression . '/' expression 181 | (6) expression : expression . '+' expression 182 | (7) expression : expression . '-' expression 183 | (8) expression : expression . '^' expression 184 | 185 | '+' shift 5 186 | '-' shift 6 187 | '*' shift 7 188 | '/' shift 8 189 | '^' shift 9 190 | . reduce (2) 191 | 192 | state 3 193 | (9) expression : '-' . expression 194 | 195 | NUMBER shift 16 and reduce (10) 196 | '-' shift 3 197 | '(' shift 4 198 | expression goto 18 and reduce (9) 199 | factor goto 17 and reduce (3) 200 | . error 201 | 202 | state 4 203 | (11) factor : '(' . expression ')' 204 | 205 | NUMBER shift 16 and reduce (10) 206 | '-' shift 3 207 | '(' shift 4 208 | expression goto 1 209 | factor goto 17 and reduce (3) 210 | . error 211 | 212 | state 5 213 | (6) expression : expression '+' . expression 214 | 215 | NUMBER shift 16 and reduce (10) 216 | '-' shift 3 217 | '(' shift 4 218 | expression goto 10 219 | factor goto 17 and reduce (3) 220 | . error 221 | 222 | state 6 223 | (7) expression : expression '-' . expression 224 | 225 | NUMBER shift 16 and reduce (10) 226 | '-' shift 3 227 | '(' shift 4 228 | expression goto 11 229 | factor goto 17 and reduce (3) 230 | . error 231 | 232 | state 7 233 | (4) expression : expression '*' . expression 234 | 235 | NUMBER shift 16 and reduce (10) 236 | '-' shift 3 237 | '(' shift 4 238 | expression goto 13 239 | factor goto 17 and reduce (3) 240 | . error 241 | 242 | state 8 243 | (5) expression : expression '/' . expression 244 | 245 | NUMBER shift 16 and reduce (10) 246 | '-' shift 3 247 | '(' shift 4 248 | expression goto 14 249 | factor goto 17 and reduce (3) 250 | . error 251 | 252 | state 9 253 | (8) expression : expression '^' . expression 254 | 255 | NUMBER shift 16 and reduce (10) 256 | '-' shift 3 257 | '(' shift 4 258 | expression goto 15 259 | factor goto 17 and reduce (3) 260 | . error 261 | 262 | state 10 263 | (4) expression : expression . '*' expression 264 | (5) expression : expression . '/' expression 265 | (6) expression : expression . '+' expression 266 | (6) expression : expression '+' expression . 267 | (7) expression : expression . '-' expression 268 | (8) expression : expression . '^' expression 269 | 270 | '*' shift 7 271 | '/' shift 8 272 | '^' shift 9 273 | . reduce (6) 274 | 275 | state 11 276 | (4) expression : expression . '*' expression 277 | (5) expression : expression . '/' expression 278 | (6) expression : expression . '+' expression 279 | (7) expression : expression . '-' expression 280 | (7) expression : expression '-' expression . 281 | (8) expression : expression . '^' expression 282 | 283 | '*' shift 7 284 | '/' shift 8 285 | '^' shift 9 286 | . reduce (7) 287 | 288 | state 12 289 | (0) start : statement . 290 | 291 | EOF accept 292 | . error 293 | 294 | state 13 295 | (4) expression : expression . '*' expression 296 | (4) expression : expression '*' expression . 297 | (5) expression : expression . '/' expression 298 | (6) expression : expression . '+' expression 299 | (7) expression : expression . '-' expression 300 | (8) expression : expression . '^' expression 301 | 302 | '^' shift 9 303 | . reduce (4) 304 | 305 | state 14 306 | (4) expression : expression . '*' expression 307 | (5) expression : expression . '/' expression 308 | (5) expression : expression '/' expression . 309 | (6) expression : expression . '+' expression 310 | (7) expression : expression . '-' expression 311 | (8) expression : expression . '^' expression 312 | 313 | '^' shift 9 314 | . reduce (5) 315 | 316 | state 15 317 | (4) expression : expression . '*' expression 318 | (5) expression : expression . '/' expression 319 | (6) expression : expression . '+' expression 320 | (7) expression : expression . '-' expression 321 | (8) expression : expression . '^' expression 322 | (8) expression : expression '^' expression . 323 | 324 | '^' shift 9 325 | . reduce (8) 326 | 327 | state 16 328 | (10) factor : NUMBER . 329 | 330 | . reduce (10) 331 | 332 | state 17 333 | (3) expression : factor . 334 | 335 | . reduce (3) 336 | 337 | state 18 338 | (4) expression : expression . '*' expression 339 | (5) expression : expression . '/' expression 340 | (6) expression : expression . '+' expression 341 | (7) expression : expression . '-' expression 342 | (8) expression : expression . '^' expression 343 | (9) expression : '-' expression . 344 | 345 | . reduce (9) 346 | 347 | state 19 348 | (11) factor : '(' expression ')' . 349 | 350 | . reduce (11) 351 | 352 | 353 | Statistics for grammar.y: 354 | 11 terminal symbols 355 | 4 nonterminal symbols 356 | 12 productions 357 | 20 states 358 | 0 shift/reduce, 0 reduce/reduce conflicts 359 | 66 items 360 | 29 lookahead sets used 361 | 82+21=103 action entries 362 | 7544 bytes used 363 | 364 | State=>class: 365 | 366 | 0=>4 1=>3 2=>2 3=>4 4=>4 5=>4 6=>4 7=>4 8=>4 9=>4 367 | 10=>1 11=>1 12=>5 13=>0 14=>0 15=>0 368 | 369 | Terminal action: 370 | T\S 0 1 2 3 4 5 371 | EOF . . . . . 0 372 | NUMBER . . . . 16 . 373 | '+' . . 5 5 . . 374 | '-' . . 6 6 3 . 375 | '*' . 7 7 7 . . 376 | '/' . 8 8 8 . . 377 | '^' 9 9 9 9 . . 378 | '(' . . . . 4 . 379 | ')' . . . 19 . . 380 | 381 | Nonterminal GOTO table: 382 | T\S 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 383 | statemen 12 . . . . . . . . . . . . . . . 384 | expressi 2 . . 18 1 10 11 13 14 15 . . . . . . 385 | factor 17 . . 17 17 17 17 17 17 17 . . . . . . 386 | 387 | Nonterminal GOTO table: 388 | T\S default 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 389 | statemen 12 = . . . . . . . . . . . . . . . 390 | expressi 2 = . . 18 1 10 11 13 14 15 . . . . . . 391 | factor 17 = . . = = = = = = = . . . . . . 392 | 393 | Candidates of aux table: 394 | Aux = (5) 7,8,9 * 0,1,2,3 395 | Aux = (6) 7,8,9 * 1,2,3 396 | Aux = (5) 5,6,7,8,9 * 2,3 397 | Used aux table: 398 | Selected aux[1]: (6) 7,8,9 * 1,2,3 399 | state 0 (class 4) 400 | state 1 (class 3): aux[1] 401 | state 2 (class 2): aux[1] 402 | state 3 (class 4) 403 | state 4 (class 4) 404 | state 5 (class 4) 405 | state 6 (class 4) 406 | state 7 (class 4) 407 | state 8 (class 4) 408 | state 9 (class 4) 409 | state 10 (class 1) 410 | state 11 (class 1) 411 | state 12 (class 5) 412 | state 13 (class 0) 413 | state 14 (class 0) 414 | state 15 (class 0) 415 | Order: 416 | 4,3,1,2,5,0, 417 | Order: 418 | 2,0,1,3, 419 | -------------------------------------------------------------------------------- /examples/20-custom-parser/y.phpyacc.output: -------------------------------------------------------------------------------- 1 | EMPTY nonterminals: $start statement 2 | First: 3 | $start [ NUMBER '-' '(' @ ] 4 | statement [ NUMBER '-' '(' @ ] 5 | expression [ NUMBER '-' '(' ] 6 | factor [ NUMBER '(' ] 7 | state unknown: 8 | (0) $start : . statement 9 | [ EOF ] 10 | (1) statement : . 11 | [ EOF ] 12 | state unknown: 13 | (10) factor : NUMBER . 14 | [ EOF '+' '-' '*' '/' '^' ')' ] 15 | state unknown: 16 | (9) expression : '-' . expression 17 | [ EOF '+' '-' '*' '/' '^' ')' ] 18 | state unknown: 19 | (11) factor : '(' . expression ')' 20 | [ EOF '+' '-' '*' '/' '^' ')' ] 21 | state unknown: 22 | (0) $start : statement . 23 | [ EOF ] 24 | state unknown: 25 | (2) statement : expression . 26 | [ EOF ] 27 | (4) expression : expression . '*' expression 28 | [ EOF '+' '-' '*' '/' '^' ] 29 | (5) expression : expression . '/' expression 30 | [ EOF '+' '-' '*' '/' '^' ] 31 | (6) expression : expression . '+' expression 32 | [ EOF '+' '-' '*' '/' '^' ] 33 | (7) expression : expression . '-' expression 34 | [ EOF '+' '-' '*' '/' '^' ] 35 | (8) expression : expression . '^' expression 36 | [ EOF '+' '-' '*' '/' '^' ] 37 | state unknown: 38 | (3) expression : factor . 39 | [ EOF '+' '-' '*' '/' '^' ')' ] 40 | state unknown: 41 | (4) expression : expression . '*' expression 42 | [ EOF '+' '-' '*' '/' '^' ')' ] 43 | (5) expression : expression . '/' expression 44 | [ EOF '+' '-' '*' '/' '^' ')' ] 45 | (6) expression : expression . '+' expression 46 | [ EOF '+' '-' '*' '/' '^' ')' ] 47 | (7) expression : expression . '-' expression 48 | [ EOF '+' '-' '*' '/' '^' ')' ] 49 | (8) expression : expression . '^' expression 50 | [ EOF '+' '-' '*' '/' '^' ')' ] 51 | (9) expression : '-' expression . 52 | [ EOF '+' '-' '*' '/' '^' ')' ] 53 | state unknown: 54 | (4) expression : expression . '*' expression 55 | [ '+' '-' '*' '/' '^' ')' ] 56 | (5) expression : expression . '/' expression 57 | [ '+' '-' '*' '/' '^' ')' ] 58 | (6) expression : expression . '+' expression 59 | [ '+' '-' '*' '/' '^' ')' ] 60 | (7) expression : expression . '-' expression 61 | [ '+' '-' '*' '/' '^' ')' ] 62 | (8) expression : expression . '^' expression 63 | [ '+' '-' '*' '/' '^' ')' ] 64 | (11) factor : '(' expression . ')' 65 | [ EOF '+' '-' '*' '/' '^' ')' ] 66 | state unknown: 67 | (6) expression : expression '+' . expression 68 | [ EOF '+' '-' '*' '/' '^' ')' ] 69 | state unknown: 70 | (7) expression : expression '-' . expression 71 | [ EOF '+' '-' '*' '/' '^' ')' ] 72 | state unknown: 73 | (4) expression : expression '*' . expression 74 | [ EOF '+' '-' '*' '/' '^' ')' ] 75 | state unknown: 76 | (5) expression : expression '/' . expression 77 | [ EOF '+' '-' '*' '/' '^' ')' ] 78 | state unknown: 79 | (8) expression : expression '^' . expression 80 | [ EOF '+' '-' '*' '/' '^' ')' ] 81 | state unknown: 82 | (11) factor : '(' expression ')' . 83 | [ EOF '+' '-' '*' '/' '^' ')' ] 84 | state unknown: 85 | (4) expression : expression . '*' expression 86 | [ EOF '+' '-' '*' '/' '^' ')' ] 87 | (5) expression : expression . '/' expression 88 | [ EOF '+' '-' '*' '/' '^' ')' ] 89 | (6) expression : expression . '+' expression 90 | [ EOF '+' '-' '*' '/' '^' ')' ] 91 | (6) expression : expression '+' expression . 92 | [ EOF '+' '-' '*' '/' '^' ')' ] 93 | (7) expression : expression . '-' expression 94 | [ EOF '+' '-' '*' '/' '^' ')' ] 95 | (8) expression : expression . '^' expression 96 | [ EOF '+' '-' '*' '/' '^' ')' ] 97 | state unknown: 98 | (4) expression : expression . '*' expression 99 | [ EOF '+' '-' '*' '/' '^' ')' ] 100 | (5) expression : expression . '/' expression 101 | [ EOF '+' '-' '*' '/' '^' ')' ] 102 | (6) expression : expression . '+' expression 103 | [ EOF '+' '-' '*' '/' '^' ')' ] 104 | (7) expression : expression . '-' expression 105 | [ EOF '+' '-' '*' '/' '^' ')' ] 106 | (7) expression : expression '-' expression . 107 | [ EOF '+' '-' '*' '/' '^' ')' ] 108 | (8) expression : expression . '^' expression 109 | [ EOF '+' '-' '*' '/' '^' ')' ] 110 | state unknown: 111 | (4) expression : expression . '*' expression 112 | [ EOF '+' '-' '*' '/' '^' ')' ] 113 | (4) expression : expression '*' expression . 114 | [ EOF '+' '-' '*' '/' '^' ')' ] 115 | (5) expression : expression . '/' expression 116 | [ EOF '+' '-' '*' '/' '^' ')' ] 117 | (6) expression : expression . '+' expression 118 | [ EOF '+' '-' '*' '/' '^' ')' ] 119 | (7) expression : expression . '-' expression 120 | [ EOF '+' '-' '*' '/' '^' ')' ] 121 | (8) expression : expression . '^' expression 122 | [ EOF '+' '-' '*' '/' '^' ')' ] 123 | state unknown: 124 | (4) expression : expression . '*' expression 125 | [ EOF '+' '-' '*' '/' '^' ')' ] 126 | (5) expression : expression . '/' expression 127 | [ EOF '+' '-' '*' '/' '^' ')' ] 128 | (5) expression : expression '/' expression . 129 | [ EOF '+' '-' '*' '/' '^' ')' ] 130 | (6) expression : expression . '+' expression 131 | [ EOF '+' '-' '*' '/' '^' ')' ] 132 | (7) expression : expression . '-' expression 133 | [ EOF '+' '-' '*' '/' '^' ')' ] 134 | (8) expression : expression . '^' expression 135 | [ EOF '+' '-' '*' '/' '^' ')' ] 136 | state unknown: 137 | (4) expression : expression . '*' expression 138 | [ EOF '+' '-' '*' '/' '^' ')' ] 139 | (5) expression : expression . '/' expression 140 | [ EOF '+' '-' '*' '/' '^' ')' ] 141 | (6) expression : expression . '+' expression 142 | [ EOF '+' '-' '*' '/' '^' ')' ] 143 | (7) expression : expression . '-' expression 144 | [ EOF '+' '-' '*' '/' '^' ')' ] 145 | (8) expression : expression . '^' expression 146 | [ EOF '+' '-' '*' '/' '^' ')' ] 147 | (8) expression : expression '^' expression . 148 | [ EOF '+' '-' '*' '/' '^' ')' ] 149 | state 0 150 | (0) $start : . statement 151 | (1) statement : . 152 | 153 | NUMBER shift 16 and reduce (10) 154 | '-' shift 3 155 | '(' shift 4 156 | statement goto 12 157 | expression goto 2 158 | factor goto 17 and reduce (3) 159 | . reduce (1) 160 | 161 | state 1 162 | (4) expression : expression . '*' expression 163 | (5) expression : expression . '/' expression 164 | (6) expression : expression . '+' expression 165 | (7) expression : expression . '-' expression 166 | (8) expression : expression . '^' expression 167 | (11) factor : '(' expression . ')' 168 | 169 | '+' shift 5 170 | '-' shift 6 171 | '*' shift 7 172 | '/' shift 8 173 | '^' shift 9 174 | ')' shift 19 and reduce (11) 175 | . error 176 | 177 | state 2 178 | (2) statement : expression . 179 | (4) expression : expression . '*' expression 180 | (5) expression : expression . '/' expression 181 | (6) expression : expression . '+' expression 182 | (7) expression : expression . '-' expression 183 | (8) expression : expression . '^' expression 184 | 185 | '+' shift 5 186 | '-' shift 6 187 | '*' shift 7 188 | '/' shift 8 189 | '^' shift 9 190 | . reduce (2) 191 | 192 | state 3 193 | (9) expression : '-' . expression 194 | 195 | NUMBER shift 16 and reduce (10) 196 | '-' shift 3 197 | '(' shift 4 198 | expression goto 18 and reduce (9) 199 | factor goto 17 and reduce (3) 200 | . error 201 | 202 | state 4 203 | (11) factor : '(' . expression ')' 204 | 205 | NUMBER shift 16 and reduce (10) 206 | '-' shift 3 207 | '(' shift 4 208 | expression goto 1 209 | factor goto 17 and reduce (3) 210 | . error 211 | 212 | state 5 213 | (6) expression : expression '+' . expression 214 | 215 | NUMBER shift 16 and reduce (10) 216 | '-' shift 3 217 | '(' shift 4 218 | expression goto 10 219 | factor goto 17 and reduce (3) 220 | . error 221 | 222 | state 6 223 | (7) expression : expression '-' . expression 224 | 225 | NUMBER shift 16 and reduce (10) 226 | '-' shift 3 227 | '(' shift 4 228 | expression goto 11 229 | factor goto 17 and reduce (3) 230 | . error 231 | 232 | state 7 233 | (4) expression : expression '*' . expression 234 | 235 | NUMBER shift 16 and reduce (10) 236 | '-' shift 3 237 | '(' shift 4 238 | expression goto 13 239 | factor goto 17 and reduce (3) 240 | . error 241 | 242 | state 8 243 | (5) expression : expression '/' . expression 244 | 245 | NUMBER shift 16 and reduce (10) 246 | '-' shift 3 247 | '(' shift 4 248 | expression goto 14 249 | factor goto 17 and reduce (3) 250 | . error 251 | 252 | state 9 253 | (8) expression : expression '^' . expression 254 | 255 | NUMBER shift 16 and reduce (10) 256 | '-' shift 3 257 | '(' shift 4 258 | expression goto 15 259 | factor goto 17 and reduce (3) 260 | . error 261 | 262 | state 10 263 | (4) expression : expression . '*' expression 264 | (5) expression : expression . '/' expression 265 | (6) expression : expression . '+' expression 266 | (6) expression : expression '+' expression . 267 | (7) expression : expression . '-' expression 268 | (8) expression : expression . '^' expression 269 | 270 | '*' shift 7 271 | '/' shift 8 272 | '^' shift 9 273 | . reduce (6) 274 | 275 | state 11 276 | (4) expression : expression . '*' expression 277 | (5) expression : expression . '/' expression 278 | (6) expression : expression . '+' expression 279 | (7) expression : expression . '-' expression 280 | (7) expression : expression '-' expression . 281 | (8) expression : expression . '^' expression 282 | 283 | '*' shift 7 284 | '/' shift 8 285 | '^' shift 9 286 | . reduce (7) 287 | 288 | state 12 289 | (0) $start : statement . 290 | 291 | EOF accept 292 | . error 293 | 294 | state 13 295 | (4) expression : expression . '*' expression 296 | (4) expression : expression '*' expression . 297 | (5) expression : expression . '/' expression 298 | (6) expression : expression . '+' expression 299 | (7) expression : expression . '-' expression 300 | (8) expression : expression . '^' expression 301 | 302 | '^' shift 9 303 | . reduce (4) 304 | 305 | state 14 306 | (4) expression : expression . '*' expression 307 | (5) expression : expression . '/' expression 308 | (5) expression : expression '/' expression . 309 | (6) expression : expression . '+' expression 310 | (7) expression : expression . '-' expression 311 | (8) expression : expression . '^' expression 312 | 313 | '^' shift 9 314 | . reduce (5) 315 | 316 | state 15 317 | (4) expression : expression . '*' expression 318 | (5) expression : expression . '/' expression 319 | (6) expression : expression . '+' expression 320 | (7) expression : expression . '-' expression 321 | (8) expression : expression . '^' expression 322 | (8) expression : expression '^' expression . 323 | 324 | '^' shift 9 325 | . reduce (8) 326 | 327 | state 16 328 | (10) factor : NUMBER . 329 | 330 | . reduce (10) 331 | 332 | state 17 333 | (3) expression : factor . 334 | 335 | . reduce (3) 336 | 337 | state 18 338 | (4) expression : expression . '*' expression 339 | (5) expression : expression . '/' expression 340 | (6) expression : expression . '+' expression 341 | (7) expression : expression . '-' expression 342 | (8) expression : expression . '^' expression 343 | (9) expression : '-' expression . 344 | 345 | . reduce (9) 346 | 347 | state 19 348 | (11) factor : '(' expression ')' . 349 | 350 | . reduce (11) 351 | 352 | 353 | Statistics for grammar.y: 354 | 11 terminal symbols 355 | 4 nonterminal symbols 356 | 12 productions 357 | 20 states 358 | 0 shift/reduce, 0 reduce/reduce conflicts 359 | 29 lookahead sets used 360 | 82+21=103 action entries 361 | State=>class: 362 | 363 | 0=>4 1=>3 2=>2 3=>4 4=>4 5=>4 6=>4 7=>4 8=>4 9=>4 364 | 10=>1 11=>1 12=>5 13=>0 14=>0 15=>0 365 | 366 | Terminal action: 367 | T\S 0 1 2 3 4 5 368 | EOF . . . . . 0 369 | NUMBER . . . . 16 . 370 | '+' . . 5 5 . . 371 | '-' . . 6 6 3 . 372 | '*' . 7 7 7 . . 373 | '/' . 8 8 8 . . 374 | '^' 9 9 9 9 . . 375 | '(' . . . . 4 . 376 | ')' . . . 19 . . 377 | 378 | Nonterminal GOTO table: 379 | T\S 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 380 | statemen 12 . . . . . . . . . . . . . . . 381 | expressi 2 . . 18 1 10 11 13 14 15 . . . . . . 382 | factor 17 . . 17 17 17 17 17 17 17 . . . . . . 383 | 384 | Nonterminal GOTO table: 385 | T\S default 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 386 | statemen 12 = . . . . . . . . . . . . . . . 387 | expressi 2 = . . 18 1 10 11 13 14 15 . . . . . . 388 | factor 17 = . . = = = = = = = . . . . . . 389 | 390 | Candidates of aux table: 391 | Aux = (5) 7,8,9 * 0,1,2,3 392 | Aux = (6) 7,8,9 * 1,2,3 393 | Aux = (5) 5,6,7,8,9 * 2,3 394 | Used aux table: 395 | Selected aux[1]: (6) 7,8,9 * 1,2,3 396 | state 0 (class 4) 397 | state 1 (class 3): aux[1] 398 | state 2 (class 2): aux[1] 399 | state 3 (class 4) 400 | state 4 (class 4) 401 | state 5 (class 4) 402 | state 6 (class 4) 403 | state 7 (class 4) 404 | state 8 (class 4) 405 | state 9 (class 4) 406 | state 10 (class 1) 407 | state 11 (class 1) 408 | state 12 (class 5) 409 | state 13 (class 0) 410 | state 14 (class 0) 411 | state 15 (class 0) 412 | Order: 413 | 4,3,1,2,5,0, 414 | Order: 415 | 2,0,1,3, 416 | -------------------------------------------------------------------------------- /examples/rebuild.php: -------------------------------------------------------------------------------- 1 | args[0])) { 15 | buildFolder($options, $generator, realpath($options->args[0])); 16 | } else { 17 | buildAll($options, $generator, __DIR__); 18 | } 19 | 20 | 21 | function buildAll(CliOptions $options, Generator $generator, string $dir) 22 | { 23 | $it = new DirectoryIterator($dir); 24 | foreach ($it as $file) { 25 | if (!$file->isDir() || $file->isDot()) { 26 | continue; 27 | } 28 | $dir = $file->getPathname(); 29 | buildFolder($options, $generator, $dir); 30 | } 31 | } 32 | 33 | 34 | function buildFolder(CliOptions $options, Generator $generator, string $dir) { 35 | chdir($dir); 36 | echo "Building $dir\n"; 37 | 38 | $grammar = "grammar.y"; 39 | $skeleton = "parser.template.php"; 40 | 41 | if ($options->runKmyacc) { 42 | shell_exec("cd $dir && kmyacc -x -t -v -L php -m $skeleton -p Parser $grammar 2>&1"); 43 | rename("$dir/y.output", "$dir/y.kmyacc.output"); 44 | rename("$dir/grammar.php", "$dir/parser.kmyacc.php"); 45 | } 46 | 47 | $errorFile = fopen("php://stderr", "w"); 48 | $debugFile = DEBUG ? fopen("$dir/y.phpyacc.output", 'w') : null; 49 | $context = new Context($grammar, $errorFile, $debugFile, VERBOSE_DEBUG); 50 | $context->tflag = true; 51 | $generator->generate( 52 | $context, 53 | file_get_contents($grammar), 54 | file_get_contents($skeleton), 55 | "$dir/parser.phpyacc.php" 56 | ); 57 | 58 | shell_exec("cd $dir && diff -w parser.kmyacc.php parser.phpyacc.php > parser.diff"); 59 | 60 | shell_exec("cd $dir && diff -w y.kmyacc.output y.phpyacc.output > y.diff"); 61 | 62 | } 63 | 64 | class CliOptions { 65 | public $runKmyacc = false; 66 | public $args = []; 67 | 68 | public static function fromArgv(array $argv) { 69 | $options = new self; 70 | foreach (array_slice($argv, 1) as $arg) { 71 | if ($arg === '-k') { 72 | $options->runKmyacc = true; 73 | } else { 74 | $options->args[] = $arg; 75 | } 76 | } 77 | return $options; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/CodeGen/Language.php: -------------------------------------------------------------------------------- 1 | fp = $file; 25 | $this->hp = $headerFile; 26 | $this->fileBuffer = ''; 27 | $this->headerBuffer = ''; 28 | } 29 | 30 | public function commit() 31 | { 32 | // Make sure there is exactly one trailing newline. 33 | $this->fileBuffer = rtrim($this->fileBuffer, "\n") . "\n"; 34 | $this->headerBuffer = rtrim($this->headerBuffer, "\n") . "\n"; 35 | 36 | fwrite($this->fp, $this->fileBuffer); 37 | fwrite($this->hp, $this->headerBuffer); 38 | $this->fp = $this->hp = null; 39 | $this->fileBuffer = ''; 40 | $this->headerBuffer = ''; 41 | } 42 | 43 | public function inline_comment(string $text) 44 | { 45 | $this->fileBuffer .= '/* ' . $text . " */"; 46 | } 47 | 48 | public function comment(string $text) 49 | { 50 | $this->fileBuffer .= '//' . $text . "\n"; 51 | } 52 | 53 | public function case_block(string $indent, int $num, string $value) 54 | { 55 | $this->fileBuffer .= sprintf("%scase %d: return %s;\n", $indent, $num, var_export($value, true)); 56 | } 57 | 58 | public function write(string $text, bool $includeHeader = false) 59 | { 60 | $this->fileBuffer .= $text; 61 | if ($includeHeader) { 62 | $this->headerBuffer .= $text; 63 | } 64 | } 65 | 66 | public function writeQuoted(string $text) 67 | { 68 | $regex = '(\\$(?=[a-zA-Z_])|")'; 69 | $text = preg_replace($regex, "\\\\$0", $text); 70 | $this->fileBuffer .= $text; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/Compress/Auxiliary.php: -------------------------------------------------------------------------------- 1 | index = $index; 16 | } 17 | 18 | public static function compare(Preimage $x, Preimage $y): int 19 | { 20 | if ($x->length !== $y->length) { 21 | return $x->length - $y->length; 22 | } 23 | foreach ($x->classes as $key => $value) { 24 | if ($value !== $y->classes[$key]) { 25 | return $value - $y->classes[$key]; 26 | } 27 | } 28 | return 0; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/Compress/TRow.php: -------------------------------------------------------------------------------- 1 | index = $index; 17 | $this->mini = -1; 18 | $this->maxi = 0; 19 | $this->nent = 0; 20 | } 21 | 22 | public function span(): int 23 | { 24 | return $this->maxi - $this->mini; 25 | } 26 | 27 | public function nhole(): int 28 | { 29 | return $this->span() - $this->nent; 30 | } 31 | 32 | public static function compare(TRow $a, TRow $b): int 33 | { 34 | if ($a->nent !== $b->nent) { 35 | return $b->nent - $a->nent; 36 | } 37 | if ($a->span() !== $b->span()) { 38 | return $b->span() - $a->span(); 39 | } 40 | return $a->mini - $b->mini; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/Compress/functions.php: -------------------------------------------------------------------------------- 1 | t), Token::decode($expecting), $token->fn, $token->ln)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/Exception/PhpYaccException.php: -------------------------------------------------------------------------------- 1 | parser = $parser ?: new Parser(new Lexer(), new MacroSet()); 23 | $this->lalr = $lalr ?: new Lalr(); 24 | $this->compressor = $compressor ?: new Compress(); 25 | } 26 | 27 | public function generate(Context $context, string $grammar, string $template, string $resultFile) 28 | { 29 | $template = new Template(new PHP(), $template, $context); 30 | 31 | $this->parser->parse($grammar, $context); 32 | 33 | $this->lalr->compute($context); 34 | 35 | $result = $this->compressor->compress($context); 36 | 37 | $template->render($result, fopen($resultFile, 'w')); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/Grammar/Context.php: -------------------------------------------------------------------------------- 1 | '', 29 | DollarExpansion::SEMVAL_LHS_UNTYPED => '', 30 | DollarExpansion::SEMVAL_RHS_TYPED => '', 31 | DollarExpansion::SEMVAL_RHS_UNTYPED => '', 32 | ]; 33 | 34 | public $nsymbols = 0; 35 | public $nterminals = 0; 36 | public $nnonterminals = 0; 37 | 38 | protected $symbolHash = []; 39 | protected $_symbols = []; 40 | protected $_nilsymbol = null; 41 | protected $finished = false; 42 | 43 | protected $_states; 44 | public $nstates = 0; 45 | public $nnonleafstates = 0; 46 | 47 | public $aflag = false; 48 | public $tflag = false; 49 | public $allowSemanticValueReferenceByName = false; 50 | public $pspref = ''; 51 | public $verboseDebug = false; 52 | 53 | public $filename = 'YY'; 54 | public $pureFlag = false; 55 | public $startSymbol = null; 56 | public $expected = 0; 57 | public $unioned = false; 58 | public $eofToken = null; 59 | public $errorToken = null; 60 | public $startPrime = null; 61 | protected $_grams = []; 62 | public $ngrams = 0; 63 | 64 | public $default_act = []; 65 | public $default_goto = []; 66 | public $term_action = []; 67 | public $class_action = []; 68 | public $nonterm_goto = []; 69 | public $class_of = []; 70 | public $ctermindex = []; 71 | public $otermindex = []; 72 | public $frequency = []; 73 | public $state_imagesorted = []; 74 | public $nprims = 0; 75 | public $prims = []; 76 | public $primof = []; 77 | public $class2nd = []; 78 | public $nclasses = 0; 79 | public $naux = 0; 80 | 81 | public $debugFile; 82 | public $errorFile; 83 | 84 | public function __construct( 85 | string $filename = 'YY', 86 | $errorFile = null, 87 | $debugFile = null, 88 | bool $verboseDebug = false 89 | ) { 90 | $this->filename = $filename; 91 | $this->errorFile = $errorFile; 92 | $this->debugFile = $debugFile; 93 | $this->verboseDebug = $verboseDebug; 94 | } 95 | 96 | public function error(string $data) 97 | { 98 | if ($this->errorFile) { 99 | fwrite($this->errorFile, $data); 100 | } 101 | } 102 | 103 | public function debug(string $data) 104 | { 105 | if ($this->debugFile) { 106 | fwrite($this->debugFile, $data); 107 | } 108 | } 109 | 110 | public function __get($name) 111 | { 112 | switch ($name) { 113 | case 'terminals': return $this->terminals(); 114 | case 'nonterminals': return $this->nonTerminals(); 115 | } 116 | if (!isset($this->{'_' . $name})) { 117 | throw new LogicException("Should never happen: unknown property $name"); 118 | } 119 | return $this->{'_' . $name}; 120 | } 121 | 122 | public function __set($name, $value) 123 | { 124 | $this->{'set' . $name}($value); 125 | } 126 | 127 | public function finish() 128 | { 129 | if ($this->finished) { 130 | return; 131 | } 132 | $this->finished = true; 133 | $code = 0; 134 | foreach ($this->terminals() as $term) { 135 | $term->code = $code++; 136 | } 137 | foreach ($this->nonTerminals() as $nonterm) { 138 | $nonterm->code = $code++; 139 | } 140 | foreach ($this->nilSymbols() as $nil) { 141 | $nil->code = $code++; 142 | } 143 | 144 | usort($this->_symbols, function ($a, $b) { 145 | return $a->code <=> $b->code; 146 | }); 147 | } 148 | 149 | public function nilSymbol(): Symbol 150 | { 151 | if ($this->_nilsymbol === null) { 152 | $this->_nilsymbol = $this->intern("@nil"); 153 | } 154 | return $this->_nilsymbol; 155 | } 156 | 157 | public function terminals(): Generator 158 | { 159 | foreach ($this->_symbols as $symbol) { 160 | if ($symbol->isterminal) { 161 | yield $symbol; 162 | } 163 | } 164 | } 165 | 166 | public function nilSymbols(): Generator 167 | { 168 | foreach ($this->_symbols as $symbol) { 169 | if ($symbol->isNilSymbol()) { 170 | yield $symbol; 171 | } 172 | } 173 | } 174 | 175 | public function nonTerminals(): Generator 176 | { 177 | foreach ($this->_symbols as $symbol) { 178 | if ($symbol->isnonterminal) { 179 | yield $symbol; 180 | } 181 | } 182 | } 183 | 184 | public function genNonTerminal(): Symbol 185 | { 186 | $buffer = sprintf("@%d", $this->nnonterminals); 187 | return $this->internSymbol($buffer, false); 188 | } 189 | 190 | public function internSymbol(string $s, bool $isTerm): Symbol 191 | { 192 | $p = $this->intern($s); 193 | 194 | if (!$p->isNilSymbol()) { 195 | return $p; 196 | } 197 | if ($isTerm || $s[0] === "'") { 198 | if ($s[0] === "'") { 199 | $p->value = character_value(substr($s, 1, -1)); 200 | } else { 201 | $p->value = -1; 202 | } 203 | $p->terminal = Symbol::TERMINAL; 204 | } else { 205 | $p->value = null; 206 | $p->terminal = Symbol::NONTERMINAL; 207 | } 208 | 209 | $p->associativity = Symbol::UNDEF; 210 | $p->precedence = Symbol::UNDEF; 211 | return $p; 212 | } 213 | 214 | public function intern(string $s): Symbol 215 | { 216 | if (isset($this->symbolHash[$s])) { 217 | return $this->symbolHash[$s]; 218 | } 219 | $p = new Symbol($this->nsymbols++, $s); 220 | return $this->addSymbol($p); 221 | } 222 | 223 | public function addSymbol(Symbol $symbol): Symbol 224 | { 225 | $this->finished = false; 226 | $this->_symbols[] = $symbol; 227 | $this->symbolHash[$symbol->name] = $symbol; 228 | $this->nterminals = 0; 229 | $this->nnonterminals = 0; 230 | foreach ($this->_symbols as $symbol) { 231 | if ($symbol->isterminal) { 232 | $this->nterminals++; 233 | } elseif ($symbol->isnonterminal) { 234 | $this->nnonterminals++; 235 | } 236 | } 237 | return $symbol; 238 | } 239 | 240 | public function symbols(): array 241 | { 242 | return $this->_symbols; 243 | } 244 | 245 | public function symbol(int $code): Symbol 246 | { 247 | foreach ($this->_symbols as $symbol) { 248 | if ($symbol->code === $code) { 249 | return $symbol; 250 | } 251 | } 252 | throw new LogicException("Should never happen: unknown symbol $code"); 253 | } 254 | 255 | public function addGram(Production $p) 256 | { 257 | $p->num = $this->ngrams++; 258 | $this->_grams[] = $p; 259 | return $p; 260 | } 261 | 262 | public function gram(int $i): Production 263 | { 264 | assert($i < $this->ngrams); 265 | return $this->_grams[$i]; 266 | } 267 | 268 | public function setStates(array $states) 269 | { 270 | foreach ($states as $state) { 271 | assert($state instanceof State); 272 | } 273 | $this->_states = $states; 274 | $this->nstates = count($states); 275 | } 276 | 277 | public function setNNonLeafStates(int $n) 278 | { 279 | $this->nnonleafstates = $n; 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /lib/Grammar/State.php: -------------------------------------------------------------------------------- 1 | through = $through; 29 | $this->items = $items; 30 | } 31 | 32 | public function isReduceOnly(): bool 33 | { 34 | return empty($this->shifts) 35 | && $this->reduce[0]->symbol->isNilSymbol(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/Grammar/Symbol.php: -------------------------------------------------------------------------------- 1 | code = $code; 46 | $this->_name = $name; 47 | $this->_value = $value; 48 | $this->setTerminal($terminal); 49 | $this->_precedence = $precedence; 50 | $this->_associativity = $associativity; 51 | $this->_type = $type; 52 | } 53 | 54 | public function isNilSymbol(): bool 55 | { 56 | return $this->_terminal === self::UNDEF; 57 | } 58 | 59 | public function __get($name) 60 | { 61 | return $this->{'_'.$name}; 62 | } 63 | 64 | public function __set($name, $value) 65 | { 66 | $this->{'set' . $name}($value); 67 | } 68 | 69 | public function setTerminal(int $terminal) 70 | { 71 | $this->_terminal = $terminal; 72 | if ($terminal === self::TERMINAL) { 73 | $this->isterminal = true; 74 | $this->isnonterminal = false; 75 | } elseif ($terminal === self::NONTERMINAL) { 76 | $this->isterminal = false; 77 | $this->isnonterminal = true; 78 | } else { 79 | $this->isterminal = false; 80 | $this->isnonterminal = false; 81 | } 82 | $this->setValue($this->_value); // force check to prevent issues 83 | } 84 | 85 | public function setAssociativity(int $associativity) 86 | { 87 | $this->_associativity = $associativity; 88 | } 89 | 90 | public function setPrecedence(int $precedence) 91 | { 92 | $this->_precedence = $precedence; 93 | } 94 | 95 | public function setValue($value) 96 | { 97 | if ($this->isterminal && !is_int($value)) { 98 | throw new LogicException("Terminals value must be an integer, " . gettype($value) . " provided"); 99 | } elseif ($this->isnonterminal && !($value instanceof Production || $value === null)) { 100 | throw new LogicException("NonTerminals value must be a production, " . gettype($value) . " provided"); 101 | } 102 | $this->_value = $value; 103 | } 104 | 105 | public function setType(?Symbol $type): void 106 | { 107 | $this->_type = $type; 108 | } 109 | 110 | public function setAssociativityFlag(int $flag) 111 | { 112 | $this->_associativity |= $flag; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/Lalr/ArrayBitset.php: -------------------------------------------------------------------------------- 1 | numBits = $numBits; 17 | $this->array = array_fill(0, intdiv($numBits + self::NBITS - 1, self::NBITS), 0); 18 | } 19 | 20 | public function __clone() 21 | { 22 | $this->array = array_values($this->array); 23 | } 24 | 25 | public function testBit(int $i): bool 26 | { 27 | $offset = intdiv($i, self::NBITS); 28 | return ($this->array[$offset] & (1 << ($i % self::NBITS))) !== 0; 29 | } 30 | 31 | public function setBit(int $i) 32 | { 33 | $offset = intdiv($i, self::NBITS); 34 | $this->array[$offset] |= (1 << ($i % self::NBITS)); 35 | } 36 | 37 | public function clearBit(int $i) 38 | { 39 | $offset = intdiv($i, self::NBITS); 40 | $this->array[$offset] &= ~(1 << ($i % self::NBITS)); 41 | } 42 | 43 | public function or(Bitset $other): bool 44 | { 45 | assert($this->numBits === $other->numBits); 46 | 47 | $changed = false; 48 | foreach ($this->array as $key => $value) { 49 | $this->array[$key] = $value | $other->array[$key]; 50 | $changed = $changed || $value !== $this->array[$key]; 51 | } 52 | return $changed; 53 | } 54 | 55 | public function getIterator(): \Traversable 56 | { 57 | $numElems = count($this->array); 58 | for ($n = 0; $n < $numElems; $n++) { 59 | $elem = $this->array[$n]; 60 | if ($elem !== 0) { 61 | for ($i = 0; $i < self::NBITS; $i++) { 62 | if ($elem & (1 << $i)) { 63 | yield $n * self::NBITS + $i; 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/Lalr/Bitset.php: -------------------------------------------------------------------------------- 1 | next = $next; 17 | $this->symbol = $symbol; 18 | } 19 | 20 | public function isShiftReduce(): bool 21 | { 22 | return false; 23 | } 24 | 25 | public function isReduceReduce(): bool 26 | { 27 | return false; 28 | } 29 | 30 | public function symbol(): Symbol 31 | { 32 | return $this->symbol; 33 | } 34 | 35 | public function next() 36 | { 37 | return $this->next; 38 | } 39 | 40 | public function setNext(Conflict $next = null) 41 | { 42 | $this->next = $next; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/Lalr/Conflict/ReduceReduce.php: -------------------------------------------------------------------------------- 1 | reduce1 = $reduce1; 20 | $this->reduce2 = $reduce2; 21 | parent::__construct($symbol, $next); 22 | } 23 | 24 | public function isReduceReduce(): bool 25 | { 26 | return true; 27 | } 28 | 29 | public function reduce1(): int 30 | { 31 | return $this->reduce1; 32 | } 33 | 34 | public function reduce2(): int 35 | { 36 | return $this->reduce2; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/Lalr/Conflict/ShiftReduce.php: -------------------------------------------------------------------------------- 1 | state = $state; 19 | $this->reduce = $reduce; 20 | parent::__construct($symbol, $next); 21 | } 22 | 23 | public function isShiftReduce(): bool 24 | { 25 | return true; 26 | } 27 | 28 | public function state(): State 29 | { 30 | return $this->state; 31 | } 32 | 33 | public function reduce(): int 34 | { 35 | return $this->reduce; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/Lalr/Item.php: -------------------------------------------------------------------------------- 1 | = 1); 20 | assert($offset <= count($production->body)); 21 | $this->production = $production; 22 | $this->pos = $offset; 23 | } 24 | 25 | public function getIterator(): \Traversable 26 | { 27 | for ($i = $this->pos; $i < \count($this->production->body); $i++) { 28 | yield $this->production->body[$i]; 29 | } 30 | } 31 | 32 | public function slice(int $n): Item 33 | { 34 | return new Item($this->production, $this->pos + $n); 35 | } 36 | 37 | public function offsetExists($offset): bool 38 | { 39 | return isset($this->production->body[$offset + $this->pos]); 40 | } 41 | 42 | public function offsetGet($offset): mixed 43 | { 44 | if (!$this->offsetExists($offset)) { 45 | throw new LogicException("Offset $offset does not exist"); 46 | } 47 | return $this->production->body[$offset + $this->pos]; 48 | } 49 | 50 | public function offsetSet($offset, $value): void 51 | { 52 | throw new LogicException("Not supported"); 53 | } 54 | 55 | public function offsetUnset($offset): void 56 | { 57 | throw new LogicException("Not supported"); 58 | } 59 | 60 | public function isHeadItem() 61 | { 62 | return $this->pos === 1; 63 | } 64 | 65 | public function isTailItem() 66 | { 67 | return $this->pos === count($this->production->body); 68 | } 69 | 70 | public function getProduction(): Production 71 | { 72 | return $this->production; 73 | } 74 | 75 | public function getPos(): int 76 | { 77 | return $this->pos; 78 | } 79 | 80 | public function __toString() 81 | { 82 | $result = "(" . $this->production->num . ")"; 83 | for ($i = 0; $i < count($this->production->body); $i++) { 84 | if ($i === 1) { 85 | $result .= " :"; 86 | } 87 | if ($i === $this->pos) { 88 | $result .= " ."; 89 | } 90 | $result .= " " . $this->production->body[$i]->name; 91 | } 92 | if ($i === 1) { 93 | $result .= " :"; 94 | } 95 | if ($i === $this->pos) { 96 | $result .= " ."; 97 | } 98 | return $result; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/Lalr/LalrResult.php: -------------------------------------------------------------------------------- 1 | grams = $grams; 18 | $this->states = $states; 19 | $this->nstates = count($states); 20 | $this->output = $output; 21 | $this->nnonleafstates = $nnonleafstates; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/Lalr/Lr1.php: -------------------------------------------------------------------------------- 1 | left = $left; 23 | $this->look = $look; 24 | $this->item = $item; 25 | } 26 | 27 | public function isTailItem(): bool 28 | { 29 | return $this->item->isTailItem(); 30 | } 31 | 32 | public function isHeadItem(): bool 33 | { 34 | return $this->item->isHeadItem(); 35 | } 36 | 37 | public function dump(): string 38 | { 39 | $result = ''; 40 | $lr1 = $this; 41 | while ($lr1 !== null) { 42 | $result .= $lr1->item . "\n"; 43 | $lr1 = $lr1->next; 44 | } 45 | return $result . "\n"; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/Lalr/Reduce.php: -------------------------------------------------------------------------------- 1 | symbol = $symbol; 19 | $this->number = $number; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/Lalr/StringBitset.php: -------------------------------------------------------------------------------- 1 | numBits = $numBits; 28 | $this->str = str_repeat("\0", intdiv($numBits + self::NBITS - 1, self::NBITS)); 29 | } 30 | 31 | public function testBit(int $i): bool 32 | { 33 | $offset = intdiv($i, self::NBITS); 34 | return ((ord($this->str[$offset]) >> ($i % self::NBITS)) & 1) !== 0; 35 | } 36 | 37 | public function setBit(int $i) 38 | { 39 | $offset = intdiv($i, self::NBITS); 40 | $char = $this->str[$offset]; 41 | $char |= self::MASKS[$i % self::NBITS]; 42 | $this->str[$offset] = $char; 43 | } 44 | 45 | public function clearBit(int $i) 46 | { 47 | $offset = intdiv($i, self::NBITS); 48 | $char = $this->str[$offset]; 49 | $char &= ~self::MASKS[$i % self::NBITS]; 50 | $this->str[$offset] = $char; 51 | } 52 | 53 | public function or(Bitset $other): bool 54 | { 55 | assert($this->numBits === $other->numBits); 56 | 57 | $changed = false; 58 | for ($i = 0; $i < $this->numBits; $i += self::NBITS) { 59 | $offset = $i / self::NBITS; 60 | if ("\0" !== ($other->str[$offset] & ~$this->str[$offset])) { 61 | $changed = true; 62 | $this->str[$offset] = $this->str[$offset] | $other->str[$offset]; 63 | } 64 | } 65 | return $changed; 66 | } 67 | 68 | public function getIterator(): \Traversable 69 | { 70 | for ($i = 0; $i < $this->numBits; $i++) { 71 | if ($this->testBit($i)) { 72 | yield $i; 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/Lalr/functions.php: -------------------------------------------------------------------------------- 1 | item != $t->item) { 16 | return false; 17 | } 18 | $p = $p->next; 19 | $t = $t->next; 20 | } 21 | return $p === null || $p->isHeadItem(); 22 | } 23 | 24 | function dumpSet(Context $ctx, Bitset $set): string 25 | { 26 | $result = ''; 27 | foreach ($set as $code) { 28 | $symbol = $ctx->symbols[$code]; 29 | $result .= "{$symbol->name} "; 30 | } 31 | return $result; 32 | } 33 | -------------------------------------------------------------------------------- /lib/Macro.php: -------------------------------------------------------------------------------- 1 | Token::MARK, 26 | "%{" => Token::BEGININC, 27 | "%}" => Token::ENDINC, 28 | "%token" => Token::TOKEN, 29 | "%term" => Token::TOKEN, 30 | "%left" => Token::LEFT, 31 | "%right" => Token::RIGHT, 32 | "%nonassoc" => Token::NONASSOC, 33 | "%prec" => Token::PRECTOK, 34 | "%type" => Token::TYPE, 35 | "%union" => Token::UNION, 36 | "%start" => Token::START, 37 | "%expect" => Token::EXPECT, 38 | "%pure_parser" => Token::PURE_PARSER, 39 | ]; 40 | 41 | 42 | protected $backToken = null; 43 | protected $token = null; 44 | protected $prevIsDollar = false; 45 | 46 | protected $filename; 47 | protected $lineNumber = 0; 48 | 49 | public function getLineNumber(): int 50 | { 51 | return $this->lineNumber; 52 | } 53 | 54 | public function peek(): Token 55 | { 56 | $result = $this->get(); 57 | $this->unget(); 58 | return $result; 59 | } 60 | 61 | public function get(): Token 62 | { 63 | $this->token = $this->rawGet(); 64 | while (in_array($this->token->t, self::SPACE_TOKENS)) { 65 | $this->token = $this->rawGet(); 66 | } 67 | return $this->token; 68 | } 69 | 70 | public function unget() 71 | { 72 | if ($this->backToken) { 73 | throw new LexingException("Too many ungetToken calls"); 74 | } 75 | $this->backToken = $this->token; 76 | } 77 | 78 | public function rawGet(): Token 79 | { 80 | if ($this->backToken) { 81 | $this->token = $this->backToken; 82 | $this->backToken = null; 83 | return $this->token; 84 | } 85 | $c = $this->getc(); 86 | $p = ''; 87 | if (is_white($c)) { 88 | while (is_white($c)) { 89 | $p .= $c; 90 | $c = $this->getc(); 91 | } 92 | $this->ungetc($c); 93 | return $this->token(Token::SPACE, $p); 94 | } 95 | if ($c === "\n") { 96 | $this->lineNumber++; 97 | return $this->token(Token::NEWLINE, $c); 98 | } 99 | if ($c === "/") { 100 | if (($c = $this->getc()) === '*') { 101 | // skip comments 102 | $p = "/*"; 103 | while (true) { 104 | if (($c = $this->getc()) === '*') { 105 | if (($c = $this->getc()) === '/') { 106 | break; 107 | } 108 | $this->ungetc($c); 109 | } 110 | if ($c === EOF) { 111 | throw ParseException::unexpected($this->token(EOF, ''), "*/"); 112 | } 113 | $p .= $c; 114 | } 115 | $p .= "*/"; 116 | return $this->token(Token::COMMENT, $p); 117 | } elseif ($c === '/') { 118 | // skip // comment 119 | $p = '//'; 120 | do { 121 | $c = $this->getc(); 122 | if ($c !== EOF) { 123 | $p .= $c; 124 | } 125 | } while ($c !== "\n" && $c !== EOF); 126 | return $this->token(Token::COMMENT, $p); 127 | } 128 | } 129 | if ($c === EOF) { 130 | return $this->token(EOF, ''); 131 | } 132 | 133 | $tag = $c; 134 | if ($c === '%') { 135 | $c = $this->getc(); 136 | if ($c === '%' || $c === '{' | $c === '}' || is_sym_character($c)) { 137 | $p .= "%"; 138 | } else { 139 | $this->ungetc($c); 140 | $c = '%'; 141 | } 142 | } 143 | 144 | if ($c === '$') { 145 | if (!$this->prevIsDollar) { 146 | $p .= '$'; 147 | $c = $this->getc(); 148 | if ($c === '$') { 149 | $this->ungetc($c); 150 | $this->prevIsDollar = true; 151 | } elseif (!ctype_digit($c) && is_sym_character($c)) { 152 | do { 153 | $p .= $c; 154 | $c = $this->getc(); 155 | } while (is_sym_character($c)); 156 | $this->ungetc($c); 157 | $tag = Token::NAME; 158 | } else { 159 | $this->ungetc($c); 160 | } 161 | } else { 162 | $p .= '$'; 163 | $this->prevIsDollar = false; 164 | } 165 | } elseif (is_sym_character($c)) { 166 | do { 167 | $p .= $c; 168 | $c = $this->getc(); 169 | } while ($c !== EOF && is_sym_character($c)); 170 | $this->ungetc($c); 171 | $tag = ctype_digit($p) ? Token::NUMBER : Token::NAME; 172 | } elseif ($c === '\'' || $c === '"') { 173 | $p .= $c; 174 | while (($c = $this->getc()) !== $tag) { 175 | if ($c === EOF) { 176 | throw ParseException::unexpected($this->token("EOF", ''), $tag); 177 | } 178 | if ($c === "\n") { 179 | throw ParseException::unexpected($this->token(Token::NEWLINE, "\n"), $tag); 180 | } 181 | $p .= $c; 182 | if ($c === '\\') { 183 | $c = $this->getc(); 184 | if ($c === EOF) { 185 | break; 186 | } 187 | if ($c === "\n") { 188 | continue; 189 | } 190 | $p .= $c; 191 | } 192 | } 193 | $p .= $c; 194 | } else { 195 | $p .= $c; 196 | } 197 | 198 | if (isset(self::TAG_MAP[$p])) { 199 | $tag = self::TAG_MAP[$p]; 200 | } 201 | return $this->token($tag, $p); 202 | } 203 | 204 | protected function token($id, $value): Token 205 | { 206 | return new Token($id, $value, $this->lineNumber, $this->filename); 207 | } 208 | 209 | 210 | 211 | protected $buffer = ''; 212 | protected $bufferOffset = 0; 213 | protected $backChar = null; 214 | 215 | public function startLexing(string $code, string $filename) 216 | { 217 | $this->filename = $filename; 218 | $this->buffer = $code; 219 | $this->bufferOffset = 0; 220 | $this->backChar = null; 221 | $this->backToken = null; 222 | $this->token = null; 223 | $this->prevIsDollar = false; 224 | } 225 | 226 | protected function getc(): string 227 | { 228 | if (null !== $this->backChar) { 229 | $result = $this->backChar; 230 | $this->backChar = null; 231 | return $result; 232 | } 233 | if ($this->bufferOffset >= strlen($this->buffer)) { 234 | return EOF; 235 | } 236 | return $this->buffer[$this->bufferOffset++]; 237 | } 238 | 239 | protected function ungetc(string $c) 240 | { 241 | if ($c === EOF) { 242 | return; 243 | } 244 | if ($this->backChar !== null) { 245 | throw new LexingException("To many unget calls"); 246 | } 247 | $this->backChar = $c; 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /lib/Yacc/LexerTest.php: -------------------------------------------------------------------------------- 1 | boot($source); 29 | $token = $lexer->rawGet(); 30 | $this->assertEquals($expected, $token->t); 31 | $this->assertEquals($source, $token->v); 32 | } 33 | 34 | protected function boot(string $source): Lexer 35 | { 36 | $lexer = new Lexer(); 37 | $lexer->startLexing($source, "xxx"); 38 | return $lexer; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/Yacc/Macro/DollarExpansion.php: -------------------------------------------------------------------------------- 1 | rewind(); $tokens->valid(); $tokens->next()) { 25 | $t = $tokens->current(); 26 | switch ($t->t) { 27 | case Token::NAME: 28 | if (!$ctx->allowSemanticValueReferenceByName) { 29 | break; 30 | } 31 | $type = null; 32 | $v = -1; 33 | for ($i = 0; $i <= $n; $i++) { 34 | if ($symbols[$i]->name === $t->v) { 35 | if ($v < 0) { 36 | $v = $i; 37 | } else { 38 | throw new ParseException("Ambiguous semantic value reference for $t"); 39 | } 40 | } 41 | } 42 | if ($v < 0) { 43 | for ($i = 0; $i <= $n; $i++) { 44 | if ($attribute[$i] === $t->v) { 45 | $v = $i; 46 | break; 47 | } 48 | } 49 | if ($t->v === $attribute[$n + 1]) { 50 | $v = 0; 51 | } 52 | } 53 | if ($v >= 0) { 54 | $t = clone $t; 55 | $t->t = $v === 0 ? '$' : 0; 56 | goto semval; 57 | } 58 | break; 59 | case '$': 60 | $type = null; 61 | $t = self::next($tokens); 62 | if ($t->t === '<') { 63 | $t = self::next($tokens); 64 | if ($t->t !== Token::NAME) { 65 | throw ParseException::unexpected($t, Token::NAME); 66 | } 67 | $type = $ctx->intern($t->v); 68 | $dump = self::next($tokens); 69 | if ($dump->t !== '>') { 70 | throw ParseException::unexpected($dump, '>'); 71 | } 72 | $t = self::next($tokens); 73 | } 74 | $v = 1; 75 | if ($t->t === '$') { 76 | $v = 0; 77 | } elseif ($t->t === '-') { 78 | $t = self::next($tokens); 79 | if ($t->t !== Token::NUMBER) { 80 | throw ParseException::unexpected($t, Token::NUMBER); 81 | } 82 | $v = -1 * ((int) $t->v); 83 | } else { 84 | if ($t->t !== Token::NUMBER) { 85 | throw new RuntimeException("Number expected"); 86 | } 87 | $v = (int) $t->v; 88 | if ($v > $n) { 89 | throw new RuntimeException("N is too big"); 90 | } 91 | } 92 | semval: 93 | if ($type === null) { 94 | $type = $symbols[$v]->type; 95 | } 96 | if ($type === null /** && $ctx->unioned */ && false) { 97 | throw new ParseException("Type not defined for " . $symbols[$v]->name); 98 | } 99 | foreach ($this->parseDollar($ctx, $t, $v, $n, $type?->name) as $t) { 100 | yield $t; 101 | } 102 | 103 | continue 2; 104 | } 105 | yield $t; 106 | } 107 | } 108 | 109 | protected function parseDollar(Context $ctx, Token $t, int $nth, int $len, ?string $type): array 110 | { 111 | if ($t->t === '$') { 112 | if ($type) { 113 | $mp = $ctx->macros[self::SEMVAL_LHS_TYPED]; 114 | } else { 115 | $mp = $ctx->macros[self::SEMVAL_LHS_UNTYPED]; 116 | } 117 | } else { 118 | if ($type) { 119 | $mp = $ctx->macros[self::SEMVAL_RHS_TYPED]; 120 | } else { 121 | $mp = $ctx->macros[self::SEMVAL_RHS_UNTYPED]; 122 | } 123 | } 124 | 125 | $result = ''; 126 | for ($i = 0; $i < strlen($mp); $i++) { 127 | if ($mp[$i] === '%') { 128 | $i++; 129 | switch ($mp[$i]) { 130 | case 'n': 131 | $result .= sprintf('%d', $nth); 132 | break; 133 | case 'l': 134 | $result .= sprintf('%d', $len); 135 | break; 136 | case 't': 137 | $result .= $type; 138 | break; 139 | default: 140 | $result .= $mp[$i]; 141 | } 142 | } else { 143 | $result .= $mp[$i]; 144 | } 145 | } 146 | return $this->parse($result, $t->ln, $t->fn); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lib/Yacc/MacroAbstract.php: -------------------------------------------------------------------------------- 1 | next(); 39 | if (!$it->valid()) { 40 | throw new LogicException("Unexpected end of action stream: this should never happen"); 41 | } 42 | return $it->current(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/Yacc/MacroSet.php: -------------------------------------------------------------------------------- 1 | addMacro(new Macro\DollarExpansion()); 18 | $this->addMacro(...$macros); 19 | } 20 | 21 | public function addMacro(MacroAbstract ...$macros) 22 | { 23 | foreach ($macros as $macro) { 24 | $this->macros[] = $macro; 25 | } 26 | } 27 | 28 | public function apply(Context $ctx, array $symbols, array $tokens, int $n, array $attribute): array 29 | { 30 | $tokens = new ArrayIterator($tokens); 31 | $macroCount = count($this->macros); 32 | if ($macroCount === 1) { 33 | // special case 34 | return iterator_to_array($this->macros[0]->apply($ctx, $symbols, $tokens, $n, $attribute)); 35 | } 36 | foreach ($this->macros as $macro) { 37 | $tokens = $macro->apply($ctx, $symbols, $tokens, $n, $attribute); 38 | } 39 | $tokens = self::cache($tokens); 40 | 41 | return iterator_to_array($tokens); 42 | } 43 | 44 | public static function cache(Traversable $t): Traversable 45 | { 46 | return new ArrayIterator(iterator_to_array($t)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/Yacc/Parser.php: -------------------------------------------------------------------------------- 1 | lexer = $lexer; 30 | $this->macros = $macros; 31 | } 32 | 33 | public function parse(string $code, Context $context) 34 | { 35 | $this->context = $context; 36 | $this->lexer->startLexing($code, $this->context->filename); 37 | $this->doDeclaration(); 38 | $this->doGrammar(); 39 | $this->context->eofToken = $this->eofToken; 40 | $this->context->errorToken = $this->errorToken; 41 | $this->context->startPrime = $this->startPrime; 42 | $this->context->finish(); 43 | return $this->context; 44 | } 45 | 46 | protected function copyAction(array $symbols, int $n, $delm, array $attribute): string 47 | { 48 | $tokens = []; 49 | $ct = 0; 50 | while (($t = $this->lexer->rawGet())->t !== $delm || $ct > 0) { 51 | switch ($t->t) { 52 | case EOF: 53 | throw ParseException::unexpected($t, Token::decode($delm)); 54 | case '{': 55 | $ct++; 56 | break; 57 | case '}': 58 | $ct--; 59 | break; 60 | } 61 | $tokens[] = $t; 62 | } 63 | $expanded = $this->macros->apply($this->context, $symbols, $tokens, $n, $attribute); 64 | return implode('', array_map(function (Token $t) { 65 | return $t->v; 66 | }, $expanded)); 67 | } 68 | 69 | protected function doType() 70 | { 71 | $type = $this->getType(); 72 | while (true) { 73 | if (($t = $this->lexer->get())->v === ',') { 74 | continue; 75 | } 76 | if ($t->t !== Token::NAME && $t->t !== "'") { 77 | break; 78 | } 79 | $p = $this->context->internSymbol($t->v, false); 80 | if ($type !== null) { 81 | $p->type = $type; 82 | } 83 | } 84 | $this->lexer->unget(); 85 | } 86 | 87 | protected function doGrammar() 88 | { 89 | $attribute = []; 90 | $gbuffer = [null]; 91 | $r = new Production('', 0); 92 | $r->body = [$this->startPrime]; 93 | $this->context->addGram($r); 94 | 95 | $t = $this->lexer->get(); 96 | 97 | while ($t->t !== Token::MARK && $t->t !== EOF) { 98 | if ($t->t === Token::NAME) { 99 | if ($this->lexer->peek()->t === '@') { 100 | $attribute[0] = $t->v; 101 | $this->lexer->get(); 102 | $t = $this->lexer->get(); 103 | } else { 104 | $attribute[0] = null; 105 | } 106 | $gbuffer[0] = $this->context->internSymbol($t->v, false); 107 | $attribute[1] = null; 108 | if ($gbuffer[0]->isterminal) { 109 | throw new RuntimeException("Nonterminal symbol expected: $t"); 110 | } elseif (($tmp = $this->lexer->get())->t !== ':') { 111 | throw new RuntimeException("':' expected, $tmp found"); 112 | } 113 | if ($this->context->startSymbol === null) { 114 | $this->context->startSymbol = $gbuffer[0]; 115 | } 116 | } elseif ($t->t === '|') { 117 | if (!$gbuffer[0]) { 118 | throw new RuntimeException("Syntax Error, unexpected $t"); 119 | } 120 | $attribute[1] = null; 121 | } elseif ($t->t === Token::BEGININC) { 122 | $this->doCopy(); 123 | $t = $this->lexer->get(); 124 | continue; 125 | } else { 126 | throw new RuntimeException("Syntax Error Unexpected $t"); 127 | } 128 | 129 | $lastTerm = $this->startPrime; 130 | $action = ''; 131 | $pos = 0; 132 | $i = 1; 133 | while (true) { 134 | $t = $this->lexer->get(); 135 | if ($t->t === '=') { 136 | $pos = $t->ln; 137 | if (($t = $this->lexer->get())->t === '{') { 138 | $pos = $t->ln; 139 | $action = $this->copyAction($gbuffer, $i - 1, '}', $attribute); 140 | } else { 141 | $this->lexer->unget(); 142 | $action = $this->copyAction($gbuffer, $i - 1, ';', $attribute); 143 | } 144 | } elseif ($t->t === '{') { 145 | $pos = $t->ln; 146 | $action = $this->copyAction($gbuffer, $i - 1, '}', $attribute); 147 | } elseif ($t->t === Token::PRECTOK) { 148 | $lastTerm = $this->context->internSymbol($this->lexer->get()->v, false); 149 | } elseif ($t->t === Token::NAME && $this->lexer->peek()->t === ':') { 150 | break; 151 | } elseif ($t->t === Token::NAME && $this->lexer->peek()->t === '@') { 152 | $attribute[$i] = $t->v; 153 | $this->lexer->get(); 154 | } elseif ($t->t === Token::NAME || $t->t === "'") { 155 | if ($action) { 156 | $g = $this->context->genNonTerminal(); 157 | $r = new Production($action, $pos); 158 | $r->body = [$g]; 159 | $gbuffer[$i++] = $g; 160 | $attribute[$i] = null; 161 | $r->link = $r->body[0]->value; 162 | $g->value = $this->context->addGram($r); 163 | } 164 | $gbuffer[$i++] = $w = $this->context->internSymbol($t->v, false); 165 | $attribute[$i] = null; 166 | if ($w->isterminal) { 167 | $lastTerm = $w; 168 | } 169 | $action = ''; 170 | } else { 171 | break; 172 | } 173 | } 174 | if (!$action) { 175 | if ($i > 1 && $gbuffer[0]->type !== null && $gbuffer[0]->type !== $gbuffer[1]->type) { 176 | throw new ParseException("Stack types are different"); 177 | } 178 | } 179 | $r = new Production($action, $pos); 180 | $r->body = array_slice($gbuffer, 0, $i); 181 | $r->precedence = $lastTerm->precedence; 182 | $r->associativity = $lastTerm->associativity & Symbol::MASK; 183 | $r->link = $r->body[0]->value; 184 | $gbuffer[0]->value = $this->context->addGram($r); 185 | 186 | if ($t->t === ';') { 187 | $t = $this->lexer->get(); 188 | } 189 | } 190 | $this->context->gram(0)->body[] = $this->context->startSymbol; 191 | $this->startPrime->value = null; 192 | foreach ($this->context->nonterminals as $key => $symbol) { 193 | if ($symbol === $this->startPrime) { 194 | continue; 195 | } 196 | if (($j = $symbol->value) === null) { 197 | throw new ParseException("Nonterminal {$symbol->name} used but not defined"); 198 | } 199 | $k = null; 200 | while ($j) { 201 | $w = $j->link; 202 | $j->link = $k; 203 | $k = $j; 204 | $j = $w; 205 | } 206 | $symbol->value = $k; 207 | } 208 | } 209 | 210 | protected function doDeclaration() 211 | { 212 | $this->eofToken = $this->context->internSymbol("EOF", true); 213 | $this->eofToken->value = 0; 214 | $this->errorToken = $this->context->internSymbol("error", true); 215 | $this->startPrime = $this->context->internSymbol("\$start", false); 216 | 217 | while (($t = $this->lexer->get())->t !== Token::MARK) { 218 | switch ($t->t) { 219 | case Token::TOKEN: 220 | case Token::RIGHT: 221 | case Token::LEFT: 222 | case Token::NONASSOC: 223 | $this->doToken($t); 224 | break; 225 | case Token::BEGININC: 226 | $this->doCopy(); 227 | break; 228 | case Token::UNION: 229 | $this->doUnion(); 230 | $this->context->unioned = true; 231 | break; 232 | case Token::TYPE: 233 | $this->doType(); 234 | break; 235 | case Token::EXPECT: 236 | $t = $this->lexer->get(); 237 | if ($t->t === Token::NUMBER) { 238 | $this->context->expected = (int) $t->v; 239 | } else { 240 | throw ParseException::unexpected($t, Token::NUMBER); 241 | } 242 | break; 243 | case Token::START: 244 | $t = $this->lexer->get(); 245 | $this->context->startSymbol = $this->context->internSymbol($t->v, false); 246 | break; 247 | case Token::PURE_PARSER: 248 | $this->context->pureFlag = true; 249 | break; 250 | case EOF: 251 | throw new ParseException("No grammar given"); 252 | default: 253 | throw new ParseException("Syntax error, unexpected {$t->v}"); 254 | } 255 | } 256 | $base = 256; 257 | foreach ($this->context->terminals as $terminal) { 258 | if ($terminal === $this->context->eofToken) { 259 | continue; 260 | } 261 | if ($terminal->value < 0) { 262 | $terminal->value = $base++; 263 | } 264 | } 265 | } 266 | 267 | protected $currentPrecedence = 0; 268 | 269 | protected function doToken(Token $tag) 270 | { 271 | $preIncr = 0; 272 | $type = $this->getType(); 273 | $t = $this->lexer->get(); 274 | 275 | while ($t->t === Token::NAME || $t->t === "'") { 276 | $p = $this->context->internSymbol($t->v, true); 277 | if ($p->name[0] === "'") { 278 | $p->value = character_value(substr($p->name, 1, -1)); 279 | } 280 | 281 | if ($type) { 282 | $p->type = $type; 283 | } 284 | switch ($tag->t) { 285 | case Token::LEFT: 286 | $p->associativity |= Symbol::LEFT; 287 | break; 288 | case Token::RIGHT: 289 | $p->associativity |= Symbol::RIGHT; 290 | break; 291 | case Token::NONASSOC: 292 | $p->associativity |= Symbol::NON; 293 | break; 294 | } 295 | if ($tag->t !== Token::TOKEN) { 296 | $p->precedence = $this->currentPrecedence; 297 | $preIncr = 1; 298 | } 299 | $t = $this->lexer->get(); 300 | if ($t->t === Token::NUMBER) { 301 | if ($p->value === null) { 302 | $p->value = (int) $t->v; 303 | } else { 304 | throw new ParseException("Unexpected Token::NUMBER as {$p->name} already has a value"); 305 | } 306 | $t = $this->lexer->get(); 307 | } 308 | if ($t->t === ',') { 309 | $t = $this->lexer->get(); 310 | } 311 | } 312 | $this->lexer->unget(); 313 | $this->currentPrecedence += $preIncr; 314 | } 315 | 316 | protected function getType() 317 | { 318 | $t = $this->lexer->get(); 319 | if ($t->t !== '<') { 320 | $this->lexer->unget(); 321 | return null; 322 | } 323 | $ct = 1; 324 | $p = ''; 325 | $t = $this->lexer->get(); 326 | while (true) { 327 | switch ($t->t) { 328 | case "\n": 329 | case EOF: 330 | throw ParseException::unexpected($t, ">"); 331 | 332 | case '<': 333 | $ct++; 334 | break; 335 | case '>': 336 | $ct--; 337 | break; 338 | } 339 | if ($ct === 0) { 340 | break; 341 | } 342 | $p .= $t->v; 343 | $t = $this->lexer->rawGet(); 344 | } 345 | $this->context->unioned = true; 346 | return $this->context->intern($p); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /lib/Yacc/ParserTest.php: -------------------------------------------------------------------------------- 1 | false, 26 | "nsymbols" => 5, 27 | "nterminals" => 2, 28 | "nnonterminals" => 2, 29 | "ngrams" => 2, 30 | ], [ 31 | "terminals" => [ 32 | 49 => "'1'", 33 | ], 34 | "nonterminals" => [ 35 | "expr" 36 | ], 37 | "grams" => [ 38 | [ 39 | "action" => "", 40 | "empty" => false, 41 | "body" => [3, 4] 42 | ], 43 | [ 44 | "action" => " m2(0,1) = 1; ", 45 | "empty" => false, 46 | "body" => [4, 2], 47 | ] 48 | ] 49 | ] 50 | ], 51 | 52 | [ 53 | << true, 64 | "nsymbols" => 6, 65 | "nterminals" => 3, 66 | "nnonterminals" => 2, 67 | "ngrams" => 3, 68 | ], [ 69 | "terminals" => [ 70 | 49 => "'1'", 71 | 50 => "'2'", 72 | ], 73 | "nonterminals" => [ 74 | "expr" 75 | ], 76 | "grams" => [ 77 | [ 78 | "action" => "", 79 | "empty" => false, 80 | "body" => [4, 5] 81 | ], 82 | [ 83 | "action" => " m2(0,1) = 1; ", 84 | "empty" => false, 85 | "body" => [5, 2], 86 | ], 87 | [ 88 | "action" => " m2(0,1) = 2; ", 89 | "empty" => false, 90 | "body" => [5, 3], 91 | ] 92 | ] 93 | ] 94 | ], 95 | 96 | [ 97 | << false, 113 | "nsymbols" => 9, 114 | "nterminals" => 7, 115 | "nnonterminals" => 1, 116 | "ngrams" => 5, 117 | ], [ 118 | "terminals" => [ 119 | 43 => "'+'", 120 | 45 => "'-'", 121 | 40 => "'('", 122 | 41 => "')'", 123 | 257 => "T_NUMBER", 124 | ], 125 | "nonterminals" => [ 126 | "expr" 127 | ], 128 | "grams" => [ 129 | [ 130 | "action" => "", 131 | "empty" => false, 132 | "body" => [7, 8] 133 | ], 134 | [ 135 | "action" => " m2(0,1) = m4(1,1); ", 136 | "empty" => false, 137 | "body" => [8, 6], 138 | ], 139 | [ 140 | "action" => " m2(0,3) = m4(1,3) + m4(3,3); ", 141 | "empty" => false, 142 | "body" => [8, 8, 2, 8], 143 | ], 144 | [ 145 | "action" => " m2(0,3) = m4(1,3) - m4(3,3); ", 146 | "empty" => false, 147 | "body" => [8, 8, 3, 8], 148 | ], 149 | [ 150 | "action" => " { m2(0,3) = (m4(2,3)); } ", 151 | "empty" => false, 152 | "body" => [8, 4, 8, 5], 153 | ] 154 | ] 155 | ] 156 | ] 157 | 158 | ]; 159 | } 160 | 161 | #[DataProvider("provideParserDebugCases")] 162 | public function testParserWithDebug(string $grammar, array $directProps, array $info) 163 | { 164 | $parser = new Parser(new Lexer(), new MacroSet()); 165 | $context = new Context('YY'); 166 | $context->macros = [ 167 | 1 => "m1(%n,%l,%t)", 168 | 2 => "m2(%n,%l)", 169 | 3 => "m3(%n,%l,%t)", 170 | 4 => "m4(%n,%l)", 171 | ]; 172 | $parser->parse($grammar, $context); 173 | foreach ($directProps as $prop => $expected) { 174 | $this->assertEquals($expected, $context->$prop, "context->$prop"); 175 | } 176 | $this->assertEquals($context->eofToken, $context->symbols[0], "eofToken: symbol[0]"); 177 | $this->assertEquals($context->errorToken, $context->symbols[1], "errorToken: symbol[1]"); 178 | $i = 2; 179 | foreach ($info['terminals'] as $value => $token) { 180 | $symbol = $context->symbols[$i]; 181 | $this->assertEquals($token, $symbol->name, "terminal: symbol[$i]->name"); 182 | $this->assertEquals($value, $symbol->value, "terminal: symbol[$i]->value"); 183 | $i++; 184 | } 185 | $this->assertEquals($context->startPrime, $context->symbols[$i], "startPrime: symbol[$i]"); 186 | $i++; 187 | foreach ($info['nonterminals'] as $token) { 188 | $symbol = $context->symbols[$i]; 189 | $this->assertEquals($token, $symbol->name, "nonterminal: symbol[$i]->name"); 190 | $i++; 191 | } 192 | foreach ($info['grams'] as $key => $expect) { 193 | $gram = $context->grams[$key]; 194 | $this->assertEquals($expect['action'], $gram->action, "gram[$key]->action"); 195 | $this->assertEquals($expect['empty'], $gram->isEmpty(), "gram[$key]->isEmpty()"); 196 | $this->assertEquals(count($expect['body']), count($gram->body), "count(gram[$key]->body)"); 197 | foreach ($expect['body'] as $k => $v) { 198 | $this->assertEquals($v, $gram->body[$k]->code, "gram[$key]->body[$k]"); 199 | } 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /lib/Yacc/Production.php: -------------------------------------------------------------------------------- 1 | action = $action; 31 | $this->position = $position; 32 | $this->body = []; 33 | } 34 | 35 | public function setAssociativityFlag(int $flag) 36 | { 37 | $this->associativity |= $flag; 38 | } 39 | 40 | public function isEmpty(): bool 41 | { 42 | return count($this->body) <= 1; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/Yacc/ProductionTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(0, $prod->associativity); 15 | $prod->setAssociativityFlag(1); 16 | $this->assertEquals(1, $prod->associativity); 17 | $prod->setAssociativityFlag(2); 18 | $this->assertEquals(3, $prod->associativity); 19 | } 20 | 21 | public function testIsEmpty() 22 | { 23 | $prod = new Production('', 1); 24 | $this->assertTrue($prod->isEmpty()); 25 | $prod->body[] = 1; 26 | $this->assertTrue($prod->isEmpty()); 27 | $prod->body[] = 1; 28 | $this->assertFalse($prod->isEmpty()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/Yacc/Token.php: -------------------------------------------------------------------------------- 1 | "NAME", 34 | self::NUMBER => "NUMBER", 35 | self::COLON => 'COLON', 36 | self::SPACE => 'SPACE', 37 | self::NEWLINE => 'NEWLINE', 38 | self::MARK => 'MARK', 39 | self::BEGININC => 'BEGININC', 40 | self::ENDINC => 'ENDINC', 41 | self::TOKEN => 'TOKEN', 42 | self::LEFT => 'LEFT', 43 | self::RIGHT => 'RIGHT', 44 | self::NONASSOC => 'NONASSOC', 45 | self::PRECTOK => 'PRECTOK', 46 | self::TYPE => 'TYPE', 47 | self::UNION => 'UNION', 48 | self::START => 'START', 49 | self::COMMENT => 'COMMENT', 50 | self::EXPECT => 'EXPECT', 51 | self::PURE_PARSER => 'PURE_PARSER', 52 | ]; 53 | 54 | public $t; 55 | public $v; 56 | public $ln; 57 | public $fn; 58 | public function __construct($token, string $value, int $lineNumber, string $filename) 59 | { 60 | if (!isset(self::TOKEN_MAP[$token]) && !is_string($token)) { 61 | throw new LexingException("Unknown token found: $token"); 62 | } 63 | $this->t = $token; 64 | $this->v = $value; 65 | $this->ln = $lineNumber; 66 | $this->fn = $filename; 67 | } 68 | 69 | public static function decode($tag): string 70 | { 71 | if (!isset(self::TOKEN_MAP[$tag])) { 72 | return "$tag"; 73 | } 74 | return "Token::" . self::TOKEN_MAP[$tag]; 75 | } 76 | 77 | public function __toString(): string 78 | { 79 | return "[{$this->fn}:{$this->ln}] " . self::decode($this->t) . " ({$this->v})"; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/Yacc/TokenTest.php: -------------------------------------------------------------------------------- 1 | assertEquals("[foo.php:42] Token::TOKEN (%token)", "$token"); 16 | } 17 | 18 | public function testToStringWithLiteralToken() 19 | { 20 | $token = new Token("'f'", "f", 42, "foo.php"); 21 | $this->assertEquals("[foo.php:42] 'f' (f)", "$token"); 22 | } 23 | 24 | public function testUnknownToken() 25 | { 26 | $this->expectException(LexingException::class); 27 | $this->expectExceptionMessage('Unknown token found: -2'); 28 | new Token(-2, '', 42, 'foo.php'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/functions.php: -------------------------------------------------------------------------------- 1 | = 48 && $n <= 55; 45 | } 46 | 47 | function is_gsym(Token $t): bool 48 | { 49 | return $t->t === Token::NAME || $t->t === "'"; 50 | } 51 | 52 | function character_value(string $string): int 53 | { 54 | $n = 0; 55 | $length = strlen($string); 56 | if ($length === 0) { 57 | return 0; 58 | } 59 | $c = $string[$n++]; 60 | if ($c !== '\\') { 61 | return ord($c); 62 | } 63 | $c = $string[$n++]; 64 | if (is_octal($c)) { 65 | $value = (int) $c; 66 | for ($i = 0; $n < $length && is_octal($string[$n]) && $i < 3; $i++) { 67 | $value = $value * 8 + $string[$n++]; 68 | } 69 | return $value; 70 | } 71 | switch ($c) { 72 | case 'n': return ord("\n"); 73 | case 't': return ord("\t"); 74 | case 'b': return ord("\x08"); 75 | case 'r': return ord("\r"); 76 | case 'f': return ord("\x0C"); 77 | case 'v': return ord("\x0B"); 78 | case 'a': return ord("\x07"); 79 | default: 80 | return ord($c); 81 | } 82 | } 83 | --------------------------------------------------------------------------------