xref: /plugin/combo/ComboStrap/PageSqlTreeListener.php (revision 37748cd8654635afbeca80942126742f0f4cc346)
1<?php
2
3
4namespace ComboStrap;
5
6
7use Antlr\Antlr4\Runtime\ParserRuleContext;
8use Antlr\Antlr4\Runtime\Tree\ErrorNode;
9use Antlr\Antlr4\Runtime\Tree\ParseTreeListener;
10use Antlr\Antlr4\Runtime\Tree\ParseTreeWalker;
11use Antlr\Antlr4\Runtime\Tree\TerminalNode;
12use ComboStrap\PageSqlParser\PageSqlLexer;
13use ComboStrap\PageSqlParser\PageSqlParser;
14
15/**
16 * Class SqlTreeListener
17 * @package ComboStrap\LogicalSqlAntlr
18 *
19 * The listener that is called by {@link  ParseTreeWalker::walk()}
20 * that performs a walk on the given parse tree starting at the root
21 * and going down recursively with depth-first search.
22 *
23 * The process is to check all token and to process them
24 * with context
25 */
26final class PageSqlTreeListener implements ParseTreeListener
27{
28    /**
29     * @var PageSqlLexer
30     */
31    private $lexer;
32    /**
33     * @var PageSqlParser
34     */
35    private $parser;
36    /**
37     * @var String
38     */
39    private $physicalSql;
40    /**
41     * @var int
42     */
43    private $ruleState;
44
45    private const STATE_VALUES = [
46        PageSqlParser::RULE_columns,
47        PageSqlParser::RULE_tables,
48        PageSqlParser::RULE_predicates,
49        PageSqlParser::RULE_orderBys,
50        PageSqlParser::RULE_limit,
51    ];
52    /**
53     * @var string[]
54     */
55    private $parameters = [];
56    /**
57     * @var array
58     */
59    private $columns;
60
61    /**
62     * SqlTreeListener constructor.
63     *
64     * @param PageSqlLexer $lexer
65     * @param PageSqlParser $parser
66     */
67    public function __construct(PageSqlLexer $lexer, PageSqlParser $parser)
68    {
69        $this->lexer = $lexer;
70        $this->parser = $parser;
71    }
72
73
74    /**
75     * Leaf node
76     * @param TerminalNode $node
77     */
78    public function visitTerminal(TerminalNode $node): void
79    {
80
81        $type = $node->getSymbol()->getType();
82        $text = $node->getText();
83        switch ($type) {
84            case PageSqlParser::SELECT:
85                $this->physicalSql .= "select\n\t*\n";
86
87                /**
88                 * The from select is optional
89                 * Check if it's there
90                 */
91                $parent = $node->getParent();
92                for ($i = 0; $i < $parent->getChildCount(); $i++) {
93                    $child = $parent->getChild($i);
94                    if ($child instanceof ParserRuleContext) {
95                        /**
96                         * @var ParserRuleContext $child
97                         */
98                        if ($child->getRuleIndex() === PageSqlParser::RULE_tables) {
99                            return;
100                        }
101                    }
102                }
103                $this->physicalSql .= "from\n\tpages\n";
104                break;
105            case PageSqlParser::SqlName:
106                switch ($this->ruleState) {
107                    case PageSqlParser::RULE_predicates:
108
109                        // variable name
110                        $variableName = strtolower($text);
111                        $this->physicalSql .= "\t{$variableName} ";
112
113                        break;
114                    case
115                    PageSqlParser::RULE_orderBys:
116                        $text = strtolower($text);
117                        $this->physicalSql .= "\t{$text} ";
118                        break;
119                    case PageSqlParser::RULE_columns:
120                        $this->columns[] = $text;
121                        break;
122                }
123                break;
124            case PageSqlParser::EQUAL:
125            case PageSqlParser::LIKE:
126            case PageSqlParser::GLOB:
127            case PageSqlParser::LESS_THAN_OR_EQUAL:
128            case PageSqlParser::LESS_THAN:
129            case PageSqlParser::GREATER_THAN:
130            case PageSqlParser::GREATER_THAN_OR_EQUAL:
131                switch ($this->ruleState) {
132                    case PageSqlParser::RULE_predicates:
133                        $this->physicalSql .= "{$text} ";
134                }
135                break;
136            case PageSqlParser::StringLiteral:
137                switch ($this->ruleState) {
138                    case PageSqlParser::RULE_predicates:
139                        // Parameters
140                        if (
141                            ($text[0] === "'" and $text[strlen($text) - 1] === "'")
142                            ||
143                            ($text[0] === '"' and $text[strlen($text) - 1] === '"')) {
144                            $quote = $text[0];
145                            $text = substr($text, 1, strlen($text) - 2);
146                            $text = str_replace("$quote$quote", "$quote", $text);
147                        }
148                        $this->parameters[] = $text;
149                        $this->physicalSql .= "?";
150                        break;
151                }
152                break;
153            case PageSqlParser:: AND:
154            case PageSqlParser:: OR:
155                if ($this->ruleState === PageSqlParser::RULE_predicates) {
156                    $this->physicalSql .= " {$text}\n";
157                }
158                return;
159            case PageSqlParser:: NOT:
160                $this->physicalSql .= "{$text} ";
161                return;
162            case PageSqlParser:: DESC:
163            case PageSqlParser:: LPAREN:
164            case PageSqlParser:: RPAREN:
165            case PageSqlParser:: ASC:
166                $this->physicalSql .= "{$text}";
167                break;
168            case PageSqlParser:: COMMA:
169                switch ($this->ruleState) {
170                    case PageSqlParser::RULE_columns:
171                        return;
172                    case PageSqlParser::RULE_orderBys:
173                        $this->physicalSql .= "{$text}\n";
174                        return;
175                    default:
176                        $this->physicalSql .= "{$text}";
177                        return;
178                }
179            case PageSqlParser::LIMIT:
180                $this->physicalSql .= "{$text} ";
181                return;
182            case PageSqlParser::ESCAPE:
183                $this->physicalSql .= " {$text} ";
184                return;
185            case PageSqlParser::Number:
186                switch ($this->ruleState) {
187                    case PageSqlParser::RULE_limit:
188                        $this->physicalSql .= "{$text}";
189                        return;
190                    case PageSqlParser::RULE_predicates:
191                        $this->parameters[] = $text;
192                        $this->physicalSql .= "?";
193                        return;
194                    default:
195                        $this->physicalSql .= "{$text} ";
196                        return;
197                }
198            default:
199                // We do nothing because the token may have been printed at a higher level such as order by
200        }
201    }
202
203
204    public
205    function visitErrorNode(ErrorNode $node): void
206    {
207    }
208
209
210    /**
211     *
212     * Parent Node
213     *
214     * On each node, enterRule is called before recursively walking down into child nodes,
215     * then {@link PageSqlTreeListener::exitEveryRule()} is called after the recursive call to wind up.
216     * Parameters:
217     * @param ParserRuleContext $ctx
218     */
219    public
220    function enterEveryRule(ParserRuleContext $ctx): void
221    {
222
223        $ruleIndex = $ctx->getRuleIndex();
224        if (in_array($ruleIndex, self::STATE_VALUES)) {
225            $this->ruleState = $ruleIndex;
226        }
227        switch ($ruleIndex) {
228            case PageSqlParser::RULE_orderBys:
229                $this->physicalSql .= "order by\n";
230                break;
231            case PageSqlParser::RULE_tables:
232                $this->physicalSql .= "from\n";
233                break;
234            case PageSqlParser::RULE_predicates:
235                $this->physicalSql .= "where\n";
236                break;
237            case PageSqlParser::RULE_functionNames:
238                // Print the function name
239                $this->physicalSql .= $ctx->getText();
240                break;
241            case PageSqlParser::RULE_tableNames:
242                // Print the table name
243                $this->physicalSql .= "\t{$ctx->getText()}\n";
244                break;
245        }
246
247
248    }
249
250    /**
251     *
252     * Parent Node
253     *
254     * On each node, {@link PageSqlTreeListener::enterEveryRule()} is called before recursively walking down into child nodes,
255     * then {@link PageSqlTreeListener::exitEveryRule()} is called after the recursive call to wind up.
256     * @param ParserRuleContext $ctx
257     */
258    public
259    function exitEveryRule(ParserRuleContext $ctx): void
260    {
261        $ruleIndex = $ctx->getRuleIndex();
262        switch ($ruleIndex) {
263            case PageSqlParser::RULE_predicates:
264            case PageSqlParser::RULE_orderBys:
265                $this->physicalSql .= "\n";
266                break;
267        }
268
269    }
270
271    public
272    function getParameters(): array
273    {
274        return $this->parameters;
275    }
276
277    public
278    function getColumns(): array
279    {
280        return $this->columns;
281    }
282
283    /**
284     * For documentation
285     * @param ParserRuleContext $ctx
286     * @return string
287     */
288    private
289    function getRuleName(ParserRuleContext $ctx): string
290    {
291        $ruleNames = $this->parser->getRuleNames();
292        return $ruleNames[$ctx->getRuleIndex()];
293    }
294
295    /**
296     * For documentation
297     * @param TerminalNode $node
298     * @return string|null
299     */
300    private
301    function getTokenName(TerminalNode $node)
302    {
303        $token = $node->getSymbol();
304        return $this->lexer->getVocabulary()->getSymbolicName($token->getType());
305    }
306
307    public
308    function getPhysicalSql(): string
309    {
310        return $this->physicalSql;
311    }
312
313
314
315}
316