// Provenance: own design — shared run scripts
using System.Globalization;
using System.Text;
namespace Godosa.Core.Runs.Scripts;
public sealed record Token(string Text, bool Quoted);
/// Line lexing for .steps: # comments outside quotes; tokens split on spaces, "…" (escapes
/// \", \\) and (…) groups stay one token.
public static class StepLexer
{
public static string StripComment(string line)
{
var quoted = false;
for (var i = 0; i < line.Length; i++)
{
var c = line[i];
if (quoted && c == '\\' && i + 1 < line.Length)
{
i++;
continue;
}
if (c == '"')
quoted = !quoted;
else if (c == '#' && !quoted)
return line[..i];
}
return line;
}
public static List Tokens(string text)
{
var tokens = new List();
var i = 0;
while (i < text.Length)
{
if (text[i] == ' ')
{
i++;
continue;
}
if (text[i] == '"')
{
var sb = new StringBuilder();
i++;
while (true)
{
if (i >= text.Length)
throw new FormatException("unterminated quote");
var c = text[i];
if (c == '\\' && i + 1 < text.Length)
{
sb.Append(text[i + 1]);
i += 2;
continue;
}
if (c == '"')
{
i++;
break;
}
sb.Append(c);
i++;
}
tokens.Add(new Token(sb.ToString(), true));
continue;
}
var start = i;
var depth = 0;
while (i < text.Length && (depth > 0 || text[i] != ' '))
{
if (text[i] == '(')
depth++;
else if (text[i] == ')' && depth > 0)
depth--;
i++;
}
tokens.Add(new Token(text[start..i], false));
}
return tokens;
}
}
/// Timeouts and waits in frames (1 frame = 1/60 s): 120 = 120 frames, 2s = 120, 0.5s = 30;
/// negative, non-whole frame counts and other units → null.
public static class Durations
{
public const int FramesPerSecond = 60;
public static int? Frames(string text)
{
if (text.EndsWith('s'))
{
var number = text[..^1];
if (number.Length == 0 || !number.All(c => char.IsAsciiDigit(c) || c == '.')
|| !decimal.TryParse(number, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var seconds))
return null;
var frames = seconds * FramesPerSecond;
return frames == decimal.Truncate(frames) ? (int)frames : null;
}
return text.Length > 0 && text.All(char.IsAsciiDigit) && int.TryParse(text, CultureInfo.InvariantCulture, out var n) ? n : null;
}
}