using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; namespace ZB.MOM.WW.OtOpcUa.Core.Scripting; /// /// Compiles + runs user scripts against a subclass. Core /// evaluator — no caching, no timeout, no logging side-effects yet (those land in /// Stream A.3, A.4, A.5 respectively). Stream B + C wrap this with the dependency /// scheduler + alarm state machine. /// /// /// /// Scripts are compiled against so the /// context member is named ctx in the script, matching the /// 's walker and the Admin UI type stub. /// /// /// Compile pipeline is a three-step gate: (1) Roslyn compile — catches syntax /// errors + type-resolution failures, throws ; /// (2) runs against the semantic model — /// catches sandbox escapes that slipped past reference restrictions due to .NET's /// type forwarding, throws ; (3) /// delegate creation — throws at this layer only for internal Roslyn bugs, not /// user error. /// /// /// Runtime exceptions thrown from user code propagate unwrapped. The virtual-tag /// engine (Stream B) catches them per-tag + maps to BadInternalError /// quality per Phase 7 decision #11 — this layer doesn't swallow anything so /// tests can assert on the original exception type. /// /// public sealed class ScriptEvaluator where TContext : ScriptContext { private readonly ScriptRunner _runner; private ScriptEvaluator(ScriptRunner runner) { _runner = runner; } public static ScriptEvaluator Compile(string scriptSource) { if (scriptSource is null) throw new ArgumentNullException(nameof(scriptSource)); var options = ScriptSandbox.Build(typeof(TContext)); var script = CSharpScript.Create( code: scriptSource, options: options, globalsType: typeof(ScriptGlobals)); // Step 1 — Roslyn compile. Throws CompilationErrorException on syntax / type errors. var diagnostics = script.Compile(); // Step 2 — forbidden-type semantic analysis. Defense-in-depth against reference-list // leaks due to type forwarding. var rejections = ForbiddenTypeAnalyzer.Analyze(script.GetCompilation()); if (rejections.Count > 0) throw new ScriptSandboxViolationException(rejections); // Step 3 — materialize the callable delegate. var runner = script.CreateDelegate(); return new ScriptEvaluator(runner); } /// Run against an already-constructed context. public Task RunAsync(TContext context, CancellationToken ct = default) { if (context is null) throw new ArgumentNullException(nameof(context)); var globals = new ScriptGlobals { ctx = context }; return _runner(globals, ct); } }