initial commit

This commit is contained in:
whale
2020-03-02 16:14:20 +08:00
parent 517082d159
commit 769b87af6a
132 changed files with 5424 additions and 2866 deletions

212
src/Framework/Console.php Executable file
View File

@@ -0,0 +1,212 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/2/10
* Time: 下午6:13
*/
namespace Framework;
use co;
use ZM\Utils\ZMUtil;
use Exception;
class Console
{
static function setColor($string, $color = "") {
switch ($color) {
case "red":
return "\x1b[38;5;203m" . $string . "\x1b[m";
case "green":
return "\x1b[38;5;83m" . $string . "\x1b[m";
case "yellow":
return "\x1b[38;5;227m" . $string . "\x1b[m";
case "blue":
return "\033[34m" . $string . "\033[0m";
case "lightpurple":
return "\x1b[38;5;207m" . $string . "\x1b[m";
case "lightblue":
return "\x1b[38;5;87m" . $string . "\x1b[m";
case "gold":
return "\x1b[38;5;214m" . $string . "\x1b[m";
case "gray":
return "\x1b[38;5;59m" . $string . "\x1b[m";
case "pink":
return "\x1b[38;5;207m" . $string . "\x1b[m";
case "lightlightblue":
return "\x1b[38;5;63m" . $string . "\x1b[m";
default:
return $string;
}
}
static function error($obj, $head = null) {
if ($head === null) $head = date("[H:i:s ") . "ERROR] ";
if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
$trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
$trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
}
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
}
echo(self::setColor($head . ($trace ?? "") . $obj, "red") . "\n");
}
static function warning($obj, $head = null) {
if ($head === null) $head = date("[H:i:s") . " WARN] ";
if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
$trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
$trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
}
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
}
echo(self::setColor($head . ($trace ?? "") . $obj, "yellow") . "\n");
}
static function info($obj, $head = null) {
if ($head === null) $head = date("[H:i:s ") . "INFO] ";
if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
$trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
$trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
}
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
}
echo(self::setColor($head . ($trace ?? "") . $obj, "lightblue") . "\n");
}
static function log($obj, $color = "") {
if (!is_string($obj)) var_dump($obj);
else echo(self::setColor($obj, $color) . "\n");
}
static function msg($obj, $self_id = "") {
if (ZMBuf::$info_level !== null && ZMBuf::$info_level->get() == 3) {
if (!isset($obj["post_type"])) {
switch ($obj["action"]) {
case "send_private_msg":
$msg = Console::setColor(date("H:i:s ") . "[" . (ZMBuf::globals("robot_alias")[$self_id] ?? "Null") . "] ", "lightlightblue");
$msg .= Console::setColor("私聊↑(" . $obj["params"]["user_id"] . ")", "lightlightblue");
$msg .= Console::setColor(" > ", "gray");
$msg .= $obj["params"]["message"];
Console::log($msg);
break;
case "send_group_msg":
//TODO: 写新的控制台消息API消息处理
Console::log(Console::setColor("[" . date("H:i:s") . " GROUP:" . $obj["params"]["group_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
case "send_discuss_msg":
Console::log(Console::setColor("[" . date("H:i:s") . " DISCUSS:" . $obj["params"]["discuss_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
case "send_msg":
$obj["action"] = "send_" . $obj["message_type"] . "_msg";
self::msg($obj);
break;
case "send_wechat_msg":
Console::log(Console::setColor("[" . date("H:i:s") . " WECHAT] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
default:
break;
}
} else {
if ($obj["post_type"] == "message") {
switch ($obj["message_type"]) {
case "group":
//
//TODO: 写新的控制台消息event处理
case "private":
case "discuss":
case "wechat":
}
}
}
}
}
static function stackTrace(){
$log = "Stack trace:\n";
$trace = debug_backtrace();
//array_shift($trace);
foreach ($trace as $i => $t) {
if (!isset($t['file'])) {
$t['file'] = 'unknown';
}
if (!isset($t['line'])) {
$t['line'] = 0;
}
if (!isset($t['function'])) {
$t['function'] = 'unknown';
}
$log .= "#$i {$t['file']}({$t['line']}): ";
if (isset($t['object']) and is_object($t['object'])) {
$log .= get_class($t['object']) . '->';
}
$log .= "{$t['function']}()\n";
}
$log = Console::setColor($log, "gray");
echo $log;
}
static function listenConsole(){
go(function () {
while (true) {
$cmd = trim(co::fread(STDIN));
if (self::executeCommand($cmd) === false) break;
}
});
}
/**
* @param string $cmd
* @return bool
*/
private static function executeCommand(string $cmd) {
$it = explodeMsg($cmd);
switch ($it[0] ?? '') {
case 'call':
$class_name = $it[1];
$function_name = $it[2];
$class = new $class_name([]);
call_user_func_array([$class, $function_name],[]);
return true;
case 'bc':
$code = base64_decode($it[1] ?? '', true);
try {
eval($code);
} catch (Exception $e) {
}
return true;
case 'echo':
Console::info($it[1]);
return true;
case 'stop':
ZMUtil::stop();
return false;
case 'reload':
case 'r':
ZMUtil::reload();
return false;
case '':
return true;
default:
Console::info("Command not found: " . $it[0]);
return true;
}
}
public static function withSleep(string $string, int $int) {
self::info($string);
sleep($int);
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace Framework;
use ZM\Event\EventHandler;
use Exception;
use Swoole\WebSocket\Server;
use ZM\Http\Response;
/**
* Class FrameworkLoader
* Everything is beginning from here
* @package Framework
*/
class FrameworkLoader
{
/** @var GlobalConfig */
public static $settings;
/** @var FrameworkLoader|null */
public static $instance = null;
/** @var float|string */
public static $run_time;
/** @var Server */
private $server;
public function __construct($args = []) {
if (self::$instance !== null) die("Cannot run two FrameworkLoader in one process!");
self::$instance = $this;
chdir(__DIR__ . '/../..');
define('WORKING_DIR', getcwd());
$this->requireGlobalFunctions();
$this->registerAutoloader('classLoader');
self::$settings = new GlobalConfig();
ZMBuf::$globals = self::$settings;
if (!self::$settings->success) die("Failed to load global config. Please check config/global.php file");
$this->defineProperties();
//start swoole Framework
$this->selfCheck();
try {
$this->server = new Server(self::$settings->get("host"), self::$settings->get("port"));
if (in_array("--remote-shell", $args)) RemoteShell::listen($this->server, "127.0.0.1");
$this->server->set(self::$settings->get("swoole"));
$this->server->on("WorkerStart", [$this, "onWorkerStart"]);
$this->server->on("message", function ($server, $frame) { EventHandler::callSwooleEvent("message", $server, $frame); });
$this->server->on("request", function ($request, $response) {
$response = new Response($response);
EventHandler::callSwooleEvent("request", $request, $response);
});
$this->server->on("open", function ($server, $request) { EventHandler::callSwooleEvent("open", $server, $request); });
$this->server->on("close", function ($server, $fd) { EventHandler::callSwooleEvent("close", $server, $fd); });
ZMBuf::initAtomic();
Console::info("host: ".self::$settings->get("host").", port: ".self::$settings->get("port"));
$this->server->start();
} catch (Exception $e) {
Console::error("Framework初始化出现错误请检查");
Console::error($e->getMessage());
die;
}
}
private function requireGlobalFunctions() {
require __DIR__ . '/global_functions.php';
}
private function registerAutoloader(string $string) {
if (!spl_autoload_register($string)) die("Failed to register autoloader named \"$string\" !");
}
private function defineProperties() {
define("ZM_START_TIME", microtime(true));
define("ZM_DATA", self::$settings->get("zm_data"));
define("CONFIG_DIR", self::$settings->get("config_dir"));
define("CRASH_DIR", self::$settings->get("crash_dir"));
@mkdir(ZM_DATA);
@mkdir(CONFIG_DIR);
@mkdir(CRASH_DIR);
define("ZM_MATCH_ALL", 0);
define("ZM_MATCH_FIRST", 1);
define("ZM_MATCH_NUMBER", 2);
define("ZM_MATCH_SECOND", 3);
}
private function selfCheck() {
if (!extension_loaded("swoole")) die("Can not find swoole extension.\n");
//if (!extension_loaded("gd")) die("Can not find gd extension.\n");
if (!extension_loaded("sockets")) die("Can not find sockets extension.\n");
if (!function_exists("mb_substr")) die("Can not find mbstring extension.\n");
if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n");
//if (!function_exists("curl_exec")) die("Can not find curl extension.\n");
//if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n");
//if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n");
return true;
}
public function onWorkerStart(\Swoole\Server $server, $worker_id) {
self::$instance = $this;
self::$run_time = microtime(true);
EventHandler::callSwooleEvent("WorkerStart", $server, $worker_id);
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2019-03-16
* Time: 13:58
*/
namespace Framework;
/**
* 请不要diss此class的语法。可能写的很糟糕。
* Class GlobalConfig
*/
class GlobalConfig
{
private $config = null;
public $success = false;
public function __construct() {
/** @noinspection PhpIncludeInspection */
include_once WORKING_DIR.'/config/global.php';
global $config;
$this->success = true;
$this->config = $config;
}
public function get($key) {
$r = $this->config[$key] ?? null;
if ($r === null) return null;
return $r;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Framework;
class InfoLevel
{
const INFO = 0;
}

18
src/Framework/Logger.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
namespace Framework;
use Swoole\Coroutine\System;
class Logger
{
private static function getTimeFormat($type = "I") {
return "[" . date("Y-m-d H:i:s") . "]\t[" . $type . "]\t";
}
public static function writeSwooleLog($log) {
System::writeFile(CRASH_DIR . "swoole_error.log", "\n" . self::getTimeFormat() . $log, FILE_APPEND);
}
}

View File

@@ -0,0 +1,264 @@
<?php
namespace Framework;
use Co;
use Exception;
use Swoole\Coroutine;
use swoole\server;
class RemoteShell
{
const STX = "DEBUG";
private static $contexts = array();
static $oriPipeMessageCallback = null;
/**
* @var server
*/
static $serv;
static $menu = array(
"p|print [variant]\t打印一个PHP变量的值",
"e|exec [code]\t执行一段PHP代码",
"w|worker [id]\t切换Worker进程",
"l|list\t打印服务器所有连接的fd",
"s|stats\t打印服务器状态",
"c|coros\t打印协程列表",
"b|bt\t打印协程调用栈",
"i|info [fd]\t显示某个连接的信息",
"h|help\t显示帮助界面",
"q|quit\t退出终端",
);
const PAGESIZE = 20;
/**
* @param $serv server
* @param string $host
* @param int $port
* @throws Exception
* @throws Exception
*/
static function listen($serv, $host = "127.0.0.1", $port = 9599) {
Console::warning("正在监听".$host.":".strval($port)."的调试接口,请注意安全");
$port = $serv->listen($host, $port, SWOOLE_SOCK_TCP);
if (!$port) {
throw new Exception("listen fail.");
}
$port->set(array(
"open_eof_split" => true,
'package_eof' => "\r\n",
));
$port->on("Connect", array(__CLASS__, 'onConnect'));
$port->on("Close", array(__CLASS__, 'onClose'));
$port->on("Receive", array(__CLASS__, 'onReceive'));
if (method_exists($serv, 'getCallback')) {
self::$oriPipeMessageCallback = $serv->getCallback('PipeMessage');
}
$serv->on("PipeMessage", array(__CLASS__, 'onPipeMessage'));
self::$serv = $serv;
}
static function onConnect($serv, $fd, $reactor_id) {
self::$contexts[$fd]['worker_id'] = $serv->worker_id;
self::output($fd, implode("\r\n", self::$menu));
}
static function output($fd, $msg) {
if (!isset(self::$contexts[$fd]['worker_id'])) {
$msg .= "\r\nworker#" . self::$serv->worker_id . "$ ";
} else {
$msg .= "\r\nworker#" . self::$contexts[$fd]['worker_id'] . "$ ";
}
self::$serv->send($fd, $msg);
}
static function onClose($serv, $fd, $reactor_id) {
unset(self::$contexts[$fd]);
}
static function onPipeMessage($serv, $src_worker_id, $message) {
//不是 debug 消息
if (!is_string($message) or substr($message, 0, strlen(self::STX)) != self::STX) {
if (self::$oriPipeMessageCallback == null) {
trigger_error("require swoole-4.3.0 or later.", E_USER_WARNING);
return true;
}
return call_user_func(self::$oriPipeMessageCallback, $serv, $src_worker_id, $message);
} else {
$request = unserialize(substr($message, strlen(self::STX)));
self::call($request['fd'], $request['func'], $request['args']);
}
return true ;
}
static protected function call($fd, $func, $args) {
ob_start();
call_user_func_array($func, $args);
self::output($fd, ob_get_clean());
}
static protected function exec($fd, $func, $args) {
//不在当前Worker进程
if (self::$contexts[$fd]['worker_id'] != self::$serv->worker_id) {
self::$serv->sendMessage(self::STX . serialize(['fd' => $fd, 'func' => $func, 'args' => $args]), self::$contexts[$fd]['worker_id']);
} else {
self::call($fd, $func, $args);
}
}
static function getCoros() {
var_export(iterator_to_array(Coroutine::listCoroutines()));
}
static function getBackTrace($_cid) {
$info = Co::getBackTrace($_cid);
if (!$info) {
echo "coroutine $_cid not found.";
} else {
echo get_debug_print_backtrace($info);
}
}
static function printVariant($var) {
$var = ltrim($var, '$ ');
var_dump($var);
var_dump($$var);
}
static function evalCode($code) {
eval($code . ';');
}
/**
* @param $serv server
* @param $fd
* @param $reactor_id
* @param $data
*/
static function onReceive($serv, $fd, $reactor_id, $data) {
$args = explode(" ", $data, 2);
$cmd = trim($args[0]);
unset($args[0]);
switch ($cmd) {
case 'w':
case 'worker':
if (!isset($args[1])) {
self::output($fd, "invalid command.");
break;
}
$dstWorkerId = intval($args[1]);
self::$contexts[$fd]['worker_id'] = $dstWorkerId;
self::output($fd, "[switching to worker " . self::$contexts[$fd]['worker_id'] . "]");
break;
case 'e':
case 'exec':
if (!isset($args[1])) {
self::output($fd, "invalid command.");
break;
}
$var = trim($args[1]);
self::exec($fd, 'self::evalCode', [$var]);
break;
case 'p':
case 'print':
$var = trim($args[1]);
self::exec($fd, 'self::printVariant', [$var]);
break;
case 'h':
case 'help':
self::output($fd, implode("\r\n", self::$menu));
break;
case 's':
case 'stats':
$stats = $serv->stats();
self::output($fd, var_export($stats, true));
break;
case 'c':
case 'coros':
self::exec($fd, 'self::getCoros', []);
break;
/**
* 查看协程堆栈
*/
case 'bt':
case 'b':
case 'backtrace':
if (empty($args[1])) {
self::output($fd, "invalid command [" . trim($args[1]) . "].");
break;
}
$_cid = intval($args[1]);
self::exec($fd, 'self::getBackTrace', [$_cid]);
break;
case 'i':
case 'info':
if (empty($args[1])) {
self::output($fd, "invalid command [" . trim($args[1]) . "].");
break;
}
$_fd = intval($args[1]);
$info = $serv->getClientInfo($_fd);
if (!$info) {
self::output($fd, "connection $_fd not found.");
} else {
self::output($fd, var_export($info, true));
}
break;
case 'l':
case 'list':
$tmp = array();
foreach ($serv->connections as $fd) {
$tmp[] = $fd;
if (count($tmp) > self::PAGESIZE) {
self::output($fd, json_encode($tmp));
$tmp = array();
}
}
if (count($tmp) > 0) {
self::output($fd, json_encode($tmp));
}
break;
case 'q':
case 'quit':
$serv->close($fd);
break;
default:
self::output($fd, "unknow command[$cmd]");
break;
}
}
}
function get_debug_print_backtrace($traces) {
$ret = array();
foreach ($traces as $i => $call) {
$object = '';
if (isset($call['class'])) {
$object = $call['class'] . $call['type'];
if (is_array($call['args'])) {
foreach ($call['args'] as &$arg) {
get_arg($arg);
}
}
}
$ret[] = '#' . str_pad($i, 3, ' ')
. $object . $call['function'] . '(' . implode(', ', $call['args'])
. ') called at [' . $call['file'] . ':' . $call['line'] . ']';
}
return implode("\n", $ret);
}
function get_arg(&$arg) {
if (is_object($arg)) {
$arr = (array)$arg;
$args = array();
foreach ($arr as $key => $value) {
if (strpos($key, chr(0)) !== false) {
$key = ''; // Private variable found
}
$args[] = '[' . $key . '] => ' . get_arg($value);
}
$arg = get_class($arg) . ' Object (' . implode(',', $args) . ')';
}
}

95
src/Framework/ZMBuf.php Executable file
View File

@@ -0,0 +1,95 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/2/25
* Time: 下午11:11
*/
namespace Framework;
use Swoole\Atomic;
use swoole_atomic;
use ZM\Annotation\MappingNode;
use ZM\connection\WSConnection;
use ZM\Utils\Scheduler;
use ZM\Utils\SQLPool;
class ZMBuf
{
//读写的缓存数据需要在worker_num = 1下才能正常使用
/** @var mixed[] ZMBuf的data */
private static $cache = [];
/** @var WSConnection[] */
static $connect = [];//储存连接实例的数组
//Scheduler计划任务连接实例只可以在单worker_num时使用
/** @var Scheduler|null */
static $scheduler = null;
//Swoole SQL连接池多进程下每个进程一个连接池
/** @var SQLPool */
static $sql_pool = null;//保存sql连接池的类
//只读的数据可以在多worker_num下使用
/** @var null|\Framework\GlobalConfig */
static $globals = null;
// swoole server操作对象每个进程均分配
/** @var swoole_websocket_server $server */
static $server;
/** @var MappingNode Http请求uri路径根节点 */
public static $req_mapping_node;
/** @var mixed TimeNLP初始化后的对象每个进程均可初始化 */
public static $time_nlp;
/** @var string[] $custom_connection_class */
public static $custom_connection_class = [];//保存自定义的ws connection连接类型的
// Atomic可跨进程读写的原子计数任何地方均可使用
/** @var null|swoole_atomic */
static $info_level = null;//保存log等级的原子计数
/** @var swoole_atomic $reload_time */
public static $events = [];
/** @var Atomic[] */
public static $atomics;
static function get($name, $default = null) { return self::$cache[$name] ?? $default; }
static function set($name, $value) { self::$cache[$name] = $value; }
static function append($name, $value) { self::$cache[$name][] = $value; }
static function appendKey($name, $key, $value) { self::$cache[$name][$key] = $value; }
static function appendKeyInKey($name, $key, $value) { self::$cache[$name][$key][] = $value; }
static function unsetCache($name) { unset(self::$cache[$name]); }
static function unsetByValue($name, $vale) {
$key = array_search($vale, self::$cache[$name]);
array_splice(self::$cache[$name], $key, 1);
}
static function isset($name) { return isset(self::$cache[$name]); }
static function array_key_exists($name, $key) { return isset(self::$cache[$name][$key]); }
static function in_array($name, $val) { return in_array($val, self::$cache[$name]); }
static function globals($key) { return self::$globals->get($key); }
public static function resetCache() {
self::$cache = [];
self::$connect = [];
self::$time_nlp = null;
}
/**
* 初始化atomic计数器
*/
public static function initAtomic() {
foreach(ZMBuf::globals("init_atomics") as $k => $v) {
self::$atomics[$k] = new Atomic($v);
}
}
}

View File

@@ -0,0 +1,146 @@
<?php
function classLoader($p) {
$filepath = getClassPath($p);
if ($filepath === null)
echo "F:Warning: get class path wrongs.$p\n";
//else echo "F:DBG: Found " . $p . "\n";
try {
require_once $filepath;
} catch (Exception $e) {
echo "Error when finding class: ".$p.PHP_EOL;
die;
}
}
function getClassPath($class_name) {
$dir = str_replace("\\", "/", $class_name);
$dir = WORKING_DIR . "/src/" . $dir . ".php";
//echo "@@@".$dir.PHP_EOL;
$dir = str_replace("\\", "/", $dir);
if (file_exists($dir)) return $dir;
else return null;
}
/**
* 使用自己定义的万san能分割函数
* @param $msg
* @param bool $ban_comma
* @return array
*/
function explodeMsg($msg, $ban_comma = false) {
$msg = str_replace(" ", "\n", trim($msg));
if (!$ban_comma) {
//$msg = str_replace("", "\n", $msg);
$msg = str_replace("\t", "\n", $msg);
}
$msgs = explode("\n", $msg);
$ls = [];
foreach ($msgs as $k => $v) {
if (trim($v) == "") continue;
$ls[] = trim($v);
}
return $ls;
}
function unicode_decode($str) {
return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) {
return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE");
}, $str);
}
/**
* 获取模块文件夹下的每个类文件的类名称
* @param $dir
* @param string $indoor_name
* @return array
*/
function getAllClasses($dir, $indoor_name) {
$list = scandir($dir);
$classes = [];
unset($list[0], $list[1]);
foreach ($list as $v) {
//echo "Finding " . $dir . $v . PHP_EOL;
//echo "At " . $indoor_name . PHP_EOL;
if (is_dir($dir . $v)) $classes = array_merge($classes, getAllClasses($dir . $v . "/", $indoor_name . "\\" . $v));
elseif (mb_substr($v, -4) == ".php") {
$class_name = $indoor_name . "\\" . mb_substr($v, 0, -4);
$classes [] = $class_name;
}
}
return $classes;
}
function matchPattern($pattern, $context) {
if (mb_substr($pattern, 0, 1) == "" && mb_substr($context, 0, 1) == "")
return true;
if ('*' == mb_substr($pattern, 0, 1) && "" != mb_substr($pattern, 1, 1) && "" == mb_substr($context, 0, 1))
return false;
if (mb_substr($pattern, 0, 1) == mb_substr($context, 0, 1))
return matchPattern(mb_substr($pattern, 1), mb_substr($context, 1));
if (mb_substr($pattern, 0, 1) == "*")
return matchPattern(mb_substr($pattern, 1), $context) || matchPattern($pattern, mb_substr($context, 1));
return false;
}
function split_explode($del, $str, $divide_en = false) {
$str = explode($del, $str);
for ($i = 0; $i < mb_strlen($str[0]); $i++) {
if (
is_numeric(mb_substr($str[0], $i, 1)) &&
(
!is_numeric(mb_substr($str[0], $i - 1, 1)) &&
mb_substr($str[0], $i - 1, 1) != ' ' &&
ctype_alpha(mb_substr($str[0], $i - 1, 1)) === false
)
) {
$str[0] = mb_substr($str[0], 0, $i) . " " . mb_substr($str[0], $i);
} elseif (
$divide_en &&
ctype_alnum(mb_substr($str[0], $i, 1)) &&
!ctype_alnum(mb_substr($str[0], $i - 1, 1)) &&
mb_substr($str[0], $i - 1, 1) != ' '
) {
$str[0] = mb_substr($str[0], 0, $i) . ' ' . mb_substr($str[0], $i);
}
}
$str = implode($del, $str);
//echo $str."\n";
$ls = [];
foreach (explode($del, $str) as $k => $v) {
if (trim($v) == "") continue;
$ls[] = $v;
}
//var_dump($ls);
return $ls == [] ? [""] : $ls;
}
function matchArgs($pattern, $context) {
$result = [];
if (matchPattern($pattern, $context)) {
if (mb_strpos($pattern, "*") === false) return [];
$exp = explode("*", $pattern);
$i = 0;
foreach ($exp as $k => $v) {
//echo "[MATCH$k] " . $v . PHP_EOL;
if ($v == "" && $k == 0) continue;
elseif ($v == "" && $k == count($exp) - 1) {
$context = $context . "^EOL";
$v = "^EOL";
}
$cur_var = "";
//echo mb_substr($context, $i) . "|" . $v . PHP_EOL;
$ori = $i;
while (($a = mb_substr($context, $i, mb_strlen($v))) != $v && $a != "") {
$cur_var .= mb_substr($context, $i, 1);
++$i;
}
if ($i != $ori || $k == 1 || $k == count($exp) - 1) {
//echo $cur_var . PHP_EOL;
$result[] = $cur_var;
}
$i += mb_strlen($v);
}
return $result;
} else return false;
}