//從檔案讀取變量 static public void Load(string filePath) { if (File.Exists(filePath)) { //用來記錄當前行數 int numLine = 1; //暫存當前這行的內容 string[] entry; //暫存解析出來的 ID string ID; //逐行讀取 foreach (string line in File.ReadAllLines(filePath)) { try { //如果是空白行或注解就跳過, 否則進行解析 if (line.Trim() != "" && !line.StartsWith("#")) { entry = line.Trim().Split('='); ID = entry[0]; //寫入變量環境 Write(ID.Trim(), line.Replace(ID + "=", "").Trim()); } } catch { //如果解析失敗代表格式有問題 Internals.ERROR(Localization.GetMessage("ILLFORMAT", "Ill-formatted data in line {arg}", numLine.ToString()) + " [" + filePath + "]"); } //更新當前行數 numLine++; } } }
//執行指令 static public void Execute(string cmd, string arg) { //如果是空白指令就直接無視掉就行 if (cmd.Trim() == "") { return; } //Internal commands switch (cmd) { // DEBUG() 開關除錯模式 case "DEBUG": Debugging = !Debugging; MESSAGE(Localization.GetMessage("DEBUG", "Entered debug mode. AZUSA will display all errors and listen to all commands.")); Variables.Write("$SYS_DEBUG", Debugging.ToString()); break; // BROADCAST({expr}) 向所有引擎廣播消息 case "BROADCAST": ProcessManager.Broadcast(arg); break; // EXIT() 退出程序 case "EXIT": //創建一個負責退出的線程 new Thread(new ThreadStart(EXIT)).Start(); break; // RESTART() 重啟程序 case "RESTART": //創建一個負責重啟的線程 new Thread(new ThreadStart(RESTART)).Start(); break; // WAIT({int}) 暫停線程 case "WAIT": System.Threading.Thread.Sleep(Convert.ToInt32(arg)); break; // ACTVIEW() 打開活動檢視器 case "ACTVIEW": itmActivity_Click(null, EventArgs.Empty); break; // PRCMON() 打開進程檢視器 case "PRCMON": itmMonitor_Click(null, EventArgs.Empty); break; // EXEC(filepath,IsApp) 創建進程 case "EXEC": string patharg = arg.Replace("{AZUSA}", Environment.CurrentDirectory); // Get the second arg (true/false) indicating whether process should be executed through AZUSA bool thruAZUSA = true; if (patharg.Contains(',')) { Boolean.TryParse(patharg.Split(',').Last(), out thruAZUSA); } patharg = patharg.Trim(); string path = patharg; string Arg = ""; if (patharg.Contains('$')) { path = patharg.Split('$')[0]; Arg = patharg.Replace(path + "$", ""); } if (path.EndsWith(".exe") && thruAZUSA) { ProcessManager.AddProcess(Path.GetFileNameWithoutExtension(path), path, Arg); } else { try { System.Diagnostics.Process target = new System.Diagnostics.Process(); target.StartInfo.FileName = path; target.StartInfo.Arguments = arg; target.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(path); target.Start(); } catch { Internals.ERROR(Localization.GetMessage("ENGSTARTFAIL", "Unable to run {arg}. Please make sure it is in the correct folder.", path)); } } break; // KILL(prcName) 終止進程 case "KILL": ProcessManager.Kill(arg); break; // SCRIPT({SID(.part)},arg1=val1,arg2=val2,...) 執行腳本檔 case "SCRIPT": //創建執行物件 MUTAN.IRunnable obj; //解析參數 string[] parsed = Utils.SplitWithProtection(arg).ToArray(); //分割參數, 以便取得部分名, 例如 TEST.part1 string[] scr = parsed[0].Split('.'); List <string> var = new List <string>(); List <string> val = new List <string>(); //處理傳入值 for (int i = 1; i < parsed.Count(); i++) { var.Add(Utils.LSplit(parsed[i], "=")); val.Add(Utils.RSplit(parsed[i], "=")); } //用來暫存腳本內容的陣列 string[] program; //首先嘗試讀入腳本內容 var scriptpath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\Scripts\" + scr[0]; if (File.Exists(scriptpath + ".mut")) { program = File.ReadAllLines(scriptpath + ".mut"); } else if (File.Exists(scriptpath + ".msf")) { program = File.ReadAllLines(scriptpath + ".msf"); } else { Internals.ERROR(Localization.GetMessage("SCRMISSING", "Unable to find the script named {arg}. Please make sure it is in the correct folder.", scr[0])); return; } //如果有分塊的話, 就先進行提取 if (scr.Length > 1) { for (int i = 1; i < scr.Length; i++) { program = MUTAN.GetPart(program, scr[i]); } } //取代傳入值 for (int ln = 0; ln < program.Count(); ln++) { for (int i = 0; i < var.Count(); i++) { program[ln] = program[ln].Replace("{" + var[i] + "}", val[i]); } } //然後進行解析 MUTAN.Parser.TryParse(program, out obj); //清理暫存 program = null; //解析結果不為空的話就執行 //否則就報錯 if (obj != null) { MUTAN.ReturnCode tmp = obj.Run(); if (tmp.Command != "END") { Execute(tmp.Command, tmp.Argument); } //扔掉物件 obj = null; } else { ERROR(Localization.GetMessage("SCRERROR", "An error occured while running script named {arg}. Please make sure there is no syntax error.", scr[0])); } break; //等待回應 case "WAITFORRESP": //把 $WAITFORRESP 設成 TRUE Variables.Write("$WAITFORRESP", "TRUE"); ////通知引擎 (主要是針對 AI) 現在正等待回應 ProcessManager.Broadcast("EVENT(WaitingForResp)"); respCache = arg; break; // MAKERESP(resp) 作出反應 case "MAKERESP": //把 $WAITFORRESP 設成 FALSE Variables.Write("$WAITFORRESP", "FALSE"); Variables.Write("$RESP", arg); if (respCache == "") { break; } ////通知引擎已作出反應 //ProcessManager.Broadcast("RESPONDED"); //解析暫存 MUTAN.Parser.TryParse(respCache.Split('\n'), out obj); //清空暫存 respCache = ""; //解析結果不為空的話就執行 //否則就報錯 if (obj != null) { MUTAN.ReturnCode tmp = obj.Run(); if (tmp.Command != "END") { Execute(tmp.Command, tmp.Argument); } //扔掉物件 obj = null; } else { ERROR(Localization.GetMessage("SCRERROR", "An error occured while running a response script. Please make sure there is no syntax error. [MUTAN, " + respCache + "]")); } break; // ERR({expr}) 發送錯誤信息 case "ERR": //ERR 是屬於表現層的系統指令, 容許被接管 bool routed = false; List <IOPortedPrc> ListCopy = new List <IOPortedPrc>(ProcessManager.GetCurrentProcesses()); //檢查每一個現在運行中的進程 foreach (IOPortedPrc prc in ListCopy) { try { //如果進程有接管這個指令, 就把指令內容傳過去 if (prc.RIDs.ContainsKey(cmd)) { //設 routed 為 true routed = true; //根據 RIDs 的值,決定只傳參數還是指令跟參數整個傳過去 //RIDs 的值如果是 true 的話就表示只傳參數 if (prc.RIDs[cmd]) { prc.WriteLine(arg); } else { prc.WriteLine(cmd + "(" + arg + ")"); } } } catch { } } //扔掉 ListCopy ListCopy = null; //否則就由圖標發出提示 if (!routed) { ERROR(arg); } break; // MSG({expr}) 發送信息 case "MSG": //MSG 是屬於表現層的系統指令, 容許被接管 routed = false; ListCopy = new List <IOPortedPrc>(ProcessManager.GetCurrentProcesses()); //檢查每一個現在運行中的進程 foreach (IOPortedPrc prc in ListCopy) { try { //如果進程有接管這個指令, 就把指令內容傳過去 if (prc.RIDs.ContainsKey(cmd)) { //設 routed 為 true routed = true; //根據 RIDs 的值,決定只傳參數還是指令跟參數整個傳過去 //RIDs 的值如果是 true 的話就表示只傳參數 if (prc.RIDs[cmd]) { prc.WriteLine(arg); } else { prc.WriteLine(cmd + "(" + arg + ")"); } } } catch { } } //扔掉 ListCopy ListCopy = null; //否則就由圖標發出提示 if (!routed) { MESSAGE(arg); } break; // MENU(name, script) 添加右鍵選單項目 case "MENU": parsed = arg.Split(','); Internals.ADDMENUITEM(parsed[0].Trim(), arg.Replace(parsed[0] + ",", "").Trim()); break; default: //如果不是系統指令, 先檢查是否有引擎登記接管了這個指令 // routed 記錄指令是否已被接管 routed = false; ListCopy = new List <IOPortedPrc>(ProcessManager.GetCurrentProcesses()); //檢查每一個現在運行中的進程 foreach (IOPortedPrc prc in ListCopy) { try { //如果進程有接管這個指令, 就把指令內容傳過去 if (prc.RIDs.ContainsKey(cmd)) { //設 routed 為 true routed = true; //根據 RIDs 的值,決定只傳參數還是指令跟參數整個傳過去 //RIDs 的值如果是 true 的話就表示只傳參數 if (prc.RIDs[cmd]) { prc.WriteLine(arg); } else { prc.WriteLine(cmd + "(" + arg + ")"); } } } catch { } } //扔掉 ListCopy ListCopy = null; //所有進程都檢查完畢 //如果 routed 為 true, 那麼已經有進程接管了, AZUSA 就可以不用繼續執行 //No need to continue executing the command because it has been routed already if (!routed) { //否則的話就當成函式呼叫, 如果有註明副檔名的就根據副檔名執行 if (cmd.Contains('.')) { switch (cmd.Split('.')[1]) { case "bat": ProcessManager.AddProcess(cmd, "cmd.exe", "/C \"" + Environment.CurrentDirectory + @"\Routines\" + cmd + "\" " + arg); return; case "cmd": ProcessManager.AddProcess(cmd, "cmd.exe", "/C \"" + Environment.CurrentDirectory + @"\Routines\" + cmd + "\" " + arg); return; case "vbs": ProcessManager.AddProcess(cmd, "cscript.exe", " \"" + Environment.CurrentDirectory + @"\Routines\" + cmd + "\" " + arg); return; } } //如果沒副檔名就先找 exe if (!ProcessManager.AddProcess(cmd, Environment.CurrentDirectory + @"\Routines\" + cmd + ".exe", arg)) { //再找 bat (利用 bat 可以呼叫基本上任何直譯器調用任何腳本語言了) if (File.Exists(Environment.CurrentDirectory + @"\Routines\" + cmd + ".bat")) { ActivityLog.Add("Calling \"" + "cmd.exe " + "/C \"" + Environment.CurrentDirectory + @"\Routines\" + cmd + ".bat\" " + arg + "\""); ProcessManager.AddProcess(cmd, "cmd.exe", "/C \"" + Environment.CurrentDirectory + @"\Routines\" + cmd + ".bat\" " + arg); } //再找 cmd else if (File.Exists(Environment.CurrentDirectory + @"\Routines\" + cmd + ".cmd")) { ActivityLog.Add("Calling \"" + "cmd.exe " + "/C \"" + Environment.CurrentDirectory + @"\Routines\" + cmd + ".cmd\" " + arg + "\""); ProcessManager.AddProcess(cmd, "cmd.exe", "/C \"" + Environment.CurrentDirectory + @"\Routines\" + cmd + ".cmd\" " + arg); //再找 vbs } else if (File.Exists(Environment.CurrentDirectory + @"\Routines\" + cmd + ".vbs")) { ProcessManager.AddProcess(cmd, "cscript.exe", " \"" + Environment.CurrentDirectory + @"\Routines\" + cmd + ".vbs\" " + arg); //都找不到就報錯 } else { ERROR(Localization.GetMessage("ENGSTARTFAIL", "Unable to run {arg}. Please make sure it is in the correct folder.", cmd)); } } } break; } }
//處理引擎的輸出 void Engine_OutputDataReceived(object sender, DataReceivedEventArgs e) { string Data = Utils.UriDecode(e.Data); //如果是空白輸出, 不要理會 //Ignore NULL and empty inputs that will crash the program if (Data == null || Data.Trim() == "") { return; } //activity log ActivityLog.Add("From " + Name + ": " + Data); //如果是詢問, 則調用 MUTAN 表達式解析器, 並返回結東 //詢問的語法是 "(表達式)?" //First check if the engine is asking a question about value of an expression if (Data.EndsWith("?")) { //首先保護進程以免受到 BROADCAST 干擾 NoBroadcast = true; string result; //去掉最後的問號, 就是表達式了 //如果格式有誤的話, 會返回 INVALIDEXPR (無效的表達式) 或 IMBALBRACKET (括號不平衡) MUTAN.ExprParser.TryParse(Data.TrimEnd('?'), out result); Engine.StandardInput.WriteLine(result); //解除 BROADCAST 干擾保護 NoBroadcast = false; //activity log ActivityLog.Add("To " + Name + ": " + result); return; } string RID = ""; string arg = ""; //首先假設是溝通用的指令, 主要是用來讓進程宣佈自己的角色和功能, 並取得可用接口等等的溝通協調用的指令 //對字串分割並去掉多餘空白 if (MUTAN.IsExec(Data)) { RID = Data.Split('(')[0]; arg = Data.Substring(RID.Length + 1, Data.Length - RID.Length - 2); RID = RID.Trim(); } switch (RID) { //這是用來進入除錯模式的, 除錯模式下不會要求完備性 case "DEBUG": Internals.Debugging = true; Variables.Write("$SYS_DEBUG", "TRUE"); Internals.MESSAGE(Localization.GetMessage("DEBUG", "Entered debug mode. AZUSA will display all errors and listen to all commands.")); return; //進行回傳 case "Return": output = arg; RESPONDED = true; return; //這是用來讓進程取得 AZUSA 的 pid, 進程可以利用 pid 檢查 AZUSA 是否存活, 當 AZUSA 意外退出時, 進程可以檢查到並一併退出 case "GetAzusaPid": //首先保護進程以免受到 BROADCAST 干擾 NoBroadcast = true; Engine.StandardInput.WriteLine(Process.GetCurrentProcess().Id); //activity log ActivityLog.Add("To " + Name + ": " + Process.GetCurrentProcess().Id); //解除 BROADCAST 干擾保護 NoBroadcast = false; return; //這是讓進程宣佈自己的身份的, 這指令應該是進程完成各種初始化之後才用的 case "RegisterAs": //先記錄現在是否完備 bool tmp = ProcessManager.CheckCompleteness(); //然後進行相應的登錄 switch (arg) { case "AI": currentType = PortType.AI; ProcessManager.AIPid.Add(pid); break; case "Input": currentType = PortType.Input; ProcessManager.InputPid.Add(pid); break; case "Output": currentType = PortType.Output; ProcessManager.OutputPid.Add(pid); break; case "Application": currentType = PortType.Application; break; default: break; } //再次檢查完備性, 如果之前不完備, 現在完備了就進行提示 if (!tmp && ProcessManager.CheckCompleteness()) { Internals.READY(); } return; //這是讓進程宣佈自己的可連接的接口, AZUSA 記錄後可以轉告其他進程, 進程之間可以直接對接而不必經 AZUSA case "RegisterPort": ProcessManager.Ports.Add(arg.Trim('"'), currentType); this.Ports.Add(arg.Trim('"')); ProcessManager.Broadcast("PortHasChanged"); return; //這是讓進程取得當前可用所有端口 case "GetAllPorts": //首先保護進程以免受到 BROADCAST 干擾 NoBroadcast = true; string result = ""; foreach (KeyValuePair <string, PortType> port in ProcessManager.Ports) { result += port.Key + ","; } Engine.StandardInput.WriteLine(result.Trim(',')); //解除 BROADCAST 干擾保護 NoBroadcast = false; //activity log ActivityLog.Add("To " + Name + ": " + result.Trim(',')); return; //這是讓進程取得當前可用的AI 端口(AI引擎的接口) case "GetAIPorts": //首先保護進程以免受到 BROADCAST 干擾 NoBroadcast = true; result = ""; foreach (KeyValuePair <string, PortType> port in ProcessManager.Ports) { if (port.Value == PortType.AI) { result += port.Key + ","; } } Engine.StandardInput.WriteLine(result.Trim(',')); //解除 BROADCAST 干擾保護 NoBroadcast = false; //activity log ActivityLog.Add("To " + Name + ": " + result.Trim(',')); return; //這是讓進程取得當前可用的輸入端口(輸入引擎的接口) case "GetInputPorts": //首先保護進程以免受到 BROADCAST 干擾 NoBroadcast = true; result = ""; foreach (KeyValuePair <string, PortType> port in ProcessManager.Ports) { if (port.Value == PortType.Input) { result += port.Key + ","; } } Engine.StandardInput.WriteLine(result.Trim(',')); //解除 BROADCAST 干擾保護 NoBroadcast = false; //activity log ActivityLog.Add("To " + Name + ": " + result.Trim(',')); return; //這是讓進程取得當前可用的輸出端口(輸出引擎的接口) case "GetOutputPorts": //首先保護進程以免受到 BROADCAST 干擾 NoBroadcast = true; result = ""; foreach (KeyValuePair <string, PortType> port in ProcessManager.Ports) { if (port.Value == PortType.Output) { result += port.Key + ","; } } Engine.StandardInput.WriteLine(result.Trim(',')); //解除 BROADCAST 干擾保護 NoBroadcast = false; //activity log ActivityLog.Add("To " + Name + ": " + result.Trim(',')); return; //這是讓進程可以宣佈自己責負甚麼函式, AZUSA 在接收到這種函件就會轉發給進程 //函式接管不是唯一的, 可以同時有多個進程接管同一個函式, AZUSA 會每個宣告了接管的進程都轉發一遍 case "LinkRID": string[] parsed = arg.Split(','); this.RIDs.Add(parsed[0], Convert.ToBoolean(parsed[1])); return; default: break; } //檢查整體架構是否完備, 完備或除錯模式下才執行指令 if (Internals.SysReady || Internals.Debugging) { //否則假設是 MUTAN 指令,嘗試解析, 如果失敗的話, 就無視掉本次輸出 //先創建一個可運行物件, 用來儲存解析結果 MUTAN.IRunnable obj; //然後用單行解析器 if (MUTAN.LineParser.TryParse(Data, out obj)) { //如果成功解析, 則運行物件, 獲取回傳碼 MUTAN.ReturnCode tmp = obj.Run(); //然後按回傳碼執行指令 if (tmp.Command != "") { Internals.Execute(tmp.Command, tmp.Argument); } } } else { Internals.ERROR(Localization.GetMessage("ENGINEMISSING", "Some engines are missing. AZUSA will not execute any MUTAN commands unless AI and I/O are all registered.")); } }
//初始化 static public void INIT() { //從 DATA 載入所有已儲存的變量 //Load all the variables Variables.Load(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\DATA"); Variables.Write("$SYS_READY", "FALSE"); //載入提示信息 Localization.Initialize(); //創建提示圖標 //Set up notify icon notifyIcon.Icon = AZUSA.Properties.Resources.icon; notifyIcon.Visible = true; notifyIcon.DoubleClick += new EventHandler(notifyIcon_DoubleClick); //創建圖標右擊菜單的項目 MenuItem itmMonitor = new MenuItem(Localization.GetMessage("PRCMON", "Process Monitor")); //進程監視器 itmMonitor.Click += new EventHandler(itmMonitor_Click); MenuItem itmActivity = new MenuItem(Localization.GetMessage("ACTMON", "Activity Monitor")); //活動監視器 itmActivity.Click += new EventHandler(itmActivity_Click); MenuItem itmRELD = new MenuItem(Localization.GetMessage("RELOAD", "Reload")); //重新載入 itmRELD.Click += new EventHandler(itmRELD_Click); MenuItem itmEXIT = new MenuItem(Localization.GetMessage("EXIT", "Exit")); //退出 itmEXIT.Click += new EventHandler(itmEXIT_Click); MenuItem sep = new MenuItem("-"); ContextMenu menu = new ContextMenu(new MenuItem[] { itmMonitor, itmActivity, sep, itmRELD, itmEXIT }); //把圖標右擊菜單設成上面創建的菜單 notifyIcon.ContextMenu = menu; //搜索 Engines\ 底下的所有執行檔, SearchOption.AllDirectories 表示子目錄也在搜索範圍內 //Start the engines string EngPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + @"\Engines"; string[] EngList = new string[] { }; if (Directory.Exists(EngPath)) { EngList = System.IO.Directory.GetFiles(EngPath, "*.exe", SearchOption.TopDirectoryOnly); } else { ERROR(Localization.GetMessage("ENGPATHMISSING", "The \\Engines folder is missing. AZUSA will not be able to perform any function without suitable engines.")); return; } //提示本體啟動成功, 待各引擎啟動完畢後會再有提示的 //MESSAGE(Localization.GetMessage("AZUSAREADY", "AZUSA is ready. Waiting for engines to initialize...")); //每一個執行檔都添加為引擎 foreach (string exePath in EngList) { //如果不成功就發錯誤信息 if (!ProcessManager.AddProcess(exePath.Replace(EngPath + @"\", "").Replace(".exe", "").Trim(), exePath)) { Internals.ERROR(Localization.GetMessage("ENGSTARTFAIL", "Unable to run {arg}. Please make sure it is in the correct folder.", exePath.Replace(EngPath + @"\", "").Replace(".exe", "").Trim())); } } }
//對進程自行退出的事件處理 void Engine_Exited(object sender, EventArgs e) { //首先暫停處理引擎的輸出 Pause(); //如果是負責接口的話 if (Ports.Count != 0) { foreach (string port in Ports) { //取消掉所有接口登錄 ProcessManager.Ports.Remove(port); } //取消掉所有接口登錄 Ports.Clear(); //通知其他進程接口有變 List <IOPortedPrc> ListCopy = new List <IOPortedPrc>(ProcessManager.GetCurrentProcesses()); foreach (IOPortedPrc prc in ListCopy) { prc.WriteLine("PortHasChanged"); } ListCopy = null; } //移除事件監聽 Engine.OutputDataReceived -= Engine_OutputDataReceived; Engine.Exited -= Engine_Exited; //拋棄進程的實體 Engine.Dispose(); Engine = null; //然後檢查引擎類型, 再從 ProcessManager 相應的名單中除名 if (ProcessManager.AIPid.Contains(pid)) { ProcessManager.AIPid.Remove(pid); } if (ProcessManager.InputPid.Contains(pid)) { ProcessManager.InputPid.Remove(pid); } if (ProcessManager.OutputPid.Contains(pid)) { ProcessManager.OutputPid.Remove(pid); } //從 ProcessManager 的進程名單中除名 ProcessManager.RemoveProcess(this); //如果是主要引擎的話,嘗試一定次數內重啟 if (currentType != PortType.Unknown && currentType != PortType.Application) { if (CrashCount <= 3) { ProcessManager.AddProcess(Name, path, "", CrashCount + 1); ActivityLog.Add(Name + Localization.GetMessage("ENGINERESTART", " has exited unexpectedly. Attempting to restart.")); } else { Internals.ERROR(Name + Localization.GetMessage("ENGINEEXIT", " has exited unexpectedly. All restart attempts failed.")); } } //釋放變量佔用的資源 Name = null; path = null; if (RIDs != null) { RIDs.Clear(); RIDs = null; } //最後檢查完備性, 如果不完備的話發出通知 if (!ProcessManager.CheckCompleteness()) { Internals.ERROR(Localization.GetMessage("ENGINEMISSING", "Some engines are missing. AZUSA will not execute any MUTAN commands unless AI and I/O are all registered.")); } }