|
|
|
@ -6,7 +6,7 @@ class JSM
|
|
|
|
|
static private $modules = array();
|
|
|
|
|
static private $consts = array();
|
|
|
|
|
|
|
|
|
|
private $base_dir;
|
|
|
|
|
private $base_dirs = array();
|
|
|
|
|
private $file;
|
|
|
|
|
private $scope_vars = array(array());
|
|
|
|
|
private $includes = array();
|
|
|
|
@ -16,29 +16,34 @@ class JSM
|
|
|
|
|
private $cur_modules = array();
|
|
|
|
|
private $args_parser;
|
|
|
|
|
|
|
|
|
|
function __construct($base_dir, $file)
|
|
|
|
|
function __construct($base_dirs/*can be a string for BC*/, $file)
|
|
|
|
|
{
|
|
|
|
|
//for rand constants
|
|
|
|
|
mt_srand();
|
|
|
|
|
|
|
|
|
|
$this->base_dir = rtrim(jsm_normalize_path($base_dir, true/*nix*/), '/');
|
|
|
|
|
if(is_string($base_dirs))
|
|
|
|
|
$base_dirs = array($base_dirs);
|
|
|
|
|
$this->base_dirs = array_map(
|
|
|
|
|
function($base_dir) { return rtrim(jsm_normalize_path($base_dir, true/*nix*/), '/'); },
|
|
|
|
|
$base_dirs
|
|
|
|
|
);
|
|
|
|
|
$this->file = $file;
|
|
|
|
|
$this->args_parser = new JSM_ArgsParser();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getRootFile()
|
|
|
|
|
function getRootFile() : string
|
|
|
|
|
{
|
|
|
|
|
return $this->file;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getModule($file)
|
|
|
|
|
static function getModule($file) : JSM_Module
|
|
|
|
|
{
|
|
|
|
|
if(isset(self::$modules[$file]))
|
|
|
|
|
return self::$modules[$file];
|
|
|
|
|
throw new Exception("Module for '$file' not found");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _extractModule($file)
|
|
|
|
|
function _extractModule($file) : JSM_Module
|
|
|
|
|
{
|
|
|
|
|
$txt = file_get_contents($file);
|
|
|
|
|
if($txt === false)
|
|
|
|
@ -49,11 +54,12 @@ class JSM
|
|
|
|
|
$m->node = new JSM_MacroDefNode($file/*using file as name*/);
|
|
|
|
|
$this->_pushCurrentModule($m);
|
|
|
|
|
|
|
|
|
|
$txt = $this->_fixJunk($txt);
|
|
|
|
|
$txt = $this->_removeBlockComments($txt);
|
|
|
|
|
$txt = $this->_removeLineComments($txt);
|
|
|
|
|
$txt = self::_fixJunk($txt);
|
|
|
|
|
$txt = self::_removeBlockComments($txt);
|
|
|
|
|
$txt = self::_removeLineComments($txt);
|
|
|
|
|
$txt = $this->_processIncludes($file, $txt);
|
|
|
|
|
$txt = $this->_extractScriptDefs($file, $txt);
|
|
|
|
|
|
|
|
|
|
$this->_parseDefBody($m->node, $file, $txt);
|
|
|
|
|
|
|
|
|
|
$this->_popCurrentModule();
|
|
|
|
@ -74,7 +80,7 @@ class JSM
|
|
|
|
|
return $m;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _currentModule()
|
|
|
|
|
function _currentModule() : JSM_Module
|
|
|
|
|
{
|
|
|
|
|
$m = end($this->cur_modules);
|
|
|
|
|
if(!$m)
|
|
|
|
@ -82,12 +88,13 @@ class JSM
|
|
|
|
|
return $m;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getCurrentModule()
|
|
|
|
|
function getCurrentModule() : JSM_Module
|
|
|
|
|
{
|
|
|
|
|
return $this->_currentModule();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function process()
|
|
|
|
|
//NOTE: returns [json, module]
|
|
|
|
|
function process() : array
|
|
|
|
|
{
|
|
|
|
|
$m = $this->_extractModule($this->file);
|
|
|
|
|
|
|
|
|
@ -97,9 +104,17 @@ class JSM
|
|
|
|
|
$this->defs = $m->defs;
|
|
|
|
|
$this->includes = $m->includes;
|
|
|
|
|
|
|
|
|
|
$this->_pushBuffer();
|
|
|
|
|
$m->node->call($this);
|
|
|
|
|
$res = $this->_popBuffer();
|
|
|
|
|
$res = '';
|
|
|
|
|
if($m->node->raw_text_node)
|
|
|
|
|
{
|
|
|
|
|
$res = $m->node->raw_text_node->txt;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
$this->_pushBuffer();
|
|
|
|
|
$m->node->call($this);
|
|
|
|
|
$res = $this->_popBuffer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if($this->buffers)
|
|
|
|
|
throw new Exception("Non closed buffers " . sizeof($this->buffers));
|
|
|
|
@ -228,61 +243,63 @@ class JSM
|
|
|
|
|
throw new Exception("Var '$name' not resolved");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function _fixJunk($txt)
|
|
|
|
|
private static function _fixJunk(string $txt) : string
|
|
|
|
|
{
|
|
|
|
|
//windows eol
|
|
|
|
|
$txt = str_replace("\r\n", "\n", $txt);
|
|
|
|
|
return $txt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function _removeBlockComments($txt)
|
|
|
|
|
private static function _removeBlockComments(string $txt) : string
|
|
|
|
|
{
|
|
|
|
|
if(strpos($txt, '/*') === false)
|
|
|
|
|
return $txt;
|
|
|
|
|
$regex = '~/\*.*?\*/~s';
|
|
|
|
|
$txt = preg_replace_callback(
|
|
|
|
|
$regex,
|
|
|
|
|
'~/\*.*?\*/~s',
|
|
|
|
|
//preserve the new lines for better error reporting
|
|
|
|
|
function($m) { return str_repeat("\n", substr_count($m[0], "\n")); },
|
|
|
|
|
$txt);
|
|
|
|
|
return $txt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function _removeLineComments($txt)
|
|
|
|
|
private static function _removeLineComments(string $txt) : string
|
|
|
|
|
{
|
|
|
|
|
//TODO: it's not robust enough, note a hack for URL addresses
|
|
|
|
|
$txt = preg_replace("~\s*(?<!:)//.*~", "\n", $txt);
|
|
|
|
|
$txt = preg_replace_callback(
|
|
|
|
|
'~\s*(?<!:)//.*~',
|
|
|
|
|
//preserve the new lines for better error reporting
|
|
|
|
|
function($m) { return str_repeat("\n", substr_count($m[0], "\n")); },
|
|
|
|
|
$txt);
|
|
|
|
|
return $txt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*private */function _relpathCallback(array $m, $curr_dir)
|
|
|
|
|
/*private */function _relpathCallback(array $m, string $curr_dir) : string
|
|
|
|
|
{
|
|
|
|
|
//1) checking if such a file exists and normalizing all .. in the path
|
|
|
|
|
$full_path = realpath($curr_dir . '/' . $m[1]);
|
|
|
|
|
if(!$full_path)
|
|
|
|
|
throw new Exception("Bad relative path '$m[1]'");
|
|
|
|
|
|
|
|
|
|
//2) now turning the path into *nix alike
|
|
|
|
|
$full_path = jsm_normalize_path($full_path, true/*nix*/);
|
|
|
|
|
$rel_path = ltrim(str_replace($this->base_dir, '', $full_path), '/');
|
|
|
|
|
$rel_path = jsm_make_rel_path($this->base_dirs, $full_path);
|
|
|
|
|
|
|
|
|
|
return $rel_path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*private */function _relconfCallback(array $m, $curr_dir)
|
|
|
|
|
/*private */function _relconfCallback(array $m, string $curr_dir) : string
|
|
|
|
|
{
|
|
|
|
|
//1) checking if such a file exists and normalizing all .. in the path
|
|
|
|
|
$full_path = realpath($curr_dir . '/' . $m[1] . '.conf.js');
|
|
|
|
|
if(!$full_path)
|
|
|
|
|
throw new Exception("Bad relative path '$m[1]'" . ($curr_dir . '/' . $m[1]));
|
|
|
|
|
|
|
|
|
|
$full_path = jsm_normalize_path($full_path, true/*nix*/);
|
|
|
|
|
$rel_path = ltrim(str_replace($this->base_dir, '', $full_path), '/');
|
|
|
|
|
$rel_path = jsm_make_rel_path($this->base_dirs, $full_path);
|
|
|
|
|
$rel_path = str_replace('.conf.js', '', $rel_path);
|
|
|
|
|
|
|
|
|
|
return "@$rel_path";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function _processRelpaths($file, $txt)
|
|
|
|
|
private function _processRelpaths(string $file, string $txt) : string
|
|
|
|
|
{
|
|
|
|
|
//normalizing all file paths
|
|
|
|
|
if(strpos($txt, './') !== false)
|
|
|
|
@ -301,21 +318,19 @@ class JSM
|
|
|
|
|
return $txt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*private */function _includesCallback(array $m, $curr_file)
|
|
|
|
|
/*private */function _includesCallback(string $inc_path, string $prefix, string $curr_file) : string
|
|
|
|
|
{
|
|
|
|
|
$file_name = $m[1];
|
|
|
|
|
$dir = ($file_name[0] == '/') ? $this->base_dir : dirname($curr_file);
|
|
|
|
|
$file = jsm_normalize_path($dir . '/' . $m[1]);
|
|
|
|
|
$file = jsm_resolve_inc_path($this->base_dirs, $curr_file, $inc_path);
|
|
|
|
|
|
|
|
|
|
$cm = $this->_currentModule();
|
|
|
|
|
|
|
|
|
|
if(!isset(self::$modules[$file]))
|
|
|
|
|
{
|
|
|
|
|
if($this->_hasExtension($file, '.js'))
|
|
|
|
|
if(self::_hasExtension($file, '.js'))
|
|
|
|
|
{
|
|
|
|
|
self::$modules[$file] = $this->_extractModule($file);
|
|
|
|
|
}
|
|
|
|
|
else if($this->_hasExtension($file, '.php'))
|
|
|
|
|
else if(self::_hasExtension($file, '.php'))
|
|
|
|
|
{
|
|
|
|
|
include_once($file);
|
|
|
|
|
//special mark for PHP include
|
|
|
|
@ -335,8 +350,11 @@ class JSM
|
|
|
|
|
{
|
|
|
|
|
foreach($m->includes as $f => $_)
|
|
|
|
|
$cm->includes[$f] = 1;
|
|
|
|
|
foreach($m->defs as $n => $d)
|
|
|
|
|
|
|
|
|
|
foreach($m->defs as $_n => $d)
|
|
|
|
|
{
|
|
|
|
|
$n = $prefix.$_n;
|
|
|
|
|
|
|
|
|
|
if(isset($cm->defs[$n]) && $cm->defs[$n] !== $d)
|
|
|
|
|
throw new Exception("Def '$n' is already defined in '{$cm->file}' (check {$cm->defs[$n]->file})");
|
|
|
|
|
$cm->defs[$n] = $d;
|
|
|
|
@ -348,68 +366,53 @@ class JSM
|
|
|
|
|
return $need_include_macro ? "<%INC($file)%>" : '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function _processIncludes($file, $txt)
|
|
|
|
|
private function _processIncludes(string $file, string $txt) : string
|
|
|
|
|
{
|
|
|
|
|
if(strpos($txt, 'INC') !== false)
|
|
|
|
|
{
|
|
|
|
|
$self = $this;
|
|
|
|
|
$txt = preg_replace_callback(
|
|
|
|
|
'~<%\s*INC\s*\(\s*"([^"]+)"\s*\)\s*%>~',
|
|
|
|
|
function($m) use($self, $file) { return $self->_includesCallback($m, $file); },
|
|
|
|
|
'~<%\s*INC\s*\(\s*"([^"]+)"\s*(?:,\s*"([^"]+)")?\s*\)\s*%>~',
|
|
|
|
|
function($m) use($self, $file) { return $self->_includesCallback($m[1], isset($m[2]) ? $m[2] : '', $file); },
|
|
|
|
|
$txt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $txt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _isIncluded($file)
|
|
|
|
|
function _isIncluded(string $file) : bool
|
|
|
|
|
{
|
|
|
|
|
$file = jsm_normalize_path($file);
|
|
|
|
|
return isset($this->includes[$file]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _hasExtension($file, $ext)
|
|
|
|
|
static function _hasExtension(string $file, string $ext) : bool
|
|
|
|
|
{
|
|
|
|
|
return strpos($file, $ext, strlen($file) - strlen($ext) - 1) !== false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static function getDeps($base_dir, $file)
|
|
|
|
|
static function getDeps(array $base_dirs, string $file) : array
|
|
|
|
|
{
|
|
|
|
|
$deps = array();
|
|
|
|
|
self::_getDeps($base_dir, $file, $deps);
|
|
|
|
|
self::_getDeps($base_dirs, $file, $deps);
|
|
|
|
|
return $deps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static private function _extractDeps($txt)
|
|
|
|
|
static private function _extractDeps(string $txt) : array
|
|
|
|
|
{
|
|
|
|
|
$dep_files = array();
|
|
|
|
|
if(preg_match_all('~<%\s*((?:INC)|(?:BHL))\s*\(\s*"([^\n]+)~', $txt, $ms))
|
|
|
|
|
if(preg_match_all('~<%\s*INC\s*\(\s*"([^\n]+)~', $txt, $ms))
|
|
|
|
|
{
|
|
|
|
|
foreach($ms[1] as $idx => $type)
|
|
|
|
|
foreach($ms[1] as $raw_dep)
|
|
|
|
|
{
|
|
|
|
|
$raw_dep = $ms[2][$idx];
|
|
|
|
|
|
|
|
|
|
$is_bhl = $type === "BHL";
|
|
|
|
|
if($is_bhl)
|
|
|
|
|
{
|
|
|
|
|
$bhl_imps = explode(',', str_replace('"', '', $raw_dep));
|
|
|
|
|
foreach($bhl_imps as $bhl_imp)
|
|
|
|
|
{
|
|
|
|
|
if($bhl_imp)
|
|
|
|
|
$dep_files[$bhl_imp . '.bhl'] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
$dep_file = substr($raw_dep, 0, strpos($raw_dep, '"'));
|
|
|
|
|
$dep_files[$dep_file] = false;
|
|
|
|
|
}
|
|
|
|
|
$dep_file = substr($raw_dep, 0, strpos($raw_dep, '"'));
|
|
|
|
|
$dep_files[$dep_file] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $dep_files;
|
|
|
|
|
return array_keys($dep_files);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static private function _getDeps($base_dir, $file, &$deps)
|
|
|
|
|
static private function _getDeps(array $base_dirs, string $file, array &$deps)
|
|
|
|
|
{
|
|
|
|
|
static $cache = array();
|
|
|
|
|
|
|
|
|
@ -424,41 +427,34 @@ class JSM
|
|
|
|
|
|
|
|
|
|
$extracted_deps = self::_extractDeps($txt);
|
|
|
|
|
|
|
|
|
|
foreach($extracted_deps as $inc => $is_bhl)
|
|
|
|
|
foreach($extracted_deps as $inc)
|
|
|
|
|
{
|
|
|
|
|
$dir = ($inc[0] == '/') ? $base_dir : dirname($file);
|
|
|
|
|
$dep_file = jsm_normalize_path($dir . "/" . $inc);
|
|
|
|
|
$local_deps[] = array($is_bhl, $dep_file);
|
|
|
|
|
$dep_file = jsm_resolve_inc_path($base_dirs, $file, $inc);
|
|
|
|
|
$local_deps[] = $dep_file;
|
|
|
|
|
$cache[$file] = $local_deps;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
$local_deps = $cache[$file];
|
|
|
|
|
|
|
|
|
|
foreach($local_deps as $local_dep)
|
|
|
|
|
foreach($local_deps as $dep_file)
|
|
|
|
|
{
|
|
|
|
|
list($is_bhl, $dep_file) = $local_dep;
|
|
|
|
|
|
|
|
|
|
if(in_array($dep_file, $deps))
|
|
|
|
|
continue;
|
|
|
|
|
$deps[] = $dep_file;
|
|
|
|
|
|
|
|
|
|
//NOTE: bhl deps are shallow
|
|
|
|
|
if(!$is_bhl)
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
self::_getDeps($base_dir, $dep_file, $deps);
|
|
|
|
|
}
|
|
|
|
|
catch(Exception $e)
|
|
|
|
|
{
|
|
|
|
|
throw new Exception("Bad deps for '$file' : " . $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
self::_getDeps($base_dirs, $dep_file, $deps);
|
|
|
|
|
}
|
|
|
|
|
catch(Exception $e)
|
|
|
|
|
{
|
|
|
|
|
throw new Exception("Bad deps for '$file' : " . $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function _extractDefNameAndArgs($full_name)
|
|
|
|
|
private function _extractDefNameAndArgs(string $full_name) : array
|
|
|
|
|
{
|
|
|
|
|
$pos = strpos($full_name, '(');
|
|
|
|
|
if($pos == -1)
|
|
|
|
@ -471,14 +467,14 @@ class JSM
|
|
|
|
|
return array($name, $args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function _parseMacroDefArgs($args_str)
|
|
|
|
|
private function _parseMacroDefArgs(string $args_str)
|
|
|
|
|
{
|
|
|
|
|
if(!$args_str)
|
|
|
|
|
return array();
|
|
|
|
|
return $this->args_parser->parseDef($args_str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function nodes2str($nodes_or_str)
|
|
|
|
|
function nodes2str($nodes_or_str) : string
|
|
|
|
|
{
|
|
|
|
|
$str = '';
|
|
|
|
|
if(is_array($nodes_or_str))
|
|
|
|
@ -495,13 +491,13 @@ class JSM
|
|
|
|
|
return $str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _processMacroArgs($arg_nodes_or_str)
|
|
|
|
|
function _processMacroArgs($arg_nodes_or_str) : array
|
|
|
|
|
{
|
|
|
|
|
$str = $this->nodes2str($arg_nodes_or_str);
|
|
|
|
|
return $this->_parseMacroArgsStr($str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _parseMacroArgsStr($orig_args_str)
|
|
|
|
|
function _parseMacroArgsStr($orig_args_str) : array
|
|
|
|
|
{
|
|
|
|
|
static $cache = array();
|
|
|
|
|
|
|
|
|
@ -513,8 +509,8 @@ class JSM
|
|
|
|
|
if($may_cache && isset($cache[$orig_args_str]))
|
|
|
|
|
return $cache[$orig_args_str];
|
|
|
|
|
|
|
|
|
|
$args_str = $this->_removeBlockComments($orig_args_str);
|
|
|
|
|
$args_str = $this->_removeLineComments($args_str);
|
|
|
|
|
$args_str = self::_removeBlockComments($orig_args_str);
|
|
|
|
|
$args_str = self::_removeLineComments($args_str);
|
|
|
|
|
$args_str = rtrim($args_str, ',');
|
|
|
|
|
|
|
|
|
|
$res = $this->args_parser->parseInvoke($args_str);
|
|
|
|
@ -525,9 +521,14 @@ class JSM
|
|
|
|
|
return $res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function _extractScriptDefs($file, $txt)
|
|
|
|
|
private function _extractScriptDefs(string $file, string $txt) : string
|
|
|
|
|
{
|
|
|
|
|
if(strpos($txt, 'def') === false)
|
|
|
|
|
$strpos = strpos($txt, 'def');
|
|
|
|
|
if($strpos === false)
|
|
|
|
|
return $txt;
|
|
|
|
|
|
|
|
|
|
//let's extra check if newline symbols preceed 'def'
|
|
|
|
|
if($strpos > 0 && !($txt[$strpos-1] === "\n" || $txt[$strpos-1] === "\r"))
|
|
|
|
|
return $txt;
|
|
|
|
|
|
|
|
|
|
//NOTE: make it more robust
|
|
|
|
@ -566,8 +567,11 @@ class JSM
|
|
|
|
|
return $txt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function _replaceVarsWithMacro($file, $txt)
|
|
|
|
|
private function _replaceVarsWithMacro(string $file, string $txt) : string
|
|
|
|
|
{
|
|
|
|
|
if(strpos($txt, '$') === false)
|
|
|
|
|
return $txt;
|
|
|
|
|
|
|
|
|
|
//simple vals
|
|
|
|
|
$txt = preg_replace(
|
|
|
|
|
'~\{(\$[a-zA-Z0-9_]+)\}~',
|
|
|
|
@ -583,13 +587,16 @@ class JSM
|
|
|
|
|
return $txt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function _parseDefBody(JSM_MacroDefNode $node, $file, $txt)
|
|
|
|
|
private function _parseDefBody(JSM_MacroDefNode $node, string $file, string $txt)
|
|
|
|
|
{
|
|
|
|
|
$txt = $this->_replaceVarsWithMacro($file, $txt);
|
|
|
|
|
|
|
|
|
|
//let's exit early if there are no macro calls
|
|
|
|
|
if(strpos($txt, '<%') === false)
|
|
|
|
|
{
|
|
|
|
|
$node->addChild(new JSM_TextNode($txt));
|
|
|
|
|
$raw_text_node = new JSM_TextNode($txt);
|
|
|
|
|
$node->raw_text_node = $raw_text_node;
|
|
|
|
|
$node->addChild($raw_text_node);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -616,7 +623,7 @@ class JSM
|
|
|
|
|
|
|
|
|
|
$mcall = $this->_newCall($name);
|
|
|
|
|
if($mcall === null)
|
|
|
|
|
throw new Exception("No node for '$name'");
|
|
|
|
|
throw new Exception("No macro for '$name'");
|
|
|
|
|
|
|
|
|
|
$this->_pushNode($mcall);
|
|
|
|
|
}
|
|
|
|
@ -627,7 +634,7 @@ class JSM
|
|
|
|
|
throw new Exception("Non paired macro ending");
|
|
|
|
|
$tmp_node = $this->_node(false/*non strict*/);
|
|
|
|
|
if($tmp_node == null)
|
|
|
|
|
throw new Exception("No current node(prev. node " . $mcall->name . ")");
|
|
|
|
|
throw new Exception("No current macro (prev. " . $mcall->name . ")");
|
|
|
|
|
$tmp_node->addChild($mcall);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
@ -645,7 +652,7 @@ class JSM
|
|
|
|
|
$this->_popNode();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _newCall($name)
|
|
|
|
|
function _newCall(string $name)
|
|
|
|
|
{
|
|
|
|
|
$func = "macro_ex_{$name}";
|
|
|
|
|
if(is_callable($func))
|
|
|
|
@ -658,7 +665,7 @@ class JSM
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _getMacro($name)
|
|
|
|
|
function _getMacro(string $name) : JSM_MacroUserNode
|
|
|
|
|
{
|
|
|
|
|
if(isset($this->defs[$name]))
|
|
|
|
|
return $this->defs[$name];
|
|
|
|
@ -676,22 +683,23 @@ class JSM
|
|
|
|
|
|
|
|
|
|
class JSM_Module
|
|
|
|
|
{
|
|
|
|
|
var $file = '';
|
|
|
|
|
var string $file = '';
|
|
|
|
|
var $defs = array();
|
|
|
|
|
//NOTE: all includes (even those included from other modules)
|
|
|
|
|
var $includes = array();
|
|
|
|
|
var $node = null;
|
|
|
|
|
|
|
|
|
|
function __construct($file)
|
|
|
|
|
function __construct(string $file)
|
|
|
|
|
{
|
|
|
|
|
$this->file = $file;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getIncludes()
|
|
|
|
|
function getIncludes() : array
|
|
|
|
|
{
|
|
|
|
|
return $this->includes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getFile()
|
|
|
|
|
function getFile() : string
|
|
|
|
|
{
|
|
|
|
|
return $this->file;
|
|
|
|
|
}
|
|
|
|
@ -702,6 +710,11 @@ interface JSM_MacroNode
|
|
|
|
|
function call(JSM $jsm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface JSM_MacroUserNode extends JSM_MacroNode
|
|
|
|
|
{
|
|
|
|
|
function setUserArgs(array $args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface JSM_MacroInternalNode extends JSM_MacroNode
|
|
|
|
|
{
|
|
|
|
|
function addChild(JSM_MacroNode $node);
|
|
|
|
@ -761,14 +774,14 @@ class JSM_MacroCallNode implements JSM_MacroInternalNode
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class JSM_MacroPHPNode implements JSM_MacroNode
|
|
|
|
|
class JSM_MacroPHPNode implements JSM_MacroUserNode
|
|
|
|
|
{
|
|
|
|
|
var $func;
|
|
|
|
|
|
|
|
|
|
var $refl;
|
|
|
|
|
var $user_args = '';
|
|
|
|
|
|
|
|
|
|
var $is_builtin;
|
|
|
|
|
var $is_global;
|
|
|
|
|
var $is_eval_args; //arguments will be evaluated and eval result will be passed
|
|
|
|
|
var $is_raw_args; //arguments result string will be passed, not evaluated
|
|
|
|
|
var $is_node_args; //argument nodes will be passed as is
|
|
|
|
@ -783,7 +796,7 @@ class JSM_MacroPHPNode implements JSM_MacroNode
|
|
|
|
|
$this->refl = new ReflectionFunction($php_func);
|
|
|
|
|
$doc = $this->refl->getDocComment();
|
|
|
|
|
|
|
|
|
|
$this->is_builtin = strpos($doc, '@builtin') !== false;
|
|
|
|
|
$this->is_global = strpos($doc, '@global') !== false;
|
|
|
|
|
$this->is_raw_args = strpos($doc, '@raw_args') !== false;
|
|
|
|
|
$this->is_eval_args = strpos($doc, '@eval_args') !== false;
|
|
|
|
|
$this->is_no_args = strpos($doc, '@no_args') !== false;
|
|
|
|
@ -804,8 +817,9 @@ class JSM_MacroPHPNode implements JSM_MacroNode
|
|
|
|
|
function call(JSM $jsm)
|
|
|
|
|
{
|
|
|
|
|
//NOTE: making sure file containing PHP macro was actually included by this file
|
|
|
|
|
if(!$this->is_builtin && !$jsm->_isIncluded($this->refl->getFileName()))
|
|
|
|
|
throw new Exception("Not found");
|
|
|
|
|
// if it's not a @global one
|
|
|
|
|
if(!$this->is_global && !$jsm->_isIncluded($this->refl->getFileName()))
|
|
|
|
|
throw new Exception("Macro source file was not included by config file itself: ".$this->refl->getFileName());
|
|
|
|
|
|
|
|
|
|
$named = false;
|
|
|
|
|
$args = array();
|
|
|
|
@ -872,13 +886,14 @@ class JSM_MacroPHPNode implements JSM_MacroNode
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class JSM_MacroDefNode implements JSM_MacroInternalNode
|
|
|
|
|
class JSM_MacroDefNode implements JSM_MacroInternalNode,JSM_MacroUserNode
|
|
|
|
|
{
|
|
|
|
|
var $name = '';
|
|
|
|
|
var $file = '';
|
|
|
|
|
var $decl_args = array();
|
|
|
|
|
var $node_args = array();
|
|
|
|
|
var $children = array();
|
|
|
|
|
var $raw_text_node = null;
|
|
|
|
|
|
|
|
|
|
function __construct($name, $decl_args = array(), $file = '')
|
|
|
|
|
{
|
|
|
|
@ -1115,7 +1130,6 @@ class JSM_Eval
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new Exception("Unknown func " . $func);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -1337,8 +1351,8 @@ class JSM_Expr
|
|
|
|
|
case ' ': case "\r": case "\t": case "\n" : break;
|
|
|
|
|
case '(': $token = array(self::T_LParen, $c); $binary = false; break;
|
|
|
|
|
case ')': $token = array(self::T_RParen, $c); $binary = true; break;
|
|
|
|
|
case '+': $token = $this->_getFuncToken(($binary ? '+' : 'u+'), $c); $binary = false; break;
|
|
|
|
|
case '-': $token = $this->_getFuncToken(($binary ? '-' : 'u-'), $c); $binary = false; break;
|
|
|
|
|
case '+': $token = $this->_getFuncToken(($binary ? '+' : 'u+')); $binary = false; break;
|
|
|
|
|
case '-': $token = $this->_getFuncToken(($binary ? '-' : 'u-')); $binary = false; break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
//try to peek the next symbol and check if such an operator exists
|
|
|
|
@ -1393,7 +1407,7 @@ function jsm_lex($string, array $tokenMap)
|
|
|
|
|
{
|
|
|
|
|
foreach($tokenMap as $regex => $token)
|
|
|
|
|
{
|
|
|
|
|
if(preg_match($regex, $string, $matches, null, $offset))
|
|
|
|
|
if(preg_match($regex, $string, $matches, 0, $offset))
|
|
|
|
|
{
|
|
|
|
|
$tokens[] = array(
|
|
|
|
|
$token, // token ID (e.g. T_FIELD_SEPARATOR)
|
|
|
|
@ -1438,6 +1452,36 @@ function jsm_normalize_path($path, $unix=null/*null means try to guess*/)
|
|
|
|
|
return $res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function jsm_resolve_inc_path(array $base_dirs, $curr_file, $inc_path)
|
|
|
|
|
{
|
|
|
|
|
return jsm_normalize_path(
|
|
|
|
|
$inc_path[0] == '/' ?
|
|
|
|
|
jsm_make_full_path($base_dirs, $inc_path) :
|
|
|
|
|
dirname($curr_file) . '/' . $inc_path
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function jsm_make_full_path(array $base_dirs, $rel_path)
|
|
|
|
|
{
|
|
|
|
|
foreach($base_dirs as $dir)
|
|
|
|
|
if(is_file($dir . '/' . $rel_path))
|
|
|
|
|
return $dir . '/' . $rel_path;
|
|
|
|
|
throw new Exception("No file for relative path '$rel_path'");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function jsm_make_rel_path(array $base_dirs, $full_path)
|
|
|
|
|
{
|
|
|
|
|
$full_path = jsm_normalize_path($full_path, true/*nix*/);
|
|
|
|
|
|
|
|
|
|
foreach($base_dirs as $base_dir)
|
|
|
|
|
{
|
|
|
|
|
$rel_path = str_replace($base_dir, '', $full_path);
|
|
|
|
|
if(strlen($rel_path) < strlen($full_path))
|
|
|
|
|
return ltrim($rel_path, '/');
|
|
|
|
|
}
|
|
|
|
|
throw new Exception("File '$full_path' not mapped to any base dir");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function jsm_eval_string(JSM $jsm, $expr_str)
|
|
|
|
|
{
|
|
|
|
|
$expr = new JSM_Expr();
|
|
|
|
@ -1551,6 +1595,8 @@ class JSM_ArgsParser
|
|
|
|
|
{
|
|
|
|
|
$this->skip_whitespace();
|
|
|
|
|
$value = $this->parse_arg_value();
|
|
|
|
|
if(isset($out[$name]))
|
|
|
|
|
throw new Exception("Argument '{$name}' is already defined in def macro");
|
|
|
|
|
$out[$name] = $value;
|
|
|
|
|
$this->skip_whitespace();
|
|
|
|
|
$ch = $this->next();
|
|
|
|
@ -1736,10 +1782,11 @@ class JSM_ArgsParser
|
|
|
|
|
++$this->c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$str_num = substr($this->in, $start, $this->c - $start);
|
|
|
|
|
if($is_float)
|
|
|
|
|
return 1*substr($this->in, $start, $this->c - $start);
|
|
|
|
|
return floatval($str_num);
|
|
|
|
|
else
|
|
|
|
|
return (int)(1*substr($this->in, $start, $this->c - $start));
|
|
|
|
|
return intval($str_num);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function _error($error)
|
|
|
|
@ -1780,22 +1827,20 @@ class JSM_ArgsParser
|
|
|
|
|
//////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @builtin @raw_args
|
|
|
|
|
* @global @raw_args
|
|
|
|
|
*/
|
|
|
|
|
function macro_DEP($jsm)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @builtin @raw_args
|
|
|
|
|
*/
|
|
|
|
|
function macro_INC($jsm, $file)
|
|
|
|
|
function macro_INC($jsm, $file_and_prefix)
|
|
|
|
|
{
|
|
|
|
|
$items = explode(',', $file_and_prefix);
|
|
|
|
|
//NOTE: we don't care about prefix here, it's handled
|
|
|
|
|
// during includes processing
|
|
|
|
|
$file = trim($items[0]);
|
|
|
|
|
$m = $jsm->getModule($file);
|
|
|
|
|
$m->node->call($jsm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @builtin @raw_args
|
|
|
|
|
* @global @raw_args
|
|
|
|
|
*/
|
|
|
|
|
function macro_LET($jsm, $txt_args)
|
|
|
|
|
{
|
|
|
|
@ -1805,7 +1850,7 @@ function macro_LET($jsm, $txt_args)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @builtin @raw_args
|
|
|
|
|
* @global @raw_args
|
|
|
|
|
*/
|
|
|
|
|
function macro_VAL($jsm, $txt_args)
|
|
|
|
|
{
|
|
|
|
@ -1826,7 +1871,7 @@ function macro_VAL($jsm, $txt_args)
|
|
|
|
|
|
|
|
|
|
//simple version of variable echoing
|
|
|
|
|
/**
|
|
|
|
|
* @builtin @raw_args
|
|
|
|
|
* @global @raw_args
|
|
|
|
|
*/
|
|
|
|
|
function macro_SVAL($jsm, $txt_args)
|
|
|
|
|
{
|
|
|
|
@ -1834,7 +1879,7 @@ function macro_SVAL($jsm, $txt_args)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @builtin
|
|
|
|
|
* @global
|
|
|
|
|
*/
|
|
|
|
|
function macro_CONST($jsm, $k, $v)
|
|
|
|
|
{
|
|
|
|
@ -2027,7 +2072,7 @@ function macro_ex_ENDREP(JSM $jsm)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @builtin @raw_args
|
|
|
|
|
* @global @raw_args
|
|
|
|
|
*/
|
|
|
|
|
function macro_CALL($proc, $raw_args)
|
|
|
|
|
{
|
|
|
|
@ -2039,7 +2084,7 @@ function macro_CALL($proc, $raw_args)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @builtin @eval_args
|
|
|
|
|
* @global @eval_args
|
|
|
|
|
*/
|
|
|
|
|
function macro_TRACE($jsm, array $eval_res)
|
|
|
|
|
{
|
|
|
|
@ -2047,7 +2092,7 @@ function macro_TRACE($jsm, array $eval_res)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @builtin @raw_args
|
|
|
|
|
* @global @raw_args
|
|
|
|
|
*/
|
|
|
|
|
function macro_LET_IF($jsm, $raw_args)
|
|
|
|
|
{
|
|
|
|
|