graccesscli: correct script-edit docs to TN-537 truth (writes DO persist)
I was wrong. AVEVA Tech Note 537 ("Creating an Application Object Script
Using GRAccess", April 2008) documents the supported pattern:
ConfigurableAttributes[<script>.<field>].SetValue(MxValue) inside a
CheckOut/Save/CheckIn cycle. graccesscli's existing
FindAttributeForMutation already follows this — writes to MxCategoryPackageOnly_Lockable
script-text fields persist correctly.
The earlier "writeback gap" diagnosis was a phantom caused by a reader-side
issue. `object attribute value get` against a script body returns
"Supported: False / Attribute value is not exposed" because
MxValueDetails uses a case-sensitive `ReadProperty(attr, "Value")` lookup
plus an accessor probe (GetBoolean -> GetInteger -> GetFloat -> GetDouble
-> GetString) that can fall through silently for some MxValue shapes. The
COM-side property is exposed as `value` (lowercase), readable as
`attr.value.GetString()` -- which the live probe at
`analysis/ide-edit-investigation/probe_setvalue/` does and confirms the
post-write content matches the marker exactly.
Live verification on $TestMachine.UpdateTestChangingInt.DeclarationsText
and $DelmiaReceiver.ProcessRecipe.{ExecuteText,DeclarationsText}:
=== verdict ===
marker landed on same-proxy ConfigurableAttributes: True
marker landed on same-proxy Attributes : True
marker landed on fresh-proxy ConfigurableAttributes: True
marker landed on fresh-proxy Attributes : True
The probe also confirmed that two earlier graccesscli `object scripts set`
invocations (which I had wrongly believed failed) had persisted -- the
marker text I wrote previously was still on disk in
ProcessRecipe.{ExecuteText,DeclarationsText} when read directly via
attr.value.GetString(). The probe restored both fields to their original
values.
This commit:
- Updates the misleading [Command(...)] / [CommandOption(...)]
descriptions in GRAccessSurfaceCommands.cs back to honest versions
citing TN-537.
- Restores the --file-using examples for `object scripts set` and
`object scripts create` across script-editing.md, llm-integration.md,
usage.md, and zb-testmachine.md.
- Removes the test that asserted the (wrong) EnsureMutableViaSetValue
guard. Re-aims ScriptCommandDescriptions_… at the corrected wording.
- Removes two leftover EnsureMutableViaSetValue calls in the trigger-period
/ trigger-type write paths (both targeted MxCategoryWriteable_C_Lockable
attributes; would never have fired even if the helper still existed).
- Adds analysis/ide-edit-investigation/REPORT.md (replacing the earlier
wrong report) plus the probe sources under probe_setvalue/.
The MxValueDetails reader gap (case-sensitive ReadProperty + accessor
probe) is a real follow-up: `object attribute value get` should
case-insensitively read `value` and try GetString first when the
underlying MxValue.DataType is MxString. Out of scope here -- that's a
separate, smaller fix.
Test count delta: 67 -> 66 (-2 wrong tests, +1 corrected description test).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# How the IDE edits scripts — investigation findings (corrected)
|
||||
|
||||
**Status:** my original report (and the safety-check + docs note that grew out of it) was **wrong**. AVEVA Tech Note 537 ("Creating an Application Object Script Using GRAccess", April 2008) documents the supported pattern, and a live round-trip probe confirmed it works on this Galaxy. graccesscli's existing `FindAttributeForMutation` already follows this pattern correctly; the apparent "writeback gap" was a reader-side gap in `MxValueDetails`.
|
||||
|
||||
This document supersedes the earlier "package-only no-op" claim. The reverts on `master`: commits `4e242ca` (safety check) and `e4e5425` (docs note) plus the corrective commit on top.
|
||||
|
||||
## What's actually true
|
||||
|
||||
Per **AVEVA Tech Note 537** (page 6-7):
|
||||
|
||||
> First we add a new script into the list of scripts. Then we save the object. Saving the object generates all the attributes required to configure the script the conventional way. […] Configurable attributes of the ScriptExtension primitive: ExecuteText, AliasReferences, Aliases, TriggerType, DataChangeDeadband, Expression, DeclarationsText, StartupText, ShutdownText, OnScanText, OffScanText, ExecutionError.Alarmed, TriggerPeriod, ScriptExecutionGroup, ScriptOrder, RunsAsync, State.Historized, ExecuteTimeout.Limit, TriggerOnQualityChange.
|
||||
|
||||
The supported edit sequence is:
|
||||
|
||||
```csharp
|
||||
// 1. Acquire the object.
|
||||
var galaxy = galaxies[GalaxyName];
|
||||
galaxy.Login(user, pwd);
|
||||
var obj = galaxy.QueryObjectsByName(EgObjectIsTemplateOrInstance.gObjectIsTemplate, ref names)[1];
|
||||
|
||||
// 2. Mutation lifecycle.
|
||||
obj.CheckOut();
|
||||
|
||||
// 3. Add the primitive (only when creating a new script).
|
||||
obj.AddExtensionPrimitive("ScriptExtension", scriptName, true);
|
||||
obj.Save(); // generates the script's child attributes
|
||||
|
||||
// 4. Write text via ConfigurableAttributes — NOT Attributes.
|
||||
IAttribute body = obj.ConfigurableAttributes[scriptName + ".ExecuteText"];
|
||||
var v = new MxValueClass();
|
||||
v.PutString("// new script body...");
|
||||
body.SetValue(v);
|
||||
// (repeat for DeclarationsText, TriggerType, Expression, etc.)
|
||||
|
||||
// 5. Persist + release.
|
||||
obj.Save();
|
||||
obj.CheckIn("");
|
||||
```
|
||||
|
||||
graccesscli's `FindAttributeForMutation` (line 988-995) already prefers `ConfigurableAttributes` first and falls back to `Attributes`. `ObjectScriptSet` in the dispatcher writes via that helper. So `object scripts set --field <name> --file <path>` follows the TN-537 pattern.
|
||||
|
||||
## Live verification
|
||||
|
||||
The probe at `analysis/ide-edit-investigation/probe_setvalue/` exercises the TN-537 sequence directly against `$TestMachine.UpdateTestChangingInt.DeclarationsText` and against `$DelmiaReceiver.ProcessRecipe.{ExecuteText,DeclarationsText}` on the local ZB galaxy. Result of the four-way verify after a marker write:
|
||||
|
||||
```
|
||||
=== verdict ===
|
||||
marker landed on same-proxy ConfigurableAttributes: True
|
||||
marker landed on same-proxy Attributes : True
|
||||
marker landed on fresh-proxy ConfigurableAttributes: True
|
||||
marker landed on fresh-proxy Attributes : True
|
||||
```
|
||||
|
||||
Same probe also confirmed two earlier `graccesscli object scripts set` invocations had persisted (the `// roundtrip-test marker (graccesscli scripts set --field DeclarationsText)` text was still in `ProcessRecipe.DeclarationsText` when re-read directly via `attr.value.GetString()`).
|
||||
|
||||
## Why my earlier diagnosis went wrong
|
||||
|
||||
My earlier round-trip test wrote with graccesscli's `object scripts set`, then read back with `object attribute value get` — and the read returned `Supported: False, Value: None, Unavailable: "Attribute value is not exposed by this GRAccess attribute."` I read this as "the write didn't persist." It actually means **the reader couldn't surface the value**, while the write *had* persisted.
|
||||
|
||||
The reader gap is in `GRAccessCommandDispatcher.MxValueDetails` (line 1382-1417) plus `AttributeValueDetails` (line 1340-1380):
|
||||
|
||||
```csharp
|
||||
var direct = MxValueDetails(ReadProperty(attr, "Value"));
|
||||
```
|
||||
|
||||
`ReadProperty(attr, "Value")` uses `Type.InvokeMember(name, BindingFlags.GetProperty, ...)` which is **case-sensitive** by default. The COM-side property is exposed as `value` (lowercase) on `IAttribute` — what the probe successfully reads via `attr.value`. The capital-V `"Value"` lookup may either return `null` or hit a different sibling, and `MxValueDetails(null)` short-circuits to "not exposed." Even when `ReadProperty` does return the MxValue object, `MxValueDetails`'s scalar accessor probe (`GetBoolean` → `GetInteger` → `GetFloat` → `GetDouble` → `GetString`) can fall through silently for some types.
|
||||
|
||||
This is a real gap in `attribute value get` that should be fixed separately — but it's a *reader* problem, not a write-side issue. The script body content does land on disk, and `object scripts get --llm-json` (which uses the package-export fallback) reflects it correctly.
|
||||
|
||||
## What changed in this correction
|
||||
|
||||
- **Reverted** `bd95ace` (the safety check `EnsureMutableViaSetValue`) and `c12fbc5` (the wrong docs note about a "package-only writeback boundary").
|
||||
- **Removed** two leftover `EnsureMutableViaSetValue` call sites the user had added in `87c0124` for the trigger-period / trigger-type write paths (both call sites also targeted `MxCategoryWriteable_C_Lockable` attributes, so they would never have fired even if the helper still existed — removing keeps the dispatcher consistent with the truth).
|
||||
- **Updated** the misleading `[Command(...)]` / `[CommandOption(...)]` descriptions in `GRAccessSurfaceCommands.cs` back to honest versions citing TN-537.
|
||||
- **Restored** the `--file`-using examples for `object scripts set` and `object scripts create` that `87c0124` removed across `script-editing.md`, `llm-integration.md`, `usage.md`, `zb-testmachine.md`.
|
||||
- **Updated** test assertions accordingly: removed `DispatcherScriptSettings_GuardsPackageOnlyNoOpsBeforeSetValue` (asserted the wrong helper) and re-aimed `ScriptCommandDescriptions_…` at the corrected wording.
|
||||
- **Rewrote** `analysis/ide-edit-investigation/REPORT.md` (this file) to document the truth.
|
||||
|
||||
## Open follow-ups (≤2)
|
||||
|
||||
1. **`MxValueDetails` reader gap.** `object attribute value get` against ScriptExtension text fields and similar `IAttribute` shapes returns `Supported: False` despite the value being set. Likely fix: case-insensitive `ReadProperty` lookup *and* try `GetString` first when the underlying `MxValue.DataType` is `MxString`. Out of scope for this corrective commit.
|
||||
2. **The decompiled `IConfigurationAccess2` / `XxGalaxyPackageServer` analysis** under `cac/`, `appcfg/`, `scriptpkg/` is still useful as an *internal IDE map* — but it's not the *required* CLI write path, just an alternative one the IDE happens to use. Keep the artifacts; they document the IDE-internal channel honestly.
|
||||
+1114
File diff suppressed because it is too large
Load Diff
+1072
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Nullable>disable</Nullable>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<Platforms>x86</Platforms>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<RootNamespace>Probe</RootNamespace>
|
||||
<AssemblyName>Probe</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="ArchestrA.GRAccess">
|
||||
<HintPath>C:\Users\dohertj2\Desktop\wwtools\graccesscli\lib\ArchestrA.GRAccess.dll</HintPath>
|
||||
<Private>true</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using ArchestrA.GRAccess;
|
||||
|
||||
class Program
|
||||
{
|
||||
static int Main(string[] args)
|
||||
{
|
||||
// Probe: write to $TestMachine.UpdateTestChangingInt.DeclarationsText (a
|
||||
// package-only ScriptExtension text field) via the TN-537 pattern using
|
||||
// ConfigurableAttributes. Read back through TWO paths to discriminate
|
||||
// between (a) write-side no-op vs (b) read-side staleness:
|
||||
// 1. Same IgObject proxy via obj.ConfigurableAttributes (in-memory edit view)
|
||||
// 2. Fresh galaxy.QueryObjectsByName proxy via .Attributes (runtime view)
|
||||
//
|
||||
// Restores DeclarationsText to empty when done.
|
||||
|
||||
const string GalaxyName = "ZB";
|
||||
const string Tagname = "$TestMachine";
|
||||
const string ScriptName = "UpdateTestChangingInt";
|
||||
const string FieldName = "DeclarationsText";
|
||||
const string Marker = "// graccesscli probe marker — TN537 ConfigurableAttributes path\n";
|
||||
|
||||
var user = Environment.GetEnvironmentVariable("MX_TEST_USER") ?? "";
|
||||
var pass = Environment.GetEnvironmentVariable("MX_TEST_PASSWORD") ?? "";
|
||||
var node = Environment.MachineName;
|
||||
|
||||
Console.WriteLine($"[probe] connecting to {node} galaxy={GalaxyName} as {user}");
|
||||
var gr = new GRAccessAppClass();
|
||||
var galaxies = gr.QueryGalaxies(node);
|
||||
var galaxy = galaxies[GalaxyName];
|
||||
if (string.IsNullOrEmpty(user))
|
||||
galaxy.Login();
|
||||
else
|
||||
galaxy.Login(user, pass);
|
||||
|
||||
var attrPath = ScriptName + "." + FieldName;
|
||||
|
||||
// Acquire the target template object.
|
||||
var names = new string[] { Tagname };
|
||||
var initial = QueryOne(galaxy, names);
|
||||
|
||||
// Read current value via ConfigurableAttributes (configuration view).
|
||||
var beforeCfg = TryReadString(initial.ConfigurableAttributes, attrPath, "before-cfg");
|
||||
var beforeRt = TryReadString(initial.Attributes, attrPath, "before-rt");
|
||||
Console.WriteLine($"[probe] BEFORE cfg={Quote(beforeCfg)} rt={Quote(beforeRt)}");
|
||||
|
||||
// Mutate via the canonical TN-537 sequence on the same proxy.
|
||||
Console.WriteLine("[probe] CheckOut...");
|
||||
initial.CheckOut();
|
||||
ThrowIfFailed(initial.CommandResult, "CheckOut");
|
||||
|
||||
Console.WriteLine("[probe] ConfigurableAttributes[...].SetValue(MxValue)...");
|
||||
var cfgAttr = initial.ConfigurableAttributes[attrPath];
|
||||
var v = new MxValueClass();
|
||||
v.PutString(Marker);
|
||||
cfgAttr.SetValue(v);
|
||||
|
||||
Console.WriteLine("[probe] Save...");
|
||||
initial.Save();
|
||||
ThrowIfFailed(initial.CommandResult, "Save");
|
||||
|
||||
Console.WriteLine("[probe] CheckIn...");
|
||||
initial.CheckIn("graccesscli probe round-trip test");
|
||||
ThrowIfFailed(initial.CommandResult, "CheckIn");
|
||||
|
||||
// Now read back THREE ways:
|
||||
// A. Same proxy (initial), ConfigurableAttributes — possibly stale.
|
||||
// B. Same proxy (initial), Attributes — possibly stale.
|
||||
// C. Fresh proxy from a fresh QueryObjectsByName call.
|
||||
var afterCfgSame = TryReadString(initial.ConfigurableAttributes, attrPath, "after-cfg-same");
|
||||
var afterRtSame = TryReadString(initial.Attributes, attrPath, "after-rt-same");
|
||||
Console.WriteLine($"[probe] AFTER (same proxy) cfg={Quote(afterCfgSame)} rt={Quote(afterRtSame)}");
|
||||
|
||||
var refreshed = QueryOne(galaxy, names);
|
||||
var afterCfgFresh = TryReadString(refreshed.ConfigurableAttributes, attrPath, "after-cfg-fresh");
|
||||
var afterRtFresh = TryReadString(refreshed.Attributes, attrPath, "after-rt-fresh");
|
||||
Console.WriteLine($"[probe] AFTER (fresh proxy) cfg={Quote(afterCfgFresh)} rt={Quote(afterRtFresh)}");
|
||||
|
||||
// Restore: write the original value back (or empty if there was none).
|
||||
var restoreTo = beforeCfg ?? "";
|
||||
Console.WriteLine($"[probe] Restoring DeclarationsText to {Quote(restoreTo)}...");
|
||||
refreshed.CheckOut();
|
||||
ThrowIfFailed(refreshed.CommandResult, "CheckOut(restore)");
|
||||
var restoreAttr = refreshed.ConfigurableAttributes[attrPath];
|
||||
var rv = new MxValueClass();
|
||||
rv.PutString(restoreTo);
|
||||
restoreAttr.SetValue(rv);
|
||||
refreshed.Save();
|
||||
ThrowIfFailed(refreshed.CommandResult, "Save(restore)");
|
||||
refreshed.CheckIn("graccesscli probe restore");
|
||||
ThrowIfFailed(refreshed.CommandResult, "CheckIn(restore)");
|
||||
Console.WriteLine("[probe] Restored.");
|
||||
|
||||
// Verdict.
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("=== verdict ===");
|
||||
Console.WriteLine($" marker landed on same-proxy ConfigurableAttributes: {(afterCfgSame == Marker)}");
|
||||
Console.WriteLine($" marker landed on same-proxy Attributes : {(afterRtSame == Marker)}");
|
||||
Console.WriteLine($" marker landed on fresh-proxy ConfigurableAttributes: {(afterCfgFresh == Marker)}");
|
||||
Console.WriteLine($" marker landed on fresh-proxy Attributes : {(afterRtFresh == Marker)}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static IgObject QueryOne(IGalaxy galaxy, string[] names)
|
||||
{
|
||||
var objs = galaxy.QueryObjectsByName(EgObjectIsTemplateOrInstance.gObjectIsTemplate, ref names);
|
||||
ThrowIfFailed(galaxy.CommandResult, "QueryObjectsByName");
|
||||
return objs[1];
|
||||
}
|
||||
|
||||
static string TryReadString(IAttributes attrs, string name, string label)
|
||||
{
|
||||
try
|
||||
{
|
||||
var attr = attrs[name];
|
||||
if (attr == null) return null;
|
||||
var val = attr.value;
|
||||
return val?.GetString();
|
||||
}
|
||||
catch (Exception ex) { Console.WriteLine($"[probe] {label} read threw: {ex.Message}"); return null; }
|
||||
}
|
||||
|
||||
static void ThrowIfFailed(ICommandResult r, string what)
|
||||
{
|
||||
if (r != null && !r.Successful)
|
||||
throw new InvalidOperationException($"{what} failed: ID={r.ID} Text='{r.Text}' Custom='{r.CustomMessage}'");
|
||||
}
|
||||
|
||||
static string Quote(string s) => s == null ? "<null>" : "\"" + s.Replace("\n", "\\n").Replace("\r", "\\r") + "\"";
|
||||
}
|
||||
+595
@@ -0,0 +1,595 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.SymbolStore;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Security;
|
||||
using ArchestrA.QuickScript;
|
||||
using ArchestrA.QuickScript.Digest;
|
||||
using ArchestrA.QuickScript.Emit;
|
||||
using ArchestrA.QuickScript.Model;
|
||||
using ArchestrA.QuickScript.Runtime;
|
||||
|
||||
[assembly: CompilationRelaxations(8)]
|
||||
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
|
||||
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
|
||||
[assembly: AssemblyFileVersion("5800.0038.7005.1")]
|
||||
[assembly: AssemblyTitle("ScriptPackage.Net Module")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("AVEVA Software, LLC")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("Copyright 2020 AVEVA Group plc and its subsidiaries. All rights reserved.")]
|
||||
[assembly: AssemblyTrademark("Refer to: https://sw.aveva.com/legal/trademarks")]
|
||||
[assembly: AssemblyDelaySign(false)]
|
||||
[assembly: AssemblyKeyFile("..\\..\\..\\SharedComponents\\Internal\\MagellanPublic\\Includes\\WWDotNETPrivateKey\\ww.snk")]
|
||||
[assembly: AssemblyKeyName("")]
|
||||
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
|
||||
[assembly: AssemblyVersion("2.0.0.0")]
|
||||
namespace ArchestrA.Scripting
|
||||
{
|
||||
[Guid("85963980-46DF-459d-8A96-2A2AF70F458F")]
|
||||
[ClassInterface(ClassInterfaceType.AutoDual)]
|
||||
public class ScriptPackage
|
||||
{
|
||||
private class NameHierarchy
|
||||
{
|
||||
public Hashtable Hashtable = new Hashtable();
|
||||
|
||||
public readonly string Name;
|
||||
|
||||
public int Index;
|
||||
|
||||
public NameHierarchy(string name)
|
||||
{
|
||||
Name = name;
|
||||
Index = -1;
|
||||
}
|
||||
|
||||
public NameHierarchy()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public void Add(string name, int index)
|
||||
{
|
||||
string text = null;
|
||||
string text2 = null;
|
||||
int num = name.IndexOf('.');
|
||||
if (num < 0)
|
||||
{
|
||||
text = name;
|
||||
}
|
||||
else
|
||||
{
|
||||
text = name.Substring(0, num);
|
||||
text2 = name.Substring(num + 1, name.Length - num - 1);
|
||||
}
|
||||
string key = text.ToLower();
|
||||
if (!Hashtable.ContainsKey(key))
|
||||
{
|
||||
Hashtable[key] = new NameHierarchy(text);
|
||||
}
|
||||
NameHierarchy nameHierarchy = (NameHierarchy)Hashtable[key];
|
||||
if (text2 != null)
|
||||
{
|
||||
nameHierarchy.Add(text2, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
nameHierarchy.Index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeResolveHandler
|
||||
{
|
||||
private Module Module;
|
||||
|
||||
private Hashtable Hashtable;
|
||||
|
||||
public TypeResolveHandler(Module module, Hashtable hashtable)
|
||||
{
|
||||
Module = module;
|
||||
Hashtable = hashtable;
|
||||
}
|
||||
|
||||
public Assembly ResolveEvent(object sender, ResolveEventArgs args)
|
||||
{
|
||||
((TypeBuilder)Hashtable[args.Name]).CreateType();
|
||||
return Module.Assembly;
|
||||
}
|
||||
}
|
||||
|
||||
private string BinFolder;
|
||||
|
||||
private bool DebugEnabled;
|
||||
|
||||
private Parser Parser;
|
||||
|
||||
private string TagName;
|
||||
|
||||
private string ScriptName;
|
||||
|
||||
private Hashtable ReferencedAssemblies;
|
||||
|
||||
private ModuleBuilder ModuleBuilder;
|
||||
|
||||
private TypeBuilder TypeBuilder;
|
||||
|
||||
private ILBuilder ILBuilder;
|
||||
|
||||
private int NestedTypeIndex;
|
||||
|
||||
private string ErrorMessage;
|
||||
|
||||
private int ErrorLine;
|
||||
|
||||
private int ErrorColumn;
|
||||
|
||||
private static readonly FieldInfo ScriptExchange;
|
||||
|
||||
private static readonly Type[] ExpessionParameterTypes;
|
||||
|
||||
private static readonly MethodInfo EnableDebugSupport;
|
||||
|
||||
private static string DebugFolder => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "QuickScriptDebug");
|
||||
|
||||
private static bool DebuggerInfoEnabled => Directory.Exists(DebugFolder);
|
||||
|
||||
public void Compile(string libraryPath, string repositoryPath, string tagName, string scriptName, string compilerAssemblyFile, string compilerTypeName, IntPtr outputAssemblyName, IntPtr aliases, IntPtr declarationsText, IntPtr startupText, IntPtr onScanText, IntPtr expression, IntPtr executeText, IntPtr offScanText, IntPtr shutdownText, IntPtr binary, IntPtr errorMessage, IntPtr errorLine, IntPtr errorColumn, IntPtr libaryDependencies, IntPtr externalReferences, IntPtr externalReferenceFlags, IntPtr aliasReferenceFlags)
|
||||
{
|
||||
IList list = null;
|
||||
byte[] array = null;
|
||||
ErrorMessage = "";
|
||||
BinFolder = Path.GetDirectoryName(Assembly.GetAssembly(GetType()).Location);
|
||||
Parser = new Parser();
|
||||
ILBuilder = new ILBuilder();
|
||||
ReferencedAssemblies = new Hashtable();
|
||||
list = new ArrayList();
|
||||
TagName = tagName;
|
||||
ScriptName = scriptName;
|
||||
DebugEnabled = DebuggerInfoEnabled;
|
||||
NestedTypeIndex = 0;
|
||||
int mxValueElementCount = GetMxValueElementCount(aliases);
|
||||
int i;
|
||||
for (i = 0; i < mxValueElementCount; i++)
|
||||
{
|
||||
ExternalReferences.AddAlias(GetMxValueElementString(aliases, i + 1));
|
||||
}
|
||||
try
|
||||
{
|
||||
string tempPath = Path.GetTempPath();
|
||||
AssemblyName assemblyName = new AssemblyName();
|
||||
assemblyName.Name = GetMxValueString(outputAssemblyName);
|
||||
string text = assemblyName.Name + ".dll";
|
||||
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
|
||||
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save, tempPath);
|
||||
if (DebugEnabled)
|
||||
{
|
||||
CustomAttributeBuilder customAttribute = new CustomAttributeBuilder(typeof(DebuggableAttribute).GetConstructor(new Type[2]
|
||||
{
|
||||
typeof(bool),
|
||||
typeof(bool)
|
||||
}), new object[2] { true, true });
|
||||
assemblyBuilder.SetCustomAttribute(customAttribute);
|
||||
}
|
||||
ModuleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, text, DebugEnabled);
|
||||
TypeBuilder = ModuleBuilder.DefineType("ArchestrA.Scripting.Script", TypeAttributes.Public, typeof(ScriptBase), new Type[0]);
|
||||
ILGenerator iLGenerator = TypeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, new Type[0]).GetILGenerator();
|
||||
ILBuilder.ILGenerator = iLGenerator;
|
||||
iLGenerator.Emit(OpCodes.Ldarg_0);
|
||||
iLGenerator.Emit(OpCodes.Call, typeof(ScriptBase).GetConstructor(new Type[0]));
|
||||
ILBuilder.DebugEnabled = false;
|
||||
ILBuilder.FieldScriptExchange = ScriptExchange;
|
||||
CompileDeclarations(GetMxValueString(declarationsText));
|
||||
ILBuilder.Emit(new Return());
|
||||
if (ErrorMessage.Length == 0 || ErrorMessage.StartsWith("Warning"))
|
||||
{
|
||||
CompileExpression(GetMxValueString(expression));
|
||||
CompileMethod("Startup", GetMxValueString(startupText));
|
||||
CompileMethod("OnScan", GetMxValueString(onScanText));
|
||||
CompileMethod("Execute", GetMxValueString(executeText));
|
||||
CompileMethod("OffScan", GetMxValueString(offScanText));
|
||||
CompileMethod("Shutdown", GetMxValueString(shutdownText));
|
||||
}
|
||||
if (ErrorMessage.Length == 0 || ErrorMessage.StartsWith("Warning"))
|
||||
{
|
||||
if (DebugEnabled)
|
||||
{
|
||||
Hashtable hashtable = new Hashtable();
|
||||
ResolveEventHandler value = new TypeResolveHandler(ModuleBuilder, hashtable).ResolveEvent;
|
||||
AppDomain.CurrentDomain.TypeResolve += value;
|
||||
NameHierarchy nameHierarchy = new NameHierarchy();
|
||||
int num = 0;
|
||||
foreach (ExternalReference item in ExternalReferences.List)
|
||||
{
|
||||
nameHierarchy.Add(item.ReferenceString.Split('[')[0], num++);
|
||||
}
|
||||
Build(nameHierarchy, TypeBuilder, hashtable);
|
||||
RecurseNestedTypes(TypeBuilder.CreateType());
|
||||
AppDomain.CurrentDomain.TypeResolve -= value;
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeBuilder.CreateType();
|
||||
}
|
||||
assemblyBuilder.Save(text);
|
||||
}
|
||||
foreach (Assembly key in ReferencedAssemblies.Keys)
|
||||
{
|
||||
list.Add(key.GetName().Name);
|
||||
}
|
||||
if (ErrorMessage.Length == 0)
|
||||
{
|
||||
string path = Path.Combine(tempPath, text);
|
||||
using (FileStream fileStream = File.OpenRead(path))
|
||||
{
|
||||
array = new byte[fileStream.Length];
|
||||
fileStream.Read(array, 0, (int)fileStream.Length);
|
||||
}
|
||||
if (DebugEnabled)
|
||||
{
|
||||
File.Exists(Path.ChangeExtension(path, "pdb"));
|
||||
}
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorMessage = "Internal QuickScript compiler error: " + ex.Message;
|
||||
}
|
||||
if (ErrorMessage.Length > 0)
|
||||
{
|
||||
PutMxValueString(errorMessage, ErrorMessage);
|
||||
PutMxValueInteger(errorLine, ErrorLine);
|
||||
PutMxValueInteger(errorColumn, ErrorColumn);
|
||||
return;
|
||||
}
|
||||
PutMxValueString(errorMessage, "");
|
||||
PutMxValueInteger(errorLine, 0);
|
||||
PutMxValueInteger(errorColumn, 0);
|
||||
PutMxValueBlob(binary, array, array.Length);
|
||||
i = 1;
|
||||
foreach (string item2 in list)
|
||||
{
|
||||
PutMxValueElementString(libaryDependencies, i++, item2);
|
||||
}
|
||||
i = 1;
|
||||
int index = 1;
|
||||
foreach (ExternalReference item3 in ExternalReferences.List)
|
||||
{
|
||||
if (item3.IsAlias)
|
||||
{
|
||||
PutMxValueElementInteger(aliasReferenceFlags, i++, item3.Flags);
|
||||
continue;
|
||||
}
|
||||
PutMxValueElementString(externalReferences, index, item3.ReferenceString);
|
||||
PutMxValueElementInteger(externalReferenceFlags, index++, item3.Flags);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecurseNestedTypes(Type type)
|
||||
{
|
||||
Type[] nestedTypes = type.GetNestedTypes();
|
||||
foreach (Type type2 in nestedTypes)
|
||||
{
|
||||
RecurseNestedTypes(type2);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetErrorsAndWarnings(string section)
|
||||
{
|
||||
if (ErrorMessage.Length > 0 && !ErrorMessage.StartsWith("Warning"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
IEnumerator enumerator = Parser.Errors.GetEnumerator();
|
||||
try
|
||||
{
|
||||
if (enumerator.MoveNext())
|
||||
{
|
||||
ErrorInfo errorInfo = (ErrorInfo)enumerator.Current;
|
||||
ErrorLine = errorInfo.Line;
|
||||
ErrorColumn = errorInfo.Column;
|
||||
ErrorMessage = $"Script {ScriptName} ({section}): {errorInfo.Message} (Line: {errorInfo.Line}, Col: {errorInfo.Column})";
|
||||
return;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IDisposable disposable = enumerator as IDisposable;
|
||||
if (disposable != null)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
enumerator = Parser.Warnings.GetEnumerator();
|
||||
try
|
||||
{
|
||||
if (enumerator.MoveNext())
|
||||
{
|
||||
ErrorInfo errorInfo2 = (ErrorInfo)enumerator.Current;
|
||||
ErrorLine = errorInfo2.Line;
|
||||
ErrorColumn = errorInfo2.Column;
|
||||
ErrorMessage = $"Warning: Script {ScriptName} ({section}): {errorInfo2.Message} (Line: {errorInfo2.Line}, Col: {errorInfo2.Column})";
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IDisposable disposable2 = enumerator as IDisposable;
|
||||
if (disposable2 != null)
|
||||
{
|
||||
disposable2.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CompileDeclarations(string declarationsText)
|
||||
{
|
||||
if (declarationsText.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ILBuilder.DeclarationsAreFields = true;
|
||||
Parser.DeclarationsAreFields = true;
|
||||
Parser.Parse(declarationsText);
|
||||
if (Parser.Tree is Expression)
|
||||
{
|
||||
CodeSpan codeSpan = (Parser.Tree as Expression).CodeSpan;
|
||||
codeSpan = new CodeSpan(codeSpan.End.Line, codeSpan.End.Column + 1, codeSpan.End.Line, codeSpan.End.Column + 1);
|
||||
Parser.Errors.Add(new ErrorInfo("Expected ';'", codeSpan.End.Line, codeSpan.End.Column, codeSpan.End.Line, codeSpan.End.Column));
|
||||
}
|
||||
GetErrorsAndWarnings("Declarations");
|
||||
if (Parser.Errors.Count != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (Statement item in Parser.Tree as IList)
|
||||
{
|
||||
Declaration declaration = item as Declaration;
|
||||
FieldBuilder value = TypeBuilder.DefineField(declaration.VariableName, declaration.Type, FieldAttributes.Public);
|
||||
ILBuilder.Variables[declaration.VariableName] = value;
|
||||
ILBuilder.Emit(declaration);
|
||||
}
|
||||
Parser.GetReferencedAssemblies(ReferencedAssemblies);
|
||||
}
|
||||
|
||||
private void CompileExpression(string expressionText)
|
||||
{
|
||||
if (expressionText.Length != 0)
|
||||
{
|
||||
ILBuilder.DeclarationsAreFields = false;
|
||||
Parser.DeclarationsAreFields = false;
|
||||
Parser.Parse(expressionText);
|
||||
if (Parser.Tree is Statements)
|
||||
{
|
||||
CodeSpan codeSpan = ((Parser.Tree as Statements)[0] as Statement).CodeSpan;
|
||||
Parser.Errors.Add(new ErrorInfo("Not expecting ';'", codeSpan.End.Line, codeSpan.End.Column, codeSpan.End.Line, codeSpan.End.Column));
|
||||
}
|
||||
GetErrorsAndWarnings("Expression");
|
||||
if (Parser.Errors.Count <= 0)
|
||||
{
|
||||
MethodAttributes attributes = MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig;
|
||||
MethodBuilder methodBuilder = TypeBuilder.DefineMethod("Expression", attributes, typeof(void), ExpessionParameterTypes);
|
||||
methodBuilder.DefineParameter(1, ParameterAttributes.Out, null);
|
||||
methodBuilder.DefineParameter(2, ParameterAttributes.Out, null);
|
||||
ILGenerator iLGenerator = methodBuilder.GetILGenerator();
|
||||
ILBuilder.ILGenerator = iLGenerator;
|
||||
ILBuilder.Variables.ClearLocals();
|
||||
ILBuilder.DebugEnabled = false;
|
||||
Expression expression = Parser.Tree as Expression;
|
||||
iLGenerator.Emit(OpCodes.Ldarg_1);
|
||||
ILBuilder.Emit(Coerce.ChangeType(expression, typeof(object)));
|
||||
iLGenerator.Emit(OpCodes.Stind_Ref);
|
||||
iLGenerator.Emit(OpCodes.Ldarg_2);
|
||||
ILBuilder.Emit(Digester.GetQuality(expression));
|
||||
iLGenerator.Emit(OpCodes.Conv_I4);
|
||||
iLGenerator.Emit(OpCodes.Stind_I4);
|
||||
ILBuilder.Emit(new Return());
|
||||
Parser.GetReferencedAssemblies(ReferencedAssemblies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CompileMethod(string methodName, string methodText)
|
||||
{
|
||||
if (methodText.Length == 0)
|
||||
{
|
||||
if (!(methodName == "Startup"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
methodText = ";";
|
||||
}
|
||||
ILBuilder.DeclarationsAreFields = false;
|
||||
Parser.DeclarationsAreFields = false;
|
||||
Parser.Parse(methodText);
|
||||
if (Parser.Tree is Expression)
|
||||
{
|
||||
CodeSpan codeSpan = (Parser.Tree as Expression).CodeSpan;
|
||||
codeSpan = new CodeSpan(codeSpan.End.Line, codeSpan.End.Column + 1, codeSpan.End.Line, codeSpan.End.Column + 1);
|
||||
Parser.Errors.Add(new ErrorInfo("Expected ';'", codeSpan.End.Line, codeSpan.End.Column, codeSpan.End.Line, codeSpan.End.Column));
|
||||
}
|
||||
GetErrorsAndWarnings(methodName);
|
||||
if (Parser.Errors.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ISymbolDocumentWriter symbolDocumentWriter = null;
|
||||
if (DebugEnabled)
|
||||
{
|
||||
string text = DebugFolder + "\\" + TagName + "." + ScriptName + "." + methodName + ".qs";
|
||||
if (methodText.Length > 1)
|
||||
{
|
||||
using StreamWriter streamWriter = File.CreateText(text);
|
||||
streamWriter.Write(methodText);
|
||||
}
|
||||
symbolDocumentWriter = ModuleBuilder.DefineDocument(text, Guid.Empty, Guid.Empty, Guid.Empty);
|
||||
}
|
||||
Type[] parameterTypes = new Type[0];
|
||||
switch (methodName)
|
||||
{
|
||||
case "Startup":
|
||||
parameterTypes = new Type[1] { typeof(IScriptExchange) };
|
||||
break;
|
||||
case "Execute":
|
||||
parameterTypes = new Type[1] { typeof(ScriptTimer) };
|
||||
break;
|
||||
}
|
||||
MethodAttributes attributes = MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig;
|
||||
ILGenerator iLGenerator = TypeBuilder.DefineMethod(methodName, attributes, typeof(void), parameterTypes).GetILGenerator();
|
||||
ILBuilder.ILGenerator = iLGenerator;
|
||||
ILBuilder.Variables.ClearLocals();
|
||||
ILBuilder.SymbolDocumentWriter = symbolDocumentWriter;
|
||||
ILBuilder.DebugEnabled = false;
|
||||
ILBuilder.CheckForTimeout = methodName == "Execute" && !DebugEnabled;
|
||||
if (methodName == "Startup")
|
||||
{
|
||||
iLGenerator.Emit(OpCodes.Ldarg_0);
|
||||
iLGenerator.Emit(OpCodes.Ldarg_1);
|
||||
iLGenerator.Emit(OpCodes.Stfld, ScriptExchange);
|
||||
if (DebugEnabled)
|
||||
{
|
||||
Expression destination = new Field(new This(), typeof(ScriptBase).GetField("TagName"));
|
||||
Expression source = new ConstantString(TagName);
|
||||
ILBuilder.Emit(new Assignment(destination, source));
|
||||
ILBuilder.Emit(new ExpressionStatement(new Method(null, EnableDebugSupport, new Expression[1]
|
||||
{
|
||||
new This()
|
||||
})));
|
||||
}
|
||||
}
|
||||
Statements statements = Parser.Tree as Statements;
|
||||
ILBuilder.DebugEnabled = DebugEnabled;
|
||||
ILBuilder.LineNumber = 0;
|
||||
ILBuilder.Emit(statements);
|
||||
ILBuilder.Emit(new Return());
|
||||
Parser.GetReferencedAssemblies(ReferencedAssemblies);
|
||||
}
|
||||
|
||||
static ScriptPackage()
|
||||
{
|
||||
ScriptExchange = typeof(ScriptBase).GetField("ScriptExchange");
|
||||
ExpessionParameterTypes = null;
|
||||
EnableDebugSupport = typeof(ArchestrA.QuickScript.Runtime.ExternalReference).GetMethod("EnableDebugSupport");
|
||||
ParameterInfo[] parameters = typeof(IScriptObject).GetMethod("Expression").GetParameters();
|
||||
ExpessionParameterTypes = new Type[parameters.Length];
|
||||
int num = 0;
|
||||
ParameterInfo[] array = parameters;
|
||||
foreach (ParameterInfo parameterInfo in array)
|
||||
{
|
||||
ExpessionParameterTypes[num++] = parameterInfo.ParameterType;
|
||||
}
|
||||
}
|
||||
|
||||
private void Build(NameHierarchy nameHierarchy, TypeBuilder typeBuilder, Hashtable hashtable)
|
||||
{
|
||||
Type typeFromHandle = typeof(ArchestrA.QuickScript.Runtime.ExternalReference);
|
||||
typeFromHandle.GetField("Index");
|
||||
if (nameHierarchy.Index == -1)
|
||||
{
|
||||
TypeBuilder typeBuilder2 = null;
|
||||
if (nameHierarchy.Name != null)
|
||||
{
|
||||
typeBuilder2 = typeBuilder.DefineNestedType("Tag" + ++NestedTypeIndex, TypeAttributes.NestedPublic, typeFromHandle);
|
||||
}
|
||||
foreach (NameHierarchy value in nameHierarchy.Hashtable.Values)
|
||||
{
|
||||
Build(value, (typeBuilder2 != null) ? typeBuilder2 : typeBuilder, hashtable);
|
||||
}
|
||||
if (nameHierarchy.Name != null)
|
||||
{
|
||||
hashtable[typeBuilder2.Name] = typeBuilder2;
|
||||
}
|
||||
if (typeBuilder2 != null)
|
||||
{
|
||||
typeBuilder.DefineField(nameHierarchy.Name, typeBuilder2, FieldAttributes.Public);
|
||||
}
|
||||
}
|
||||
else if (nameHierarchy.Name != null)
|
||||
{
|
||||
string name = "T" + nameHierarchy.Index;
|
||||
TypeBuilder typeBuilder3 = typeBuilder.DefineNestedType(name, TypeAttributes.NestedPublic, typeof(ExternalReferenceDebug));
|
||||
typeBuilder.DefineField(nameHierarchy.Name, typeBuilder3, FieldAttributes.Public);
|
||||
hashtable[typeBuilder3.Name] = typeBuilder3;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("ScriptPackage.dll", CharSet = CharSet.Unicode)]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[return: MarshalAs(UnmanagedType.BStr)]
|
||||
private static extern string GetMxValueString(IntPtr rawMxValue);
|
||||
|
||||
[DllImport("ScriptPackage.dll")]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern void PutMxValueInteger(IntPtr rawMxValue, int value);
|
||||
|
||||
[DllImport("ScriptPackage.dll", CharSet = CharSet.Unicode)]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern void PutMxValueString(IntPtr rawMxValue, string value);
|
||||
|
||||
[DllImport("ScriptPackage.dll")]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern void PutMxValueBlob(IntPtr rawMxValue, [MarshalAs(UnmanagedType.LPArray)] byte[] value, int size);
|
||||
|
||||
[DllImport("ScriptPackage.dll")]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern int GetMxValueElementCount(IntPtr rawMxValue);
|
||||
|
||||
[DllImport("ScriptPackage.dll", CharSet = CharSet.Unicode)]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[return: MarshalAs(UnmanagedType.BStr)]
|
||||
private static extern string GetMxValueElementString(IntPtr rawMxValue, int index);
|
||||
|
||||
[DllImport("ScriptPackage.dll", CharSet = CharSet.Unicode)]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern void PutMxValueElementString(IntPtr rawMxValue, int index, string value);
|
||||
|
||||
[DllImport("ScriptPackage.dll")]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern void PutMxValueElementInteger(IntPtr rawMxValue, int index, int value);
|
||||
}
|
||||
}
|
||||
namespace AcmeBuildVersion
|
||||
{
|
||||
internal sealed class Versioning
|
||||
{
|
||||
public const string Company = "AVEVA Software, LLC";
|
||||
|
||||
public const string Product = "";
|
||||
|
||||
public const string Copyright = "Copyright 2020 AVEVA Group plc and its subsidiaries. All rights reserved.";
|
||||
|
||||
public const string Trademark = "Refer to: https://sw.aveva.com/legal/trademarks";
|
||||
|
||||
public const string Configuration = "Release";
|
||||
|
||||
public const string BuildNumber = "5800";
|
||||
|
||||
public const string BuildMaintenanceNumber = "7005";
|
||||
|
||||
public const string BldRevision = "1";
|
||||
|
||||
private Versioning()
|
||||
{
|
||||
}
|
||||
}
|
||||
internal sealed class ComponentVersioning
|
||||
{
|
||||
public const string ComponentVersion = "0038";
|
||||
|
||||
public const string ComponentMaintenanceVersion = "0000";
|
||||
|
||||
public const string ComponentName = "ScriptPrimitive";
|
||||
|
||||
private ComponentVersioning()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user