diff --git a/src/OneScript.Core/OneScript.Core.csproj b/src/OneScript.Core/OneScript.Core.csproj index d69d06de6..a66fb39d1 100644 --- a/src/OneScript.Core/OneScript.Core.csproj +++ b/src/OneScript.Core/OneScript.Core.csproj @@ -9,6 +9,9 @@ + + + OneScript.CoreLib diff --git a/src/ScriptEngine.HostedScript/HostedScriptEngine.cs b/src/ScriptEngine.HostedScript/HostedScriptEngine.cs index 6a26da3d6..979da365a 100644 --- a/src/ScriptEngine.HostedScript/HostedScriptEngine.cs +++ b/src/ScriptEngine.HostedScript/HostedScriptEngine.cs @@ -117,6 +117,14 @@ public Process CreateProcess(IHostApplication host, SourceCode src) return InitProcess(bslProcess, host, module); } + public Process CreateProcess(IHostApplication host, IExecutableModule module) + { + Initialize(); + SetGlobalEnvironment(host, module.Source); + var bslProcess = _engine.NewProcess(); + return InitProcess(bslProcess, host, module); + } + private void DefineConstants(ICompilerFrontend compilerSvc) { var definitions = _workingConfig.PreprocessorDefinitions; diff --git a/src/ScriptEngine/Machine/Core.cs b/src/ScriptEngine/Machine/Core.cs index 0403ccffe..4ebb6d36b 100644 --- a/src/ScriptEngine/Machine/Core.cs +++ b/src/ScriptEngine/Machine/Core.cs @@ -5,6 +5,7 @@ This Source Code Form is subject to the terms of the at http://mozilla.org/MPL/2.0/. ----------------------------------------------------------*/ using System; +using MessagePack; namespace ScriptEngine.Machine { @@ -141,10 +142,12 @@ public enum OperationCode ModuleInfo } - [Serializable] + [MessagePackObjectAttribute] public struct Command { + [global::MessagePack.Key(0)] public OperationCode Code; + [global::MessagePack.Key(1)] public int Argument; public override string ToString() diff --git a/src/ScriptEngine/Machine/MachineInstance.cs b/src/ScriptEngine/Machine/MachineInstance.cs index cea8f0c62..e20bdd59f 100644 --- a/src/ScriptEngine/Machine/MachineInstance.cs +++ b/src/ScriptEngine/Machine/MachineInstance.cs @@ -660,6 +660,8 @@ private void InitCommands() #region Simple operations private void PushVar(int arg) { + if (arg < 0 || _module.VariableRefs == null || arg >= _module.VariableRefs.Count) + throw new ScriptException($"Invalid variable-ref index {arg} at instruction {_currentFrame?.InstructionPointer} in module {_module?.Source?.Location ?? ""}"); var binding = _module.VariableRefs[arg]; var scope = _currentFrame.Scopes[binding.ScopeNumber]; _operationStack.Push(scope.Variables[binding.MemberNumber]); @@ -668,6 +670,8 @@ private void PushVar(int arg) private void PushConst(int arg) { + if (arg < 0 || _module.Constants == null || arg >= _module.Constants.Count) + throw new ScriptException($"Invalid constant index {arg} at instruction {_currentFrame?.InstructionPointer} in module {_module?.Source?.Location ?? ""}"); _operationStack.Push(_module.Constants[arg]); NextInstruction(); } @@ -698,12 +702,16 @@ private void PushNull(int arg) private void PushLoc(int arg) { + if (arg < 0 || _currentFrame == null || _currentFrame.Locals == null || arg >= _currentFrame.Locals.Length) + throw new ScriptException($"Invalid local index {arg} at instruction {_currentFrame?.InstructionPointer} in module {_module?.Source?.Location ?? ""}"); _operationStack.Push(_currentFrame.Locals[arg]); NextInstruction(); } private void PushRef(int arg) { + if (arg < 0 || _module.VariableRefs == null || arg >= _module.VariableRefs.Count) + throw new ScriptException($"Invalid variable-ref index {arg} at instruction {_currentFrame?.InstructionPointer} in module {_module?.Source?.Location ?? ""}"); var binding = _module.VariableRefs[arg]; var scope = _currentFrame.Scopes[binding.ScopeNumber]; var reference = Variable.CreateContextPropertyReference(scope.Instance, binding.MemberNumber, "$stackvar"); @@ -713,17 +721,19 @@ private void PushRef(int arg) private void LoadVar(int arg) { + if (arg < 0 || _module.VariableRefs == null || arg >= _module.VariableRefs.Count) + throw new ScriptException($"Invalid variable-ref index {arg} at instruction {_currentFrame?.InstructionPointer} in module {_module?.Source?.Location ?? ""}"); var binding = _module.VariableRefs[arg]; var scope = _currentFrame.Scopes[binding.ScopeNumber]; scope.Variables[binding.MemberNumber].Value = PopRawValue(); NextInstruction(); } - private void LoadLoc(int arg) - { - _currentFrame.Locals[arg].Value = PopRawValue(); - NextInstruction(); - } + private void LoadLoc(int arg) + { + _currentFrame.Locals[arg].Value = PopRawValue(); + NextInstruction(); + } private void AssignRef(int arg) { @@ -995,7 +1005,10 @@ private void PushDefaultArg(int arg) private void ResolveProp(int arg) { var objIValue = _operationStack.Pop(); - + + if (arg < 0 || _module.Constants == null || arg >= _module.Constants.Count) + throw new ScriptException($"Invalid constant index {arg} at instruction {_currentFrame?.InstructionPointer} in module {_module?.Source?.Location ?? ""}"); + var context = objIValue.AsObject(); var propName = _module.Constants[arg].ToString(_process); var propNum = context.GetPropertyNumber(propName); @@ -1007,6 +1020,9 @@ private void ResolveProp(int arg) private void ResolveMethodProc(int arg) { + if (arg < 0 || _module.Constants == null || arg >= _module.Constants.Count) + throw new ScriptException($"Invalid constant index {arg} at instruction {_currentFrame?.InstructionPointer} in module {_module?.Source?.Location ?? ""}"); + PrepareContextCallArguments(arg, out IRuntimeContextInstance context, out int methodId, out IValue[] argValues); context.CallAsProcedure(methodId, argValues, _process); @@ -1015,6 +1031,9 @@ private void ResolveMethodProc(int arg) private void ResolveMethodFunc(int arg) { + if (arg < 0 || _module.Constants == null || arg >= _module.Constants.Count) + throw new ScriptException($"Invalid constant index {arg} at instruction {_currentFrame?.InstructionPointer} in module {_module?.Source?.Location ?? ""}"); + PrepareContextCallArguments(arg, out IRuntimeContextInstance context, out int methodId, out IValue[] argValues); if (!context.DynamicMethodSignatures && context.GetMethodInfo(methodId).ReturnType == typeof(void)) diff --git a/src/ScriptEngine/Machine/PrecompiledRuntimeModule.cs b/src/ScriptEngine/Machine/PrecompiledRuntimeModule.cs new file mode 100644 index 000000000..d386df69d --- /dev/null +++ b/src/ScriptEngine/Machine/PrecompiledRuntimeModule.cs @@ -0,0 +1,91 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +using MessagePack; +using System.IO; +using System; +using System.Collections.Generic; +using OneScript.Contexts; +using OneScript.Execution; +using OneScript.Sources; + +namespace ScriptEngine.Machine +{ + + [MessagePackObject] + public class PrecompiledRuntimeModule : IExecutableModule + { + [Key(0)] + public IList ModuleAttributes { get; set; } + + [Key(1)] + public IList Fields { get; set; } + + [Key(2)] + public IList Properties { get; set; } + + [Key(3)] + public IList Methods { get; set; } + + [Key(4)] + public BslScriptMethodInfo ModuleBody { get; set; } + + [Key(5)] + public SourceCode Source { get; set; } + + [Key(6)] + public IDictionary Interfaces { get; set; } + } + + public class ModuleSerializer + { + public static void SaveModule(IExecutableModule module, string filePath) + { + SaveModuleDto(module, filePath); + } + + public static IExecutableModule LoadModule(string filePath) + { + + if (!File.Exists(filePath)) + throw new FileNotFoundException($"Precompiled module not found: {filePath}"); + + byte[] bytes = File.ReadAllBytes(filePath); + + try + { + var dto = MessagePackSerializer.Deserialize(bytes); + if (dto != null) + { + return RuntimeModuleDtoMapper.FromDto(dto); + } + } + catch (MessagePack.MessagePackSerializationException) + { + // not DTO format, fall through to legacy deserialization + } + + return MessagePackSerializer.Deserialize(bytes); + } + + public static void SaveModuleDto(IExecutableModule module, string filePath) + { + var dto = RuntimeModuleDtoMapper.ToDto(module); + byte[] bytes = MessagePackSerializer.Serialize(dto); + File.WriteAllBytes(filePath, bytes); + } + + public static PrecompiledRuntimeModuleDto LoadModuleDto(string filePath) + { + if (!File.Exists(filePath)) + throw new FileNotFoundException($"Precompiled module not found: {filePath}"); + + byte[] bytes = File.ReadAllBytes(filePath); + return MessagePackSerializer.Deserialize(bytes); + } + } +} \ No newline at end of file diff --git a/src/ScriptEngine/Machine/PrecompiledRuntimeModuleDtos.cs b/src/ScriptEngine/Machine/PrecompiledRuntimeModuleDtos.cs new file mode 100644 index 000000000..251a890fc --- /dev/null +++ b/src/ScriptEngine/Machine/PrecompiledRuntimeModuleDtos.cs @@ -0,0 +1,800 @@ +/*---------------------------------------------------------- + DTO classes for PrecompiledRuntimeModule serialization via MessagePack. + These are simple POCOs annotated with MessagePack attributes and + helper methods to convert runtime types to DTOs. +----------------------------------------------------------*/ + +using MessagePack; +using System; +using System.Collections.Generic; +using System.Linq; +using OneScript.Contexts; +using OneScript.Execution; +using OneScript.Values; +using OneScript.Sources; + +namespace ScriptEngine.Machine +{ + [MessagePackObject] + public class PrecompiledRuntimeModuleDto + { + [Key(2)] public string ModuleTypeName { get; set; } + // stack-specific + [Key(3)] public IList Code { get; set; } + [Key(4)] public IList Constants { get; set; } + [Key(5)] public int EntryMethodIndex { get; set; } = -1; + [Key(6)] public IList VariableRefs { get; set; } + [Key(7)] public IList MethodRefs { get; set; } + // then the common fields + [Key(8)] public IList ModuleAttributes { get; set; } + [Key(9)] public IList Fields { get; set; } + [Key(10)] public IList Properties { get; set; } + [Key(11)] public IList Methods { get; set; } + [Key(12)] public BslScriptMethodInfoDto ModuleBody { get; set; } + [Key(13)] public SourceCodeDto Source { get; set; } + // Interfaces: key is interface type assembly-qualified name, value is object (serializable representation) + [Key(14)] public IDictionary Interfaces { get; set; } + } + + [MessagePackObject] + public class BslAnnotationAttributeDto + { + [Key(0)] public string Name { get; set; } + [Key(1)] public IList Parameters { get; set; } + } + + [MessagePackObject] + public class BslAnnotationParameterDto + { + [Key(0)] public string Name { get; set; } + [Key(1)] public object Value { get; set; } + [Key(2)] public int ConstantValueIndex { get; set; } = -1; + } + + [MessagePackObject] + public class BslScriptFieldInfoDto + { + [Key(0)] public string Name { get; set; } + [Key(1)] public string DeclaringType { get; set; } + [Key(2)] public string FieldType { get; set; } + [Key(3)] public int DispatchId { get; set; } = -1; + [Key(4)] public bool IsExported { get; set; } + [Key(5)] public IList Annotations { get; set; } + } + + [MessagePackObject] + public class BslScriptPropertyInfoDto + { + [Key(0)] public string Name { get; set; } + [Key(1)] public string Alias { get; set; } + [Key(2)] public string DeclaringType { get; set; } + [Key(3)] public string PropertyType { get; set; } + [Key(4)] public int DispatchId { get; set; } = -1; + [Key(5)] public bool IsExported { get; set; } + [Key(6)] public bool CanRead { get; set; } + [Key(7)] public bool CanWrite { get; set; } + [Key(8)] public IList Annotations { get; set; } + } + + [MessagePackObject] + public class BslParameterInfoDto + { + [Key(0)] public string Name { get; set; } + [Key(1)] public int Position { get; set; } + [Key(2)] public string ParameterType { get; set; } + [Key(3)] public bool HasDefault { get; set; } + [Key(4)] public object DefaultValue { get; set; } + [Key(5)] public int ConstantValueIndex { get; set; } = -1; + [Key(6)] public bool ExplicitByVal { get; set; } + } + + [MessagePackObject] + public class BslScriptMethodInfoDto + { + [Key(0)] public string Name { get; set; } + [Key(1)] public string DeclaringType { get; set; } + [Key(2)] public string ReturnType { get; set; } + [Key(3)] public int DispatchId { get; set; } = -1; + [Key(4)] public bool IsExported { get; set; } + [Key(5)] public IList Parameters { get; set; } + [Key(6)] public IList Annotations { get; set; } + [Key(7)] public int EntryPoint { get; set; } = -1; + [Key(8)] public IList Locals { get; set; } + } + + [MessagePackObject] + public class SourceCodeDto + { + [Key(0)] public string Name { get; set; } + [Key(1)] public string Location { get; set; } + [Key(2)] public string Code { get; set; } + } + + [MessagePackObject] + public class SymbolBindingDto + { + [Key(0)] public int ScopeNumber { get; set; } + [Key(1)] public int MemberNumber { get; set; } + } + + public static class RuntimeModuleDtoMapper + { + public static PrecompiledRuntimeModuleDto ToDto(IExecutableModule module) + { + if (module == null) return null; + var dto = new PrecompiledRuntimeModuleDto(); + dto.ModuleTypeName = module.GetType().AssemblyQualifiedName; + + if (module is StackRuntimeModule stack) + { + dto.Code = stack.Code?.ToList(); + dto.Constants = stack.Constants?.Select(PrimitiveToObject).ToList(); + dto.EntryMethodIndex = stack.EntryMethodIndex; + dto.VariableRefs = stack.VariableRefs?.Select(sb => new SymbolBindingDto { ScopeNumber = sb.ScopeNumber, MemberNumber = sb.MemberNumber }).ToList(); + dto.MethodRefs = stack.MethodRefs?.Select(sb => new SymbolBindingDto { ScopeNumber = sb.ScopeNumber, MemberNumber = sb.MemberNumber }).ToList(); + } + + dto.ModuleAttributes = module.ModuleAttributes?.Select(ToDto).ToList(); + dto.Fields = module.Fields?.Select(ToDto).ToList(); + dto.Properties = module.Properties?.Select(ToDto).ToList(); + dto.Methods = module.Methods?.Select(ToDto).ToList(); + dto.ModuleBody = ToDto(module.ModuleBody); + dto.Source = ToDto(module.Source); + dto.Interfaces = module.Interfaces?.ToDictionary(kv => kv.Key?.AssemblyQualifiedName ?? kv.Key?.FullName ?? kv.Key?.Name, kv => kv.Value); + + return dto; + } + + public static OneScript.Execution.IExecutableModule FromDto(PrecompiledRuntimeModuleDto dto) + { + if (dto == null) return null; + + bool isStack = !string.IsNullOrEmpty(dto.ModuleTypeName) && dto.ModuleTypeName.Contains("StackRuntimeModule") || dto.Code != null; + + if (isStack) + { + // try to determine owner type from declarations + Type ownerType = null; + ownerType = dto.Methods?.Select(m => TryGetType(m.DeclaringType)).FirstOrDefault(t => t != null); + ownerType ??= dto.Fields?.Select(f => TryGetType(f.DeclaringType)).FirstOrDefault(t => t != null); + if (ownerType == null) + { + ownerType = typeof(ScriptEngine.Machine.Contexts.UserScriptContextInstance); + } + + var stackMod = new StackRuntimeModule(ownerType); + + // fill stack-specific fields + if (dto.Code != null) + foreach (var c in dto.Code) stackMod.Code.Add(c); + + if (dto.Constants != null) + foreach (var cv in dto.Constants) stackMod.Constants.Add(CreatePrimitiveFromObject(cv)); + + if (dto.EntryMethodIndex >= 0 && dto.MethodRefs != null && dto.EntryMethodIndex < stackMod.MethodRefs.Count) + stackMod.EntryMethodIndex = dto.EntryMethodIndex; + else + stackMod.EntryMethodIndex = -1; + + if (dto.VariableRefs != null) + foreach (var vb in dto.VariableRefs) stackMod.VariableRefs.Add(new OneScript.Compilation.Binding.SymbolBinding { ScopeNumber = vb.ScopeNumber, MemberNumber = vb.MemberNumber }); + + if (dto.MethodRefs != null) + foreach (var mb in dto.MethodRefs) stackMod.MethodRefs.Add(new OneScript.Compilation.Binding.SymbolBinding { ScopeNumber = mb.ScopeNumber, MemberNumber = mb.MemberNumber }); + + // we'll populate annotations/fields/properties/methods below into stackMod + + // use separate variable for population + var modObj = (object)stackMod; + // continue with population logic using stackMod instead of PrecompiledRuntimeModule + // fall through to population code but adapted by conditional checks below + + // populate module attributes + if (dto.ModuleAttributes != null) + foreach (var a in dto.ModuleAttributes) stackMod.ModuleAttributes.Add((BslAnnotationAttribute)FromAnnotationDto(a)); + + // Fields + if (dto.Fields != null) + { + var fType = typeof(BslScriptFieldInfo); + foreach (var fd in dto.Fields) + { + BslScriptFieldInfo f = null; + var ctor = fType.GetConstructor(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, new Type[] { typeof(string) }, null); + if (ctor != null) + f = (BslScriptFieldInfo)ctor.Invoke(new object[] { fd.Name }); + else + f = (BslScriptFieldInfo)Activator.CreateInstance(fType, true); + + void SetField(string name, object value) + { + var fi = fType.GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + if (fi != null) fi.SetValue(f, value); + } + + SetField("_declaringType", TryGetType(fd.DeclaringType)); + SetField("_dataType", TryGetType(fd.FieldType) ?? typeof(OneScript.Values.BslValue)); + SetField("_dispId", fd.DispatchId); + SetField("_isExported", fd.IsExported); + SetField("_name", fd.Name); + + if (fd.Annotations != null && fd.Annotations.Count > 0) + { + var ah = new AnnotationHolder(fd.Annotations.Select(a => (object)FromAnnotationDto(a)).ToArray()); + var setAnn = fType.GetMethod("SetAnnotations", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + setAnn?.Invoke(f, new object[] { ah }); + } + + stackMod.Fields.Add(f); + } + } + + // Properties + if (dto.Properties != null) + { + var pType = typeof(BslScriptPropertyInfo); + foreach (var pd in dto.Properties) + { + BslScriptPropertyInfo p = null; + var ctor = pType.GetConstructor(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, Type.EmptyTypes, null); + if (ctor != null) + p = (BslScriptPropertyInfo)ctor.Invoke(null); + else + p = (BslScriptPropertyInfo)Activator.CreateInstance(pType, true); + + void SetP(string name, object value) + { + var fi = pType.GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + if (fi != null) fi.SetValue(p, value); + } + + SetP("_declaringType", TryGetType(pd.DeclaringType)); + SetP("_propertyType", TryGetType(pd.PropertyType) ?? typeof(OneScript.Values.BslValue)); + SetP("_name", pd.Name); + SetP("_alias", pd.Alias); + SetP("_isPrivate", !pd.IsExported); + SetP("_canRead", pd.CanRead); + SetP("_canWrite", pd.CanWrite); + + var prop = pType.GetProperty("DispatchId", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); + if (prop != null) + { + var setm = prop.GetSetMethod(true); + setm?.Invoke(p, new object[] { pd.DispatchId }); + } + + if (pd.Annotations != null && pd.Annotations.Count > 0) + { + var ah = new AnnotationHolder(pd.Annotations.Select(a => (object)FromAnnotationDto(a)).ToArray()); + var setAnn = pType.GetMethod("SetAnnotations", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + setAnn?.Invoke(p, new object[] { ah }); + } + + stackMod.Properties.Add(p); + } + } + + // Methods + if (dto.Methods != null) + { + var mType = typeof(MachineMethodInfo); + foreach (var md in dto.Methods) + { + BslScriptMethodInfo m = null; + var create = mType.GetMethod("Create", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public); + if (create != null) + m = (BslScriptMethodInfo)create.Invoke(null, null); + else + { + var ctor = mType.GetConstructor(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, Type.EmptyTypes, null); + if (ctor != null) + m = (BslScriptMethodInfo)ctor.Invoke(null); + else + m = (BslScriptMethodInfo)Activator.CreateInstance(mType, true); + } + + void SetM(string name, object value) + { + var fi = mType.GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + if (fi != null) fi.SetValue(m, value); + } + + SetM("_declaringType", TryGetType(md.DeclaringType)); + SetM("_returnType", TryGetType(md.ReturnType) ?? typeof(void)); + SetM("_name", md.Name); + SetM("_isPrivate", !md.IsExported); + + if (md.Parameters != null) + { + var paramList = md.Parameters.Select(CreateBslParameterFromDto).ToArray(); + var fiParams = mType.GetField("_parameters", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + fiParams?.SetValue(m, paramList); + } + + var prop = mType.GetProperty("DispatchId", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); + if (prop != null) + { + var setm = prop.GetSetMethod(true); + setm?.Invoke(m, new object[] { md.DispatchId }); + } + + if (md.Annotations != null && md.Annotations.Count > 0) + { + var ah = new AnnotationHolder(md.Annotations.Select(a => (object)FromAnnotationDto(a)).ToArray()); + var setAnn = mType.GetMethod("SetAnnotations", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + setAnn?.Invoke(m, new object[] { ah }); + } + + // set runtime parameters when available in DTO + try + { + if (m is MachineMethodInfo mm && md != null && md.EntryPoint >= 0) + { + mm.SetRuntimeParameters(md.EntryPoint, md.Locals?.ToArray() ?? new string[0]); + } + } + catch { } + + stackMod.Methods.Add(m); + } + } + + if (dto.ModuleBody != null) + { + // locate by dispatch id or name + var body = stackMod.Methods.FirstOrDefault(m => m.DispatchId == dto.ModuleBody.DispatchId) ?? stackMod.Methods.FirstOrDefault(m => m.Name == dto.ModuleBody.Name); + if (dto.EntryMethodIndex >= 0 && stackMod.MethodRefs != null && dto.EntryMethodIndex < stackMod.MethodRefs.Count) + stackMod.EntryMethodIndex = dto.EntryMethodIndex; + else + stackMod.EntryMethodIndex = -1; + // ModuleBody property will compute based on EntryMethodIndex when used + } + + if (dto.Interfaces != null) + { + foreach (var kv in dto.Interfaces) + { + var t = TryGetTypeByName(kv.Key); + if (t != null) stackMod.Interfaces[t] = kv.Value; + } + } + + return stackMod; + } + + var mod = new PrecompiledRuntimeModule(); + // Source + if (dto.Source != null) + { + // Create a simple in-memory ICodeSource implementation via StringCodeSource if available + try + { + var scType = typeof(OneScript.Sources.SourceCode).Assembly.GetType("OneScript.Sources.StringCodeSource"); + if (scType != null) + { + // try to find ctor (string location, string code) + var ctor = scType.GetConstructor(new Type[] { typeof(string), typeof(string) }); + object srcObj = null; + if (ctor != null) + { + srcObj = ctor.Invoke(new object[] { dto.Source.Name ?? dto.Source.Location, dto.Source.Code }); + } + + if (srcObj != null) + { + // create SourceCode instance via internal ctor: SourceCode(string, ICodeSource) + var sourceCodeType = typeof(OneScript.Sources.SourceCode); + var scCtor = sourceCodeType.GetConstructor(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, new Type[] { typeof(string), scType.GetInterface("OneScript.Language.ICodeSource") ?? typeof(object) }, null); + if (scCtor != null) + { + var sourceCode = scCtor.Invoke(new object[] { dto.Source.Name ?? dto.Source.Location, srcObj }) as OneScript.Sources.SourceCode; + mod.Source = sourceCode; + } + } + } + } + catch + { + // ignore and leave Source null if we can't reconstruct + } + } + + mod.ModuleAttributes = dto.ModuleAttributes?.Select(a => (BslAnnotationAttribute)FromAnnotationDto(a)).ToList(); + + // Fields + if (dto.Fields != null) + { + var list = new List(); + var fType = typeof(BslScriptFieldInfo); + foreach (var fd in dto.Fields) + { + BslScriptFieldInfo f = null; + + var ctor = fType.GetConstructor(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, new Type[] { typeof(string) }, null); + if (ctor != null) + f = (BslScriptFieldInfo)ctor.Invoke(new object[] { fd.Name }); + else + f = (BslScriptFieldInfo)Activator.CreateInstance(fType, true); + + void SetField(string name, object value) + { + var fi = fType.GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + if (fi != null) fi.SetValue(f, value); + } + + SetField("_declaringType", TryGetType(fd.DeclaringType)); + SetField("_dataType", TryGetType(fd.FieldType) ?? typeof(OneScript.Values.BslValue)); + SetField("_dispId", fd.DispatchId); + SetField("_isExported", fd.IsExported); + SetField("_name", fd.Name); + + // Annotations + if (fd.Annotations != null && fd.Annotations.Count > 0) + { + var ah = new AnnotationHolder(fd.Annotations.Select(a => (object)FromAnnotationDto(a)).ToArray()); + var setAnn = fType.GetMethod("SetAnnotations", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + setAnn?.Invoke(f, new object[] { ah }); + } + + list.Add(f); + } + + mod.Fields = list; + } + + // Properties + if (dto.Properties != null) + { + var list = new List(); + var pType = typeof(BslScriptPropertyInfo); + foreach (var pd in dto.Properties) + { + BslScriptPropertyInfo p = null; + var ctor = pType.GetConstructor(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, Type.EmptyTypes, null); + if (ctor != null) + p = (BslScriptPropertyInfo)ctor.Invoke(null); + else + p = (BslScriptPropertyInfo)Activator.CreateInstance(pType, true); + + void SetP(string name, object value) + { + var fi = pType.GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + if (fi != null) fi.SetValue(p, value); + } + + SetP("_declaringType", TryGetType(pd.DeclaringType)); + SetP("_propertyType", TryGetType(pd.PropertyType) ?? typeof(OneScript.Values.BslValue)); + SetP("_name", pd.Name); + SetP("_alias", pd.Alias); + SetP("_isPrivate", !pd.IsExported); + SetP("_canRead", pd.CanRead); + SetP("_canWrite", pd.CanWrite); + + // set DispatchId backing field for auto-property + var prop = pType.GetProperty("DispatchId", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); + if (prop != null) + { + var setm = prop.GetSetMethod(true); + setm?.Invoke(p, new object[] { pd.DispatchId }); + } + + // annotations + if (pd.Annotations != null && pd.Annotations.Count > 0) + { + var ah = new AnnotationHolder(pd.Annotations.Select(a => (object)FromAnnotationDto(a)).ToArray()); + var setAnn = pType.GetMethod("SetAnnotations", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + setAnn?.Invoke(p, new object[] { ah }); + } + + list.Add(p); + } + + mod.Properties = list; + } + + // Methods + if (dto.Methods != null) + { + var list = new List(); + var mType = typeof(BslScriptMethodInfo); + foreach (var md in dto.Methods) + { + BslScriptMethodInfo m = null; + var create = mType.GetMethod("Create", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public); + if (create != null) + m = (BslScriptMethodInfo)create.Invoke(null, null); + else + { + var ctor = mType.GetConstructor(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, Type.EmptyTypes, null); + if (ctor != null) + m = (BslScriptMethodInfo)ctor.Invoke(null); + else + m = (BslScriptMethodInfo)Activator.CreateInstance(mType, true); + } + + void SetM(string name, object value) + { + var fi = mType.GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + if (fi != null) fi.SetValue(m, value); + } + + SetM("_declaringType", TryGetType(md.DeclaringType)); + SetM("_returnType", TryGetType(md.ReturnType) ?? typeof(void)); + SetM("_name", md.Name); + SetM("_isPrivate", !md.IsExported); + + // parameters + if (md.Parameters != null) + { + var paramList = md.Parameters.Select(pd => CreateBslParameterFromDto(pd)).ToArray(); + var fiParams = mType.GetField("_parameters", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + fiParams?.SetValue(m, paramList); + } + + // DispatchId + var prop = mType.GetProperty("DispatchId", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); + if (prop != null) + { + var setm = prop.GetSetMethod(true); + setm?.Invoke(m, new object[] { md.DispatchId }); + } + + // Annotations + if (md.Annotations != null && md.Annotations.Count > 0) + { + var ah = new AnnotationHolder(md.Annotations.Select(a => (object)FromAnnotationDto(a)).ToArray()); + var setAnn = mType.GetMethod("SetAnnotations", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + setAnn?.Invoke(m, new object[] { ah }); + } + + list.Add(m); + } + + mod.Methods = list; + } + + // Module body + if (dto.ModuleBody != null) + { + mod.ModuleBody = FromDto(dto.ModuleBody, typeof(BslScriptMethodInfo)) as BslScriptMethodInfo; + } + + if (dto.Interfaces != null) + { + mod.Interfaces = dto.Interfaces.ToDictionary(kv => TryGetTypeByName(kv.Key), kv => kv.Value as object); + } + + return mod; + } + + private static Type TryGetType(string assemblyQualifiedName) + { + if (string.IsNullOrEmpty(assemblyQualifiedName)) return null; + try + { + var t = Type.GetType(assemblyQualifiedName, false); + return t; + } + catch + { + return null; + } + } + + private static Type TryGetTypeByName(string name) + { + if (string.IsNullOrEmpty(name)) return null; + try + { + var t = Type.GetType(name, false); + return t; + } + catch { return null; } + } + + private static object FromAnnotationDto(BslAnnotationAttributeDto a) + { + if (a == null) return null; + var parameters = a.Parameters?.Select(p => + { + var val = CreatePrimitiveFromObject(p.Value); + return (object)new OneScript.Contexts.BslAnnotationParameter(p.Name, val as BslPrimitiveValue) { ConstantValueIndex = p.ConstantValueIndex }; + }).ToArray(); + + if (parameters != null && parameters.Length > 0) + return new BslAnnotationAttribute(a.Name, parameters.Cast().ToList()); + return new BslAnnotationAttribute(a.Name); + } + + private static BslParameterInfo CreateBslParameterFromDto(BslParameterInfoDto pd) + { + var pType = typeof(BslParameterInfo); + BslParameterInfo p = (BslParameterInfo)Activator.CreateInstance(pType, true); + + void SetField(string name, object value) + { + var fi = pType.GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + if (fi != null) fi.SetValue(p, value); + } + + SetField("NameImpl", pd.Name); + SetField("PositionImpl", pd.Position); + SetField("ClassImpl", TryGetType(pd.ParameterType) ?? typeof(OneScript.Values.BslValue)); + SetField("DefaultValueImpl", pd.HasDefault ? CreatePrimitiveFromObject(pd.DefaultValue) : null); + + try { p.ConstantValueIndex = pd.ConstantValueIndex; } catch { } + return p; + } + + private static BslPrimitiveValue CreatePrimitiveFromObject(object o) + { + if (o == null) return null; + if (o is string s) return BslStringValue.Create(s); + if (o is decimal dec) return (BslNumericValue)BslNumericValue.Create(dec); + if (o is double dbl) return (BslNumericValue)BslNumericValue.Create((decimal)dbl); + if (o is int i) return (BslNumericValue)BslNumericValue.Create(i); + if (o is bool b) return (BslBooleanValue)BslBooleanValue.Create(b); + if (o is DateTime dt) return BslDateValue.Create(dt); + return BslStringValue.Create(o.ToString()); + } + + private static object FromDto(BslScriptMethodInfoDto md, Type expectedType = null) + { + var mType = expectedType ?? typeof(BslScriptMethodInfo); + var create = mType.GetMethod("Create", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public); + BslScriptMethodInfo m = null; + if (create != null) + m = (BslScriptMethodInfo)create.Invoke(null, null); + else + m = (BslScriptMethodInfo)Activator.CreateInstance(mType, true); + + void SetM(string name, object value) + { + var fi = mType.GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + if (fi != null) fi.SetValue(m, value); + } + + SetM("_declaringType", TryGetType(md.DeclaringType)); + SetM("_returnType", TryGetType(md.ReturnType) ?? typeof(void)); + SetM("_name", md.Name); + SetM("_isPrivate", !md.IsExported); + + if (md.Parameters != null) + { + var paramList = md.Parameters.Select(CreateBslParameterFromDto).ToArray(); + var fiParams = mType.GetField("_parameters", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + fiParams?.SetValue(m, paramList); + } + + var prop = mType.GetProperty("DispatchId", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); + if (prop != null) + { + var setm = prop.GetSetMethod(true); + setm?.Invoke(m, new object[] { md.DispatchId }); + } + + if (md.Annotations != null && md.Annotations.Count > 0) + { + var ah = new AnnotationHolder(md.Annotations.Select(a => (object)FromAnnotationDto(a)).ToArray()); + var setAnn = mType.GetMethod("SetAnnotations", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy); + setAnn?.Invoke(m, new object[] { ah }); + } + + // set runtime parameters if this DTO carries method runtime info + try + { + if (m is MachineMethodInfo mm && md != null && md.EntryPoint >= 0) + { + mm.SetRuntimeParameters(md.EntryPoint, md.Locals?.ToArray() ?? new string[0]); + } + } + catch { } + + return m; + } + + public static BslAnnotationAttributeDto ToDto(BslAnnotationAttribute a) + { + if (a == null) return null; + return new BslAnnotationAttributeDto + { + Name = a.Name, + Parameters = a.Parameters?.Select(p => new BslAnnotationParameterDto + { + Name = p.Name, + Value = PrimitiveToObject(p.Value), + ConstantValueIndex = p.ConstantValueIndex + }).ToList() + }; + } + + private static object PrimitiveToObject(BslPrimitiveValue v) + { + if (v == null) return null; + return v switch + { + BslStringValue s => (string) s, + BslNumericValue n => (decimal) n, + BslBooleanValue b => (bool) b, + BslDateValue d => (DateTime) d, + _ => v.ToString() + }; + } + + public static BslScriptFieldInfoDto ToDto(BslScriptFieldInfo f) + { + if (f == null) return null; + return new BslScriptFieldInfoDto + { + Name = f.Name, + DeclaringType = f.DeclaringType?.AssemblyQualifiedName, + FieldType = f.FieldType?.AssemblyQualifiedName, + DispatchId = f.DispatchId, + IsExported = (f.Attributes & System.Reflection.FieldAttributes.Public) != 0, + Annotations = f.GetCustomAttributes(false)?.OfType()?.Select(ToDto).ToList() ?? new List() + }; + } + + public static BslScriptPropertyInfoDto ToDto(BslScriptPropertyInfo p) + { + if (p == null) return null; + return new BslScriptPropertyInfoDto + { + Name = p.Name, + Alias = p.Alias, + DeclaringType = p.DeclaringType?.AssemblyQualifiedName, + PropertyType = p.PropertyType?.AssemblyQualifiedName, + DispatchId = p.DispatchId, + IsExported = p.IsExported, + CanRead = p.CanRead, + CanWrite = p.CanWrite, + Annotations = p.GetCustomAttributes(false)?.OfType()?.Select(ToDto).ToList() ?? new List() + }; + } + + public static BslParameterInfoDto ToDto(BslParameterInfo p) + { + if (p == null) return null; + return new BslParameterInfoDto + { + Name = p.Name, + Position = p.Position, + ParameterType = p.ParameterType?.AssemblyQualifiedName, + HasDefault = p.HasDefaultValue, + DefaultValue = p.HasDefaultValue ? PrimitiveToObject(p.DefaultValue as BslPrimitiveValue) : null, + ConstantValueIndex = p.ConstantValueIndex, + ExplicitByVal = p.ExplicitByVal + }; + } + + public static BslScriptMethodInfoDto ToDto(BslScriptMethodInfo m) + { + if (m == null) return null; + var mm = m as MachineMethodInfo; + return new BslScriptMethodInfoDto + { + Name = m.Name, + DeclaringType = m.DeclaringType?.AssemblyQualifiedName, + ReturnType = m.ReturnType?.AssemblyQualifiedName, + DispatchId = m.DispatchId, + IsExported = (m.Attributes & System.Reflection.MethodAttributes.Public) != 0, + Parameters = m.GetBslParameters()?.Select(ToDto).ToList() ?? new List(), + Annotations = m.GetCustomAttributes(false)?.OfType()?.Select(ToDto).ToList() ?? new List() + , + EntryPoint = mm?.GetRuntimeMethod().EntryPoint ?? -1, + Locals = mm?.GetRuntimeMethod().LocalVariables?.ToList() + }; + } + + public static SourceCodeDto ToDto(SourceCode s) + { + if (s == null) return null; + return new SourceCodeDto + { + Name = s.Name, + Location = s.Location, + Code = s.GetSourceCode() + }; + } + } +} diff --git a/src/oscript/BehaviorSelector.cs b/src/oscript/BehaviorSelector.cs index 264cfb64f..1aa778ff2 100644 --- a/src/oscript/BehaviorSelector.cs +++ b/src/oscript/BehaviorSelector.cs @@ -47,6 +47,7 @@ private static AppBehavior SelectParametrized(CmdLineHelper helper) initializers.Add("-encoding", ProcessEncodingKey); initializers.Add("-codestat", EnableCodeStatistics); initializers.Add("-debug", DebugBehavior.Create); + initializers.Add("-build", BuildPrecompiledBehavior.Create); var param = helper.Parse(helper.Current()); if(initializers.TryGetValue(param.Name.ToLowerInvariant(), out var action)) diff --git a/src/oscript/BuildPrecompiledBehavior.cs b/src/oscript/BuildPrecompiledBehavior.cs new file mode 100644 index 000000000..f7e902373 --- /dev/null +++ b/src/oscript/BuildPrecompiledBehavior.cs @@ -0,0 +1,61 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +using System; +using System.IO; +using OneScript.Compilation; +using OneScript.Language; +using ScriptEngine.Compiler; +using ScriptEngine.Machine; + +namespace oscript +{ + internal class BuildPrecompiledBehavior : AppBehavior + { + private readonly string _path; + + public BuildPrecompiledBehavior(string path) + { + _path = path; + } + + public override int Execute() + { + var builder = ConsoleHostBuilder.Create(_path); + var hostedScript = ConsoleHostBuilder.Build(builder); + hostedScript.Initialize(); + + var source = hostedScript.Loader.FromFile(_path); + var compiler = hostedScript.GetCompilerService(); + hostedScript.SetGlobalEnvironment(new DoNothingHost(), source); + + try + { + var module = compiler.Compile(source, hostedScript.Engine.NewProcess()); + ModuleSerializer.SaveModule(module, Path.ChangeExtension(_path, ".osc")); + } + catch (CompilerException e) + { + Output.WriteLine(e.Message); + return 1; + } + catch (ScriptException e) + { + Output.WriteLine(e.Message); + return 1; + } + + return 0; + } + + public static AppBehavior Create(CmdLineHelper helper) + { + var path = helper.Next(); + return path != null ? new BuildPrecompiledBehavior(path) : null; + } + } +} \ No newline at end of file diff --git a/src/oscript/ExecuteScriptBehavior.cs b/src/oscript/ExecuteScriptBehavior.cs index 4ba1589fa..5c56173a4 100644 --- a/src/oscript/ExecuteScriptBehavior.cs +++ b/src/oscript/ExecuteScriptBehavior.cs @@ -5,6 +5,7 @@ This Source Code Form is subject to the terms of the at http://mozilla.org/MPL/2.0/. ----------------------------------------------------------*/ using System; +using System.IO; using OneScript.DebugServices; using OneScript.StandardLibrary; using ScriptEngine; @@ -52,17 +53,34 @@ public override int Execute() } var hostedScript = ConsoleHostBuilder.Build(builder); - - var source = hostedScript.Loader.FromFile(_path); - Process process; - try + bool isPrecompiled = Path.GetExtension(_path).Equals(".osc", StringComparison.OrdinalIgnoreCase); + + Process process; + if (isPrecompiled) { - process = hostedScript.CreateProcess(this, source); + try + { + var module = ModuleSerializer.LoadModule(_path); + process = hostedScript.CreateProcess(this, module); + } + catch (Exception e) + { + ShowExceptionInfo(e); + return 1; + } } - catch (Exception e) + else { - ShowExceptionInfo(e); - return 1; + try + { + var source = hostedScript.Loader.FromFile(_path); + process = hostedScript.CreateProcess(this, source); + } + catch (Exception e) + { + ShowExceptionInfo(e); + return 1; + } } var result = process.Start();