2
using System.Globalization;
5
internal static partial class Parser {
9
/// <summary>Represents the type of an expression.</summary>
10
internal enum ExpressionType {
11
/// <summary>The type of the expression has not been determined yet.</summary>
13
/// <summary>The expression is a track position.</summary>
15
/// <summary>The expression is from a namespace other than Track.</summary>
17
/// <summary>The expression is from the Track namespace.</summary>
21
/// <summary>Represents an expression consisting of a command, and optionally, indices, a suffix or arguments.</summary>
22
internal class Expression {
24
/// <summary>The file in which the expression occured.</summary>
26
/// <summary>The zero-based row at which the expression occured.</summary>
28
/// <summary>The zero-based column at which the expression occured.</summary>
30
/// <summary>The name of the command as it appears in the file.</summary>
31
internal string OriginalCommand;
32
/// <summary>The name of the command in CSV-equivalent form.</summary>
33
internal string CsvEquivalentCommand;
34
/// <summary>An array of indices. This field may be a null reference.</summary>
35
internal string[] Indices;
36
/// <summary>The suffix to the command. This field may be a null reference.</summary>
37
internal string Suffix;
38
/// <summary>An array of arguments. This field may be a null reference.</summary>
39
internal string[] Arguments;
40
/// <summary>The type of expression.</summary>
41
internal ExpressionType Type;
42
/// <summary>For commands from the Track namespace, this stores the associated track position in text form.</summary>
43
internal string PositionString;
44
/// <summary>For commands from the Track namespace, this stores the associated track position as a number.</summary>
45
internal double Position;
47
/// <summary>Creates a new instance of this class.</summary>
48
/// <param name="file">The file in which the expression occured.</param>
49
/// <param name="row">The zero-based row at which the expression occured.</param>
50
/// <param name="column">The zero-based column at which the expression occured.</param>
51
/// <param name="command">The name of the command.</param>
52
/// <param name="indices">An array of indices. This field may be a null reference.</param>
53
/// <param name="suffix">The suffix to the command. This field may be a null reference.</param>
54
/// <param name="arguments">An array of arguments. This field may be a null reference.</param>
55
/// <remarks>This constructor may only be used for CSV routes.</remarks>
56
internal Expression(string file, int row, int column, string command, string[] indices, string suffix, string[] arguments) {
60
this.OriginalCommand = command;
61
this.CsvEquivalentCommand = command;
62
this.Indices = indices;
64
this.Arguments = arguments;
65
this.PositionString = null;
68
/// <summary>Creates a new instance of this class.</summary>
69
/// <param name="file">The file in which the expression occured.</param>
70
/// <param name="row">The zero-based row at which the expression occured.</param>
71
/// <param name="column">The zero-based column at which the expression occured.</param>
72
/// <param name="originalCommand">The name of the command as it appears in the file.</param>
73
/// <param name="csvEquivalentCommand">The name of the command in CSV-equivalent form.</param>
74
/// <param name="indices">An array of indices. This field may be a null reference.</param>
75
/// <param name="suffix">The suffix to the command. This field may be a null reference.</param>
76
/// <param name="arguments">An array of arguments. This field may be a null reference.</param>
77
internal Expression(string file, int row, int column, string originalCommand, string csvEquivalentCommand, string[] indices, string suffix, string[] arguments) {
81
this.OriginalCommand = originalCommand;
82
this.CsvEquivalentCommand = csvEquivalentCommand;
83
this.Indices = indices;
85
this.Arguments = arguments;
86
this.PositionString = null;
94
/// <summary>Takes a cell from a CSV file and splits it into command, indices, suffix and arguments.</summary>
95
/// <param name="file">The path to the route file.</param>
96
/// <param name="row">The zero-based row at which the cell is stored.</param>
97
/// <param name="column">The zero-based column at which the cell is stored.</param>
98
/// <param name="cell">The content of the trimmed cell.</param>
99
/// <param name="with">The last argument to the With command.</param>
100
/// <param name="expression">Receives the expression on success.</param>
101
/// <returns>Whether an expression could be extracted.</returns>
102
private static bool GetExpressionFromCsvCell(string file, int row, int column, string cell, ref string with, out Expression expression) {
104
* The following valid syntax variations are detected by this algorithm:
106
* command (indexSequence) .suffix (argumentSequence) command, indices, suffix, arguments
107
* command (indexSequence) .suffix argumentSequence command, indices, suffix, arguments
108
* command (indexSequence) .suffix command, indices, suffix
109
* command (indexSequence) (argumentSequence) command, indices, arguments
110
* command (indexSequence) argumentSequence command, indices, arguments
111
* command (argumentSequence) command, arguments
112
* command argumentSequence command, arguments
113
* command command only
115
* Non-valid syntax is reported but still processed for the best of it.
117
if (cell.Length == 0) {
123
} else if (cell[0] == '.') {
125
* The cell starts with a period. Append it to the
126
* argument to the last With command. If no With
127
* command was used before, this is invalid.
130
IO.ReportInvalidData(file, row, column, "A With statement is required before commands starting with a period can be used.");
138
* Find the first character that is not part of
139
* the command and not part of whitespaces
140
* following the command.
142
int firstNonCommandNonWhitespace;
143
for (firstNonCommandNonWhitespace = 0; firstNonCommandNonWhitespace < cell.Length; firstNonCommandNonWhitespace++) {
144
if (cell[firstNonCommandNonWhitespace] == '(') {
146
} else if (char.IsWhiteSpace(cell[firstNonCommandNonWhitespace])) {
147
firstNonCommandNonWhitespace++;
149
if (!char.IsWhiteSpace(cell[firstNonCommandNonWhitespace])) {
152
firstNonCommandNonWhitespace++;
158
if (firstNonCommandNonWhitespace == cell.Length) {
160
* Neither a whitespace or an opening paranthesis was found.
161
* The entire cell is just the command.
165
expression = new Expression(file, row, column, cell, null, null, null);
166
} else if (cell[firstNonCommandNonWhitespace] == '(') {
168
* An opening paranthesis was found. This could mark
169
* the start of indices or of arguments. Let's find
170
* the matching closing paranthesis first.
172
string command = cell.Substring(0, firstNonCommandNonWhitespace).TrimEnd();
173
int closingParanthesis = cell.IndexOf(')', firstNonCommandNonWhitespace + 1);
174
if (closingParanthesis != -1) {
176
* The closing paranthesis was found. The text in-between
177
* the parantheses could be indices or arguments. If the
178
* closing paranthesis is at the end of the cell, the
179
* text is the arguments, otherwise, the indices.
181
* command (indexSequence) .suffix (argumentSequence)
182
* command (indexSequence) .suffix argumentSequence
183
* command (indexSequence) .suffix
184
* command (indexSequence) (argumentSequence)
185
* command (indexSequence) argumentSequence
186
* command (argumentSequence)
188
if (closingParanthesis == cell.Length - 1) {
190
* The closing paranthesis is at the end of the cell.
191
* The text in-between the parantheses is the
192
* arguments. Indices or suffixes do not occur.
193
* If the argument sequence contains further
194
* opening parantheses, this is invalid.
196
* command (argumentSequence)
198
string argumentSequence = cell.Substring(firstNonCommandNonWhitespace + 1, closingParanthesis - firstNonCommandNonWhitespace - 1);
199
if (argumentSequence.IndexOf('(') != -1) {
200
IO.ReportInvalidData(file, row, column, "The argument sequence contains an invalid opening paranthesis.");
202
string[] arguments = argumentSequence.Split(';');
203
for (int k = 0; k < arguments.Length; k++) {
204
arguments[k] = arguments[k].Trim();
206
expression = new Expression(file, row, column, command, null, null, arguments);
209
* The closing paranthesis is followed by something.
210
* The text in-between the parantheses is the indices.
211
* If the index sequence contains further opening
212
* parantheses, this is invalid.
214
* command (indexSequence) .suffix (argumentSequence)
215
* command (indexSequence) .suffix argumentSequence
216
* command (indexSequence) .suffix
217
* command (indexSequence) (argumentSequence)
218
* command (indexSequence) argumentSequence
220
string indexSequence = cell.Substring(firstNonCommandNonWhitespace + 1, closingParanthesis - firstNonCommandNonWhitespace - 1);
221
if (indexSequence.IndexOf('(') != -1) {
222
IO.ReportInvalidData(file, row, column, "The index sequence contains an invalid opening paranthesis.");
224
string[] indices = indexSequence.Split(';');
225
for (int k = 0; k < indices.Length; k++) {
226
indices[k] = indices[k].Trim();
229
* If the first non-whitespace character after the
230
* closing paranthesis is a period, this marks the
231
* start of a suffix, otherwise, of arguments.
233
int firstNonWhitespace;
234
for (firstNonWhitespace = closingParanthesis + 1; firstNonWhitespace < cell.Length; firstNonWhitespace++) {
235
if (!char.IsWhiteSpace(cell[firstNonWhitespace])) {
239
if (cell[firstNonWhitespace] == '.') {
241
* The first non-whitespace character is a period.
242
* This marks the start of a suffix. The suffix
243
* terminates at the first whitespace or opening
244
* paranthesis. If not present, the rest of the
245
* cell is just the suffix.
247
* command (indexSequence) .suffix (argumentSequence)
248
* command (indexSequence) .suffix argumentSequence
249
* command (indexSequence) .suffix
252
for (firstNonSuffix = firstNonWhitespace + 1; firstNonSuffix < cell.Length; firstNonSuffix++) {
253
if (cell[firstNonSuffix] == '(' || char.IsWhiteSpace(cell[firstNonSuffix])) {
257
if (firstNonSuffix == cell.Length) {
259
* No whitespace or opening parantheses was found.
260
* The rest of the cell is just the suffix. If
261
* the suffix contains closing parantheses, this
262
* is invalid. If the suffix ends in a period,
263
* this is invalid as well.
265
* command (indexSequence) .suffix
267
string suffix = cell.Substring(firstNonWhitespace);
268
if (suffix.IndexOf(')') != -1) {
269
IO.ReportInvalidData(file, row, column, "The suffix contains an invalid closing paranthesis.");
270
} else if (suffix[suffix.Length - 1] == '.') {
271
IO.ReportInvalidData(file, row, column, "The suffix must not end in a period.");
273
expression = new Expression(file, row, column, command, indices, suffix, null);
276
* A whitespace or opening paranthesis was found.
277
* This marks the end of the suffix and the start
278
* of arguments. The arguments may be enclosed
279
* by parantheses. Other occurences of parantheses
282
* command (indexSequence) .suffix (argumentSequence)
283
* command (indexSequence) .suffix argumentSequence
285
string suffix = cell.Substring(firstNonWhitespace, firstNonSuffix - firstNonWhitespace);
286
string argumentSequence = cell.Substring(firstNonSuffix).TrimStart();
287
if (argumentSequence[0] == '(' & argumentSequence[argumentSequence.Length - 1] == ')') {
288
argumentSequence = argumentSequence.Substring(1, argumentSequence.Length - 2);
289
if (argumentSequence.IndexOf('(') != -1) {
290
IO.ReportInvalidData(file, row, column, "The argument sequence contains an invalid opening paranthesis.");
291
} else if (argumentSequence.IndexOf(')') != -1) {
292
IO.ReportInvalidData(file, row, column, "The argument sequence contains an invalid closing paranthesis.");
294
} else if (argumentSequence[0] == '(') {
295
IO.ReportInvalidData(file, row, column, "The argument sequence contains an invalid opening paranthesis.");
296
argumentSequence = argumentSequence.Substring(1);
297
} else if (argumentSequence[argumentSequence.Length - 1] == ')') {
298
IO.ReportInvalidData(file, row, column, "The argument sequence contains an invalid closing paranthesis.");
299
argumentSequence = argumentSequence.Substring(0, argumentSequence.Length - 1);
301
string[] arguments = argumentSequence.Split(';');
302
for (int k = 0; k < arguments.Length; k++) {
303
arguments[k] = arguments[k].Trim();
305
expression = new Expression(file, row, column, command, indices, suffix, arguments);
309
* The first non-whitespace character is not a period.
310
* This marks the start of arguments. The arguments
311
* may be enclosed by parantheses. Other occurences
312
* of parantheses are invalid.
314
* command (indexSequence) (argumentSequence)
315
* command (indexSequence) argumentSequence
317
string argumentSequence = cell.Substring(closingParanthesis + 1).TrimStart();
318
if (argumentSequence[0] == '(' & argumentSequence[argumentSequence.Length - 1] == ')') {
319
argumentSequence = argumentSequence.Substring(1, argumentSequence.Length - 2);
320
if (argumentSequence.IndexOf('(') != -1) {
321
IO.ReportInvalidData(file, row, column, "The argument sequence contains an invalid opening paranthesis.");
322
} else if (argumentSequence.IndexOf(')') != -1) {
323
IO.ReportInvalidData(file, row, column, "The argument sequence contains an invalid closing paranthesis.");
325
} else if (argumentSequence[0] == '(') {
326
IO.ReportInvalidData(file, row, column, "The argument sequence contains an invalid opening paranthesis.");
327
argumentSequence = argumentSequence.Substring(1);
328
} else if (argumentSequence[argumentSequence.Length - 1] == ')') {
329
IO.ReportInvalidData(file, row, column, "The argument sequence contains an invalid closing paranthesis.");
330
argumentSequence = argumentSequence.Substring(0, argumentSequence.Length - 1);
332
string[] arguments = argumentSequence.Split(';');
333
for (int k = 0; k < arguments.Length; k++) {
334
arguments[k] = arguments[k].Trim();
336
expression = new Expression(file, row, column, command, indices, null, arguments);
341
* A closing paranthesis was not found. This
342
* is invalid. Let's treat the text after the
343
* opening paranthesis as arguments.
345
IO.ReportInvalidData(file, row, column, "Missing closing paranthesis.");
346
string argumentSequence = cell.Substring(firstNonCommandNonWhitespace + 1);
347
string[] arguments = argumentSequence.Split(';');
348
for (int k = 0; k < arguments.Length; k++) {
349
arguments[k] = arguments[k].Trim();
351
expression = new Expression(file, row, column, command, null, null, arguments);
355
* Whitespace was found before any opening paranthesis.
356
* The text following the whitespace is the arguments.
358
* command argumentSequence
360
string command = cell.Substring(0, firstNonCommandNonWhitespace).TrimEnd();
361
string argumentSequence = cell.Substring(firstNonCommandNonWhitespace);
362
string[] arguments = argumentSequence.Split(';');
363
for (int k = 0; k < arguments.Length; k++) {
364
arguments[k] = arguments[k].Trim();
366
expression = new Expression(file, row, column, command, null, null, arguments);
369
* Now that we have the expression, check if it is
370
* a With command, and extract the argument to it
373
if (expression.CsvEquivalentCommand.Equals("With", StringComparison.OrdinalIgnoreCase)) {
374
if (expression.Arguments == null) {
375
IO.ReportInvalidData(file, row, column, "The With statement must have exactly one argument.");
377
if (expression.Indices != null | expression.Suffix != null | expression.Arguments.Length != 1) {
378
IO.ReportInvalidData(file, row, column, "The With statement must not contain indices or a suffix, and must have exactly one argument.");
380
if (expression.Arguments[0][0] == '.' | expression.Arguments[0][expression.Arguments[0].Length - 1] == '.') {
381
IO.ReportInvalidData(file, row, column, "The argument to the With statement must not start or end with a period.");
383
with = expression.Arguments[0];
392
/// <summary>Takes a cell from an RW file and splits it into command, indices, suffix and arguments.</summary>
393
/// <param name="file">The path to the route file.</param>
394
/// <param name="row">The zero-based row at which the cell is stored.</param>
395
/// <param name="column">The zero-based column at which the cell is stored.</param>
396
/// <param name="cell">The content of the trimmed cell.</param>
397
/// <param name="section">The last opened section.</param>
398
/// <param name="expression">Receives the expression on success.</param>
399
/// <returns>Whether an expression could be extracted.</returns>
400
private static bool GetExpressionFromRwCell(string file, int row, int column, string cell, string section, out Expression expression) {
402
* The following valid syntax variations are detected by this algorithm:
404
* command (indexSequence) .suffix = argumentSequence command, indices, suffix, arguments
405
* command (indexSequence) = argumentSequence command, indices, arguments
406
* command = argumentSequence command, arguments
408
* @ command (indexSequence) .suffix (argumentSequence) command, indices, suffix, arguments
409
* @ command (indexSequence) (argumentSequence) command, indices, arguments
410
* @ command (indexSequence) .suffix command, indices, suffix
411
* @ command (argumentSequence) command, arguments
412
* @ command command only
414
* The @ character is optional. Non-valid syntax is reported.
416
* The syntax variations with the equals sign are not possible
417
* for the [Railway] section and are thus not considered.
419
* The syntax variations without the equals sign are invalid
420
* when used outside the [Railway] section.
422
if (cell.Length == 0) {
430
* Check if the cell contains an equals sign that
431
* determines the possible syntax variations, but
432
* only if outside the [Railway] section.
435
bool isRailwaySection = section.Equals("railway", StringComparison.OrdinalIgnoreCase);
436
if (isRailwaySection) {
439
equals = cell.IndexOf('=');
443
* An equals sign was found. This means the argument
444
* sequence will follow the equals sign. If the text
445
* prior to the equals sign contains parantheses,
446
* this marks the presence of indices.
448
* command (indexSequence) .suffix = argumentSequence
449
* command (indexSequence) = argumentSequence
450
* command = argumentSequence
452
string argumentSequence = cell.Substring(equals + 1).TrimStart();
453
string[] arguments = argumentSequence.Split(',', ';');
454
for (int k = 0; k < arguments.Length; k++) {
455
arguments[k] = arguments[k].Trim();
457
string commandSequence = cell.Substring(0, equals).TrimEnd();
458
int openingParanthesis = commandSequence.IndexOf('(');
459
if (openingParanthesis >= 0) {
461
* An opening paranthesis was found.
462
* This marks the start of indices.
463
* Let's find the matching closing
466
* command (indexSequence) .suffix = argumentSequence
467
* command (indexSequence) = argumentSequence
469
string originalCommand = commandSequence.Substring(0, openingParanthesis).TrimEnd();
470
string csvEquivalentCommand = GetCsvEquivalentCommand(file, row, column, originalCommand, section, false);
471
int closingParanthesis = commandSequence.IndexOf(')', openingParanthesis + 1);
472
if (closingParanthesis >= 0) {
474
* A closing paranthesis was found.
476
string indexSequence = commandSequence.Substring(openingParanthesis + 1, closingParanthesis - openingParanthesis - 1);
477
if (indexSequence.IndexOf('(') != -1) {
478
IO.ReportInvalidData(file, row, column, "The index sequence contains an invalid opening paranthesis.");
480
string[] indices = indexSequence.Split(',', ';');
481
for (int k = 0; k < indices.Length; k++) {
482
indices[k] = indices[k].Trim();
484
if (closingParanthesis == commandSequence.Length - 1) {
486
* The closing paranthesis was found at the end
487
* of the command sequence (just before the
488
* equals sign). This means that there is
491
* command (indexSequence) = argumentSequence
493
expression = new Expression(file, row, column, originalCommand, csvEquivalentCommand, indices, null, arguments);
496
* The closing paranthesis was found before
497
* the end of the command sequence. This
498
* means that the indices are followed by
499
* a suffix. The suffix must start in a
500
* period and must not end in a period.
502
* command (indexSequence) .suffix = argumentSequence
504
string suffix = commandSequence.Substring(closingParanthesis + 1).TrimStart();
505
if (suffix.IndexOf('(') != -1) {
506
IO.ReportInvalidData(file, row, column, "The suffix contains an invalid opening paranthesis.");
508
if (suffix[0] != '.' || suffix[suffix.Length - 1] == '.') {
509
IO.ReportInvalidData(file, row, column, "The suffix must start with a period but must not end in a period.");
511
expression = new Expression(file, row, column, originalCommand, csvEquivalentCommand, indices, suffix, arguments);
515
* A closing paranthesis was not found. This
516
* is invalid. Let's treat the text after the
517
* opening paranthesis as indices.
519
IO.ReportInvalidData(file, row, column, "Missing closing paranthesis.");
520
string indexSequence = commandSequence.Substring(openingParanthesis + 1).TrimStart();
521
string[] indices = indexSequence.Split(',', ';');
522
for (int k = 0; k < indices.Length; k++) {
523
indices[k] = indices[k].Trim();
525
expression = new Expression(file, row, column, originalCommand, csvEquivalentCommand, indices, null, arguments);
529
* An opening paranthesis was not found. There
530
* is only the command and the arguments.
532
* command = argumentSequence
534
if (commandSequence.IndexOf(')') != -1) {
535
IO.ReportInvalidData(file, row, column, "The command contains an invalid closing paranthesis.");
537
string originalCommand = commandSequence;
538
string csvEquivalentCommand = GetCsvEquivalentCommand(file, row, column, originalCommand, section, false);
539
expression = new Expression(file, row, column, originalCommand, csvEquivalentCommand, null, null, arguments);
543
* No equals sign was found. If this situation is
544
* encountered outside the [Railway] section, it
545
* is invalid. Otherwise, now try to find an
546
* an opening paranthesis.
548
* @ command (indexSequence) .suffix (argumentSequence)
549
* @ command (indexSequence) (argumentSequence)
550
* @ command (indexSequence) .suffix
551
* @ command (argumentSequence)
554
if (!isRailwaySection) {
555
IO.ReportInvalidData(file, row, column, "This syntax is not allowed outside the [Railway] section.");
557
int openingParanthesis = cell.IndexOf('(');
558
if (openingParanthesis >= 0) {
560
* An opening paranthesis was found. This can
561
* mark the start of indices or arguments.
562
* First, find the matching closing paranthesis.
564
string originalCommand = cell.Substring(0, openingParanthesis).TrimEnd();
565
if (originalCommand.IndexOf(')') != -1) {
566
IO.ReportInvalidData(file, row, column, "The command contains an invalid closing paranthesis.");
568
string csvEquivalentCommand = GetCsvEquivalentCommand(file, row, column, originalCommand, section, true);
569
int closingParanthesis = cell.IndexOf(')', openingParanthesis + 1);
570
if (closingParanthesis >= 0) {
572
* The matching closing paranthesis was found. If
573
* it is at the end of the cell, this marks the
574
* end of arguments, otherwise of indices.
576
* @ command (indexSequence) .suffix (argumentSequence)
577
* @ command (indexSequence) (argumentSequence)
578
* @ command (indexSequence) .suffix
579
* @ command (argumentSequence)
581
if (closingParanthesis == cell.Length - 1) {
583
* The matching closing paranthesis is at
584
* the end of the cell. The text between
585
* the opening and closing parantheses
588
* @ command (argumentSequence)
590
string argumentSequence = cell.Substring(openingParanthesis + 1, closingParanthesis - openingParanthesis - 1);
591
if (argumentSequence.IndexOf('(') != -1) {
592
IO.ReportInvalidData(file, row, column, "The argument sequence contains an invalid opening paranthesis.");
594
string[] arguments = argumentSequence.Split(',');
595
for (int k = 0; k < arguments.Length; k++) {
596
arguments[k] = arguments[k].Trim();
598
expression = new Expression(file, row, column, originalCommand, csvEquivalentCommand, null, null, arguments);
601
* The matching closing paranthesis is before
602
* the end of the cell. The text between the
603
* opening and closing parantheses is the
604
* indices. If another opening paranthesis
605
* is found after the closing paranthesis,
606
* this marks the start of arguments.
608
* @ command (indexSequence) .suffix (argumentSequence)
609
* @ command (indexSequence) (argumentSequence)
610
* @ command (indexSequence) .suffix
612
string indexSequence = cell.Substring(openingParanthesis + 1, closingParanthesis - openingParanthesis - 1);
613
if (indexSequence.IndexOf('(') != -1) {
614
IO.ReportInvalidData(file, row, column, "The index sequence contains an invalid opening paranthesis.");
616
string[] indices = indexSequence.Split(',');
617
for (int k = 0; k < indices.Length; k++) {
618
indices[k] = indices[k].Trim();
620
openingParanthesis = cell.IndexOf('(', closingParanthesis + 1);
621
if (openingParanthesis >= 0) {
623
* Another opening paranthesis was found. The cell
624
* must end in a closing paranthesis and must not
625
* contain further parantheses.
627
* @ command (indexSequence) .suffix (argumentSequence)
628
* @ command (indexSequence) (argumentSequence)
630
string suffix = cell.Substring(closingParanthesis + 1, openingParanthesis - closingParanthesis - 1).Trim();
631
if (suffix.Length != 0) {
632
if (suffix.IndexOf(')') != -1) {
633
IO.ReportInvalidData(file, row, column, "The suffix contains an invalid closing paranthesis.");
635
if (suffix[0] != '.' || suffix[suffix.Length - 1] == '.') {
636
IO.ReportInvalidData(file, row, column, "The suffix must start with a period but must not end in a period.");
641
closingParanthesis = cell.IndexOf(')', openingParanthesis + 1);
642
string argumentSequence;
643
if (closingParanthesis >= 0) {
644
if (closingParanthesis != cell.Length - 1) {
646
* The closing paranthesis was found before
647
* the end of the cell. This is invalid.
649
IO.ReportInvalidData(file, row, column, "The cell must end in a closing paranthesis.");
651
argumentSequence = cell.Substring(openingParanthesis + 1, closingParanthesis - openingParanthesis - 1);
654
* No closing paranthesis was found. This is invalid.
656
argumentSequence = cell.Substring(openingParanthesis + 1);
657
IO.ReportInvalidData(file, row, column, "Missing closing paranthesis.");
659
if (argumentSequence.IndexOf('(') != -1) {
660
IO.ReportInvalidData(file, row, column, "The argument sequence contains an invalid opening paranthesis.");
662
string[] arguments = argumentSequence.Split(',');
663
for (int k = 0; k < arguments.Length; k++) {
664
arguments[k] = arguments[k].Trim();
666
expression = new Expression(file, row, column, originalCommand, csvEquivalentCommand, indices, suffix, arguments);
669
* Another opening paranthesis was not found.
670
* The rest of the cell is the suffix.
672
* @ command (indexSequence) .suffix
674
string suffix = cell.Substring(closingParanthesis + 1).TrimStart();
675
if (suffix.IndexOf(')') != -1) {
676
IO.ReportInvalidData(file, row, column, "The suffix contains an invalid closing paranthesis.");
677
if (suffix.Length == 1) {
679
* Special invalid case. What we thought were
680
* indices were meant to be arguments.
682
expression = new Expression(file, row, column, originalCommand, csvEquivalentCommand, null, null, indices);
686
if (suffix[0] != '.' || suffix[suffix.Length - 1] == '.') {
687
IO.ReportInvalidData(file, row, column, "The suffix must start with a period but must not end in a period.");
689
expression = new Expression(file, row, column, originalCommand, csvEquivalentCommand, indices, suffix, null);
694
* A closing paranthesis was not found. This
695
* is invalid. Let's treat the text after the
696
* opening paranthesis as arguments.
698
IO.ReportInvalidData(file, row, column, "Missing closing paranthesis.");
699
string argumentSequence = cell.Substring(openingParanthesis + 1);
700
string[] arguments = argumentSequence.Split(',');
701
for (int k = 0; k < arguments.Length; k++) {
702
arguments[k] = arguments[k].Trim();
704
expression = new Expression(file, row, column, originalCommand, csvEquivalentCommand, null, null, arguments);
708
* An opening paranthesis was not found. This
709
* means that the entire cell is just the
710
* command. Check for track positions and
711
* don't transform them to a CSV equivalent.
715
// TODO: Make sure to support colon-separated track positions here, too.
717
if (double.TryParse(cell, NumberStyles.Float, CultureInfo.InvariantCulture, out value)) {
718
expression = new Expression(file, row, column, cell, null, null, null);
720
string originalCommand = cell;
721
if (originalCommand.IndexOf(')') != -1) {
722
IO.ReportInvalidData(file, row, column, "The command contains an invalid closing paranthesis.");
724
string csvEquivalentCommand = GetCsvEquivalentCommand(file, row, column, originalCommand, section, true);
725
expression = new Expression(file, row, column, originalCommand, csvEquivalentCommand, null, null, null);
730
AdjustCsvEquivalentExpression(expression);
734
/// <summary>Gets the CSV-equivalent command from an RW command.</summary>
735
/// <param name="file">The path to the route file.</param>
736
/// <param name="row">The zero-based row at which the cell is stored.</param>
737
/// <param name="column">The zero-based column at which the cell is stored.</param>
738
/// <param name="command">The RW command.</param>
739
/// <param name="section">The last used section without the enclosing brackets.</param>
740
/// <param name="allowAtSign">Whether to allow an at-sign at the beginning of the command.</param>
741
/// <returns>The CSV-equivalent command.</returns>
742
private static string GetCsvEquivalentCommand(string file, int row, int column, string command, string section, bool allowAtSign) {
743
switch (section.ToLowerInvariant()) {
745
section = "Structure";
751
section = "Cycle.Ground";
754
if (command.Length != 0 && command[0] == '@') {
756
IO.ReportInvalidData(file, row, column, "An @-sign is not allowed at the beginning of the command.");
758
command = command.Substring(1).TrimStart();
760
return section + "." + command;
763
/// <summary>Adjusts an expression to convert from RW-specific organization to CSV-equivalent organization.</summary>
764
/// <param name="expression">The expression to convert.</param>
765
private static void AdjustCsvEquivalentExpression(Expression expression) {
766
if (expression.Indices == null & expression.Suffix == null) {
768
if (double.TryParse(expression.OriginalCommand, NumberStyles.Float, CultureInfo.InvariantCulture, out value)) {
769
if (expression.CsvEquivalentCommand.StartsWith("signal.", StringComparison.OrdinalIgnoreCase)) {
770
expression.Indices = new string[] { expression.CsvEquivalentCommand.Substring(7) };
771
expression.CsvEquivalentCommand = "Signal";
772
expression.Suffix = ".Load";
773
} else if (expression.CsvEquivalentCommand.StartsWith("cycle.ground.", StringComparison.OrdinalIgnoreCase)) {
774
expression.Indices = new string[] { expression.CsvEquivalentCommand.Substring(13) };
775
expression.CsvEquivalentCommand = "Cycle.Ground";
776
expression.Suffix = ".Params";
b'\\ No newline at end of file'