r2108 + introduce $any_op autotag
[racktables] / inc / code.php
index 4c8dd25de83df0bb58145c59a9a3e4fc21bb156d..867bc66eb23f4dcb8f5a30bfd0631dfa839682bb 100644 (file)
  */
 
 // Complain about martian char.
-function abortLex1 ($state, $text, $pos)
+function lexError1 ($state, $text, $pos)
 {
-       echo "Error! Could not parse char with code " . ord (substr ($text, $pos + 1, 1)) . " (current state is '${state}'): ";
-       echo substr ($text, 0, $pos);
-       echo '<font color = red>-&gt;' . $text{$pos} . '&lt;-</font>';
-       echo substr ($text, $pos + 1);
-       die;
+       $message = "invalid char with code " . ord (mb_substr ($text, $pos, 1)) . ' (';
+       $message .= mb_substr ($text, $pos, 1) . ") at position ${pos} (FSM state is '${state}')";
+       return array ('result' => 'NAK', 'load' => $message);
 }
 
 // Complain about martian keyword.
-function abortLex2 ($state, $word)
+function lexError2 ($word)
 {
-       echo "Error! Could not parse word (current state is '${state}'): '${word}'.";
-       die;
+       return array
+       (
+               'result' => 'NAK',
+               'load' => "could not parse keyword '${word}'"
+       );
 }
 
 // Complain about wrong FSM state.
-function abortLex3 ($state)
+function lexError3 ($state)
 {
-       echo "Error! Lexical scanner final state is still '${state}' after scanning the last char.";
-       die;
+       return array
+       (
+               'result' => 'NAK',
+               'load' => "FSM state is still '${state}' at end of input"
+       );
 }
 
-function abortSynt ($lexname)
+function lexError4 ($s)
 {
-       echo "Error! Unknown lexeme '${lexname}'.";
-       die;
+       return array
+       (
+               'result' => 'NAK',
+               'load' => "Invalid tag or predicate name '${s}'"
+       );
 }
 
 // Produce a list of lexems from the given text. Possible lexems are:
 function getLexemsFromRackCode ($text)
 {
        $ret = array();
-       $textlen = strlen ($text);
+       $textlen = mb_strlen ($text);
        $state = "ESOTSM";
        for ($i = 0; $i < $textlen; $i++) :
-               $char = $text{$i};
+               $char = mb_substr ($text, $i, 1);
                $newstate = $state;
                switch ($state) :
                        case 'ESOTSM':
@@ -69,8 +76,8 @@ function getLexemsFromRackCode ($text)
                                        case ($char == '#'):
                                                $newstate = 'skipping comment';
                                                break;
-                                       case (preg_match ('/^[a-zA-Z]$/', $char)):
-                                               $newstate = 'reading word';
+                                       case (mb_ereg ('[[:alpha:]]', $char) > 0):
+                                               $newstate = 'reading keyword';
                                                $buffer = $char;
                                                break;
                                        case (preg_match ('/^[ \t\n\r]$/', $char)):
@@ -83,13 +90,13 @@ function getLexemsFromRackCode ($text)
                                                $newstate = 'reading predicate 1';
                                                break;
                                        default:
-                                               abortLex1 ($state, $text, $i);
+                                               return lexError1 ($state, $text, $i);
                                }
                                break;
-                       case 'reading word':
+                       case 'reading keyword':
                                switch (TRUE)
                                {
-                                       case (preg_match ('/^[a-zA-Z]$/', $char)):
+                                       case (mb_ereg ('[[:alpha:]]', $char) > 0):
                                                $buffer .= $char;
                                                break;
                                        case (preg_match ('/^[ \t\n]$/', $char)):
@@ -115,12 +122,12 @@ function getLexemsFromRackCode ($text)
                                                                $ret[] = array ('type' => 'LEX_BOOLCONST', 'load' => $buffer);
                                                                break;
                                                        default:
-                                                               abortLex2 ($state, $buffer);
+                                                               return lexError2 ($buffer);
                                                }
                                                $newstate = 'ESOTSM';
                                                break;
                                        default:
-                                               abortLex1 ($state, $text, $i);
+                                               return lexError1 ($state, $text, $i);
                                }
                                break;
                        case 'reading tag 1':
@@ -129,12 +136,12 @@ function getLexemsFromRackCode ($text)
                                        case (preg_match ('/^[ \t\n\r]$/', $char)):
                                                // nom-nom...
                                                break;
-                                       case (preg_match ('/^[a-zA-Z\$]$/', $char)):
+                                       case (mb_ereg ('[[:alnum:]\$]', $char) > 0):
                                                $buffer = $char;
                                                $newstate = 'reading tag 2';
                                                break;
                                        default:
-                                               abortLex1 ($state, $text, $i);
+                                               return lexError1 ($state, $text, $i);
                                }
                                break;
                        case 'reading tag 2':
@@ -142,16 +149,16 @@ function getLexemsFromRackCode ($text)
                                {
                                        case ($char == '}'):
                                                $buffer = rtrim ($buffer);
-                                               if (!preg_match ('/^[a-zA-Z0-9]$/', substr ($buffer, -1)))
-                                                       abortLex1 ($state, $text, $i);
+                                               if (!validTagName ($buffer, TRUE))
+                                                       return lexError4 ($buffer);
                                                $ret[] = array ('type' => 'LEX_TAG', 'load' => $buffer);
                                                $newstate = 'ESOTSM';
                                                break;
-                                       case (preg_match ('/^[a-zA-Z0-9 _-]$/', $char)):
+                                       case (mb_ereg ('[[:alnum:]\. _~-]', $char) > 0):
                                                $buffer .= $char;
                                                break;
                                        default:
-                                               abortLex1 ($state, $text, $i);
+                                               return lexError1 ($state, $text, $i);
                                }
                                break;
                        case 'reading predicate 1':
@@ -160,12 +167,12 @@ function getLexemsFromRackCode ($text)
                                        case (preg_match ('/^[ \t\n\r]$/', $char)):
                                                // nom-nom...
                                                break;
-                                       case (preg_match ('/^[a-zA-Z]$/', $char)):
+                                       case (mb_ereg ('[[:alnum:]]', $char) > 0):
                                                $buffer = $char;
                                                $newstate = 'reading predicate 2';
                                                break;
                                        default:
-                                               abortLex1 ($state, $text, $i);
+                                               return lexError1 ($state, $text, $i);
                                }
                                break;
                        case 'reading predicate 2':
@@ -173,20 +180,20 @@ function getLexemsFromRackCode ($text)
                                {
                                        case ($char == ']'):
                                                $buffer = rtrim ($buffer);
-                                               if (!preg_match ('/^[a-zA-Z0-9]$/', substr ($buffer, -1)))
-                                                       abortLex1 ($state, $text, $i);
+                                               if (!validTagName ($buffer))
+                                                       return lexError4 ($buffer);
                                                $ret[] = array ('type' => 'LEX_PREDICATE', 'load' => $buffer);
                                                $newstate = 'ESOTSM';
                                                break;
-                                       case (preg_match ('/^[a-zA-Z0-9 _-]$/', $char)):
+                                       case (mb_ereg ('[[:alnum:]\. _~-]', $char) > 0):
                                                $buffer .= $char;
                                                break;
                                        default:
-                                               abortLex1 ($state, $text, $i);
+                                               return lexError1 ($state, $text, $i);
                                }
                                break;
                        case 'skipping comment':
-                               switch ($text{$i})
+                               switch ($char)
                                {
                                        case "\n":
                                                $newstate = 'ESOTSM';
@@ -200,8 +207,8 @@ function getLexemsFromRackCode ($text)
                $state = $newstate;
        endfor;
        if ($state != 'ESOTSM' and $state != 'skipping comment')
-               abortLex3 ($state);
-       return $ret;
+               return lexError3 ($state);
+       return array ('result' => 'ACK', 'load' => $ret);
 }
 
 // Parse the given lexems stream into a list of RackCode sentences. Each such
@@ -467,8 +474,8 @@ function getSentencesFromLexems ($lexems)
                }
                // The moment of truth.
                if (count ($stack) == 1 and $stack[0]['type'] == 'SYNT_CODETEXT')
-                       return $stack[0]['load'];
-               return NULL;
+                       return array ('result' => 'ACK', 'load' => $stack[0]['load']);
+               return array ('result' => 'NAK', 'load' => 'Syntax error!');
        }
 }
 
@@ -485,11 +492,11 @@ function eval_expression ($expr, $tagchain, $ptable)
                        $pname = $expr['load'];
                        if (!isset ($ptable[$pname]))
                        {
-                               showError ("Predicate '${pname}' is referenced before declaration");
+                               showError ("Predicate '${pname}' is referenced before declaration", __FUNCTION__);
                                return;
                        }
                        return eval_expression ($ptable[$pname], $tagchain, $ptable);
-               case 'BOOLCONST': // Evaluate a boolean constant.
+               case 'LEX_BOOLCONST': // Evaluate a boolean constant.
                        switch ($expr['load'])
                        {
                                case 'true':
@@ -497,7 +504,7 @@ function eval_expression ($expr, $tagchain, $ptable)
                                case 'false':
                                        return FALSE;
                                default:
-                                       showError ("Could not parse a boolean constant with value '${expr['load']}'");
+                                       showError ("Could not parse a boolean constant with value '${expr['load']}'", __FUNCTION__);
                                        return; // should failure be harder?
                        }
                case 'SYNT_NOTEXPR':
@@ -519,7 +526,7 @@ function eval_expression ($expr, $tagchain, $ptable)
                                        return;
                        }
                default:
-                       showError ("Evaluation error, cannot process expression type '${expr['type']}'");
+                       showError ("Evaluation error, cannot process expression type '${expr['type']}'", __FUNCTION__);
                        break;
        }
 }
@@ -544,66 +551,89 @@ function gotClearanceForTagChain ($tagchain)
                                                case 'deny':
                                                        return FALSE;
                                                default:
-                                                       showError ("Condition match for unknown grant decision '${sentence['decision']}'");
+                                                       showError ("Condition match for unknown grant decision '${sentence['decision']}'", __FUNCTION__);
                                                        break;
                                        }
                                break;
                        default:
-                               showError ("Can't process sentence of unknown type '${sentence['type']}'");
+                               showError ("Can't process sentence of unknown type '${sentence['type']}'", __FUNCTION__);
                                break;
                }
        }
        return FALSE;
 }
 
-function getRackCode ()
+function getRackCode ($text)
 {
-       // FIXME: handle errors and display a message with an option to reset RackCode text
-       // FIXME: perform semantical analysis to prove the tree be reference-wise error
-       // free regardless of evaluation order
-       return getSentencesFromLexems (getLexemsFromRackCode (loadScript ('RackCode')));
+       if (!mb_strlen ($text))
+               return array ('result' => 'NAK', 'load' => 'The RackCode text was found empty in ' . __FUNCTION__);
+       $text = str_replace ("\r", '', $text) . "\n";
+       $lex = getLexemsFromRackCode ($text);
+       if ($lex['result'] != 'ACK')
+               return $lex;
+       $synt = getSentencesFromLexems ($lex['load']);
+       if ($synt['result'] != 'ACK')
+               return $synt;
+       // An empty sentence list is semantically valid, yet senseless,
+       // so checking intermediate result once more won't hurt.
+       if (!count ($synt['load']))
+               return array ('result' => 'NAK', 'load' => 'Empty parse tree found in ' . __FUNCTION__);
+       return semanticFilter ($synt['load']);
 }
 
-// Return true, if the given expression can be evaluated against the given
-// predicate list.
-function valid_expression ($plist, $expr)
+// Return NULL, if the given expression can be evaluated against the given
+// predicate list. Return the name of the first show stopper otherwise.
+function firstUnrefPredicate ($plist, $expr)
 {
        switch ($expr['type'])
        {
-               case 'BOOLCONST':
+               case 'LEX_BOOLCONST':
                case 'LEX_TAG':
-                       return TRUE;
+                       return NULL;
                case 'LEX_PREDICATE':
-                       return in_array ($expr['load'], $plist);
+                       return in_array ($expr['load'], $plist) ? NULL : $expr['load'];
                case 'SYNT_NOTEXPR':
-                       return valid_expression ($plist, $expr['load']);
+                       return firstUnrefPredicate ($plist, $expr['load']);
                case 'SYNT_BOOLOP':
-                       return valid_expression ($plist, $expr['left']) and valid_expression ($plist, $expr['right']);
+                       if (($tmp = firstUnrefPredicate ($plist, $expr['left'])) !== NULL)
+                               return $tmp;
+                       if (($tmp = firstUnrefPredicate ($plist, $expr['right'])) !== NULL)
+                               return $tmp;
+                       return NULL;
                default:
-                       showError ("Validation error, cannot process expression type '${expr['type']}'");
-                       break;
+                       return NULL;
        }
 }
 
-function valid_rackcode ($code)
+function semanticFilter ($code)
 {
        $predicatelist = array();
        foreach ($code as $sentence)
                switch ($sentence['type'])
                {
                        case 'SYNT_DEFINITION':
-                               if (!valid_expression ($predicatelist, $sentence['definition']))
-                                       return FALSE;
+                               $up = firstUnrefPredicate ($predicatelist, $sentence['definition']);
+                               if ($up !== NULL)
+                                       return array
+                                       (
+                                               'result' => 'NAK',
+                                               'load' => "definition [${sentence['term']}] uses unknown predicate [${up}]"
+                                       );
                                $predicatelist[] = $sentence['term'];
                                break;
                        case 'SYNT_GRANT':
-                               if (!valid_expression ($predicatelist, $sentence['condition']))
-                                       return FALSE;
+                               $up = firstUnrefPredicate ($predicatelist, $sentence['condition']);
+                               if ($up !== NULL)
+                                       return array
+                                       (
+                                               'result' => 'NAK',
+                                               'load' => "grant sentence uses unknown predicate [${up}]"
+                                       );
                                break;
                        default:
-                               return FALSE;
+                               return array ('result' => 'NAK', 'load' => 'unknown sentence type');
                }
-       return TRUE;
+       return array ('result' => 'ACK', 'load' => $code);
 }
 
 ?>