diff --git a/ports/php/example/Makefile b/ports/php/example/Makefile new file mode 100644 index 000000000..c84983799 --- /dev/null +++ b/ports/php/example/Makefile @@ -0,0 +1,6 @@ +test: compile + node filter.js example + php test.php + +compile: + node ../php.js filter.jison diff --git a/ports/php/example/example b/ports/php/example/example new file mode 100644 index 000000000..e2c7265c2 --- /dev/null +++ b/ports/php/example/example @@ -0,0 +1 @@ +'Hello world' LIKE ['helo','123','3453'] \ No newline at end of file diff --git a/ports/php/example/filter.jison b/ports/php/example/filter.jison new file mode 100644 index 000000000..94f68a652 --- /dev/null +++ b/ports/php/example/filter.jison @@ -0,0 +1,280 @@ + +/* description: Parses end executes mathematical expressions with strings. */ + +/* lexical grammar */ +%lex +%% + +\s+ /* skip whitespace */ +[0-9]+("."[0-9]+)?\b return 'NUMBER'; +"*" return '*'; +"/" return '/'; +"-" return '-'; +"+" return '+'; +"^" return '^'; +"!" return '!'; +"%" return '%'; +"(" return '('; +")" return ')'; +"[" return '['; +"]" return ']'; +"," return ','; +"PI" return 'PI'; +"E" return 'E'; +"SUM" return 'SUM'; +"LIKE" return 'LIKE'; +\"([^"]*)\" %{ + //js + yytext = yytext.substr(1,yyleng-2); + return 'STRING'; + /*php + $yytext = substr($yytext, 1, strlen($yytext) - 2); + return 'STRING'; + */ + %} +\'([^']*)\' %{ + //js + yytext = yytext.substr(1,yyleng-2); + return 'STRING'; + /*php + $yytext = substr($yytext, 1, strlen($yytext) - 2); + return 'STRING'; + */ + %} +'AND' return 'AND'; +'OR' return 'OR'; +'NOT' return 'NOT'; +'>' return '>'; +'<' return '<'; +'=' return '='; +'<>' return '<>'; +'>=' return '>='; +'=>' return '>='; +'<=' return '=<'; +'=<' return '=<'; +'true' return 'true'; +'false' return 'false'; +<> return 'EOF'; +. return 'INVALID'; + +/lex + +/* operator associations and precedence */ + +%right ',' +%left 'OR', +%left 'AND' +%left 'NOT' +%left '>','<','=<','>=', '<>','=' +%left '+' '-' +%left '*' '/' +%left '^' +%right '!' +%right '%' +%left UMINUS + +%start expressions + +%% /* language grammar */ +expressions + : e EOF + { + //js + typeof console !== 'undefined' ? console.log($1) : print($1); + return $1; + /*php + return $1; + */ + } + ; + +sub_list + : sub_list ',' e + { + //js + var A = $1; + A.push($3); + $$ = A; + /*php + $a = $1; + array_push($a, $3); + $$ = $a; + */ + } + | e + { + //js + $$ = [ $1 ]; + //php $$ = array($1); + } + ; + +list + : '[' sub_list ']' + { + $$ = $2; + } + ; + +e + : e '+' e + { + $$ = ( $1 + $3 ); + } + | e '-' e + { + $$ = ($1 - $3); + } + | e '*' e + { + $$ = ( $1*$3 ); + } + + | e '/' e + { + $$ = ( $1 /$3 ); + } + | e '^' e + { + //js + $$ = Math.pow($1, $3); + //php $$ = pow( $1, $3); + } + | e '!' + { + //js + $$ = (function fact (n) { return n==0 ? 1 : fact(n-1) * n })($1); + /*php + $f = function($n) use (&$f) { return ($n<1) ? 1 : ($f($n-1) * $n); }; + $$ = $f((int) $1); + */ + } + | e '%' + { + $$ = $1/100; + } + | '-' e %prec UMINUS + { + $$ = -$2; + } + | '(' e ')' + { + //js + $$ = $2; + //php $$ = $2; + } + | NUMBER + { + //js + $$ = Number(yytext); + //php $$ = $1; + } + | STRING + { + //js + $$ = yytext; + //php $$ = $1; + } + | E + { + //js + $$ = Math.E; + //php $$ = exp(1); + } + | PI + { + //js + $$ = Math.PI; + //php $$ = 3.141592653589793; + } + | 'SUM' list + { + //js + var sum = 0; + $2.forEach(function(item) { sum+= Number(item); }); + $$ = sum; + /*php + $sum = 0; + foreach($2 as $value) { + $sum += $value; + } + $$ = $sum; + */ + } + | e 'LIKE' list + { + //js + var flag = false; + var escape = function(text) { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + } + $3.forEach(function(item) { + var regexp = new RegExp(escape(item).replace('%', '(.*)'),'gi'); + flag = flag || regexp.test($1); + }); + $$ = flag; + /*php + // TODO: Синхронизировать метод с JS + $flag = false; + $template = $1; + foreach($3 as $item) { + $flag = $flag || (mb_stripos($template, $item,0, 'utf-8') !== false); + if ($flag) break; + } + $$ = $flag; + */ + } + | e 'AND' e + { + //js + $$ = ($1!=false) && ($3!=false); + //php $$ = ($1 !== false) && ($3 !== false); + } + | e 'OR' e + { + //js + $$ = ($1!=false) || ($3!=false); + /*php + $$ = ($1 !== false) || ($3 !== false); + */ + } + | 'NOT' e + { + //js + $$ = !($2!=false) ; + //php $$ = ! ($2 !== false) ; + } + | 'true' + { + $$ = true; + } + | 'false' + { + $$ = false; + } + | e '>' e + { + $$ = ( $1 > $3 ); + } + | e '<' e + { + $$ = ( $1 < $3 ); + } + | e '>=' e + { + $$ = ( $1 >= $3 ); + } + | e '=<' e + { + $$ = ( $1 <= $3 ); + } + | e '=' e + { + $$ = ( $1 == $3 ); + } + | e '<>' e + { + $$ = ( $1 != $3 ); + } + ; + diff --git a/ports/php/example/test.php b/ports/php/example/test.php new file mode 100644 index 000000000..6625cd57c --- /dev/null +++ b/ports/php/example/test.php @@ -0,0 +1,7 @@ +parse($text)); +echo "\n"; \ No newline at end of file diff --git a/ports/php/php.js b/ports/php/php.js index 5e9f84254..c9d10d8d6 100644 --- a/ports/php/php.js +++ b/ports/php/php.js @@ -65,7 +65,7 @@ exec("jison " + process.argv[2], function (error) { str = str.replace("var $0 = $$.length - 1;", ''); str = str.replace("var YYSTATE=YY_START", ''); str = str.replace(new RegExp('[$]0', 'g'), '$o'); - str = str.replace(new RegExp('[$][$]', 'g'), '$s'); + str = str.replace(new RegExp('[$][$](\\[(.+?)\\])', 'g'), '$$s$1->value()'); str = str.replace(new RegExp('default[:][;]', 'g'), ''); str = str.replace(new RegExp('this[.][$]', 'g'), '$thisS'); str = str.replace(new RegExp('this[-][>]', 'g'), '$this->'); @@ -112,7 +112,7 @@ exec("jison " + process.argv[2], function (error) { console.log(option); - var parserRaw = fs.readFileSync(__dirname + "/template.php", "utf8"); + var parserRaw = fs.readFileSync(process.argv[3] ? process.argv[3] : (__dirname + "/template.php"), "utf8"); function parserInject() { var result = '\n'; @@ -221,7 +221,7 @@ exec("jison " + process.argv[2], function (error) { this.conditions = []; for (var i in rules) { - this.rules.push('\t\t\t\t\t' + i + '=>"/' + rules[i].substring(1, rules[i].length - 1).replace(/"/g, '\\"') + '/"'); + this.rules.push('\t\t\t\t\t' + i + '=>"' + rules[i].replace(/"/g, '\\"') + 'u"'); } result += '\t\t\t$this->rules = array(\n\t\t\t\t\n' + this.rules.join(',\n') + '\n\t\t\t\t);\n\n'; diff --git a/ports/php/readme b/ports/php/readme index 4f7c84d34..3d4f8cb3e 100644 --- a/ports/php/readme +++ b/ports/php/readme @@ -4,7 +4,7 @@ Description: A jison wrapper that first processes a jison file and then injects DIRECTIONS: 1. After you've downloaded jison & node.js, navigate in command line interface to the folder that contains the ".jison" file that you want to process -2. Process the ".jison" file like this "nodejs /location_of_jison/ports/php/php.js my_jison_file.jison" +2. Process the ".jison" file like this "nodejs /location_of_jison/ports/php/php.js my_jison_file.jison [path_to_template.php]" CONFIGURATION: Configuration takes advantage of the commenting in javascript so as not to conflict with. diff --git a/ports/php/template.php b/ports/php/template.php index 436b679f4..6ce35930d 100644 --- a/ports/php/template.php +++ b/ports/php/template.php @@ -6,17 +6,17 @@ /**/class Parser/**/ { - public $symbols = array(); - public $terminals = array(); - public $productions = array(); - public $table = array(); - public $defaultActions = array(); - public $version = '0.3.12'; - public $debug = false; - public $none = 0; - public $shift = 1; - public $reduce = 2; - public $accept = 3; + protected $symbols = array(); + protected $terminals = array(); + protected $productions = array(); + protected $table = array(); + protected $defaultActions = array(); + protected $version = '0.3.12'; + protected $debug = false; + protected $none = 0; + protected $shift = 1; + protected $reduce = 2; + protected $accept = 3; function trace() { @@ -48,16 +48,18 @@ function parserLex() return $this->symbols["end"]; } - function parseError($str = "", ParserError $hash = null) - { - throw new Exception($str); + protected function parseError(ParserError $error) { + throw $error; } - function lexerError($str = "", LexerError $hash = null) - { - throw new Exception($str); + protected function lexerError(LexerError $error) { + throw $error; } + protected function runTimeError(RunTimeError $error) { + throw $error; + } + function parse($input) { if (empty($this->table)) { @@ -112,7 +114,7 @@ function parse($input) $errStr = "Parse error on line " . ($this->yy->lineNo + 1) . ":\n" . $this->showPosition() . "\nExpecting " . implode(", ", $expected) . ", got '" . (isset($this->terminals[$symbol->index]) ? $this->terminals[$symbol->index]->name : 'NOTHING') . "'"; - $this->parseError($errStr, new ParserError($this->match, $state, $symbol, $this->yy->lineNo, $this->yy->loc, $expected)); + $this->parseError(new ParserError($errStr,$this->match, $state, $symbol, $this->yy->lineNo, $this->yy->loc, $expected)); } } @@ -150,10 +152,20 @@ function parse($input) if (isset($this->ranges)) { //TODO: add ranges } - - $r = $this->parserPerformAction($_yy->text, $yy, $action->state->index, $vstack, $vstackCount - 1); - - if (isset($r)) { + try { + $r = $this->parserPerformAction($_yy->text, $yy, $action->state->index, $vstack, $vstackCount - 1); + } + catch (RunTimeError $error) { + $newError = new RunTimeError($error->getMessage(). "\n". $this->showPosition()); + $newError->text = $this->match; + $newError->state = $state; + $newError->symbol = $symbol; + $newError->lineNo = $this->yy->lineNo; + $newError->loc = $this->yy->loc; + throw $newError; + } + + if (isset($r)) { return $r; } @@ -200,21 +212,21 @@ function parse($input) /* Jison generated lexer */ - public $eof; - public $yy = null; - public $match = ""; - public $matched = ""; - public $conditionStack = array(); - public $conditionStackCount = 0; - public $rules = array(); - public $conditions = array(); - public $done = false; - public $less; - public $more; - public $input; - public $offset; - public $ranges; - public $flex = false; + protected $eof; + protected $yy = null; + protected $match = ""; + protected $matched = ""; + protected $conditionStack = array(); + protected $conditionStackCount = 0; + protected $rules = array(); + protected $conditions = array(); + protected $done = false; + protected $less; + protected $more; + protected $input; + protected $offset; + protected $ranges; + protected $flex = false; function setInput($input) { @@ -295,31 +307,31 @@ function more() $this->more = true; } - function pastInput() + function pastInput($len) { - $past = substr($this->matched, 0, strlen($this->matched) - strlen($this->match)); - return (strlen($past) > 20 ? '...' : '') . preg_replace("/\n/", "", substr($past, -20)); + $past = mb_substr($this->matched, 0, mb_strlen($this->matched,'utf8') - mb_strlen($this->match,'utf8'),'utf8'); + return (mb_strlen($past,'utf8') > $len ? '...' : '') . str_replace("\n", "", mb_substr($past, -$len, NULL, 'utf8')); } - function upcomingInput() + function upcomingInput($len) { $next = $this->match; - if (strlen($next) < 20) { - $next .= substr($this->input, 0, 20 - strlen($next)); + if (mb_strlen($next,'utf8') < $len) { + $next .= mb_substr($this->input, 0, $len - mb_strlen($next,'utf8')); } - return preg_replace("/\n/", "", substr($next, 0, 20) . (strlen($next) > 20 ? '...' : '')); + return str_replace("\n", "", mb_substr($next, 0, $len,'utf8') . (mb_strlen($next,'utf8') > $len ? '...' : '')); } - function showPosition() + function showPosition($len = 20) { - $pre = $this->pastInput(); + $pre = $this->pastInput($len); $c = ''; - for($i = 0, $preLength = strlen($pre); $i < $preLength; $i++) { + for($i = 0, $preLength = mb_strlen($pre, 'utf8'); $i < $preLength; $i++) { $c .= '-'; } - return $pre . $this->upcomingInput() . "\n" . $c . "^"; + return $pre . $this->upcomingInput($len) . "\n" . $c . "^"; } function next() @@ -328,7 +340,7 @@ function next() return $this->eof; } - if (empty($this->input)) { + if ($this->input == '' || $this->input === false) { $this->done = true; } @@ -380,24 +392,21 @@ function next() $nextCondition = $this->conditionStack[$this->conditionStackCount - 1]; $token = $this->lexerPerformAction($ruleIndex, $nextCondition); - - if ($this->done == true && empty($this->input) == false) { + if ($this->done == true && $this->input != '' && $this->input !== false) { $this->done = false; } if (empty($token) == false) { - return $this->symbols[ - $token - ]; + if (isset($this->symbols[$token])) return $this->symbols[$token]; } else { return null; } } - if (empty($this->input)) { + if ($this->input == '' || $this->input === false) { return $this->eof; } else { - $this->lexerError("Lexical error on line " . ($this->yy->lineNo + 1) . ". Unrecognized text.\n" . $this->showPosition(), new LexerError("", -1, $this->yy->lineNo)); + $this->lexerError(new LexerError("Lexical error on line " . ($this->yy->lineNo + 1) . ". Unrecognized text.\n" . $this->showPosition(), "", -1, $this->yy->lineNo)); return null; } } @@ -481,6 +490,15 @@ function __clone() { $clone->text = $this->text; return $clone; } + + public function value() { + if ($this->text instanceof ParserValue) return $this->text->value(); + return $this->text; + } + + public function __toString() { + return (string) $this->value(); + } } class LexerConditions @@ -552,7 +570,18 @@ public function addAction($a) } } -class ParserError +class JisonError extends \Exception { +} + +class RunTimeError extends JisonError { + public $text; + public $state; + public $symbol; + public $lineNo; + public $loc; +} + +class ParserError extends JisonError { public $text; public $state; @@ -561,8 +590,9 @@ class ParserError public $loc; public $expected; - function __construct($text, $state, $symbol, $lineNo, $loc, $expected) + function __construct($msg,$text, $state, $symbol, $lineNo, $loc, $expected) { + parent::__construct($msg); $this->text = $text; $this->state = $state; $this->symbol = $symbol; @@ -572,14 +602,15 @@ function __construct($text, $state, $symbol, $lineNo, $loc, $expected) } } -class LexerError +class LexerError extends JisonError { public $text; public $token; public $lineNo; - public function __construct($text, $token, $lineNo) + public function __construct($msg, $text, $token, $lineNo) { + parent::__construct($msg); $this->text = $text; $this->token = $token; $this->lineNo = $lineNo;