Passed
Push — master ( 707ba4...19aa6f )
by Sebastian
02:43
created

getSourceCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * File containing the {@see Mailcode_Parser_Statement_Tokenizer} class.
4
 *
5
 * @package Mailcode
6
 * @subpackage Parser
7
 * @see Mailcode_Parser_Statement_Tokenizer
8
 */
9
10
declare(strict_types=1);
11
12
namespace Mailcode;
13
14
/**
15
 * Mailcode statement tokenizer: parses a mailcode statement
16
 * into its logical parts.
17
 *
18
 * @package Mailcode
19
 * @subpackage Parser
20
 * @author Sebastian Mordziol <[email protected]>
21
 */
22
class Mailcode_Parser_Statement_Tokenizer
23
{
24
    const ERROR_TOKENIZE_METHOD_MISSING = 49801;
25
    const ERROR_INVALID_TOKEN_CREATED = 49802;
26
    
27
    /**
28
     * @var string[]
29
     */
30
    protected $tokenCategories = array(
31
        'Variables',
32
        'NormalizeQuotes',
33
        'EscapedQuotes',
34
        'StringLiterals',
35
        'Keywords',
36
        'Numbers',
37
        'Operands',
38
        'ExtractTokens'
39
    );
40
    
41
   /**
42
    * @var Mailcode_Parser_Statement
43
    */
44
    protected $statement;
45
    
46
   /**
47
    * @var string
48
    */
49
    protected $tokenized;
50
    
51
    /**
52
     * @var Mailcode_Parser_Statement_Tokenizer_Token[]
53
     */
54
    protected $tokensOrdered = array();
55
    
56
   /**
57
    * @var string[]
58
    */
59
    protected static $ids = array();
60
61
    /**
62
     * @var callable[]
63
     */
64
    protected $changeHandlers = array();
65
66
    public function __construct(Mailcode_Parser_Statement $statement)
67
    {
68
        $this->statement = $statement;
69
70
        $this->tokenize($statement->getStatementString());
71
    }
72
73
    public function getSourceCommand() : ?Mailcode_Commands_Command
74
    {
75
        return $this->statement->getSourceCommand();
76
    }
77
78
   /**
79
    * Retrieves all tokens detected in the statement string, in 
80
    * the order they were found.
81
    * 
82
    * @return Mailcode_Parser_Statement_Tokenizer_Token[]
83
    */
84
    public function getTokens()
85
    {
86
        return $this->tokensOrdered;
87
    }
88
89
    public function hasTokens() : bool
90
    {
91
        return !empty($this->tokensOrdered);
92
    }
93
    
94
   /**
95
    * Whether there were any unknown tokens in the statement.
96
    * 
97
    * @return bool
98
    */
99
    public function hasUnknown() : bool
100
    {
101
        $unknown = $this->getUnknown();
102
        
103
        return !empty($unknown);
104
    }
105
    
106
   /**
107
    * Retrieves all unknown content tokens, if any.
108
    * 
109
    * @return Mailcode_Parser_Statement_Tokenizer_Token_Unknown[]
110
    */
111
    public function getUnknown()
112
    {
113
        $result = array();
114
        
115
        foreach($this->tokensOrdered as $token)
116
        {
117
            if($token instanceof Mailcode_Parser_Statement_Tokenizer_Token_Unknown)
118
            {
119
                $result[] = $token;
120
            }
121
        }
122
        
123
        return $result;
124
    }
125
    
126
    public function getFirstUnknown() : ?Mailcode_Parser_Statement_Tokenizer_Token_Unknown
127
    {
128
        $unknown = $this->getUnknown();
129
        
130
        if(!empty($unknown))
131
        {
132
            return array_shift($unknown);
133
        }
134
        
135
        return null;
136
    }
137
    
138
    public function getNormalized() : string
139
    {
140
        $parts = array();
141
        
142
        foreach($this->tokensOrdered as $token)
143
        {
144
            $string = $token->getNormalized();
145
            
146
            if($string != '')
147
            {
148
                $parts[] = $string;
149
            }
150
        }
151
        
152
        return implode(' ', $parts);
153
    }
154
155
    /**
156
     * Goes through all tokenization processors, in the order that
157
     * they are defined in the tokenCategories property. This filters
158
     * the statement string, and extracts the tokens contained within.
159
     *
160
     * @param string $statement
161
     * @throws Mailcode_Exception
162
     *
163
     * @see Mailcode_Parser_Statement_Tokenizer_Process
164
     */
165
    protected function tokenize(string $statement) : void
166
    {
167
        $statement = trim($statement);
168
        $tokens = array();
169
170
        foreach($this->tokenCategories as $tokenCategory)
171
        {
172
            $processor = $this->createProcessor($tokenCategory, $statement, $tokens);
173
            $processor->process();
174
175
            $statement = $processor->getStatement();
176
            $tokens = $processor->getTokens();
177
        }
178
179
        $this->tokenized = $statement;
180
        $this->tokensOrdered = $tokens;
181
    }
182
183
    /**
184
     * @param string $id
185
     * @param string $statement
186
     * @param Mailcode_Parser_Statement_Tokenizer_Token[] $tokens
187
     * @return Mailcode_Parser_Statement_Tokenizer_Process
188
     * @throws Mailcode_Exception
189
     */
190
    protected function createProcessor(string $id, string $statement, array $tokens) : Mailcode_Parser_Statement_Tokenizer_Process
191
    {
192
        $class = 'Mailcode\Mailcode_Parser_Statement_Tokenizer_Process_'.$id;
193
194
        $instance = new $class($this, $statement, $tokens);
195
196
        if($instance instanceof Mailcode_Parser_Statement_Tokenizer_Process)
197
        {
198
            return $instance;
199
        }
200
201
        throw new Mailcode_Exception(
202
            'Unknown statement token.',
203
            sprintf(
204
                'The tokenize class [%s] is not present.',
205
                $class
206
            ),
207
            self::ERROR_TOKENIZE_METHOD_MISSING
208
        );
209
    }
210
211
    /**
212
     * @param string $type
213
     * @param string $matchedText
214
     * @param mixed $subject
215
     * @return Mailcode_Parser_Statement_Tokenizer_Token
216
     */
217
    public function createToken(string $type, string $matchedText, $subject=null) : Mailcode_Parser_Statement_Tokenizer_Token
218
    {
219
        $tokenID = $this->generateID();
220
221
        $class = '\Mailcode\Mailcode_Parser_Statement_Tokenizer_Token_'.$type;
222
223
        return new $class($tokenID, $matchedText, $subject, $this->getSourceCommand());
224
    }
225
226
    public function appendKeyword(string $name) : Mailcode_Parser_Statement_Tokenizer_Token_Keyword
227
    {
228
        $name = rtrim($name, ':').':';
229
230
        $token = $this->appendToken('Keyword', $name);
231
232
        if($token instanceof Mailcode_Parser_Statement_Tokenizer_Token_Keyword)
233
        {
234
            return $token;
235
        }
236
237
        throw new Mailcode_Exception(
238
            'Invalid token created',
239
            '',
240
            self::ERROR_INVALID_TOKEN_CREATED
241
        );
242
    }
243
244
    public function removeToken(Mailcode_Parser_Statement_Tokenizer_Token $token) : Mailcode_Parser_Statement_Tokenizer
245
    {
246
        $keep = array();
247
        $tokenID = $token->getID();
248
249
        foreach ($this->tokensOrdered as $checkToken)
250
        {
251
            if($checkToken->getID() !== $tokenID)
252
            {
253
                $keep[] = $checkToken;
254
            }
255
        }
256
257
        $this->tokensOrdered = $keep;
258
259
        $this->triggerTokensChanged();
260
261
        return $this;
262
    }
263
264
    /**
265
     * @param string $type
266
     * @param string $matchedText
267
     * @param mixed $subject
268
     * @return Mailcode_Parser_Statement_Tokenizer_Token
269
     */
270
    protected function appendToken(string $type, string $matchedText, $subject=null) : Mailcode_Parser_Statement_Tokenizer_Token
271
    {
272
        $token = $this->createToken($type, $matchedText, $subject);
273
274
        $this->tokensOrdered[] = $token;
275
276
        $this->triggerTokensChanged();
277
278
        return $token;
279
    }
280
    
281
   /**
282
    * Generates a unique alphabet-based ID without numbers
283
    * to use as token name, to avoid conflicts with the
284
    * numbers detection.
285
    *
286
    * @return string
287
    */
288
    protected function generateID() : string
289
    {
290
        static $alphas;
291
292
        if(!isset($alphas))
293
        {
294
            $alphas = range('A', 'Z');
295
        }
296
297
        $amount = 12;
298
299
        $result = '';
300
301
        for($i=0; $i < $amount; $i++)
302
        {
303
            $result .= $alphas[array_rand($alphas)];
304
        }
305
306
        if(!in_array($result, self::$ids))
307
        {
308
            self::$ids[] = $result;
309
            return $result;
310
        }
311
312
        return $this->generateID();
313
    }
314
315
    /**
316
     * @param callable $callback
317
     */
318
    public function onTokensChanged($callback) : void
319
    {
320
        if(is_callable($callback))
321
        {
322
            $this->changeHandlers[] = $callback;
323
        }
324
    }
325
326
    protected function triggerTokensChanged() : void
327
    {
328
        foreach ($this->changeHandlers as $callback)
329
        {
330
            $callback($this);
331
        }
332
    }
333
}
334