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