packages/ztd-query-core/src/Sql/LexicalDelimiters.php

1<?php
2
3declare(strict_types=1);
4
5namespace ZtdQuery\Sql;
6
7use ZtdQuery\Exception\InvalidDefinitionException;
8
9/**
10 * Refuses lexical data a scanner could not use.
11 *
12 * An empty delimiter matches at every position without consuming anything, so
13 * a scanner given one would never move on. Refusing it where the profile is
14 * built means the scanner can take every delimiter it is handed as something
15 * it can actually advance past.
16 */
17final class LexicalDelimiters
18{
19    /**
20     * @param LexicalPattern $patterns Reads a regular expression
21     */
22    public function __construct(private readonly LexicalPattern $patterns = new LexicalPattern())
23    {
24    }
25
26    /**
27     * Answers the delimiters, having refused any that is empty.
28     *
29     * @param list<string> $values Delimiters as the dialect declared them
30     *
31     * @return list<non-empty-string> The same delimiters
32     *
33     * @throws InvalidDefinitionException When one of them is empty
34     */
35    public function nonEmpty(array $values): array
36    {
37        foreach ($values as $value) {
38            if ($value === '') {
39                throw new InvalidDefinitionException('A lexical delimiter must not be empty.');
40            }
41        }
42
43        return $values;
44    }
45
46    /**
47     * Answers the opening-to-closing pairs, having refused any empty end.
48     *
49     * @param array<string, string> $pairs Opening delimiter => the one that closes it
50     * @param string $kind What the pairs delimit, for the refusal
51     *
52     * @return array<non-empty-string, non-empty-string> The same pairs
53     *
54     * @throws InvalidDefinitionException When either end of a pair is empty
55     */
56    public function pairs(array $pairs, string $kind): array
57    {
58        foreach ($pairs as $opening => $closing) {
59            if ($opening === '' || $closing === '') {
60                throw new InvalidDefinitionException($kind . ' delimiters must not be empty.');
61            }
62        }
63
64        return $pairs;
65    }
66
67    /**
68     * Answers the per-prefix lists, having refused any empty prefix or entry.
69     *
70     * @param array<string, list<string>> $parameters Parameter prefix => delimiters that may follow it
71     *
72     * @return array<non-empty-string, list<non-empty-string>> The same lists
73     *
74     * @throws InvalidDefinitionException When a prefix or one of its entries is empty
75     */
76    public function perPrefixLists(array $parameters): array
77    {
78        foreach ($parameters as $prefix => $values) {
79            if ($prefix === '') {
80                throw new InvalidDefinitionException('A parameter prefix must not be empty.');
81            }
82            $parameters[$prefix] = $this->nonEmpty($values);
83        }
84
85        return $parameters;
86    }
87
88    /**
89     * Answers the per-prefix patterns, having refused any preg cannot read.
90     *
91     * @param array<string, string> $patterns Parameter prefix => pattern for what may follow its name
92     *
93     * @return array<non-empty-string, non-empty-string> The same patterns
94     *
95     * @throws InvalidDefinitionException When a prefix is empty or a pattern is unreadable
96     */
97    public function perPrefixPatterns(array $patterns): array
98    {
99        foreach ($patterns as $prefix => $pattern) {
100            if ($prefix === '' || $pattern === '') {
101                throw new InvalidDefinitionException('Parameter suffix patterns and prefixes must not be empty.');
102            }
103            $this->patterns->assertValid($pattern);
104        }
105
106        return $patterns;
107    }
108
109    /**
110     * Answers the patterns, having refused any preg cannot read.
111     *
112     * @param list<string> $patterns Patterns as the dialect declared them
113     *
114     * @return list<non-empty-string> The same patterns
115     *
116     * @throws InvalidDefinitionException When one of them is empty or unreadable
117     */
118    public function validPatterns(array $patterns): array
119    {
120        $patterns = $this->nonEmpty($patterns);
121        foreach ($patterns as $pattern) {
122            $this->patterns->assertValid($pattern);
123        }
124
125        return $patterns;
126    }
127}
128