Skip to content

Commit ca93e43

Browse files
committed
Switch expressions
1 parent 3abde5d commit ca93e43

11 files changed

Lines changed: 1091 additions & 162 deletions
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
using NUnit.Framework;
2+
3+
namespace Mond.Tests.Expressions
4+
{
5+
[TestFixture]
6+
public class SwitchExpressionTests
7+
{
8+
[Test]
9+
public void JumpTableArms()
10+
{
11+
var result = Script.Run(@"
12+
fun describe(x) {
13+
return x switch {
14+
1 -> 'one',
15+
2 -> 'two',
16+
3 -> 'three',
17+
4 -> 'four',
18+
_ -> 'many'
19+
};
20+
}
21+
22+
return describe(1) + describe(3) + describe(9);
23+
");
24+
25+
Assert.That(result, Is.EqualTo((MondValue)"onethreemany"));
26+
}
27+
28+
[Test]
29+
public void MultipleValuesPerArm()
30+
{
31+
var result = Script.Run(@"
32+
fun describe(x) {
33+
return x switch {
34+
'a', 'b' -> 1,
35+
'c' -> 2,
36+
_ -> 3
37+
};
38+
}
39+
40+
return [describe('a'), describe('b'), describe('c'), describe('d')];
41+
");
42+
43+
Assert.That(result[0], Is.EqualTo((MondValue)1));
44+
Assert.That(result[1], Is.EqualTo((MondValue)1));
45+
Assert.That(result[2], Is.EqualTo((MondValue)2));
46+
Assert.That(result[3], Is.EqualTo((MondValue)3));
47+
}
48+
49+
[Test]
50+
public void NonConstantArms()
51+
{
52+
var result = Script.Run(@"
53+
const a = 10;
54+
var b = 20;
55+
56+
fun describe(x) {
57+
return x switch {
58+
a -> 'a',
59+
b -> 'b',
60+
_ -> 'other'
61+
};
62+
}
63+
64+
return describe(10) + describe(20) + describe(30);
65+
");
66+
67+
Assert.That(result, Is.EqualTo((MondValue)"abother"));
68+
}
69+
70+
[Test]
71+
public void TrailingComma()
72+
{
73+
var result = Script.Run(@"
74+
return 1 switch {
75+
1 -> 'one',
76+
_ -> 'other',
77+
};
78+
");
79+
80+
Assert.That(result, Is.EqualTo((MondValue)"one"));
81+
}
82+
83+
[Test]
84+
public void BindingArmWithGuard()
85+
{
86+
var result = Script.Run(@"
87+
fun describe(x) {
88+
return x switch {
89+
var n when n > 100 -> 'big',
90+
var n when n > 10 -> 'medium',
91+
_ -> 'small'
92+
};
93+
}
94+
95+
return describe(500) + describe(50) + describe(5);
96+
");
97+
98+
Assert.That(result, Is.EqualTo((MondValue)"bigmediumsmall"));
99+
}
100+
101+
[Test]
102+
public void ValueArmWithGuard()
103+
{
104+
var result = Script.Run(@"
105+
var flag = false;
106+
107+
fun describe(x) {
108+
return x switch {
109+
1 when flag -> 'flagged',
110+
1 -> 'one',
111+
_ -> 'other'
112+
};
113+
}
114+
115+
var a = describe(1);
116+
flag = true;
117+
return a + describe(1);
118+
");
119+
120+
Assert.That(result, Is.EqualTo((MondValue)"oneflagged"));
121+
}
122+
123+
[Test]
124+
public void ObjectDestructuringArm()
125+
{
126+
var result = Script.Run(@"
127+
fun describe(x) {
128+
return x switch {
129+
{ name, age } -> name + ' is ' + age,
130+
{ name } -> name,
131+
_ -> 'unknown'
132+
};
133+
}
134+
135+
return [
136+
describe({ name: 'bob', age: 5 }),
137+
describe({ name: 'joe' }),
138+
describe(123)
139+
];
140+
");
141+
142+
Assert.That(result[0], Is.EqualTo((MondValue)"bob is 5"));
143+
Assert.That(result[1], Is.EqualTo((MondValue)"joe"));
144+
Assert.That(result[2], Is.EqualTo((MondValue)"unknown"));
145+
}
146+
147+
[Test]
148+
public void ObjectDestructuringAlias()
149+
{
150+
var result = Script.Run(@"
151+
return { name: 'bob' } switch {
152+
{ name: n } -> n,
153+
_ -> 'unknown'
154+
};
155+
");
156+
157+
Assert.That(result, Is.EqualTo((MondValue)"bob"));
158+
}
159+
160+
[Test]
161+
public void ArrayDestructuringArm()
162+
{
163+
var result = Script.Run(@"
164+
fun describe(x) {
165+
return x switch {
166+
[a, b] -> a + b,
167+
[a, ...rest] -> a + rest.length(),
168+
_ -> -1
169+
};
170+
}
171+
172+
return [describe([1, 2]), describe([1, 2, 3]), describe('nope')];
173+
");
174+
175+
Assert.That(result[0], Is.EqualTo((MondValue)3));
176+
Assert.That(result[1], Is.EqualTo((MondValue)3));
177+
Assert.That(result[2], Is.EqualTo((MondValue)(-1)));
178+
}
179+
180+
[Test]
181+
public void ArmScopesAreIsolated()
182+
{
183+
Assert.Throws<MondCompilerException>(() => Script.Run(@"
184+
var x = { a: 1 } switch {
185+
{ a } -> a,
186+
_ -> 0
187+
};
188+
189+
return a;
190+
"));
191+
}
192+
193+
[Test]
194+
public void NestedInExpressions()
195+
{
196+
var result = Script.Run(@"
197+
fun add(a, b) -> a + b;
198+
199+
var x = 1;
200+
return add(x switch { 1 -> 10, _ -> 0 }, x == 1 ? x switch { 1 -> 5, _ -> 0 } : 0);
201+
");
202+
203+
Assert.That(result, Is.EqualTo((MondValue)15));
204+
}
205+
206+
[Test]
207+
public void NestedSwitchExpression()
208+
{
209+
var result = Script.Run(@"
210+
return 1 switch {
211+
1 -> 2 switch {
212+
2 -> 'nested',
213+
_ -> 'no'
214+
},
215+
_ -> 'no'
216+
};
217+
");
218+
219+
Assert.That(result, Is.EqualTo((MondValue)"nested"));
220+
}
221+
222+
[Test]
223+
public void SubjectEvaluatedOnce()
224+
{
225+
var result = Script.Run(@"
226+
var calls = 0;
227+
fun next() {
228+
calls++;
229+
return 3;
230+
}
231+
232+
var value = next() switch {
233+
1 -> 'one',
234+
3 -> 'three',
235+
_ -> 'other'
236+
};
237+
238+
return value + calls;
239+
");
240+
241+
Assert.That(result, Is.EqualTo((MondValue)"three1"));
242+
}
243+
244+
[Test]
245+
public void SwitchStatementStillWorks()
246+
{
247+
var result = Script.Run(@"
248+
var x = 2;
249+
switch (x) {
250+
case 1:
251+
return 'one';
252+
case 2:
253+
return 'two';
254+
default:
255+
return 'other';
256+
}
257+
");
258+
259+
Assert.That(result, Is.EqualTo((MondValue)"two"));
260+
}
261+
262+
[Test]
263+
public void MissingDiscardArm()
264+
{
265+
Assert.Throws<MondCompilerException>(() => Script.Run(@"
266+
return 1 switch {
267+
1 -> 'one'
268+
};
269+
"));
270+
}
271+
272+
[Test]
273+
public void ArmAfterDiscardArm()
274+
{
275+
Assert.Throws<MondCompilerException>(() => Script.Run(@"
276+
return 1 switch {
277+
_ -> 'other',
278+
1 -> 'one'
279+
};
280+
"));
281+
}
282+
283+
[Test]
284+
public void DiscardArmCantHaveGuard()
285+
{
286+
Assert.Throws<MondCompilerException>(() => Script.Run(@"
287+
return 1 switch {
288+
_ when true -> 'other'
289+
};
290+
"));
291+
}
292+
293+
[Test]
294+
public void DuplicateConstantArms()
295+
{
296+
Assert.Throws<MondCompilerException>(() => Script.Run(@"
297+
return 1 switch {
298+
1 -> 'one',
299+
1 -> 'uno',
300+
_ -> 'other'
301+
};
302+
"));
303+
}
304+
}
305+
}

Mond/Compiler/CompilerError.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ static class CompilerError
3232
public const string DuplicateCase = "Duplicate case value";
3333
public const string DuplicateDefault = "Cannot have more than one default case";
3434

35+
public const string SwitchExprMissingDiscard = "Switch expressions must end with a '_' arm";
36+
public const string SwitchExprArmAfterDiscard = "Switch expression arms cannot appear after the '_' arm";
37+
public const string SwitchExprDiscardGuard = "The '_' arm of a switch expression cannot have a 'when' guard";
38+
3539
public const string ExportMustBeFollowedByKeywords = "'export' modifier can only be applied to 'const', 'fun', or 'seq' declarations";
3640
public const string ExportMustBeFollowedByDeclaration = "'export' modifier can only be applied to declaration expressions";
3741
public const string ExportMustBeFollowedByNonEmptyDeclaration = "'export' modifier cannot be applied to expressions with no declarations";

0 commit comments

Comments
 (0)