// Provenance: own design — shared run scripts
namespace Godosa.Core.Runs.Scripts;
public enum Op { Verb, Wait, Expect, Set, Checkpoint, LabelBegin, LabelEnd, ScopeBegin, ScopeEnd, TryBegin, TryEnd, SetupBegin, SetupEnd }
/// One flattened instruction. = include / call sites, outermost first.
public sealed record Instr(Op Op, SourceRef At, string Text, IReadOnlyList Chain, int Format)
{
public string A { get; init; } = "";
public string B { get; init; } = "";
public int Timeout { get; init; }
public IReadOnlyList> Bindings { get; init; } = [];
public bool LocalVars { get; init; }
public int Jump { get; init; } = -1;
}
public sealed record ScriptProgram(string Entry, RunMode Mode, ulong? Seed, IReadOnlyDictionary Vars,
IReadOnlySet Locked, IReadOnlyList Code, IReadOnlyList Errors);
public interface IScriptSource
{
string? Read(string path);
}
public sealed class MemoryScriptSource(IReadOnlyDictionary files) : IScriptSource
{
public string? Read(string path) => files.GetValueOrDefault(path);
}
/// Files under ; paths are '/'-separated and relative to it.
public sealed class FileScriptSource(string root) : IScriptSource
{
public string? Read(string path)
{
var full = Path.GetFullPath(Path.Combine(root, path));
return File.Exists(full) ? File.ReadAllText(full) : null;
}
}
public static class ScriptPaths
{
public static string Resolve(string fromFile, string relative)
{
var dir = fromFile.Contains('/') ? fromFile[..fromFile.LastIndexOf('/')] : "";
var parts = new List(dir.Length == 0 ? [] : dir.Split('/'));
foreach (var part in relative.Replace('\\', '/').Split('/'))
{
if (part is "" or ".")
continue;
if (part == ".." && parts.Count > 0)
parts.RemoveAt(parts.Count - 1);
else if (part != "..")
parts.Add(part);
}
return string.Join('/', parts);
}
}