diff options
| author | godosa <godosa@godosa.eu> | 2026-10-06 23:39:36 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-06 23:39:36 +0200 |
| commit | 39066900773e7857faf2d02e7ed51b71d219d97d (patch) | |
| tree | ffab8e4ddd971626776c3b1f4ce1da2c186c2187 /src/Godosa.Core/Runs/Scripts/StepLexer.cs | |
| download | godosa-engine-39066900773e7857faf2d02e7ed51b71d219d97d.tar.gz godosa-engine-39066900773e7857faf2d02e7ed51b71d219d97d.zip | |
godosa-engine: initial public history
Diffstat (limited to 'src/Godosa.Core/Runs/Scripts/StepLexer.cs')
| -rw-r--r-- | src/Godosa.Core/Runs/Scripts/StepLexer.cs | 104 |
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; + } +} |
