3,1,3)", "if(1>3,1)", "if([Price]<10,[RSI15]*5,[RV]+min([BS],5))", "if([Price]<10,[RSI15]*5,[RV]+max([BS],5))", "if([Price]<10,[RSI15]*5,[RV]+min(6,5))", "if([Price]<10,[RSI15]*5,[RV]+min(6,quality))", "if([Price]<10,[RV]*5,[BAR]+min(6,quality))+abs(1)+ceil(1)+floor(1)+exp(1)+ln(1)+log(2,1024)+pow(2,3)+sqrt(4)", ); function pretty_print_source($source) { global $static_base; $match = "/\\[([A-Za-z0-9_]*)\\]/"; $replace = // Add help. '$0'; return preg_replace($match, $replace, htmlspecialchars($source)); } class Parser { public static function make_tokens($input) { $output = array(); $position = 0; while ($input != "") { if (preg_match('/^([\s]+)(.*)$/', $input, $pieces)) { $position += strlen($pieces[1]); $input = $pieces[2]; continue; } $token = substr($input, 0, 2); //echo "

possible token ", htmlspecialchars($token), "

"; switch ($token) { case '!=': case '<=': case '>=': case '==': case '||': case '&&': case '??': //echo "

HERE

"; $output[] = array("position" => $position, "original" => $token, "type" => "punctuation", "punctuation" => $token); $position += 2; $input = substr($input, 2); continue 2; } //echo "

and gone

"; $token = $input[0]; //echo "

possible token ", htmlspecialchars($token), "

"; switch ($token) { case '!': case '<': case '>': case '+': case '-': case '*': case '/': case '(': case ')': case '{': case '}': case ',': $output[] = array("position" => $position, "original" => $token, "type" => "punctuation", "punctuation" => $token); $position += 1; $input = substr($input, 1); continue 2; } if (preg_match('/^(\\[([0-9a-zA-Z_]+)\\])(.*)$/', $input, $pieces)) { $output[] = array("position" => $position, "original" => $pieces[1], "type" => 'filter', "filter" => $pieces[2]); $position += strlen($pieces[1]); $input = $pieces[3]; continue; } if (preg_match('/^([a-zA-Z_][0-9a-zA-Z_]*)(.*)$/', $input, $pieces)) { $output[] = array("position" => $position, "original" => $pieces[1], "type" => 'symbol', "symbol" => $pieces[1]); $position += strlen($pieces[1]); $input = $pieces[2]; continue; } if (preg_match('/^([0-9]+(\\.[0-9]+)?)(.*)$/', $input, $pieces)) { $output[] = array("position" => $position, "original" => $pieces[1], "type" => 'number', "number" => $pieces[1]+0); $position += strlen($pieces[1]); $input = $pieces[3]; continue; } throw new Exception("Unexpected character", $position); } return $output; } private $tokens; private function peek_token($skip=0) { if ($skip >= count($this->tokens)) return array("type" => "end", "position" => -1); return $this->tokens[$skip]; } private function pop_token() { $result = $this->peek_token(); array_shift($this->tokens); return $result; } private function report_parse_error($token, $msg = "parse error") { throw new Exception($msg, $token['position']); } private $success; private $allow_top_list; private $result; private $error_message; private $error_location; private $source; public function parse($input) { $this->source = $input; $this->success = false; $this->allow_top_list = true; $this->result = "NULL"; $this->error_message = "Unknown error"; $this->error_location = 0; try { $this->tokens = self::make_tokens($input); $this->result = $this->parse_binary_ops(); $next = $this->peek_token(); if ($next['type'] != "end") $this->report_parse_error($next); $this->success = true; return true; } catch (Exception $ex) { $this->success = false; $this->result = "NULL"; $this->error_message = $ex->getMessage(); $this->error_location = $ex->getCode(); return false; } } public function getSuccess() { return $this->success; } public function getAllowTopList() { return $this->allow_top_list; } public function getResult() { return $this->result; } public function getErrorMessage() { return $this->error_message; } public function getErrorLocation() { return $this->error_location; } public function getTestQueries() { $result = array(); $expr = strtr($this->result, array("$$$" => "price")); $result[] = "SELECT " . $expr . " FROM alerts LEFT JOIN alerts_daily " . "ON symbol=d_symbol AND date=date(timestamp) LIMIT 1"; if ($this->allow_top_list) $result[] = "SELECT " . $expr . " FROM top_list LEFT JOIN alerts_daily " . "ON symbol=d_symbol AND date=date(timestamp) LIMIT 1"; return $result; } private function parse_binary_ops() { $result = $this->parse_binary_ops1(); $next = $this->peek_token(); if ($next['punctuation'] != "??") return $result; array_shift($this->tokens); return "IFNULL(" . $result . "," . $this->parse_binary_ops() . ")"; } private function parse_binary_ops1() { $result = $this->parse_unary_ops(); while (true) { $next = $this->peek_token(); $p = $next['punctuation']; switch ($p) { case '!': case '<': case '>': case '+': case '-': case '*': case '/': case '!=': case '<=': case '>=': case '||': case '&&': $result .= $p; break; case '==': $result .= "="; break; default: break 2; } array_shift($this->tokens); $result .= $this->parse_unary_ops(); } return $result; } private function parse_unary_ops() { $next = $this->peek_token(); $p = $next['punctuation']; if ($p == "-") { array_shift($this->tokens); return "-" . $this->parse_unary_ops(); } else return $this->parse_base(); } private function parse_base() { $next = $this->pop_token(); if ($next['punctuation'] == "(") { $result = "(" . $this->parse_binary_ops() . ")"; $next = $this->pop_token(); if ($next['punctuation'] != ")") $this->report_parse_error($next, "Expecting )"); return $result; } else if ($next['type'] == "number") { return $next['number']; } else if ($next['type'] == "symbol") { switch ($next['symbol']) { case 'null': return 'NULL'; case 'min': return $this->parse_min_max_f("LEAST"); case 'max': return $this->parse_min_max_f("GREATEST"); case 'if': return $this->parse_if_f(); case 'abs': return $this->parse_function("ABS", 1); case 'ceil': return $this->parse_function("CEIL", 1); case 'floor': return $this->parse_function("FLOOR", 1); case 'exp': return $this->parse_function("EXP", 1); case 'ln': return $this->parse_function("LN", 1); case 'log': // The first argument is the base. The first argument is optional // in mysql, but I didn't see the value in that. return $this->parse_function("LOG", 2); case 'pow': // This is strange. pow(-2,0.5) returns nan, not null. // if(pow(-2,0.5), "true","false") returns true, not false. // I haven't see anything else like this. // I'm not sure what happens to TI Pro, etc, if nan is returned. return $this->parse_function("POW", 2); case 'sqrt': return $this->parse_function("SQRT", 1); case 'quality': $this->allow_top_list = false; // fall through! case 'price': case 'last': case 'most_recent_close': case 'expected_open': case 't_high': case 't_low': case 'v_up_1': case 'v_up_5': case 'v_up_10': case 'v_up_15': case 'v_up_30': case 'sma_2_8': case 'sma_2_20': case 'sma_2_200': case 'sma_5_8': case 'sma_5_20': case 'sma_5_200': case 'sma_15_8': case 'sma_15_20': case 'sma_15_200': case 'sma_60_8': case 'sma_60_20': case 'sma_60_200': case 'std_5_20': case 'std_15_20': case 'std_60_20': case 'vwap': case 'p_up_1': case 'v_up_1': // alerts_daily case 'sma_200': case 'sma_50': case 'sma_20': case 'sma_8': case 'high_p': case 'low_p': case 'close_p': case 'open_p': case 'volume_p': case 'high_52w': case 'low_52w': case 'std_20': case 'consolidation_high': case 'consolidation_low': case 'last_price': case 'high_life': case 'low_life': case 'prev_put_volume': case 'prev_call_volume': case 'avg_put_call_volume': case 'high_5d': case 'low_5d': case 'close_5d': case 'high_10d': case 'low_10d': case 'close_10d': case 'high_20d': case 'low_20d': case 'close_20d': case 'adx_14d': case 'pdi_14d': case 'mdi_14d': case 'shares_per_print': case 'short_interest': case 'vf_buy': case 'vf_sell': case 'vf_expect': return $next['symbol']; case 'seconds_after_open' : return '(TIMESTAMPDIFF(SECOND, date, timestamp)-23400)'; } $this->report_parse_error($next, "Unknown symbol"); } else if ($next['type'] == "filter") { global $window_filters; $info = $window_filters[$next['filter']]; if (!$info) $this->report_parse_error($next, "Unknown filter"); if ($info['alerts_only']) $this->allow_top_list = false; // This might include $$$ return "(" . $info['sql'] . ")"; } $this->report_parse_error($next); } private function parse_min_max_f($f_name) { $result = $f_name . "("; $next = $this->pop_token(); if ($next['punctuation'] != "(") $this->report_parse_error($next, "Expecting ("); $result .= $this->parse_binary_ops(); $next = $this->pop_token(); if ($next['punctuation'] != ",") $this->report_parse_error($next, "Expecting ,"); $result .= ","; $result .= $this->parse_binary_ops(); while (true) { $next = $this->pop_token(); if ($next['punctuation'] == ")") { return $result . ")"; } else if ($next['punctuation'] != ",") { $this->report_parse_error($next, "Expecting , or )"); } $result .= ","; $result .= $this->parse_binary_ops(); } } private function parse_if_f() { $result = "if("; $next = $this->pop_token($tokens); if ($next['punctuation'] != "(") $this->report_parse_error($next, "Expecting ("); $result .= $this->parse_binary_ops(); $next = $this->pop_token(); if ($next['punctuation'] != ",") $this->report_parse_error($next, "Expecting ,"); $result .= ","; $result .= $this->parse_binary_ops(); $next = $this->pop_token(); if ($next['punctuation'] == ")") { return $result . ",NULL)"; } else if ($next['punctuation'] != ",") { $this->report_parse_error($next, "Expecting , or )"); } $result .= ","; $result .= $this->parse_binary_ops(); $next = $this->pop_token($tokens); if ($next['punctuation'] != ")") { $this->report_parse_error($next, "Expecting )"); } return $result . ")"; } private function parse_function($f_name, $arg_count) { $result = $f_name . "("; $next = $this->pop_token(); if ($next['punctuation'] != "(") $this->report_parse_error($next, "Expecting ("); while (true) { $result .= $this->parse_binary_ops(); $next = $this->pop_token(); $p = $next['punctuation']; $arg_count--; if (!$arg_count) break; if ($p != ',') $this->report_parse_error($next, "Expecting ,"); $result .= ","; } if ($p != ')') $this->report_parse_error($next, "Expecting )"); return $result . ")"; } // Make these memeber variables rather than function parameters so you // can reference them by name. public $user_id; public $internal_code; public $description; public $units; public $format; public $graphics; public function getInsertQuery() { return "REPLACE INTO user_filters(user_id, internal_code, source, sql_code, description, units, top_list, format, graphics) values (" . ($this->user_id + 0) . ", '" . mysql_escape_string($this->internal_code) . "', '" . mysql_escape_string($this->source) . "', '" . mysql_escape_string($this->result) . "', '" . mysql_escape_string($this->description) . "', '" . mysql_escape_string($this->units) . "', '" . ($this->allow_top_list?"Y":"N") . "', '" . mysql_escape_string($this->format) . "', '" . mysql_escape_string($this->graphics) . "')"; } public function safeSave() { $list = $this->getTestQueries(); $list[] = $this->getInsertQuery(); foreach ($list as $sql) { $result = mysql_query($sql); if (!$result) { error_log('safeSave(' . $source . ') => ' . $sql . ' => ' . mysql_error()); return false; } } return true; } } ?>