1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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;
}
}
|