blob: 3d81c14bbe042a783ba203a78138661be2bab4ba [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php namespace Sieve;
2
3include_once 'SieveTree.php';
4include_once 'SieveScanner.php';
5include_once 'SieveSemantics.php';
6include_once 'SieveException.php';
7
8class SieveParser
9{
10 protected $scanner_;
11 protected $script_;
12 protected $tree_;
13 protected $status_;
14
15 public function __construct($script = null)
16 {
17 if (isset($script))
18 $this->parse($script);
19 }
20
21 public function GetParseTree()
22 {
23 return $this->tree_;
24 }
25
26 public function dumpParseTree()
27 {
28 return $this->tree_->dump();
29 }
30
31 public function getScriptText()
32 {
33 return $this->tree_->getText();
34 }
35
36 protected function getPrevToken_($parent_id)
37 {
38 $childs = $this->tree_->getChilds($parent_id);
39
40 for ($i = count($childs); $i > 0; --$i)
41 {
42 $prev = $this->tree_->getNode($childs[$i-1]);
43 if ($prev->is(SieveToken::Comment|SieveToken::Whitespace))
44 continue;
45
46 // use command owning a block or list instead of previous
47 if ($prev->is(SieveToken::BlockStart|SieveToken::Comma|SieveToken::LeftParenthesis))
48 $prev = $this->tree_->getNode($parent_id);
49
50 return $prev;
51 }
52
53 return $this->tree_->getNode($parent_id);
54 }
55
56 /*******************************************************************************
57 * methods for recursive descent start below
58 */
59 public function passthroughWhitespaceComment($token)
60 {
61 return 0;
62 }
63
64 public function passthroughFunction($token)
65 {
66 $this->tree_->addChild($token);
67 }
68
69 public function parse($script)
70 {
71 $this->script_ = $script;
72
73 $this->scanner_ = new SieveScanner($this->script_);
74
75 // Define what happens with passthrough tokens like whitespacs and comments
76 $this->scanner_->setPassthroughFunc(
77 array(
78 $this, 'passthroughWhitespaceComment'
79 )
80 );
81
82 $this->tree_ = new SieveTree('tree');
83
84 $this->commands_($this->tree_->getRoot());
85
86 if (!$this->scanner_->nextTokenIs(SieveToken::ScriptEnd)) {
87 $token = $this->scanner_->nextToken();
88 throw new SieveException($token, SieveToken::ScriptEnd);
89 }
90 }
91
92 protected function commands_($parent_id)
93 {
94 while (true)
95 {
96 if (!$this->scanner_->nextTokenIs(SieveToken::Identifier))
97 break;
98
99 // Get and check a command token
100 $token = $this->scanner_->nextToken();
101 $semantics = new SieveSemantics($token, $this->getPrevToken_($parent_id));
102
103 // Process eventual arguments
104 $this_node = $this->tree_->addChildTo($parent_id, $token);
105 $this->arguments_($this_node, $semantics);
106
107 $token = $this->scanner_->nextToken();
108 if (!$token->is(SieveToken::Semicolon))
109 {
110 // TODO: check if/when semcheck is needed here
111 $semantics->validateToken($token);
112
113 if ($token->is(SieveToken::BlockStart))
114 {
115 $this->tree_->addChildTo($this_node, $token);
116 $this->block_($this_node, $semantics);
117 continue;
118 }
119
120 throw new SieveException($token, SieveToken::Semicolon);
121 }
122
123 $semantics->done($token);
124 $this->tree_->addChildTo($this_node, $token);
125 }
126 }
127
128 protected function arguments_($parent_id, &$semantics)
129 {
130 while (true)
131 {
132 if ($this->scanner_->nextTokenIs(SieveToken::Number|SieveToken::Tag))
133 {
134 // Check if semantics allow a number or tag
135 $token = $this->scanner_->nextToken();
136 $semantics->validateToken($token);
137 $this->tree_->addChildTo($parent_id, $token);
138 }
139 else if ($this->scanner_->nextTokenIs(SieveToken::StringList))
140 {
141 $this->stringlist_($parent_id, $semantics);
142 }
143 else
144 {
145 break;
146 }
147 }
148
149 if ($this->scanner_->nextTokenIs(SieveToken::TestList))
150 {
151 $this->testlist_($parent_id, $semantics);
152 }
153 }
154
155 protected function stringlist_($parent_id, &$semantics)
156 {
157 if (!$this->scanner_->nextTokenIs(SieveToken::LeftBracket))
158 {
159 $this->string_($parent_id, $semantics);
160 return;
161 }
162
163 $token = $this->scanner_->nextToken();
164 $semantics->startStringList($token);
165 $this->tree_->addChildTo($parent_id, $token);
166
167 if($this->scanner_->nextTokenIs(SieveToken::RightBracket)) {
168 //allow empty lists
169 $token = $this->scanner_->nextToken();
170 $this->tree_->addChildTo($parent_id, $token);
171 $semantics->endStringList();
172 return;
173 }
174
175 do
176 {
177 $this->string_($parent_id, $semantics);
178 $token = $this->scanner_->nextToken();
179
180 if (!$token->is(SieveToken::Comma|SieveToken::RightBracket))
181 throw new SieveException($token, array(SieveToken::Comma, SieveToken::RightBracket));
182
183 if ($token->is(SieveToken::Comma))
184 $semantics->continueStringList();
185
186 $this->tree_->addChildTo($parent_id, $token);
187 }
188 while (!$token->is(SieveToken::RightBracket));
189
190 $semantics->endStringList();
191 }
192
193 protected function string_($parent_id, &$semantics)
194 {
195 $token = $this->scanner_->nextToken();
196 $semantics->validateToken($token);
197 $this->tree_->addChildTo($parent_id, $token);
198 }
199
200 protected function testlist_($parent_id, &$semantics)
201 {
202 if (!$this->scanner_->nextTokenIs(SieveToken::LeftParenthesis))
203 {
204 $this->test_($parent_id, $semantics);
205 return;
206 }
207
208 $token = $this->scanner_->nextToken();
209 $semantics->validateToken($token);
210 $this->tree_->addChildTo($parent_id, $token);
211
212 do
213 {
214 $this->test_($parent_id, $semantics);
215
216 $token = $this->scanner_->nextToken();
217 if (!$token->is(SieveToken::Comma|SieveToken::RightParenthesis))
218 {
219 throw new SieveException($token, array(SieveToken::Comma, SieveToken::RightParenthesis));
220 }
221 $this->tree_->addChildTo($parent_id, $token);
222 }
223 while (!$token->is(SieveToken::RightParenthesis));
224 }
225
226 protected function test_($parent_id, &$semantics)
227 {
228 // Check if semantics allow an identifier
229 $token = $this->scanner_->nextToken();
230 $semantics->validateToken($token);
231
232 // Get semantics for this test command
233 $this_semantics = new SieveSemantics($token, $this->getPrevToken_($parent_id));
234 $this_node = $this->tree_->addChildTo($parent_id, $token);
235
236 // Consume eventual argument tokens
237 $this->arguments_($this_node, $this_semantics);
238
239 // Check that all required arguments were there
240 $token = $this->scanner_->peekNextToken();
241 $this_semantics->done($token);
242 }
243
244 protected function block_($parent_id, &$semantics)
245 {
246 $this->commands_($parent_id, $semantics);
247
248 $token = $this->scanner_->nextToken();
249 if (!$token->is(SieveToken::BlockEnd))
250 {
251 throw new SieveException($token, SieveToken::BlockEnd);
252 }
253 $this->tree_->addChildTo($parent_id, $token);
254 }
255}