Code

Updated sieve classes.
authorhickert <hickert@594d385d-05f5-0310-b6e9-bd551577e9d8>
Thu, 22 Mar 2007 10:33:14 +0000 (10:33 +0000)
committerhickert <hickert@594d385d-05f5-0310-b6e9-bd551577e9d8>
Thu, 22 Mar 2007 10:33:14 +0000 (10:33 +0000)
The author has updated his sieve classes.

git-svn-id: https://oss.gonicus.de/repositories/gosa/trunk@5859 594d385d-05f5-0310-b6e9-bd551577e9d8

include/sieve/class_My_Parser.inc
include/sieve/class_My_Scanner.inc [new file with mode: 0644]
include/sieve/class_parser.inc
include/sieve/class_scanner.inc
include/sieve/class_semantics.inc

index 34d5de1215c3f01ed93422854527398510f20d57..197d94ab2922e6a44032f344e1249c3d5cebcb32 100644 (file)
@@ -9,8 +9,11 @@ class My_Parser extends Parser
 {
        var $parent = NULL;
 
+       var $registeredExtensions_ =array();
+
        function My_Parser($parent)
        {
+               $this->registeredExtensions_ = array();         
                $this->parent = $parent;
        }
 
@@ -33,11 +36,12 @@ class My_Parser extends Parser
      */
        function parse($script) 
        {
+               $this->registeredExtensions_ = array();
         $this->status_text = "incomplete";
         $this->script_ = $script;
         $this->tree_ = new My_Tree(@Scanner::scriptStart(),$this);
         $this->tree_->setDumpFunc(array(&$this, 'dumpToken_'));
-        $this->scanner_ = new Scanner($this->script_);
+        $this->scanner_ = new My_Scanner($this->script_);
         $this->scanner_->setCommentFunc(array($this, 'comment_'));
 
         if ($this->commands_($this->tree_->getRoot()) &&
diff --git a/include/sieve/class_My_Scanner.inc b/include/sieve/class_My_Scanner.inc
new file mode 100644 (file)
index 0000000..878927a
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+class My_Scanner extends Scanner 
+{
+       function tokenize(&$script)
+       {
+               $pos = 0;
+               $line = 1;
+               $script_length = mb_strlen($script);
+
+               while ($pos < $script_length)
+               {
+                       foreach ($this->token_match_ as $class => $regex)
+                       {
+                               if (preg_match('/^'. $regex .'/', mb_substr($script, $pos), $match))
+                               {
+                                       $length = mb_strlen($match[0]);
+
+                                       if ($class != 'whitespace')
+                                       {
+                                               array_push($this->tokens_, array(
+                                                       'class' => $class,
+                                                       'text'  => chop(mb_substr($script, $pos, $length)),
+                                                       'line'  => $line,
+                                               ));
+                                       }
+                                       if ($class == 'unknown')
+                                       {
+                                               return;
+                                       }
+
+                                       $pos += $length;
+                                       $line += mb_substr_count($match[0], "\n");
+                                       break;
+                               }
+                       }
+               }
+               array_push($this->tokens_, array(
+                       'class' => 'script-end',
+                       'text'  => 'script-end',
+                       'line'  => $line,
+               ));
+       }
+
+       var $commentFn_ = null;
+       var $tokenPos_ = 0;
+       var $tokens_ = array();
+       var $token_match_ = array (
+               'left-bracket'   =>  '\[',
+               'right-bracket'  =>  '\]',
+               'block-start'    =>  '\{',
+               'block-end'      =>  '\}',
+               'left-parant'    =>  '\(',
+               'right-parant'   =>  '\)',
+               'comma'          =>  ',',
+               'semicolon'      =>  ';',
+               'whitespace'     =>  '[ \r\n\t]+',
+               'tag'            =>  ':[[:alpha:]_][[:alnum:]_]*(?=\b)',
+               'quoted-string'  =>  '"(?:\\[\\"]|[^\x00"])*"',
+               'number'         =>  '[[:digit:]]+(?:[KMG])?(?=\b)',
+               'comment'        =>  '(?:\/\*(?:[^\*]|\*(?=[^\/]))*\*\/|#[^\r\n]*\r?\n)',
+#              'multi-line'     =>  'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.]*\r?\n)*\.\r?\n',
+               'multi-line'     =>  'text:[^;]*',
+               'identifier'     =>  '[[:alpha:]_][[:alnum:]_]*(?=\b)',
+               'unknown token'  =>  '[^ \r\n\t]+'
+       );
+}
+
+?>
index 933232ecab081da86ff37986a0f662a2153f58e9..345895231f2ec801cd55107e00153f68ec004406 100644 (file)
@@ -1,15 +1,22 @@
 <?php
+
+#include_once 'class.tree.php';
+#include_once 'class.scanner.php';
+#include_once 'class.semantics.php';
+
 class Parser
 {
        var $scanner_;
        var $script_;
        var $tree_;
        var $status_;
+       var $registeredExtensions_;
 
        var $status_text;
 
        function parse($script)
        {
+               $this->registeredExtensions_ = array();
                $this->status_text = "incomplete";
 
                $this->script_ = $script;
@@ -47,6 +54,47 @@ class Parser
                return strval($token);
        }
 
+       function getPrevTokenText_($parent_id)
+       {
+               $childs = $this->tree_->getChilds($parent_id);
+
+               for ($i=count($childs); $i>0; --$i)
+               {
+                       $prev = $this->tree_->getNode($childs[$i-1]);
+
+                       if (in_array($prev['text'], array('{', '(', ',')))
+                       {
+                               // use command owning a block or list
+                               $prev = $this->tree_->getNode($parent_id);
+                       }
+
+                       if ($prev['class'] != 'comment')
+                       {
+                               return $prev['text'];
+                       }
+               }
+
+               $prev = $this->tree_->getNode($parent_id);
+               return $prev['text'];
+       }
+
+       function getSemantics_($token_text)
+       {
+               $semantics = new Semantics($token_text);
+               $semantics->setExtensionFuncs(array(&$this, 'registerExtension_'), array(&$this, 'isExtensionRegistered_'));
+               return $semantics;
+       }
+
+       function registerExtension_($extension)
+       {
+               array_push($this->registeredExtensions_, str_replace('"', '', $extension));
+       }
+
+       function isExtensionRegistered_($extension)
+       {
+               return (in_array($extension, $this->registeredExtensions_) ? true : false);
+       }
+
        function success_($text = null)
        {
                if ($text != null)
@@ -74,6 +122,10 @@ class Parser
                return false;
        }
 
+       /*******************************************************************************
+        * methods for recursive descent start below
+        */
+
        function comment_($token)
        {
                $this->tree_->addChild($token);
@@ -100,16 +152,10 @@ class Parser
 
                // Get and check a command token
                $token = $this->scanner_->nextToken();
-               $semantics = new Semantics($token['text']);
-               if ($semantics->unknown)
+               $semantics = $this->getSemantics_($token['text']);
+               if (!$semantics->validCommand($this->getPrevTokenText_($parent_id), $token['line']))
                {
-                       return $this->error_('unknown command: '. $token['text']);
-               }
-
-               $last = $this->tree_->getLastNode($parent_id);
-               if (!$semantics->validAfter($last['text']))
-               {
-                       #return $this->error_('"'. $token['text'] .'" may not appear after "'. $last['text'] .'"');
+                       return $this->error_($semantics->message);
                }
 
                // Process eventual arguments
@@ -274,10 +320,10 @@ class Parser
                }
 
                // Get semantics for this test command
-               $this_semantics = new Semantics($token['text']);
-               if ($this_semantics->unknown)
+               $this_semantics = $this->getSemantics_($token['text']);
+               if (!$this_semantics->validCommand($this->getPrevTokenText_($parent_id), $token['line']))
                {
-                       return $this->error_('unknown test: '. $token['text']);
+                       return $this->error_($this_semantics->message);
                }
 
                $this_node = $this->tree_->addChildTo($parent_id, $token);
index 2a7fc37d2e77a0b7aec019bc1cdeeb4293a679d3..3e22bb1d0ee8c6a3a2aae7b6662fa35da5e54a50 100644 (file)
@@ -58,6 +58,7 @@ class Scanner
                                }
                        }
                }
+
                array_push($this->tokens_, array(
                        'class' => 'script-end',
                        'text'  => 'script-end',
@@ -130,11 +131,10 @@ class Scanner
                'quoted-string'  =>  '"(?:\\[\\"]|[^\x00"])*"',
                'number'         =>  '[[:digit:]]+(?:[KMG])?(?=\b)',
                'comment'        =>  '(?:\/\*(?:[^\*]|\*(?=[^\/]))*\*\/|#[^\r\n]*\r?\n)',
-#              'multi-line'     =>  'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.]*\r?\n)*\.\r?\n',
-               'multi-line'     =>  'text:[^;]*',
+               'multi-line'     =>  'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.]*\r?\n)*\.\r?\n',
                'identifier'     =>  '[[:alpha:]_][[:alnum:]_]*(?=\b)',
                'unknown token'  =>  '[^ \r\n\t]+'
        );
 }
 
-?>
+?>
\ No newline at end of file
index 77b27ccacbd5d9397e04074eca33c39b6beac538..173c5cfb736c43b9cb00ba50fdc43e91c076a415 100644 (file)
@@ -1,22 +1,24 @@
 <?php
 
-$requires_ = array();
-
 class Semantics
 {
+       var $registerExtensionFn_;
+       var $isExtensionRegisteredFn_;
+
        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 $requireStrings_ = '(envelope|fileinto|reject|vacation|relational|subaddress)';
+       var $requireStrings_ = '(envelope|fileinto|reject|vacation|relational|subaddress|regex|imapflags|copy)';
 
        function Semantics($command)
        {
                $this->command_ = $command;
-
                $this->unknown = false;
                switch ($command)
                {
@@ -27,7 +29,7 @@ class Semantics
                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_ .'"'),
@@ -40,8 +42,7 @@ class Semantics
                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')
@@ -56,7 +57,7 @@ class Semantics
                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')
@@ -71,7 +72,7 @@ class Semantics
                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')
@@ -84,22 +85,24 @@ class Semantics
                /*******************
                 * action commands
                 */
+               case 'discard':
                case 'keep':
                case 'stop':
-               case 'discard':
-                       /* keep / stop / discard */
+                       /* discard / keep / stop */
                        $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':
-                       /* fileinto <folder: string> */
+                       /* fileinto [":copy"] <folder: string> */
                        $this->s_ = array(
                                'requires' => 'fileinto',
-                               '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' => ':copy', 'requires' => 'copy', 'name' => 'copy')
+                                       )),
                                        array('class' => 'string', 'occurrences' => '1', 'values' => array(
                                                array('occurrences' => '1', 'regex' => '".*"', 'name' => 'folder')
                                        ))
@@ -107,11 +110,23 @@ class Semantics
                        );
                        break;
 
+               case 'mark':
+               case 'unmark':
+                       /* mark / unmark */
+                       $this->s_ = array(
+                               'requires' => 'imapflags',
+                               'valid_after' => $this->nonTestCommands_
+                       );
+                       break;
+
                case 'redirect':
-                       /* redirect <address: string> */
+                       /* redirect [":copy"] <address: string> */
                        $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' => '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')
                                        ))
@@ -123,10 +138,27 @@ class Semantics
                        /* 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(
-                                               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')
                                        ))
                                )
                        );
@@ -136,7 +168,7 @@ class Semantics
                        /* 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',
@@ -163,7 +195,7 @@ class Semantics
                                                array('occurrences' => '?', 'regex' => ':mime', 'name' => 'mime')
                                        )),
                                        array('class' => 'string', 'occurrences' => '1', 'values' => array(
-                                               array('occurrences' => '1', 'regex' => '.*', 'name' => 'reason')
+                                               array('occurrences' => '1', 'regex' => '".*"', 'name' => 'reason')
                                        ))
                                )
                        );
@@ -176,10 +208,10 @@ class Semantics
                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(
-                                               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(
@@ -205,7 +237,7 @@ class Semantics
                        /* 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')
@@ -221,10 +253,10 @@ class Semantics
                        /* 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(
-                                               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(
@@ -248,7 +280,7 @@ class Semantics
                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')
@@ -260,10 +292,10 @@ class Semantics
                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(
-                                               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(
@@ -286,7 +318,7 @@ class Semantics
                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')
@@ -298,7 +330,7 @@ class Semantics
                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')
@@ -314,7 +346,7 @@ class Semantics
                case 'false':
                        /* true / false */
                        $this->s_ = array(
-                               'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not')
+                               'valid_after' => $this->testsValidAfter_
                        );
                        break;
 
@@ -327,25 +359,32 @@ class Semantics
                }
        }
 
-       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;
        }
 
+       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')
                {
-                       global $requires_;
-                       if (!in_array('"relational"', $requires_))
+                       if (!$this->wasRequired_('relational'))
                        {
                                $this->message = sprintf(_("Missing require statement for '%s' object."), $text);
                                return false;
@@ -357,6 +396,12 @@ class Semantics
                                ))
                        );
                }
+               // 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;
        }
@@ -371,8 +416,7 @@ class Semantics
        {
                if ($text == ':user' || $text == ':detail')
                {
-                       global $requires_;
-                       if (!in_array('"subaddress"', $requires_))
+                       if (!$this->wasRequired_('subaddress'))
                        {
                                $this->message = sprintf(_("Missing require statement for '%s' object."), $text);
                                return false;
@@ -393,9 +437,30 @@ class Semantics
                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)
@@ -457,14 +522,10 @@ class Semantics
 
        function validToken($class, &$text, &$line)
        {
-               global $requires_;
-
-               if(!is_array($requires_)){
-                       $requires_ = array();
-               }
-
                $name = $class . ($class != $text ? " $text" : '');
 
+<<<<<<< .mine
+=======
                // Check if the command needs to be required
                // TODO: move this to somewhere more appropriate
                if (isset($this->s_['requires']) &&
@@ -474,6 +535,7 @@ class Semantics
                        return false;
                }
 
+>>>>>>> .r5858
                // Make sure the argument has a valid class
                if (!$this->validClass_($class, $name))
                {
@@ -484,12 +546,10 @@ class Semantics
                $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 (isset($val['requires']) &&
-                                       !in_array('"'.$val['requires'].'"', $requires_))
+                               if (isset($val['requires']) && !$this->wasRequired_($val['requires']))
                                {
                                        $this->message = sprintf(_("Error in line %s"),$line)." :".sprintf(_("Missing require statement for '%s' object."), $val['name']);
                                        return false;
@@ -562,4 +622,4 @@ class Semantics
        }
 }
 
-?>
+?>
\ No newline at end of file