pascalabcnet/Compiler/RemoteCompiler.cs

642 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (©) Ivan Bondarev, Stanislav Mikhalkovich (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Threading;
using PascalABCCompiler.Errors;
using PascalABCCompiler.TreeRealization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Data;
namespace PascalABCCompiler
{
public enum RemoteCompilerChannelEventType { Send, Receive };
public delegate void RemoteCompilerChannelEventDelegate(RemoteCompilerChannelEventType eventType, string text);
public interface IRemoteCompiler
{
int MaxProcessMemoryMB
{
get;
set;
}
event RemoteCompilerChannelEventDelegate RemoteCompilerChannelAction;
}
public class RemoteCompilerError : TreeConverter.CompilationErrorWithLocation
{
string msg;
public RemoteCompilerError(string msg, location loc)
: base(loc)
{
this.msg = msg;
}
public override string ToString()
{
return msg;
}
}
public class RemoteCompilerInternalError : CompilerInternalError
{
public RemoteCompilerInternalError(string msg)
: base("[pabcnetc.exe]",new Exception(msg))
{
}
}
public class RemoteCompilerWarning : Errors.CompilerWarning
{
string msg;
location loc;
public override SourceLocation SourceLocation
{
get
{
if(loc!=null)
return new SourceLocation(loc.file_name, loc.begin_line_num, loc.begin_column_num, loc.end_line_num, loc.end_column_num);
else
return null;
}
}
public RemoteCompilerWarning(string msg, location loc)
{
this.msg = msg;
this.loc = loc;
}
public override string ToString()
{
return msg;
}
}
public class RemoteCompiler: ICompiler
{
int maxProcessMemoryMB = ConsoleCompilerConstants.MaxProcessMemoryMB;
Process pabcnetcProcess=null;
EventedStreamReaderList pabcnetcStreamReader;
string inputId = "pabcnetc_input";
string pabcnetcFileName = "pabcnetc.exe";
public const int sendCommandStartNumber = 100;
public volatile CompilerState compilerState = CompilerState.Reloading; // PVS 01/2022 volatile
Encoding inputEncoding = System.Text.Encoding.UTF8;
bool compilationStarted = false;
public int MaxProcessMemoryMB
{
get
{
return maxProcessMemoryMB;
}
set
{
maxProcessMemoryMB=value;
CheckProcessMemory();
}
}
public RemoteCompiler(int maxProcessMemoryMB, ChangeCompilerStateEventDelegate ChangeCompilerState, SourceFilesProviderDelegate sourceFilesProvider)
{
this.OnChangeCompilerState += ChangeCompilerState;
this.sourceFilesProvider = sourceFilesProvider;
this.maxProcessMemoryMB = maxProcessMemoryMB;
pabcnetcStreamReader = new EventedStreamReaderList(stringRecived);
pabcnetcStreamReader.DataSeparator = ConsoleCompilerConstants.DataSeparator;
Reload();
}
public void stringRecived(string id, string line)
{
string arg = null;
if (line.Length < 3)
return;
if ((int)line[0] == 65279 && (int)line[1] == 65279)
{
line = line.Substring(2);
if (line.Length < 3)
return;
}
if (line.Length > 3)
arg = line.Substring(4);
string[] args = Tools.SplitString(arg, ConsoleCompilerConstants.MessageSeparator);
int command = Convert.ToInt32(line.Substring(0, 3));
if (command < sendCommandStartNumber + 50)
{
ChangeCompilerState((CompilerState)(command - sendCommandStartNumber), arg);
return;
}
switch (command)
{
case ConsoleCompilerConstants.PABCHealth:
pABCCodeHealth = Convert.ToInt32(arg);
break;
case ConsoleCompilerConstants.LinesCompiled:
linesCompiled = Convert.ToUInt32(arg);
break;
case ConsoleCompilerConstants.BeginOffest:
beginOffset = Convert.ToInt32(arg);
break;
case ConsoleCompilerConstants.VarBeginOffest:
varBeginOffset = Convert.ToInt32(arg);
break;
case ConsoleCompilerConstants.Warning:
case ConsoleCompilerConstants.InternalError:
case ConsoleCompilerConstants.Error:
location loc = null;
if (args.Length == 6)
loc = new location(
Convert.ToInt32(args[1]), Convert.ToInt32(args[2]),
Convert.ToInt32(args[3]), Convert.ToInt32(args[4]),
args[5]);
if (args.Length == 2)
loc = new location(0,0,0,0,args[1]);
switch(command)
{
case ConsoleCompilerConstants.Error:
errorsList.Add(new RemoteCompilerError(args[0], loc));
break;
case ConsoleCompilerConstants.Warning:
warnings.Add(new RemoteCompilerWarning(args[0], loc));
break;
case ConsoleCompilerConstants.InternalError:
errorsList.Add(new RemoteCompilerInternalError(args[0]));
break;
}
break;
case ConsoleCompilerConstants.WorkingSet:
remoteCompilerWorkingSet = Convert.ToInt64(arg);
break;
case ConsoleCompilerConstants.CompilerOptionsOutputType:
compilerOptions.OutputFileType = (CompilerOptions.OutputType)Convert.ToInt32(arg);
break;
case ConsoleCompilerConstants.FileExsist:
bool b = (bool)sourceFilesProvider(arg, SourceFileOperation.Exists);
sendCommand(ConsoleCompilerConstants.FileExsist, b);
break;
case ConsoleCompilerConstants.GetLastWriteTime:
DateTime d = (DateTime)sourceFilesProvider(arg, SourceFileOperation.GetLastWriteTime);
sendCommand(ConsoleCompilerConstants.GetLastWriteTime, d.Ticks);
break;
case ConsoleCompilerConstants.SourceFileText:
string text = (string)sourceFilesProvider(arg, SourceFileOperation.GetText);
if (text == null)
{
sendCommand(ConsoleCompilerConstants.Error);
return;
//throw new CompilerInternalError("RemoteCompiler", new Exception(string.Format("Could not compile: {0}", arg)));
}
//Encoding enc = (Encoding)sourceFilesProvider(arg, SourceFileOperation.FileEncoding);
//text = Tools.ChangeEncoding(text, enc, Encoding.UTF8);
//text = Tools.ChangeEncoding(text, Encoding.GetEncoding(1251), Encoding.UTF8);
if (pabcnetcProcess == null)
Reload();
Process pr = pabcnetcProcess;
sendCommand(ConsoleCompilerConstants.SourceFileText, text.Length);
byte[] buf = Encoding.UTF8.GetBytes(text);
pabcnetcProcess.StandardInput.BaseStream.Write(buf, 0, buf.Length);
pabcnetcProcess.StandardInput.Flush();
break;
default:
throw new CompilerInternalError("RemoteCompiler", new Exception(string.Format("Unkown command from pabcnetc.exe: {0}", command)));
}
}
void ChangeCompilerState(CompilerState State, string FileName)
{
if (State == CompilerState.Ready && compilationStarted)
{
compilationStarted = false;
if (CheckProcessMemory())
return;
}
compilerState = State;
if (OnChangeCompilerState != null)
OnChangeCompilerState(this, State, FileName);
}
/*void stopCompiler()
{
try
{
if (pabcnetcProcess != null && !pabcnetcProcess.HasExited)
{
pabcnetcProcess.EnableRaisingEvents = false;
pabcnetcStreamReader.Remove(inputId);
//sendCommand(ConsoleCompilerConstants.CommandExit);
pabcnetcProcess.Kill();
pabcnetcProcess = null;
}
remoteCompilerWorkingSet = 0;
}
catch
{
pabcnetcProcess = null;
remoteCompilerWorkingSet = 0;
}
}*/
// SSM 09/01/26 - по совету DeepSeek добавил WaitForExit(10) и Dispose() чтобы освободить ресурсы
void stopCompiler()
{
try
{
if (pabcnetcProcess != null && !pabcnetcProcess.HasExited)
{
pabcnetcProcess.EnableRaisingEvents = false;
if (pabcnetcStreamReader != null)
pabcnetcStreamReader.Remove(inputId);
pabcnetcProcess.Kill();
pabcnetcProcess.WaitForExit(10);
pabcnetcProcess.Dispose(); // ← ЕДИНСТВЕННОЕ ДОБАВЛЕНИЕ
}
}
catch
{
}
pabcnetcProcess = null;
remoteCompilerWorkingSet = 0;
}
uint linesCompiled = 0;
public uint LinesCompiled
{
get
{
return linesCompiled;
}
}
// SSM 18/09/20
int pABCCodeHealth = 0;
public int PABCCodeHealth { get { return pABCCodeHealth; } }
CompilerInternalDebug internalDebug=new CompilerInternalDebug();
public CompilerInternalDebug InternalDebug
{
get
{
return internalDebug;
}
set
{
internalDebug = value;
}
}
public CompilerState State
{
get
{
return compilerState;
}
}
CompilerOptions compilerOptions = new CompilerOptions();
public CompilerOptions CompilerOptions
{
get
{
return compilerOptions;
}
set
{
compilerOptions = value;
}
}
List<PascalABCCompiler.Errors.Error> errorsList = new List<PascalABCCompiler.Errors.Error>();
public List<PascalABCCompiler.Errors.Error> ErrorsList
{
get
{
return errorsList;
}
}
public List<PascalABCCompiler.Errors.CompilerWarning> warnings = new List<PascalABCCompiler.Errors.CompilerWarning>();
public List<PascalABCCompiler.Errors.CompilerWarning> Warnings
{
get
{
return warnings;
}
}
int beginOffset = 0;
public int BeginOffset
{
get { return beginOffset; }
}
int varBeginOffset = 0;
public int VarBeginOffset
{
get { return varBeginOffset; }
}
public event ChangeCompilerStateEventDelegate OnChangeCompilerState;
void sendCommand(int command, object arg)
{
if (pabcnetcProcess == null)
Reload();
pabcnetcProcess.StandardInput.WriteLine(string.Format("{0} {1}", command, arg));
pabcnetcProcess.StandardInput.Flush();
}
void sendObjectAsByteArray(int command, string obj)
{
if (pabcnetcProcess == null)
Reload();
pabcnetcProcess.StandardInput.WriteLine(string.Format("{0} {1}", command, obj.Length));
pabcnetcProcess.StandardInput.Flush();
byte[] buf = Encoding.UTF8.GetBytes(obj);
pabcnetcProcess.StandardInput.BaseStream.Write(buf, 0, buf.Length);
pabcnetcProcess.StandardInput.Flush();
}
void sendCommand(int command, params object[] args)
{
string s = args[0].ToString();
for (int i = 1; i < args.Length; i++)
s += ConsoleCompilerConstants.MessageSeparator + args[i].ToString();
sendCommand(command, s);
}
void sendCommand(int command)
{
if (pabcnetcProcess == null)
Reload();
pabcnetcProcess.StandardInput.WriteLine(command);
}
/*
* Не используется!
void sendObject(int id, object o)
{
sendCommand(id);
MemoryStream ms = new MemoryStream();
(new BinaryFormatter()).Serialize(ms, o);
ms.WriteTo(pabcnetcProcess.StandardInput.BaseStream);
}*/
void sendCompilerOptions()
{
byte[] encoded = Encoding.Unicode.GetBytes(compilerOptions.SourceFileName);
string file_name = Encoding.UTF8.GetString(encoded);
sendObjectAsByteArray(ConsoleCompilerConstants.CompilerOptionsFileName, compilerOptions.SourceFileName);
//sendCommand(ConsoleCompilerConstants.CompilerOptionsFileName, file_name);//compilerOptions.SourceFileName);
sendCommand(ConsoleCompilerConstants.CompilerOptionsProjectCompiled, compilerOptions.ProjectCompiled);
sendCommand(ConsoleCompilerConstants.UseDllForSystemUnits, compilerOptions.UseDllForSystemUnits);
sendCommand(ConsoleCompilerConstants.CompilerOptionsDebug, compilerOptions.Debug);
sendCommand(ConsoleCompilerConstants.CompilerOptionsRebuild, compilerOptions.Rebuild);
sendCommand(ConsoleCompilerConstants.CompilerOptionsOutputType, (int)compilerOptions.OutputFileType);
sendCommand(ConsoleCompilerConstants.InternalDebugSavePCU, InternalDebug.PCUGenerate);
sendCommand(ConsoleCompilerConstants.CompilerOptionsForDebugging, CompilerOptions.ForDebugging);
sendCommand(ConsoleCompilerConstants.CompilerOptionsRunWithEnvironment, CompilerOptions.RunWithEnvironment);
sendCommand(ConsoleCompilerConstants.IDELocale, Encoding.UTF8.GetString(Encoding.Unicode.GetBytes(StringResourcesLanguage.CurrentLanguageName)));
encoded = Encoding.Unicode.GetBytes(compilerOptions.OutputDirectory);
string dir_name = Encoding.UTF8.GetString(encoded);
//sendCommand(ConsoleCompilerConstants.CompilerOptionsOutputDirectory, dir_name);
sendObjectAsByteArray(ConsoleCompilerConstants.CompilerOptionsOutputDirectory, compilerOptions.OutputDirectory);
sendCommand(ConsoleCompilerConstants.CompilerOptionsClearStandartModules);
if (compilerOptions.Locale != null)
sendCommand(ConsoleCompilerConstants.CompilerLocale, compilerOptions.Locale);
foreach (var kv in compilerOptions.StandardModules)
{
foreach (CompilerOptions.StandardModule module in kv.Value)
{
sendCommand(ConsoleCompilerConstants.CompilerOptionsStandartModule,
module.name, (int)module.addMethod, module.languageToAdd);
}
}
}
public delegate void EnvironmentIdleDelegate();
public event EnvironmentIdleDelegate EnvironmentIdle;
void waitCompilerReloading()
{
while (compilerReloading)
{
Thread.Sleep(5);
if (EnvironmentIdle != null)
EnvironmentIdle();
}
}
public string Compile()
{
errorsList.Clear();
warnings.Clear();
//sendObject(ConsoleCompilerConstants.InternalDebug, internalDebug);
//sendObject(ConsoleCompilerConstants.CompilerOptions, compilerOptions);
waitCompilerReloading();
sendCompilerOptions();
compilerState = CompilerState.CompilationStarting;
compilationStarted = true;
sendCommand(ConsoleCompilerConstants.CommandCompile);
const int timeoutSeconds = 30;
DateTime startTime = DateTime.Now;
int checkCounter = 0;
while (compilerState != CompilerState.Ready)
{
if (checkCounter++ % 50 == 0) // 1000ms / 20ms = 50 - чтобы не вызывать дорогую DateTime.Now
{
if ((DateTime.Now - startTime).TotalSeconds > timeoutSeconds)
{
// ТАЙМАУТ! Компилятор не ответил за 30 секунд
errorsList.Add(new RemoteCompilerInternalError(
$"Таймаут компиляции ({timeoutSeconds} секунд). " +
"Компилятор не отвечает."));
Reload(); // Перезапускаем компилятор (он вероятно завис)
return null;
}
}
Thread.Sleep(20); // SSM 09.01.26 - изменил - было 5 мс - большая нагрузка на систему
}
if (errorsList.Count > 0)
return null;
return compilerOptions.OutputFileName;
}
bool CheckProcessMemory()
{
if (RemoteCompilerWorkingSet / 1024 / 1024 > maxProcessMemoryMB)
{
(new Thread(Reload)).Start();
return true;
}
return false;
}
long remoteCompilerWorkingSet = 0;
public long RemoteCompilerWorkingSet
{
get
{
return remoteCompilerWorkingSet;
//return pabcnetcProcess.WorkingSet64;
}
}
public void StartCompile()
{
errorsList.Clear();
warnings.Clear();
waitCompilerReloading();
sendCompilerOptions();
compilationStarted = true;
sendCommand(ConsoleCompilerConstants.CommandCompile);
}
public void AddWarnings(List<PascalABCCompiler.Errors.CompilerWarning> WarningList)
{
warnings.AddRange(WarningList);
}
SourceFilesProviderDelegate sourceFilesProvider = null;
public SourceFilesProviderDelegate SourceFilesProvider
{
get
{
return sourceFilesProvider;
}
set
{
sourceFilesProvider = value;
}
}
// SSM 2026 Исключил это из ICompiler
/*public PascalABCCompiler.SyntaxTree.compilation_unit ParseText(string FileName, string Text, List<PascalABCCompiler.Errors.Error> ErrorList, List<CompilerWarning> Warnings)
{
throw new NotSupportedException();
}
public string GetSourceFileText(string FileName)
{
throw new NotSupportedException();
}*/
public PascalABCCompiler.SemanticTreeConverters.SemanticTreeConvertersController SemanticTreeConvertersController
{
get
{
throw new NotSupportedException();
}
}
public PascalABCCompiler.SemanticTree.IProgramNode SemanticTree
{
get
{
throw new NotSupportedException();
}
}
public CompilationUnitHashTable UnitTable
{
get
{
throw new NotSupportedException();
}
}
bool compilerReloading = true;
public void Reload()
{
pABCCodeHealth = 0;
compilerReloading = true;
stopCompiler();
pabcnetcProcess = new Process();
if (!IsUnix())
{
pabcnetcProcess.StartInfo.FileName = Path.Combine(Tools.GetExecutablePath(), pabcnetcFileName);
pabcnetcProcess.StartInfo.Arguments = "commandmode";
}
else
{
pabcnetcProcess.StartInfo.FileName = "mono";
pabcnetcProcess.StartInfo.Arguments = Path.Combine(Tools.GetExecutablePath(), pabcnetcFileName)+" commandmode";
}
pabcnetcProcess.StartInfo.UseShellExecute = false;
pabcnetcProcess.StartInfo.CreateNoWindow = true;
pabcnetcProcess.StartInfo.RedirectStandardOutput = true;
pabcnetcProcess.StartInfo.RedirectStandardInput = true;
pabcnetcProcess.StartInfo.RedirectStandardError = true;
pabcnetcProcess.EnableRaisingEvents = true;
pabcnetcProcess.StartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8;
pabcnetcProcess.Exited += pabcnetcProcess_Exited;
pabcnetcStreamReader.Remove(inputId);
pabcnetcProcess.Start();
//pabcnetcProcess.StandardInput.Encoding;
pabcnetcStreamReader.Add(pabcnetcProcess.StandardOutput, inputId, inputEncoding);
compilerReloading = false;
}
public static bool IsUnix()
{
return System.Environment.OSVersion.Platform == System.PlatformID.Unix || System.Environment.OSVersion.Platform == System.PlatformID.MacOSX;
}
void pabcnetcProcess_Exited(object sender, EventArgs e)
{
string error = pabcnetcProcess.StandardError.ReadToEnd();
if (!compilerReloading)
{
pabcnetcProcess = null;
//Reload();
}
}
public CompilerType CompilerType
{
get
{
return CompilerType.Remote;
}
}
~RemoteCompiler()
{
Free();
}
public void Free()
{
stopCompiler();
}
}
}