Skip to content

Commit 9508e86

Browse files
committed
Add half-open range operator (...).
1 parent 702e39b commit 9508e86

File tree

10 files changed

+150
-35
lines changed

10 files changed

+150
-35
lines changed

Schyntax.Tests/tests.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@
3636
"date": "2014-12-25T00:00:00.000Z",
3737
"prev": "2014-12-25T00:00:00.000Z",
3838
"next": "2015-12-25T00:00:00.000Z"
39+
},
40+
{
41+
"format": "dates(12/25..12/27)",
42+
"date": "2014-12-26T00:00:00.000Z",
43+
"prev": "2014-12-26T00:00:00.000Z",
44+
"next": "2014-12-27T00:00:00.000Z"
45+
},
46+
{
47+
"format": "dates(12/25...12/27)",
48+
"date": "2014-12-26T00:00:00.000Z",
49+
"prev": "2014-12-26T00:00:00.000Z",
50+
"next": "2015-12-25T00:00:00.000Z"
3951
}
4052
]
4153
},
@@ -310,6 +322,12 @@
310322
"date": "2014-05-01T00:00:00.000Z",
311323
"prev": "2014-04-30T00:00:00.000Z",
312324
"next": "2014-05-02T00:00:00.000Z"
325+
},
326+
{
327+
"format": "dom(20...10)",
328+
"date": "2014-05-09T00:00:00.000Z",
329+
"prev": "2014-05-09T00:00:00.000Z",
330+
"next": "2014-05-20T00:00:00.000Z"
313331
}
314332
]
315333
},
@@ -741,6 +759,18 @@
741759
"prev": "2014-07-14T00:00:00.000Z",
742760
"next": "2014-07-15T00:00:00.000Z"
743761
},
762+
{
763+
"format": "dow(mon...sat)",
764+
"date": "2014-07-13T00:00:00.000Z",
765+
"prev": "2014-07-11T00:00:00.000Z",
766+
"next": "2014-07-14T00:00:00.000Z"
767+
},
768+
{
769+
"format": "dow(mon...sat)",
770+
"date": "2014-07-14T00:00:00.000Z",
771+
"prev": "2014-07-14T00:00:00.000Z",
772+
"next": "2014-07-15T00:00:00.000Z"
773+
},
744774
{
745775
"format": "dow(mon..thu, sat)",
746776
"date": "2014-07-13T00:00:00.000Z",
@@ -925,6 +955,18 @@
925955
"prev": "2014-06-25T00:00:00.000Z",
926956
"next": "2014-06-25T01:00:00.000Z"
927957
},
958+
{
959+
"format": "h(0...23)",
960+
"date": "2014-06-25T00:00:00.000Z",
961+
"prev": "2014-06-25T00:00:00.000Z",
962+
"next": "2014-06-25T01:00:00.000Z"
963+
},
964+
{
965+
"format": "h(0...23)",
966+
"date": "2014-06-25T22:00:00.000Z",
967+
"prev": "2014-06-25T22:00:00.000Z",
968+
"next": "2014-06-26T00:00:00.000Z"
969+
},
928970
{
929971
"format": "h(12..20, 21)",
930972
"date": "2014-06-25T20:00:00.000Z",
@@ -1229,6 +1271,12 @@
12291271
"prev": "2014-06-25T18:00:00.000Z",
12301272
"next": "2014-06-25T18:01:00.000Z"
12311273
},
1274+
{
1275+
"format": "m(0...10)",
1276+
"date": "2014-06-25T17:20:00.000Z",
1277+
"prev": "2014-06-25T17:09:00.000Z",
1278+
"next": "2014-06-25T18:00:00.000Z"
1279+
},
12321280
{
12331281
"format": "m(12..26, 27)",
12341282
"date": "2014-06-25T18:26:12.326Z",
@@ -1533,6 +1581,12 @@
15331581
"prev": "2014-06-25T18:00:00.000Z",
15341582
"next": "2014-06-25T18:00:01.000Z"
15351583
},
1584+
{
1585+
"format": "s(0...10)",
1586+
"date": "2014-06-25T17:50:20.000Z",
1587+
"prev": "2014-06-25T17:50:09.000Z",
1588+
"next": "2014-06-25T17:51:00.000Z"
1589+
},
15361590
{
15371591
"format": "s(12..26, 27)",
15381592
"date": "2014-06-25T18:01:26.000Z",

Schyntax/Internals/IntermediateRepresentation.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,27 @@ internal IrGroup() { }
7272
public class IrIntegerRange
7373
{
7474
public bool IsRange { get; }
75+
public bool IsHalfOpen { get; }
7576
public bool IsSplit { get; }
7677
public int Start { get; private set; }
7778
public int End { get; private set; }
7879
public int Interval { get; }
7980
public bool HasInterval { get; }
8081

81-
internal IrIntegerRange(int start, int? end, int interval, bool isSplit)
82+
internal IrIntegerRange(int start, int? end, int interval, bool isSplit, bool isHalfOpen)
8283
{
8384
Start = start;
84-
End = end ?? 0;
85-
IsRange = end.HasValue;
85+
86+
if (end.HasValue)
87+
{
88+
IsSplit = isSplit;
89+
IsHalfOpen = isHalfOpen;
90+
IsRange = true;
91+
End = end.Value;
92+
}
93+
8694
Interval = interval;
8795
HasInterval = interval != 0;
88-
IsSplit = isSplit;
8996
}
9097

9198
public IrIntegerRange CloneWithRevisedRange(int start, int end)
@@ -102,27 +109,29 @@ public IrIntegerRange CloneWithRevisedRange(int start, int end)
102109
public class IrDateRange
103110
{
104111
public bool IsRange { get; }
112+
public bool IsHalfOpen { get; }
105113
public bool IsSplit { get; }
106114
public IrDate Start { get; }
107115
public IrDate End { get; }
108116
public bool DatesHaveYear { get; }
109117
public int Interval { get; }
110118
public bool HasInterval { get; }
111119

112-
internal IrDateRange(IrDate start, IrDate? end, int interval, bool isSplit)
120+
internal IrDateRange(IrDate start, IrDate? end, int interval, bool isSplit, bool isHalfOpen)
113121
{
114122
Start = start;
115123
DatesHaveYear = start.Year != 0;
116124

117125
if (end.HasValue)
118126
{
119127
IsRange = true;
128+
IsSplit = isSplit;
129+
IsHalfOpen = isHalfOpen;
120130
End = end.Value;
121131
}
122132

123133
Interval = interval;
124134
HasInterval = interval != 0;
125-
IsSplit = isSplit;
126135
}
127136
}
128137

Schyntax/Internals/IrBuilder.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ private static void CompileDateArgument(IrGroup irGroup, ArgumentNode arg)
134134
}
135135
}
136136

137-
var irArg = new IrDateRange(irStart, irEnd, arg.HasInterval ? arg.IntervalValue : 0, isSplit);
137+
var irArg = new IrDateRange(irStart, irEnd, arg.HasInterval ? arg.IntervalValue : 0, isSplit, arg.Range?.IsHalfOpen ?? false);
138138
(arg.IsExclusion ? irGroup.DatesExcluded : irGroup.Dates).Add(irArg);
139139
}
140140

@@ -206,12 +206,12 @@ private static IrIntegerRange CompileIntegerArgument(ArgumentNode arg, int wildS
206206
}
207207
}
208208

209-
return new IrIntegerRange(start, end, arg.HasInterval ? arg.IntervalValue : 0, isSplit);
209+
return new IrIntegerRange(start, end, arg.HasInterval ? arg.IntervalValue : 0, isSplit, arg.Range?.IsHalfOpen ?? false);
210210
}
211211

212212
private static IrIntegerRange GetZeroInteger()
213213
{
214-
return new IrIntegerRange(0, null, 0, false);
214+
return new IrIntegerRange(0, null, 0, false, false);
215215
}
216216
}
217217
}

Schyntax/Internals/Lexer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ private LexMethod LexExpressionArgument()
8383
if (ConsumeNumberDayOrDate(false))
8484
{
8585
// might be a range
86-
if (ConsumeOptionalTerm(Terms.Range))
86+
if (ConsumeOptionalTerm(Terms.RangeHalfOpen) || ConsumeOptionalTerm(Terms.RangeInclusive))
8787
ConsumeNumberDayOrDate(true);
8888
}
8989
}

Schyntax/Internals/Nodes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public class RangeNode : Node
9494
{
9595
public ValueNode Start { get; internal set; }
9696
public ValueNode End { get; internal set; }
97+
public bool IsHalfOpen { get; internal set; }
9798
}
9899

99100
public abstract class ValueNode : Node

Schyntax/Internals/Parser.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,18 @@ private RangeNode ParseRange(ExpressionType expressionType)
119119
var range = new RangeNode();
120120
range.Start = expressionType == ExpressionType.Dates ? (ValueNode)ParseDate() : ParseIntegerValue(expressionType);
121121

122-
if (IsNext(TokenType.Range))
122+
var isRange = false;
123+
if (IsNext(TokenType.RangeInclusive))
124+
{
125+
isRange = true;
126+
}
127+
else if (IsNext(TokenType.RangeHalfOpen))
128+
{
129+
isRange = true;
130+
range.IsHalfOpen = true;
131+
}
132+
133+
if (isRange)
123134
{
124135
range.AddToken(Advance());
125136
range.End = expressionType == ExpressionType.Dates ? (ValueNode)ParseDate() : ParseIntegerValue(expressionType);

Schyntax/Internals/Terms.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ namespace Schyntax.Internals
55
internal static class Terms
66
{
77
// literal terminals
8-
public static Terminal Range { get; } = new Terminal(TokenType.Range, "..");
8+
public static Terminal RangeInclusive { get; } = new Terminal(TokenType.RangeInclusive, "..");
9+
public static Terminal RangeHalfOpen { get; } = new Terminal(TokenType.RangeHalfOpen, "...");
910
public static Terminal Interval { get; } = new Terminal(TokenType.Interval, "%");
1011
public static Terminal Not { get; } = new Terminal(TokenType.Not, "!");
1112
public static Terminal OpenParen { get; } = new Terminal(TokenType.OpenParen, "(");

Schyntax/Internals/Token.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public enum TokenType
1919
EndOfInput,
2020

2121
// operators
22-
Range,
22+
RangeInclusive,
23+
RangeHalfOpen,
2324
Interval,
2425
Not,
2526
OpenParen,

Schyntax/Internals/Validator.cs

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -75,28 +75,7 @@ private void Expression(ExpressionNode expression)
7575
" use the wildcard operator \"*\" instead of an interval", Input, arg.IntervalTokenIndex);
7676
}
7777

78-
ValueValidator validator;
79-
switch (expression.ExpressionType)
80-
{
81-
case ExpressionType.Seconds:
82-
case ExpressionType.Minutes:
83-
validator = SecondOrMinute;
84-
break;
85-
case ExpressionType.Hours:
86-
validator = Hour;
87-
break;
88-
case ExpressionType.DaysOfWeek:
89-
validator = DayOfWeek;
90-
break;
91-
case ExpressionType.DaysOfMonth:
92-
validator = DayOfMonth;
93-
break;
94-
case ExpressionType.Dates:
95-
validator = Date;
96-
break;
97-
default:
98-
throw new NotImplementedException("ExpressionType " + expression.ExpressionType + " has not been implemented by the validator.");
99-
}
78+
var validator = GetValidator(expression.ExpressionType);
10079

10180
if (arg.IsWildcard)
10281
{
@@ -124,14 +103,39 @@ private void Expression(ExpressionNode expression)
124103
}
125104
}
126105

106+
private ValueValidator GetValidator(ExpressionType expType)
107+
{
108+
switch (expType)
109+
{
110+
case ExpressionType.Seconds:
111+
case ExpressionType.Minutes:
112+
return SecondOrMinute;
113+
case ExpressionType.Hours:
114+
return Hour;
115+
case ExpressionType.DaysOfWeek:
116+
return DayOfWeek;
117+
case ExpressionType.DaysOfMonth:
118+
return DayOfMonth;
119+
case ExpressionType.Dates:
120+
return Date;
121+
default:
122+
throw new NotImplementedException("ExpressionType " + expType + " has not been implemented by the validator.");
123+
}
124+
}
125+
127126
private delegate void ValueValidator(ExpressionType expType, ValueNode value);
128127

129128
private void Range(ExpressionType expType, RangeNode range, ValueValidator validator)
130129
{
131130
validator(expType, range.Start);
132131
if (range.End != null)
132+
{
133133
validator(expType, range.End);
134134

135+
if (range.IsHalfOpen && ValuesAreEqual(expType, range.Start, range.End))
136+
throw new SchyntaxParseException("Start and end values of a half-open range cannot be equal.", Input, range.Start.Index);
137+
}
138+
135139
if (expType == ExpressionType.Dates && range.End != null)
136140
{
137141
// special validation to make the date range is sane
@@ -207,6 +211,29 @@ private int IntegerValue(ExpressionType type, ValueNode value, int min, int max)
207211
return ival;
208212
}
209213

214+
private bool ValuesAreEqual(ExpressionType expType, ValueNode a, ValueNode b)
215+
{
216+
if (expType == ExpressionType.Dates)
217+
{
218+
var ad = (DateValueNode)a;
219+
var bd = (DateValueNode)b;
220+
221+
if (ad.Day != bd.Day || ad.Month != bd.Month)
222+
return false;
223+
224+
if (ad.Year.HasValue && ad.Year != bd.Year)
225+
return false;
226+
227+
return true;
228+
}
229+
230+
// integer values
231+
var ai = ((IntegerValueNode)a).Value;
232+
var bi = ((IntegerValueNode)b).Value;
233+
234+
return ai == bi;
235+
}
236+
210237
// returns true if the start date is before or equal to the end date
211238
private bool IsStartBeforeEnd(DateValueNode start, DateValueNode end)
212239
{

Schyntax/Schedule.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,14 @@ private static bool InDateRange(IrDateRange range, int year, int month, int dayO
245245
return true;
246246
}
247247

248+
if (range.IsHalfOpen)
249+
{
250+
// check if this is the last date in a half-open range
251+
var end = range.End;
252+
if (end.Day == dayOfMonth && end.Month == month && (!range.DatesHaveYear || end.Year == year))
253+
return false;
254+
}
255+
248256
// check if in-between start and end dates.
249257
if (range.DatesHaveYear)
250258
{
@@ -357,6 +365,9 @@ private static bool InIntegerRange(IrIntegerRange range, int value, int lengthOf
357365
return value == range.Start;
358366
}
359367

368+
if (range.IsHalfOpen && value == range.End)
369+
return false;
370+
360371
if (range.IsSplit) // range spans across the max value and loops back around
361372
{
362373
if (value <= range.End || value >= range.Start)

0 commit comments

Comments
 (0)