aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Core/Runs/Scripts/StepLexer.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Godosa.Core/Runs/Scripts/StepLexer.cs')
-rw-r--r--src/Godosa.Core/Runs/Scripts/StepLexer.cs104
1 files changed, 104 insertions, 0 deletions
diff --git a/src/Godosa.Core/Runs/Scripts/StepLexer.cs b/src/Godosa.Core/Runs/Scripts/StepLexer.cs
new file mode 100644
index 0000000..4c54544
--- /dev/null
+++ b/src/Godosa.Core/Runs/Scripts/StepLexer.cs
@@ -0,0 +1,104 @@
+// 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);
+
+/// <summary>Line lexing for <c>.steps</c>: <c>#</c> comments outside quotes; tokens split on spaces, <c>"…"</c> (escapes
+/// <c>\"</c>, <c>\\</c>) and <c>(…)</c> groups stay one token.</summary>
+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<Token> Tokens(string text)
+ {
+ var tokens = new List<Token>();
+ 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;
+ }
+}
+
+/// <summary>Timeouts and waits in frames (1 frame = 1/60 s): <c>120</c> = 120 frames, <c>2s</c> = 120, <c>0.5s</c> = 30;
+/// negative, non-whole frame counts and other units → null.</summary>
+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;
+ }
+}