Avail feature updated
[ninja.git] / modules / exp / libraries / ExpParser.php
blob436b190e71a7a521f910c2f28611f304e57580d9
1 <?php
3 /**
4 * Exception from ExpParser
5 */
6 class ExpParserException extends Exception {}
8 /**
9 * Base class to handle the logic and lexing in a descendent recursive parser
11 abstract class ExpParser {
12 /**
13 * The expression to parse
15 protected $expr; /* Protected, just so we can add custom acceptors... */
16 /**
17 * The current position during parsing, as an integer
19 protected $ptr;
21 /**
22 * Start parsing of an expression
24 public function parse( $expr ) {
25 $this->expr = $expr;
26 $this->ptr = 0;
28 $result = $this->run();
30 $curptr = $this->ptr; /* Keep $this->ptr for error message */
31 while( ctype_space( substr( $this->expr, $curptr, 1 ) ) ) $this->ptr++;
32 if( $curptr < strlen( $this->expr ) ) {
33 $this->error( 'Expected end' );
36 return $result;
39 /**
40 * Entry point to start parsing. Runned when the state is set up correctly
42 abstract protected function run();
44 /**
45 * Trim whitespaces at the current pointer
47 protected function trimLeft() {
48 while( ctype_space( substr( $this->expr, $this->ptr, 1 ) ) ) $this->ptr++;
51 /**
52 * Accept a symbol.
54 * Takes an array of possible symbols, must be ordered from the longest to the shortest.
56 protected function acceptSym( $tokenlist ) {
57 $this->trimLeft();
59 /* Test tokens */
60 foreach( $tokenlist as $token ) {
61 if( substr( $this->expr, $this->ptr, strlen( $token ) ) == $token ) {
62 $this->ptr += strlen( $token );
63 return $token;
66 return false;
69 /**
70 * Excpect a symbol, but fail if not availible
72 * @see acceptSym
74 protected function expectSym( $tokenlist ) {
75 $sym = $this->acceptSym( $tokenlist );
76 if( $sym === false )
77 $this->error('Unexpected token, expected '.implode(',',$tokenlist));
78 return $sym;
81 /**
82 * Accept a keyword, finished by a whitespace.
84 protected function acceptKeyword( $keywordlist = false, $case_insensitive = false, $numeric = false ) {
85 $this->trimLeft();
87 /* Peek at next keyword */
88 $curptr = $this->ptr;
89 $buffer = '';
90 $c = substr( $this->expr, $curptr, 1 );
91 while( ctype_alpha( $c ) || $c == '_' || ($buffer !== '' && ctype_digit($c)) ) {
92 $curptr++;
93 $buffer .= $c;
94 $c = substr( $this->expr, $curptr, 1 );
96 if( $case_insensitive )
97 $buffer = strtolower( $buffer );
98 if( $keywordlist === false || in_array( $buffer, $keywordlist ) ) {
99 $this->ptr = $curptr;
100 return $buffer;
102 return false;
106 * Excpect a keyword
108 * @see acceptKeyword
110 protected function expectKeyword( $keywordlist = false, $case_insensitive = false, $numeric = false ) {
111 $sym = $this->acceptKeyword( $keywordlist, $case_insensitive, $numeric );
112 if( $sym === false )
113 $this->error('Unexpected token, expected '.(($keywordlist===false)?('keyword'):implode(',',$keywordlist)));
114 return $sym;
118 * Accept a string
120 * Strings can be quoted by doublequotes, and may have backslash escape sequences
122 protected function acceptString( ) {
123 $this->trimLeft();
125 /* Fetch first " */
126 if( substr( $this->expr, $this->ptr, 1 ) != '"' ) {
127 return false;
129 $this->ptr++;
131 $buffer = '';
132 while( ($c = substr( $this->expr, $this->ptr++, 1 )) != '"' ) {
133 if( $c == '\\' ) {
134 $c = substr( $this->expr, $this->ptr++, 1 );
136 $buffer .= $c;
138 return $buffer;
142 * Expect a string
144 * @see acceptString
146 protected function expectString() {
147 $sym = $this->acceptString();
148 if( $sym === false )
149 $this->error('Unexpected token, expected string');
150 return $sym;
154 * Accept a number, (only digits)
156 protected function acceptNum() {
157 $this->trimLeft();
159 /* Peek at next integer */
160 $curptr = $this->ptr;
161 $buffer = '';
162 while( ctype_digit( $c = substr( $this->expr, $curptr, 1 ) ) ) {
163 $curptr++;
164 $buffer .= $c;
167 if( strlen( $buffer ) > 0 ) {
168 $this->ptr = $curptr;
169 return intval($buffer);
171 return false;
175 * Except a number
177 * @see acceptNumber
179 protected function expectNum() {
180 $sym = $this->acceptNum();
181 if( $sym === false )
182 $this->error('Unexpected token, expected number');
183 return $sym;
187 * Trigger a parser error, format the current position and throw an exception.
189 protected function error( $msg ) {
190 throw new ExpParserException($msg . ' at ' . $this->ptr);