index da6f6998b5eb57b4ec00ef393114e0adcfd71885..1c00051c052bb1f477b3eead6c19f9433e7ce445 100644 (file)
<?php
<?php
-$requires_ = array();
-
class Semantics
{
class Semantics
{
+ var $registerExtensionFn_;
+ var $isExtensionRegisteredFn_;
+
var $command_;
var $comparator_;
var $matchType_;
var $s_;
var $unknown;
var $message;
var $command_;
var $comparator_;
var $matchType_;
var $s_;
var $unknown;
var $message;
+ var $nonTestCommands_ = '(require|if|elsif|else|reject|fileinto|redirect|stop|keep|discard|mark|unmark|setflag|addflag|removeflag)';
+ var $testsValidAfter_ = '(if|elsif|anyof|allof|not)';
var $testCommands_ = '(address|envelope|header|size|allof|anyof|exists|not|true|false)';
var $testCommands_ = '(address|envelope|header|size|allof|anyof|exists|not|true|false)';
- var $requireStrings_ = '(envelope|fileinto|reject|vacation|relational|subaddress)';
+ var $requireStrings_ = '(envelope|fileinto|reject|vacation|relational|subaddress|regex|imapflags|copy)';
function Semantics($command)
{
$this->command_ = $command;
function Semantics($command)
{
$this->command_ = $command;
-
$this->unknown = false;
switch ($command)
{
$this->unknown = false;
switch ($command)
{
case 'require':
/* require <capabilities: string-list> */
$this->s_ = array(
case 'require':
/* require <capabilities: string-list> */
$this->s_ = array(
- 'valid_after' => array('script-start', 'require'),
+ 'valid_after' => '(script-start|require)',
'arguments' => array(
array('class' => 'string', 'list' => true, 'name' => 'require-string', 'occurrences' => '1', 'call' => 'setRequire_', 'values' => array(
array('occurrences' => '+', 'regex' => '"'. $this->requireStrings_ .'"'),
'arguments' => array(
array('class' => 'string', 'list' => true, 'name' => 'require-string', 'occurrences' => '1', 'call' => 'setRequire_', 'values' => array(
array('occurrences' => '+', 'regex' => '"'. $this->requireStrings_ .'"'),
case 'if':
/* if <test> <block> */
$this->s_ = array(
case 'if':
/* if <test> <block> */
$this->s_ = array(
- 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else',
- 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'valid_after' => str_replace('(', '(script-start|', $this->nonTestCommands_),
'arguments' => array(
array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
'arguments' => array(
array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
case 'elsif':
/* elsif <test> <block> */
$this->s_ = array(
case 'elsif':
/* elsif <test> <block> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif'),
+ 'valid_after' => '(if|elsif)',
'arguments' => array(
array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
'arguments' => array(
array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
case 'else':
/* else <block> */
$this->s_ = array(
case 'else':
/* else <block> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif'),
+ 'valid_after' => '(if|elsif)',
'arguments' => array(
array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
'arguments' => array(
array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
/*******************
* action commands
*/
/*******************
* action commands
*/
+ case 'discard':
case 'keep':
case 'stop':
case 'keep':
case 'stop':
- case 'discard':
- /* keep / stop / discard */
+ /* discard / keep / stop */
$this->s_ = array(
$this->s_ = array(
- 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else',
- 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard')
+ 'valid_after' => str_replace('(', '(script-start|', $this->nonTestCommands_)
);
break;
case 'fileinto':
);
break;
case 'fileinto':
- /* fileinto <folder: string> */
+ /* fileinto [":copy"] <folder: string> */
$this->s_ = array(
'requires' => 'fileinto',
$this->s_ = array(
'requires' => 'fileinto',
- 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'valid_after' => $this->nonTestCommands_,
'arguments' => array(
'arguments' => array(
+ array('class' => 'tag', 'occurrences' => '?', 'values' => array(
+ array('occurrences' => '?', 'regex' => ':copy', 'requires' => 'copy', 'name' => 'copy')
+ )),
array('class' => 'string', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '".*"', 'name' => 'folder')
))
array('class' => 'string', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '".*"', 'name' => 'folder')
))
);
break;
);
break;
+ case 'mark':
+ case 'unmark':
+ /* mark / unmark */
+ $this->s_ = array(
+ 'requires' => 'imapflags',
+ 'valid_after' => $this->nonTestCommands_
+ );
+ break;
+
case 'redirect':
case 'redirect':
- /* redirect <address: string> */
+ /* redirect [":copy"] <address: string> */
$this->s_ = array(
$this->s_ = array(
- 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'valid_after' => str_replace('(', '(script-start|', $this->nonTestCommands_),
'arguments' => array(
'arguments' => array(
+ array('class' => 'tag', 'occurrences' => '?', 'values' => array(
+ array('occurrences' => '?', 'regex' => ':copy', 'requires' => 'copy', 'name' => 'size-type')
+ )),
array('class' => 'string', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '".*"', 'name' => 'address')
))
array('class' => 'string', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '".*"', 'name' => 'address')
))
/* reject <reason: string> */
$this->s_ = array(
'requires' => 'reject',
/* reject <reason: string> */
$this->s_ = array(
'requires' => 'reject',
- 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'valid_after' => $this->nonTestCommands_,
'arguments' => array(
array('class' => 'string', 'occurrences' => '1', 'values' => array(
'arguments' => array(
array('class' => 'string', 'occurrences' => '1', 'values' => array(
- array('occurrences' => '1', 'regex' => '.*', 'name' => 'reason')
+ array('occurrences' => '1', 'regex' => '("|).*("|)', 'name' => 'reason')
+ ))
+ )
+ );
+ break;
+
+ case 'setflag':
+ case 'addflag':
+ case 'removeflag':
+ /* setflag <flag-list: string-list> */
+ /* addflag <flag-list: string-list> */
+ /* removeflag <flag-list: string-list> */
+ $this->s_ = array(
+ 'requires' => 'imapflags',
+ 'valid_after' =>$this->nonTestCommands_,
+ 'arguments' => array(
+ array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
))
)
);
))
)
);
/* vacation [":days" number] [":addresses" string-list] [":subject" string] [":mime"] <reason: string> */
$this->s_ = array(
'requires' => 'vacation',
/* vacation [":days" number] [":addresses" string-list] [":subject" string] [":mime"] <reason: string> */
$this->s_ = array(
'requires' => 'vacation',
- 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'valid_after' => $this->nonTestCommands_,
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'values' => array(
array('occurrences' => '?', 'regex' => ':days', 'name' => 'days',
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'values' => array(
array('occurrences' => '?', 'regex' => ':days', 'name' => 'days',
case 'address':
/* address [address-part: tag] [comparator: tag] [match-type: tag] <header-list: string-list> <key-list: string-list> */
$this->s_ = array(
case 'address':
/* address [address-part: tag] [comparator: tag] [match-type: tag] <header-list: string-list> <key-list: string-list> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
- array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
+ array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value|regex)', 'call' => 'setMatchType_', 'name' => 'match-type'),
array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
'add' => array(
array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
'add' => array(
/* allof <tests: test-list>
anyof <tests: test-list> */
$this->s_ = array(
/* allof <tests: test-list>
anyof <tests: test-list> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'left-parant', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '\(', 'name' => 'test-list')
'arguments' => array(
array('class' => 'left-parant', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '\(', 'name' => 'test-list')
/* envelope [address-part: tag] [comparator: tag] [match-type: tag] <envelope-part: string-list> <key-list: string-list> */
$this->s_ = array(
'requires' => 'envelope',
/* envelope [address-part: tag] [comparator: tag] [match-type: tag] <envelope-part: string-list> <key-list: string-list> */
$this->s_ = array(
'requires' => 'envelope',
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
- array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
+ array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value|regex)', 'call' => 'setMatchType_', 'name' => 'match-type'),
array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
'add' => array(
array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
'add' => array(
case 'exists':
/* exists <header-names: string-list> */
$this->s_ = array(
case 'exists':
/* exists <header-names: string-list> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
'arguments' => array(
array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
case 'header':
/* header [comparator: tag] [match-type: tag] <header-names: string-list> <key-list: string-list> */
$this->s_ = array(
case 'header':
/* header [comparator: tag] [match-type: tag] <header-names: string-list> <key-list: string-list> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
- array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
+ array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value|regex)', 'call' => 'setMatchType_', 'name' => 'match-type'),
array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
'add' => array(
array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
'add' => array(
array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
case 'not':
/* not <test> */
$this->s_ = array(
case 'not':
/* not <test> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
'arguments' => array(
array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
case 'size':
/* size <":over" / ":under"> <limit: number> */
$this->s_ = array(
case 'size':
/* size <":over" / ":under"> <limit: number> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'tag', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => ':(over|under)', 'name' => 'size-type')
'arguments' => array(
array('class' => 'tag', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => ':(over|under)', 'name' => 'size-type')
case 'false':
/* true / false */
$this->s_ = array(
case 'false':
/* true / false */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not')
+ 'valid_after' => $this->testsValidAfter_
);
break;
);
break;
}
}
}
}
- function setRequire_($text)
+ function setExtensionFuncs($setFn, $checkFn)
{
{
- global $requires_;
-
- if(!is_array($requires_)){
- $requires_ = array();
+ if (is_callable($setFn) && is_callable($checkFn))
+ {
+ $this->registerExtensionFn_ = $setFn;
+ $this->isExtensionRegisteredFn_ = $checkFn;
}
}
+ }
- array_push($requires_, $text);
+ function setRequire_($extension)
+ {
+ call_user_func($this->registerExtensionFn_, $extension);
return true;
}
return true;
}
+ function wasRequired_($extension)
+ {
+ return call_user_func($this->isExtensionRegisteredFn_, $extension);
+ }
+
function setMatchType_($text)
{
// Do special processing for relational test extension
if ($text == ':count' || $text == ':value')
{
function setMatchType_($text)
{
// Do special processing for relational test extension
if ($text == ':count' || $text == ':value')
{
- global $requires_;
- if (!in_array('"relational"', $requires_))
+ if (!$this->wasRequired_('relational'))
{
$this->message = 'missing require for match-type '. $text;
return false;
{
$this->message = 'missing require for match-type '. $text;
return false;
))
);
}
))
);
}
+ // Do special processing for regex match-type extension
+ else if ($text == ':regex' && !$this->wasRequired_('regex'))
+ {
+ $this->message = 'missing require for match-type '. $text;
+ return false;
+ }
$this->matchType_ = $text;
return true;
}
$this->matchType_ = $text;
return true;
}
{
if ($text == ':user' || $text == ':detail')
{
{
if ($text == ':user' || $text == ':detail')
{
- global $requires_;
- if (!in_array('"subaddress"', $requires_))
+ if (!$this->wasRequired_('subaddress'))
{
$this->message = 'missing require for tag '. $text;
return false;
{
$this->message = 'missing require for tag '. $text;
return false;
return true;
}
return true;
}
- function validAfter($prev)
+ function validCommand($prev, $line)
{
{
- return in_array($prev, $this->s_['valid_after']);
+ // Check if command is known
+ if ($this->unknown)
+ {
+ $this->message = 'line '. $line .': unknown command "'. $this->command_ .'"';
+ return false;
+ }
+
+ // Check if the command needs to be required
+ if (isset($this->s_['requires']) && !$this->wasRequired_($this->s_['requires']))
+ {
+ $this->message = 'line '. $line .': missing require for command "'. $this->command_ .'"';
+ return false;
+ }
+
+ // Check if command may appear here
+ if (!ereg($this->s_['valid_after'], $prev))
+ {
+# $this->message = 'line '. $line .': "'. $this->command_ .'" may not appear after "'. $prev .'"';
+# return false;
+ }
+
+ return true;
}
function validClass_($class, $id)
}
function validClass_($class, $id)
function validToken($class, &$text, &$line)
{
function validToken($class, &$text, &$line)
{
- global $requires_;
-
- if(!is_array($requires_)){
- $requires_ = array();
- }
-
$name = $class . ($class != $text ? " $text" : '');
$name = $class . ($class != $text ? " $text" : '');
- // Check if the command needs to be required
- // TODO: move this to somewhere more appropriate
- if (isset($this->s_['requires']) &&
- !in_array('"'.$this->s_['requires'].'"', $requires_))
- {
- $this->message = 'line '. $line .': missing require for '. $this->command_;
- return false;
- }
-
// Make sure the argument has a valid class
if (!$this->validClass_($class, $name))
{
// Make sure the argument has a valid class
if (!$this->validClass_($class, $name))
{
$arg = &$this->s_['arguments'][0];
foreach ($arg['values'] as $val)
{
$arg = &$this->s_['arguments'][0];
foreach ($arg['values'] as $val)
{
-
if (preg_match('/^'. $val['regex'] .'$/m', $text))
{
// Check if the argument value needs a 'require'
if (preg_match('/^'. $val['regex'] .'$/m', $text))
{
// Check if the argument value needs a 'require'
- if (isset($val['requires']) &&
- !in_array('"'.$val['requires'].'"', $requires_))
+ if (isset($val['requires']) && !$this->wasRequired_($val['requires']))
{
$this->message = 'line '. $line .': missing require for '. $val['name'] .' '. $text;
return false;
{
$this->message = 'line '. $line .': missing require for '. $val['name'] .' '. $text;
return false;