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
105
106
107
108
109
110
111
|
using Godosa.Core.Runs.Scripts;
using Xunit;
namespace Godosa.Core.Tests.Runs.Scripts;
public class ScriptParserTests
{
private static ScriptFile Parse(string text) => ScriptParser.Parse("main.steps", text);
private static string Errors(ScriptFile f) => string.Join(" | ", f.Errors.Select(e => e.ToString()));
[Fact]
public void Header_AndStatements()
{
var f = Parse("""
# comment
format 1
mode player
seed 7
vars ui/${res}.vars
click 10,20
wait dialog-open 2s
expect quest(1) >= accepted
set x = "a b"
include ch/one.steps
call rest(8, inn)
checkpoint done
""");
Assert.Equal("", Errors(f));
Assert.Equal((1, RunMode.Player, 7UL), (f.Format, f.Mode, f.Seed!.Value));
Assert.Equal(["ui/${res}.vars"], f.VarFiles);
var v = Assert.IsType<VerbNode>(f.Body[0]);
Assert.Equal(("click", "10,20", 6), (v.Verb, v.Args, v.At.Line));
var w = Assert.IsType<WaitNode>(f.Body[1]);
Assert.Equal(("dialog-open", 120), (w.Condition, w.Timeout));
Assert.Equal("quest(1) >= accepted", Assert.IsType<ExpectNode>(f.Body[2]).Condition);
var s = Assert.IsType<SetNode>(f.Body[3]);
Assert.Equal(("x", "a b"), (s.Name, s.Value));
Assert.Equal("ch/one.steps", Assert.IsType<IncludeNode>(f.Body[4]).Path);
var c = Assert.IsType<CallNode>(f.Body[5]);
Assert.Equal("rest", c.Name);
Assert.Equal(["8", "inn"], c.Args);
Assert.Equal("done", Assert.IsType<CheckpointNode>(f.Body[6]).Name);
}
[Fact]
public void Blocks_NestByIndent()
{
var f = Parse("""
format 1
mode debug
setup:
cheat gold 5
step talk-virgil:
click 1,2
try:
key R
def rest(hours):
key R
wait-frames ${hours}
key Q
""");
Assert.Equal("", Errors(f));
var setup = Assert.IsType<BlockNode>(f.Body[0]);
Assert.Equal((BlockKind.Setup, 1), (setup.Kind, setup.Body.Count));
var step = Assert.IsType<BlockNode>(f.Body[1]);
Assert.Equal((BlockKind.Step, "talk-virgil", 2), (step.Kind, step.Label, step.Body.Count));
Assert.Equal(BlockKind.Try, Assert.IsType<BlockNode>(step.Body[1]).Kind);
var def = Assert.IsType<DefNode>(f.Body[2]);
Assert.Equal(("rest", 2), (def.Name, def.Body.Count));
Assert.Equal(["hours"], def.Params);
Assert.Equal("Q", Assert.IsType<VerbNode>(f.Body[3]).Args);
}
[Theory]
[InlineData("mode player\nclick 1,1", "main.steps:1: first line must be 'format N'")]
[InlineData("format 9\nmode player", "main.steps:1: unknown format 9 (latest 1)")]
[InlineData("format 1\nmode sneaky", "main.steps:2: mode must be player or debug")]
[InlineData("format 1\nmode debug\nclick 1,1\nseed 3", "main.steps:4: header line after steps")]
[InlineData("format 1\nmode debug\nwait dialog-open 0.01s", "main.steps:3: bad timeout '0.01s'")]
[InlineData("format 1\nmode debug\nwait 2s", "main.steps:3: wait needs a condition and a timeout")]
[InlineData("format 1\nmode debug\nstep a:\n click 1,1", "main.steps:4: indent not a multiple of 2")]
[InlineData("format 1\nmode debug\nstep a:\n\tclick 1,1", "main.steps:4: tab in indentation")]
[InlineData("format 1\nmode debug\nstep a:\nclick 1,1", "main.steps:3: empty block")]
[InlineData("format 1\nmode debug\nloop:\n click 1,1", "main.steps:3: unknown block 'loop:'")]
[InlineData("format 1\nmode debug\nclick 1,1\n key R", "main.steps:4: unexpected indent")]
[InlineData("format 1\nmode debug\nlog \"abc", "main.steps:3: unterminated quote")]
[InlineData("format 1\nmode debug\nset 9x = 1", "main.steps:3: bad set (set name = value)")]
public void Errors_NameTheLine(string text, string error) => Assert.Contains(error, Errors(Parse(text)));
// Review focus 5: CRLF, BOM and trailing spaces parse as LF.
[Fact]
public void CrlfBomTrailingSpaces_SameAsLf()
{
var lf = Parse("format 1\nmode debug\nstep a:\n click 1,2\n");
var crlf = Parse("\uFEFFformat 1 \r\nmode debug\r\nstep a: \r\n click 1,2 \r\n");
Assert.Equal("", Errors(crlf));
var a = Assert.IsType<BlockNode>(lf.Body[0]);
var b = Assert.IsType<BlockNode>(crlf.Body[0]);
Assert.Equal(a.Text, b.Text);
Assert.Equal(((VerbNode)a.Body[0]).Text, ((VerbNode)b.Body[0]).Text);
Assert.Equal(((VerbNode)a.Body[0]).At, ((VerbNode)b.Body[0]).At);
}
[Fact]
public void ReportsEveryError_NotOnlyTheFirst()
{
var f = Parse("format 1\nmode debug\nwait x 2m\nset = 1\nloop:\n key R");
Assert.Equal(3, f.Errors.Count);
}
}
|