blob: 3d81c14bbe042a783ba203a78138661be2bab4ba [file] [log] [blame]
<?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);
}
}