JsonGrammar.php

<?php

namespace Tlf\Lexer;

/**
 *
 * This is an incomplete grammar, mainly used for testing & building v0.5 of the lexer. It gets "quoted values" and nested arrays.
 *
 */
class JsonGrammar extends Grammar {

    public function getNamespace(){return 'json';}
    public function getAstClass():string{
        return \Tlf\Lexer\JsonAst::class;
    }

    public function onLexerStart($lexer,$file,$token){
        // $lexer->stackDirectiveList('phpgrammar:html', 'phpgrammar:php_open');
        // $lexer->setDirective([$this->getDirective('html')]);
        $directiveList = $this->getDirectives(':file');
        foreach ($directiveList as $d){
            $lexer->addDirective($d);
        }

    }

    public function appendValueToAst($lexer, $ast, $token){
        $ast->add('value', $token->buffer());
    }

    public $directives = [

        'file'=>[
            // it automatically starts
            // and immediately rewinds one
            // so each of its 'then's can see the first character
            'onStart'=>[
                'rewind'=>1,
                'then'=>[
                    ':object',
                    ':array',
                    ':whitespace',
                ],
            ],
        ],
        // @todo I need to apply php_close to almost everything...
    
        'object' => [
            'start'=>'{',
            'onStart'=>[
                'buffer.clear'=>true,
                'ast.new'=>[
                    '_setto'=>'root',
                    'type'=>'object',
                ],
            ],
            'stop'=>'}',
            'onStop'=>
            [
                'pop_head'=>true,
                'pop_directive'=>true,
                'buffer.clear'=>true,
            ],

            'then'=>[
                ':key'=>[],
                ':whitespace'=>[],
            ]
        ],

        'array' => [ 
            'start'=>'[',
            'onStart'=>[
                'buffer.clear'=>true,
                //ast.head =>[ // to say new & set as head?
                'ast.new'=>[
                    '_setto'=> 'root',
                    //automatically sets as head?
                    'type'=>"array",
                ],
                'then'=>[
                    //':array.stop', // it would add a duplicate?
                    ':array'=>[
                        'onStart'=>[
                            'buffer.clear'=>true,
                            //ast.head =>[ // to say new & set as head?
                            'ast.new'=>[
                                '_addto'=>'value',
                                //automatically sets as head?
                                'type'=>"array",
                            ],
                        ],
                        'onStop'=>[
                            'pop_head'=>true,
                            'buffer.clear'=>true,
                            'pop_directive'=>false,
                            'then'=>[
                                ':whitespace',
                                ':comma',
                                ':array.stop'=>[
                                    'onStart'=>[
                                        'pop_directive'=>true,
                                        'pop_head'=>false,
                                        // 'rewind'=>1,
                                        // 'badbubble'=>true,
                                    ]
                                ],
                            ]
                        ],
                    ],
                    ':whitespace',
                    ':value'=>[
                        'onStop'=>[
                            'rewind'=>1,
                            'this:appendValueToAst',
                            'forward'=>1,
                            'buffer.clear'=>true,
                            // 'pop_directive'=>true,

                            'then'=>[
                                ':whitespace',
                                ':comma'=>[
                                    // 'onStart'=>[
                                        // 'then'=>[
                                            // ''
                                        // ],
                                    // ]
                                ],
                                ':array.stop'=>[
                                    'onStart'=>[
                                        'pop_head'=>false,
                                        'pop_directive'=>true,
                                        'rewind'=>1,
                                        // 'bubble'=>true,
                                    ]
                                ],
                            ],

                        ],
                    ],
                    ':array.stop'=>[
                        'onStart'=>[
                            'pop_head'=>false,
                            'pop_directive'=>true,
                            'rewind'=>1,
                            // 'bubble'=>true,
                        ]
                    ],
                ],
            ],
            'stop'=>']',
            'onStop'=>[
                'pop_head'=>true,
                'pop_directive'=>true,
                'buffer.clear'=>true,
            ],

        ],


        'key'=>[
            // I think it takes other chars too... need to look up
            // 'start' => '/(\'|\")/',
            'start' => ["'", '"'],
                'onStart'=>[
                    'buffer.clear',
                ],
            'match'=>'/^$1([a-zA-Z0-9_\-]+)/',
            'stop'=>'/$1$/',
            'onStop'=>[
                // 'call'=> 'keyFound',
                //this could set the whole match stored to previous
                //So, like... whatever onStop would have had access to gets stored as previous('key')???
                'previous'=>'key',
                'pop_directive'=>true,
            ],

            'then'=>[
                ':whitespace',
                ':colon'=>[
                ],
            ]
        ],

        'value'=>[
            'is'=>[
                // ':bool_value',
                // ':numeric_value',
                ':str_value',
            ],
        ],

        // 'bool_value'=>[

        // ],
        // 'numeric_value'=>[

        // ],
        'str_value'=>[
            'start' => [
                    "'",
                    '"',
                ],
            'onStart'=>[
                'buffer.clear',
            ],
            // 'match'=>'/((?<!\\\\)[^$1])+/',
            // fill with the 1st match
            'match'=>[['/((?<!\\\\)[^' ,1, '])+/']],
            // fill with the 1st match
            'stop'=>[['/[^\\\\]',1,'$/']],
            'onStop'=>[
                // this will have to either save it to the object with the previous key
                // or append it to an array
                // I suppose previous(key) will be null if we're inside an array
                // 'call'=> 'store_value',
                'buffer.clear'=>true,
                // 'pop_directive'=>true,

                //because its only after the stop
                'then'=>[
                    ':comma'=>[
                    ],
                    ':whitespace',
                ]
            ],
        ],

        'comma'=>[
            'start'=>',',
            'onStart'=>[
                'buffer.clear'=>true,
                // 'stop'=>true,
                //pop this directive stack?
                'pop_directive'=>true,
            ],
        ],

        'colon'=>[
            'match'=> ':',
            'then'=> [
                ':whitespace'=> [

                ],
                ':value'=>[
                    'then'=>[
                        ':comma'=>[
                            'then'=>[
                                ':key',
                                ':object.stop'=>[
                                    'bubble'=>true,
                                ],
                            ]
                        ],

                    ],
                ],
            ],
        ],
        'whitespace'=>[
            // serves as start.
            // Since there's no 'stop', not matching will trigger onStop
            // is i dotall?
            'match'=>'/^\s+$/i',
            'onStop'=>[
                'buffer.clear'=>true,
            ],
        ],
    ];

}