<?php namespace Sieve; include_once 'SieveTree.php'; include_once 'SieveScanner.php'; include_once 'SieveSemantics.php'; include_once 'SieveException.php'; class SieveParser { protected $scanner_; protected $script_; protected $tree_; protected $status_; public function __construct($script = null) { if (isset($script)) $this->parse($script); } public function GetParseTree() { return $this->tree_; } public function dumpParseTree() { return $this->tree_->dump(); } public function getScriptText() { return $this->tree_->getText(); } protected function getPrevToken_($parent_id) { $childs = $this->tree_->getChilds($parent_id); for ($i = count($childs); $i > 0; --$i) { $prev = $this->tree_->getNode($childs[$i-1]); if ($prev->is(SieveToken::Comment|SieveToken::Whitespace)) continue; // use command owning a block or list instead of previous if ($prev->is(SieveToken::BlockStart|SieveToken::Comma|SieveToken::LeftParenthesis)) $prev = $this->tree_->getNode($parent_id); return $prev; } return $this->tree_->getNode($parent_id); } /******************************************************************************* * methods for recursive descent start below */ public function passthroughWhitespaceComment($token) { return 0; } public function passthroughFunction($token) { $this->tree_->addChild($token); } public function parse($script) { $this->script_ = $script; $this->scanner_ = new SieveScanner($this->script_); // Define what happens with passthrough tokens like whitespacs and comments $this->scanner_->setPassthroughFunc( array( $this, 'passthroughWhitespaceComment' ) ); $this->tree_ = new SieveTree('tree'); $this->commands_($this->tree_->getRoot()); if (!$this->scanner_->nextTokenIs(SieveToken::ScriptEnd)) { $token = $this->scanner_->nextToken(); throw new SieveException($token, SieveToken::ScriptEnd); } } protected function commands_($parent_id) { while (true) { if (!$this->scanner_->nextTokenIs(SieveToken::Identifier)) break; // Get and check a command token $token = $this->scanner_->nextToken(); $semantics = new SieveSemantics($token, $this->getPrevToken_($parent_id)); // Process eventual arguments $this_node = $this->tree_->addChildTo($parent_id, $token); $this->arguments_($this_node, $semantics); $token = $this->scanner_->nextToken(); if (!$token->is(SieveToken::Semicolon)) { // TODO: check if/when semcheck is needed here $semantics->validateToken($token); if ($token->is(SieveToken::BlockStart)) { $this->tree_->addChildTo($this_node, $token); $this->block_($this_node, $semantics); continue; } throw new SieveException($token, SieveToken::Semicolon); } $semantics->done($token); $this->tree_->addChildTo($this_node, $token); } } protected function arguments_($parent_id, &$semantics) { while (true) { if ($this->scanner_->nextTokenIs(SieveToken::Number|SieveToken::Tag)) { // Check if semantics allow a number or tag $token = $this->scanner_->nextToken(); $semantics->validateToken($token); $this->tree_->addChildTo($parent_id, $token); } else if ($this->scanner_->nextTokenIs(SieveToken::StringList)) { $this->stringlist_($parent_id, $semantics); } else { break; } } if ($this->scanner_->nextTokenIs(SieveToken::TestList)) { $this->testlist_($parent_id, $semantics); } } protected function stringlist_($parent_id, &$semantics) { if (!$this->scanner_->nextTokenIs(SieveToken::LeftBracket)) { $this->string_($parent_id, $semantics); return; } $token = $this->scanner_->nextToken(); $semantics->startStringList($token); $this->tree_->addChildTo($parent_id, $token); if($this->scanner_->nextTokenIs(SieveToken::RightBracket)) { //allow empty lists $token = $this->scanner_->nextToken(); $this->tree_->addChildTo($parent_id, $token); $semantics->endStringList(); return; } do { $this->string_($parent_id, $semantics); $token = $this->scanner_->nextToken(); if (!$token->is(SieveToken::Comma|SieveToken::RightBracket)) throw new SieveException($token, array(SieveToken::Comma, SieveToken::RightBracket)); if ($token->is(SieveToken::Comma)) $semantics->continueStringList(); $this->tree_->addChildTo($parent_id, $token); } while (!$token->is(SieveToken::RightBracket)); $semantics->endStringList(); } protected function string_($parent_id, &$semantics) { $token = $this->scanner_->nextToken(); $semantics->validateToken($token); $this->tree_->addChildTo($parent_id, $token); } protected function testlist_($parent_id, &$semantics) { if (!$this->scanner_->nextTokenIs(SieveToken::LeftParenthesis)) { $this->test_($parent_id, $semantics); return; } $token = $this->scanner_->nextToken(); $semantics->validateToken($token); $this->tree_->addChildTo($parent_id, $token); do { $this->test_($parent_id, $semantics); $token = $this->scanner_->nextToken(); if (!$token->is(SieveToken::Comma|SieveToken::RightParenthesis)) { throw new SieveException($token, array(SieveToken::Comma, SieveToken::RightParenthesis)); } $this->tree_->addChildTo($parent_id, $token); } while (!$token->is(SieveToken::RightParenthesis)); } protected function test_($parent_id, &$semantics) { // Check if semantics allow an identifier $token = $this->scanner_->nextToken(); $semantics->validateToken($token); // Get semantics for this test command $this_semantics = new SieveSemantics($token, $this->getPrevToken_($parent_id)); $this_node = $this->tree_->addChildTo($parent_id, $token); // Consume eventual argument tokens $this->arguments_($this_node, $this_semantics); // Check that all required arguments were there $token = $this->scanner_->peekNextToken(); $this_semantics->done($token); } protected function block_($parent_id, &$semantics) { $this->commands_($parent_id, $semantics); $token = $this->scanner_->nextToken(); if (!$token->is(SieveToken::BlockEnd)) { throw new SieveException($token, SieveToken::BlockEnd); } $this->tree_->addChildTo($parent_id, $token); } }