Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of Psy Shell.
5
 *
200 lars 6
 * (c) 2012-2023 Justin Hileman
148 lars 7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Psy\Command;
13
 
14
use PhpParser\Node;
15
use PhpParser\Parser;
16
use Psy\Context;
17
use Psy\ContextAware;
18
use Psy\Input\CodeArgument;
19
use Psy\ParserFactory;
20
use Psy\VarDumper\Presenter;
21
use Psy\VarDumper\PresenterAware;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Output\OutputInterface;
25
use Symfony\Component\VarDumper\Caster\Caster;
26
 
27
/**
28
 * Parse PHP code and show the abstract syntax tree.
29
 */
30
class ParseCommand extends Command implements ContextAware, PresenterAware
31
{
32
    /**
33
     * Context instance (for ContextAware interface).
34
     *
35
     * @var Context
36
     */
37
    protected $context;
38
 
39
    private $presenter;
40
    private $parserFactory;
41
    private $parsers;
42
 
43
    /**
44
     * {@inheritdoc}
45
     */
46
    public function __construct($name = null)
47
    {
48
        $this->parserFactory = new ParserFactory();
49
        $this->parsers = [];
50
 
51
        parent::__construct($name);
52
    }
53
 
54
    /**
55
     * ContextAware interface.
56
     *
57
     * @param Context $context
58
     */
59
    public function setContext(Context $context)
60
    {
61
        $this->context = $context;
62
    }
63
 
64
    /**
65
     * PresenterAware interface.
66
     *
67
     * @param Presenter $presenter
68
     */
69
    public function setPresenter(Presenter $presenter)
70
    {
71
        $this->presenter = clone $presenter;
72
        $this->presenter->addCasters([
73
            Node::class => function (Node $node, array $a) {
74
                $a = [
75
                    Caster::PREFIX_VIRTUAL.'type'       => $node->getType(),
76
                    Caster::PREFIX_VIRTUAL.'attributes' => $node->getAttributes(),
77
                ];
78
 
79
                foreach ($node->getSubNodeNames() as $name) {
80
                    $a[Caster::PREFIX_VIRTUAL.$name] = $node->$name;
81
                }
82
 
83
                return $a;
84
            },
85
        ]);
86
    }
87
 
88
    /**
89
     * {@inheritdoc}
90
     */
91
    protected function configure()
92
    {
93
        $kindMsg = 'One of PhpParser\\ParserFactory constants: '
94
            .\implode(', ', ParserFactory::getPossibleKinds())
95
            ." (default is based on current interpreter's version).";
96
 
97
        $this
98
            ->setName('parse')
99
            ->setDefinition([
100
            new CodeArgument('code', CodeArgument::REQUIRED, 'PHP code to parse.'),
101
            new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse.', 10),
102
            new InputOption('kind', '', InputOption::VALUE_REQUIRED, $kindMsg, $this->parserFactory->getDefaultKind()),
103
        ])
104
            ->setDescription('Parse PHP code and show the abstract syntax tree.')
105
            ->setHelp(
106
                <<<'HELP'
107
Parse PHP code and show the abstract syntax tree.
108
 
109
This command is used in the development of PsySH. Given a string of PHP code,
110
it pretty-prints the PHP Parser parse tree.
111
 
112
See https://github.com/nikic/PHP-Parser
113
 
114
It prolly won't be super useful for most of you, but it's here if you want to play.
115
HELP
116
            );
117
    }
118
 
119
    /**
120
     * {@inheritdoc}
121
     */
122
    protected function execute(InputInterface $input, OutputInterface $output)
123
    {
124
        $code = $input->getArgument('code');
125
        if (\strpos($code, '<?') === false) {
126
            $code = '<?php '.$code;
127
        }
128
 
129
        $parserKind = $input->getOption('kind');
130
        $depth = $input->getOption('depth');
131
        $nodes = $this->parse($this->getParser($parserKind), $code);
132
        $output->page($this->presenter->present($nodes, $depth));
133
 
134
        $this->context->setReturnValue($nodes);
135
 
136
        return 0;
137
    }
138
 
139
    /**
140
     * Lex and parse a string of code into statements.
141
     *
142
     * @param Parser $parser
143
     * @param string $code
144
     *
145
     * @return array Statements
146
     */
147
    private function parse(Parser $parser, string $code): array
148
    {
149
        try {
150
            return $parser->parse($code);
151
        } catch (\PhpParser\Error $e) {
152
            if (\strpos($e->getMessage(), 'unexpected EOF') === false) {
153
                throw $e;
154
            }
155
 
156
            // If we got an unexpected EOF, let's try it again with a semicolon.
157
            return $parser->parse($code.';');
158
        }
159
    }
160
 
161
    /**
162
     * Get (or create) the Parser instance.
163
     *
164
     * @param string|null $kind One of Psy\ParserFactory constants (only for PHP parser 2.0 and above)
165
     */
166
    private function getParser(string $kind = null): Parser
167
    {
168
        if (!\array_key_exists($kind, $this->parsers)) {
169
            $this->parsers[$kind] = $this->parserFactory->createParser($kind);
170
        }
171
 
172
        return $this->parsers[$kind];
173
    }
174
}