Passed
Push — master ( 62880d...2f714f )
by Sebastian
07:50
created

Mailcode_Parser_Statement::isValid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
/**
3
 * File containing the {@see Mailcode_Parser_Statement} class.
4
 *
5
 * @package Mailcode
6
 * @subpackage Parser
7
 * @see Mailcode_Parser_Statement
8
 */
9
10
declare(strict_types=1);
11
12
namespace Mailcode;
13
14
use AppUtils\OperationResult;
15
16
/**
17
 * Mailcode statement parser: parses arbitrary statements
18
 * to check for validation issues.
19
 *
20
 * @package Mailcode
21
 * @subpackage Parser
22
 * @author Sebastian Mordziol <[email protected]>
23
 */
24
class Mailcode_Parser_Statement
25
{
26
    const ERROR_TOKENIZE_METHOD_MISSING = 48901;
27
    
28
    const VALIDATION_EMPTY = 48801;
29
    
30
    const VALIDATION_UNQUOTED_STRING_LITERALS = 48802;
31
    
32
   /**
33
    * @var string
34
    */
35
    protected $statement;
36
    
37
   /**
38
    * @var OperationResult
39
    */
40
    protected $result;
41
    
42
    protected $operands = array(
43
        '==',
44
        '<=',
45
        '>=',
46
        '!=',
47
        '=',
48
        '+',
49
        '-',
50
        '/',
51
        '*'
52
    );
53
    
54
    public function __construct(string $statement)
55
    {
56
        $this->statement = $statement;
57
    }
58
    
59
    public function isValid() : bool
60
    {
61
        return $this->getValidationResult()->isValid();
62
    }
63
    
64
    public function getValidationResult() : OperationResult
65
    {
66
        if(isset($this->result))
67
        {
68
            return $this->result;
69
        }
70
        
71
        $this->result = new OperationResult($this);
72
        
73
        $this->validate();
74
        
75
        return $this->result;
76
    }
77
    
78
    protected function validate() : void
79
    {
80
        $statement = trim($this->statement);
81
        
82
        if(empty($statement))
83
        {
84
            $this->result->makeError(
85
                t('Empty statement'),
86
                self::VALIDATION_EMPTY
87
            );
88
            
89
            return;
90
        }
91
        
92
        $tokenized = $this->tokenize($statement);        
93
        
94
        $leftover = $this->removeTokens($tokenized);
95
        $leftover = str_replace(' ', '', $leftover);
96
         
97
        if(!empty($leftover))
98
        {
99
            $this->result->makeError(
100
               t('Unquoted string literals found:').' "'.$leftover.'"',
101
                self::VALIDATION_UNQUOTED_STRING_LITERALS
102
            );
103
        }
104
        
105
        /*
106
        echo PHP_EOL;
107
        print_r(array(
108
            'statement' => $this->statement,
109
            'tokenized' => $tokenized,
110
            'leftover' => $leftover
111
        ));
112
        echo PHP_EOL;
113
        */
114
    }
115
    
116
    protected function removeTokens(string $statement) : string
117
    {
118
        $matches = array();
119
        preg_match_all('/'.sprintf($this->token, '[A-Z0-9_]+').'/sx', $statement, $matches, PREG_PATTERN_ORDER);
120
        
121
        foreach($matches[0] as $match)
122
        {
123
            $statement = str_replace($match, '', $statement);
124
        }
125
        
126
        return $statement;
127
    }
128
    
129
    protected $token = '__TOKEN_%s__';
130
    
131
    protected $tokens = array(
132
        'variables',
133
        'escaped_quotes',
134
        'operands',
135
        'string_literals',
136
        'numbers'
137
    );
138
    
139
    protected function tokenize(string $statement) : string
140
    {
141
        $tokenized = trim($statement);
142
        
143
        foreach($this->tokens as $token)
144
        {
145
            $method = 'tokenize_'.$token;
146
            
147
            if(!method_exists($this, $method))
148
            {
149
                throw new Mailcode_Exception(
150
                    'Unknown statement token.',    
151
                    sprintf(
152
                        'The tokenize method [%s] is not present in class [%s].',
153
                        $method,
154
                        get_class($this)
155
                    ),
156
                    self::ERROR_TOKENIZE_METHOD_MISSING
157
                );
158
            }
159
            
160
            $tokenized = $this->$method($tokenized);
161
        }
162
        
163
        return $tokenized;
164
    }
165
    
166
    protected function tokenize_escaped_quotes(string $tokenized) : string
167
    {
168
        return str_replace('\"', '__QUOTE__', $tokenized);
169
    }
170
171
    protected function tokenize_variables(string $tokenized) : string
172
    {
173
        $vars = Mailcode::create()->findVariables($tokenized)->getGroupedByHash();
174
        
175
        foreach($vars as $var)
176
        {
177
            $tokenized = str_replace($var->getMatchedText(), $this->getToken('VARIABLE'), $tokenized);
178
        }
179
        
180
        return $tokenized;
181
    }
182
    
183
    protected function tokenize_operands(string $tokenized) : string
184
    {
185
        foreach($this->operands as $operand)
186
        {
187
            $tokenized = str_replace($operand, $this->getToken('OPERAND'), $tokenized);
188
        }
189
        
190
        return $tokenized;
191
    }
192
    
193
    protected function tokenize_string_literals(string $tokenized) : string
194
    {
195
        $matches = array();
196
        preg_match_all('/"(.*)"/sx', $tokenized, $matches, PREG_PATTERN_ORDER);
197
        
198
        foreach($matches[0] as $match)
199
        {
200
            $tokenized = str_replace($match, $this->getToken('STRING_LITERAL'), $tokenized);
201
        }
202
        
203
        return $tokenized;
204
    }
205
    
206
    protected function tokenize_numbers(string $tokenized) : string
207
    {
208
        $matches = array();
209
        preg_match_all('/[0-9]+\s*[.,]\s*[0-9]+|[0-9]+/sx', $tokenized, $matches, PREG_PATTERN_ORDER);
210
        
211
        foreach($matches[0] as $match)
212
        {
213
            $tokenized = str_replace($match, $this->getToken('NUMBER'), $tokenized);
214
        }
215
        
216
        return $tokenized;
217
    }
218
    
219
    protected function getToken(string $name) : string
220
    {
221
        return sprintf($this->token, $name);
222
    }
223
}
224