using System.Text.Json; namespace ScadaLink.SiteRuntime.Scripts; /// /// Read-only globals a trigger expression is compiled against. Exposes only /// attribute reads, backed by an in-memory snapshot — no I/O, no actor Ask, /// no side-effecting APIs. A missing attribute key reads as null and /// never throws. /// /// Canonical attribute keys are dotted (e.g. "TempSensor.Reading"); the prefix /// logic here mirrors . /// public sealed class TriggerExpressionGlobals { /// /// Extracts the "expression" field from an Expression-trigger config /// JSON document. Returns null for a missing, blank, or malformed /// config — the single parsing idiom shared by InstanceActor, ScriptActor, /// and AlarmActor. /// public static string? ExtractExpression(string? triggerConfigJson) { if (string.IsNullOrEmpty(triggerConfigJson)) return null; try { using var doc = JsonDocument.Parse(triggerConfigJson); var expr = doc.RootElement.TryGetProperty("expression", out var e) ? e.GetString() : null; return string.IsNullOrWhiteSpace(expr) ? null : expr; } catch (JsonException) { return null; } } private readonly IReadOnlyDictionary _snapshot; public TriggerExpressionGlobals(IReadOnlyDictionary snapshot) => _snapshot = snapshot; /// Attributes in the expression's own scope (root prefix). public ReadOnlyAttributes Attributes => new(_snapshot, ""); /// Indexed access to child compositions' attributes. public ReadOnlyChildren Children => new(_snapshot); /// /// Parent composition (null at root). Set by the caller for derived/composed /// scopes; the runtime actors evaluate at root scope, so this stays null. /// public ReadOnlyComposition? Parent { get; init; } /// /// Read-only attribute view anchored at a canonical-name prefix. Indexing /// resolves to the canonical key ("" → key, "TempSensor" → "TempSensor.key"). /// public sealed class ReadOnlyAttributes { private readonly IReadOnlyDictionary _s; private readonly string _prefix; public ReadOnlyAttributes(IReadOnlyDictionary s, string prefix) { _s = s; _prefix = prefix; } public object? this[string key] => _s.TryGetValue(_prefix.Length == 0 ? key : _prefix + "." + key, out var v) ? v : null; } /// A read-only view of one composition at a canonical-name path. public sealed class ReadOnlyComposition { private readonly IReadOnlyDictionary _s; private readonly string _path; public ReadOnlyComposition(IReadOnlyDictionary s, string path) { _s = s; _path = path; } public ReadOnlyAttributes Attributes => new(_s, _path); } /// Dictionary-style accessor for child compositions. public sealed class ReadOnlyChildren { private readonly IReadOnlyDictionary _s; public ReadOnlyChildren(IReadOnlyDictionary s) => _s = s; public ReadOnlyComposition this[string compositionName] => new(_s, compositionName); } }