1 /*
2  * ActorParser.cs
3  *
4  * This source file is part of the FoundationDB open source project
5  *
6  * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 using System;
22 using System.Collections.Generic;
23 using System.Linq;
24 using System.Text;
25 using System.Text.RegularExpressions;
26 
27 namespace actorcompiler
28 {
29     class Error : Exception
30     {
31         public int SourceLine { get; private set; }
Error(int SourceLine, string format, params object[] args)32         public Error(int SourceLine, string format, params object[] args)
33             : base(string.Format(format,args))
34         {
35             this.SourceLine = SourceLine;
36         }
37     };
38 
39     class ErrorMessagePolicy
40     {
41         public bool DisableActorWithoutWaitWarning = false;
HandleActorWithoutWait(String sourceFile, Actor actor)42         public void HandleActorWithoutWait(String sourceFile, Actor actor)
43         {
44             if (!DisableActorWithoutWaitWarning && !actor.isTestCase)
45             {
46                 // TODO(atn34): Once cmake is the only build system we can make this an error instead of a warning.
47                 Console.Error.WriteLine("{0}:{1}: warning: ACTOR {2} does not contain a wait() statement", sourceFile, actor.SourceLine, actor.name);
48             }
49         }
50     }
51 
52     class Token
53     {
54         public string Value;
55         public int Position;
56         public int SourceLine;
57         public int BraceDepth;
58         public int ParenDepth;
59         public bool IsWhitespace { get { return Value == " " || Value == "\n" || Value == "\r" || Value == "\r\n" || Value == "\t" || Value.StartsWith("//") || Value.StartsWith("/*"); } }
ToString()60         public override string ToString() { return Value; }
Assert(string error, Func<Token, bool> pred)61         public Token Assert(string error, Func<Token, bool> pred)
62         {
63             if (!pred(this)) throw new Error(SourceLine, error);
64             return this;
65         }
GetMatchingRangeIn(TokenRange range)66         public TokenRange GetMatchingRangeIn(TokenRange range)
67         {
68             Func<Token,bool> pred;
69             int dir;
70             switch (Value) {
71                 case "(": pred = t=> t.Value != ")" || t.ParenDepth != ParenDepth; dir = +1; break;
72                 case ")": pred = t=> t.Value != "(" || t.ParenDepth != ParenDepth; dir = -1; break;
73                 case "{": pred = t=> t.Value != "}" || t.BraceDepth != BraceDepth; dir = +1; break;
74                 case "}": pred = t=> t.Value != "{" || t.BraceDepth != BraceDepth; dir = -1; break;
75                 case "<": return
76                     new TokenRange(range.GetAllTokens(),
77                         Position+1,
78                         AngleBracketParser.NotInsideAngleBrackets(
79                                     new TokenRange(range.GetAllTokens(), Position, range.End))
80                                 .Skip(1)  // skip the "<", which is considered "outside"
81                                 .First()  // get the ">", which is likewise "outside"
82                                 .Position);
83                 default: throw new NotSupportedException("Can't match this token!");
84             }
85             TokenRange r;
86             if (dir == -1)
87             {
88                 r = new TokenRange(range.GetAllTokens(), range.Begin, Position)
89                     .RevTakeWhile(pred);
90                 if (r.Begin == range.Begin)
91                     throw new Error(SourceLine, "Syntax error: Unmatched " + Value);
92             }
93             else
94             {
95                 r = new TokenRange(range.GetAllTokens(), Position+1, range.End)
96                     .TakeWhile(pred);
97                 if (r.End == range.End)
98                     throw new Error(SourceLine, "Syntax error: Unmatched " + Value);
99             }
100             return r;
101         }
102     };
103 
104     class TokenRange : IEnumerable<Token>
105     {
TokenRange(Token[] tokens, int beginPos, int endPos)106         public TokenRange(Token[] tokens, int beginPos, int endPos)
107         {
108             if (beginPos > endPos) throw new InvalidOperationException("Invalid TokenRange");
109             this.tokens = tokens;
110             this.beginPos = beginPos;
111             this.endPos = endPos;
112         }
113 
114         public bool IsEmpty { get { return beginPos==endPos; } }
115         public int Begin { get { return beginPos; } }
116         public int End { get { return endPos; } }
First()117         public Token First() {
118             if (beginPos == endPos) throw new InvalidOperationException("Empty TokenRange");
119             return tokens[beginPos];
120         }
Last()121         public Token Last() {
122             if (beginPos == endPos) throw new InvalidOperationException("Empty TokenRange");
123             return tokens[endPos - 1];
124         }
Last(Func<Token, bool> pred)125         public Token Last(Func<Token, bool> pred)
126         {
127             for (int i = endPos - 1; i >= beginPos; i--)
128                 if (pred(tokens[i]))
129                     return tokens[i];
130             throw new Exception("Matching token not found");
131         }
Skip(int count)132         public TokenRange Skip(int count)
133         {
134             return new TokenRange(tokens, beginPos + count, endPos);
135         }
Consume(string value)136         public TokenRange Consume(string value)
137         {
138             First().Assert("Expected " + value, t => t.Value == value);
139             return Skip(1);
140         }
Consume(string error, Func<Token, bool> pred)141         public TokenRange Consume(string error, Func<Token, bool> pred)
142         {
143             First().Assert(error, pred);
144             return Skip(1);
145         }
System.Collections.IEnumerable.GetEnumerator()146         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
GetEnumerator()147         public IEnumerator<Token> GetEnumerator()
148         {
149             for (int i = beginPos; i < endPos; i++)
150                 yield return tokens[i];
151         }
SkipWhile(Func<Token, bool> pred)152         public TokenRange SkipWhile(Func<Token, bool> pred)
153         {
154             for (int e = beginPos; e < endPos; e++)
155                 if (!pred(tokens[e]))
156                     return new TokenRange(tokens, e, endPos);
157             return new TokenRange(tokens, endPos, endPos);
158         }
TakeWhile(Func<Token, bool> pred)159         public TokenRange TakeWhile(Func<Token, bool> pred)
160         {
161             for (int e = beginPos; e < endPos; e++)
162                 if (!pred(tokens[e]))
163                     return new TokenRange(tokens, beginPos, e);
164             return new TokenRange(tokens, beginPos, endPos);
165         }
RevTakeWhile(Func<Token, bool> pred)166         public TokenRange RevTakeWhile(Func<Token, bool> pred)
167         {
168             for (int e = endPos-1; e >= beginPos; e--)
169                 if (!pred(tokens[e]))
170                     return new TokenRange(tokens, e+1, endPos);
171             return new TokenRange(tokens, beginPos, endPos);
172         }
RevSkipWhile(Func<Token, bool> pred)173         public TokenRange RevSkipWhile(Func<Token, bool> pred)
174         {
175             for (int e = endPos - 1; e >= beginPos; e--)
176                 if (!pred(tokens[e]))
177                     return new TokenRange(tokens, beginPos, e + 1);
178             return new TokenRange(tokens, beginPos, beginPos);
179         }
GetAllTokens()180         public Token[] GetAllTokens() { return tokens; }
181 
182         public int Length {
183             get {
184                 return endPos - beginPos;
185             }
186         }
187 
188         Token[] tokens;
189         int beginPos;
190         int endPos;
191     };
192 
193     static class AngleBracketParser
194     {
NotInsideAngleBrackets(IEnumerable<Token> tokens)195         public static IEnumerable<Token> NotInsideAngleBrackets(IEnumerable<Token> tokens)
196         {
197             int AngleDepth = 0;
198             int? BasePD = null;
199             foreach (var tok in tokens)
200             {
201                 if (BasePD == null) BasePD = tok.ParenDepth;
202                 if (tok.ParenDepth == BasePD && tok.Value == ">") AngleDepth--;
203                 if (AngleDepth == 0)
204                     yield return tok;
205                 if (tok.ParenDepth == BasePD && tok.Value == "<") AngleDepth++;
206             }
207         }
208     };
209 
210     class ActorParser
211     {
212         public bool LineNumbersEnabled = true;
213 
214         Token[] tokens;
215         string sourceFile;
216         ErrorMessagePolicy errorMessagePolicy;
217 
ActorParser(string text, string sourceFile, ErrorMessagePolicy errorMessagePolicy)218         public ActorParser(string text, string sourceFile, ErrorMessagePolicy errorMessagePolicy)
219         {
220             this.sourceFile = sourceFile;
221             this.errorMessagePolicy = errorMessagePolicy;
222             tokens = Tokenize(text).Select(t=>new Token{ Value=t }).ToArray();
223             CountParens();
224             //if (sourceFile.EndsWith(".h")) LineNumbersEnabled = false;
225             //Console.WriteLine("{0} chars -> {1} tokens", text.Length, tokens.Length);
226             //showTokens();
227         }
228 
Write(System.IO.TextWriter writer, string destFileName)229         public void Write(System.IO.TextWriter writer, string destFileName)
230         {
231             writer.NewLine = "\n";
232             writer.WriteLine("#define POST_ACTOR_COMPILER 1");
233             int outLine = 1;
234             if (LineNumbersEnabled)
235             {
236                 writer.WriteLine("#line {0} \"{1}\"", tokens[0].SourceLine, sourceFile);
237                 outLine++;
238             }
239             int inBlocks = 0;
240             for(int i=0; i<tokens.Length; i++)
241             {
242                 if(tokens[0].SourceLine == 0)
243                 {
244                     throw new Exception("Internal error: Invalid source line (0)");
245                 }
246                 if (tokens[i].Value == "ACTOR" || tokens[i].Value == "TEST_CASE")
247                 {
248                     int end;
249                     var actor = ParseActor(i, out end);
250                     var actorWriter = new System.IO.StringWriter();
251                     actorWriter.NewLine = "\n";
252                     new ActorCompiler(actor, sourceFile, inBlocks==0, LineNumbersEnabled).Write(actorWriter);
253                     string[] actorLines = actorWriter.ToString().Split('\n');
254 
255                     bool hasLineNumber = false;
256                     bool hadLineNumber = true;
257                     foreach (var line in actorLines)
258                     {
259                         if (LineNumbersEnabled)
260                         {
261                             bool isLineNumber = line.Contains("#line");
262                             if (isLineNumber) hadLineNumber = true;
263                             if (!isLineNumber && !hasLineNumber && hadLineNumber)
264                             {
265                                 writer.WriteLine("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t#line {0} \"{1}\"", outLine + 1, destFileName);
266                                 outLine++;
267                                 hadLineNumber = false;
268                             }
269                             hasLineNumber = isLineNumber;
270                         }
271                         writer.WriteLine(line.TrimEnd('\n','\r'));
272                         outLine++;
273                     }
274 
275                     i = end;
276                     if (i != tokens.Length && LineNumbersEnabled)
277                     {
278                         writer.WriteLine("#line {0} \"{1}\"", tokens[i].SourceLine, sourceFile);
279                         outLine++;
280                     }
281                 }
282                 else if (tokens[i].Value == "DESCR")
283                 {
284                     int end;
285                     var descr = ParseDescr(i, out end);
286                     int lines;
287                     new DescrCompiler(descr, tokens[i].BraceDepth).Write(writer, out lines);
288                     i = end;
289                     outLine += lines;
290                     if (i != tokens.Length && LineNumbersEnabled)
291                     {
292                         writer.WriteLine("#line {0} \"{1}\"", tokens[i].SourceLine, sourceFile);
293                         outLine++;
294                     }
295                 }
296                 else
297                 {
298                     if (tokens[i].Value == "{") inBlocks++;
299                     else if (tokens[i].Value == "}") inBlocks--;
300                     writer.Write(tokens[i].Value);
301                     outLine += tokens[i].Value.Count(c => c == '\n');
302                 }
303             }
304         }
305 
SplitParameterList( TokenRange toks, string delimiter )306         IEnumerable<TokenRange> SplitParameterList( TokenRange toks, string delimiter ) {
307             if (toks.Begin==toks.End) yield break;
308             while (true) {
309                 Token comma = AngleBracketParser.NotInsideAngleBrackets( toks )
310                     .FirstOrDefault( t=> t.Value==delimiter && t.ParenDepth == toks.First().ParenDepth );
311                 if (comma == null) break;
312                 yield return range(toks.Begin,comma.Position);
313                 toks = range(comma.Position + 1, toks.End);
314             }
315             yield return toks;
316         }
317 
NormalizeWhitespace(IEnumerable<Token> tokens)318         IEnumerable<Token> NormalizeWhitespace(IEnumerable<Token> tokens)
319         {
320             bool inWhitespace = false;
321             bool leading = true;
322             foreach (var tok in tokens)
323             {
324                 if (!tok.IsWhitespace)
325                 {
326                     if (inWhitespace && !leading) yield return new Token { Value = " " };
327                     inWhitespace = false;
328                     yield return tok;
329                     leading = false;
330                 }
331                 else
332                 {
333                     inWhitespace = true;
334                 }
335             }
336         }
337 
ParseDeclaration(TokenRange tokens, out Token name, out TokenRange type, out TokenRange initializer, out bool constructorSyntax)338         void ParseDeclaration(TokenRange tokens,
339             out Token name,
340             out TokenRange type,
341             out TokenRange initializer,
342             out bool constructorSyntax)
343         {
344             initializer = null;
345             TokenRange beforeInitializer = tokens;
346             constructorSyntax = false;
347 
348             Token equals = AngleBracketParser.NotInsideAngleBrackets(tokens)
349                 .FirstOrDefault(t => t.Value == "=" && t.ParenDepth == tokens.First().ParenDepth);
350             if (equals != null)
351             {
352                 // type name = initializer;
353                 beforeInitializer = range(tokens.Begin,equals.Position);
354                 initializer = range(equals.Position + 1, tokens.End);
355             }
356             else
357             {
358                 Token paren = AngleBracketParser.NotInsideAngleBrackets(tokens)
359                     .FirstOrDefault(t => t.Value == "(");
360                 if (paren != null)
361                 {
362                     // type name(initializer);
363                     constructorSyntax = true;
364                     beforeInitializer = range(tokens.Begin, paren.Position);
365                     initializer =
366                         range(paren.Position + 1, tokens.End)
367                             .TakeWhile(t => t.ParenDepth > paren.ParenDepth);
368                 }
369             }
370             name = beforeInitializer.Last(NonWhitespace);
371             if (beforeInitializer.Begin == name.Position)
372                 throw new Error(beforeInitializer.First().SourceLine, "Declaration has no type.");
373             type = range(beforeInitializer.Begin, name.Position);
374         }
375 
ParseVarDeclaration(TokenRange tokens)376         VarDeclaration ParseVarDeclaration(TokenRange tokens)
377         {
378             Token name;
379             TokenRange type, initializer;
380             bool constructorSyntax;
381             ParseDeclaration( tokens, out name, out type, out initializer, out constructorSyntax );
382             return new VarDeclaration
383             {
384                 name = name.Value,
385                 type = str(NormalizeWhitespace(type)),
386                 initializer = initializer == null ? "" : str(NormalizeWhitespace(initializer)),
387                 initializerConstructorSyntax = constructorSyntax
388             };
389         }
390 
391         readonly Func<Token, bool> Whitespace = (Token t) => t.IsWhitespace;
392         readonly Func<Token, bool> NonWhitespace = (Token t) => !t.IsWhitespace;
393 
ParseDescrHeading(Descr descr, TokenRange toks)394         void ParseDescrHeading(Descr descr, TokenRange toks)
395         {
396             toks.First(NonWhitespace).Assert("non-struct DESCR!", t => t.Value == "struct");
397             toks = toks.SkipWhile(Whitespace).Skip(1).SkipWhile(Whitespace);
398 
399             var colon = toks.FirstOrDefault(t => t.Value == ":");
400             if (colon != null)
401             {
402                 descr.superClassList = str(range(colon.Position + 1, toks.End)).Trim();
403                 toks = range(toks.Begin, colon.Position);
404             }
405             descr.name = str(toks).Trim();
406         }
407 
ParseTestCaseHeading(Actor actor, TokenRange toks)408         void ParseTestCaseHeading(Actor actor, TokenRange toks)
409         {
410             actor.isStatic = true;
411 
412             // The parameter(s) to the TEST_CASE macro are opaque to the actor compiler
413             TokenRange paramRange = toks.Last(NonWhitespace)
414                 .Assert("Unexpected tokens after test case parameter list.",
415                     t => t.Value == ")" && t.ParenDepth == toks.First().ParenDepth)
416                 .GetMatchingRangeIn(toks);
417             actor.testCaseParameters = str(paramRange);
418 
419             actor.name = "flowTestCase" + toks.First().SourceLine;
420             actor.parameters = new VarDeclaration[] { };
421             actor.returnType = "Void";
422         }
423 
ParseActorHeading(Actor actor, TokenRange toks)424         void ParseActorHeading(Actor actor, TokenRange toks)
425         {
426             var template = toks.First(NonWhitespace);
427             if (template.Value == "template")
428             {
429                 var templateParams = range(template.Position+1, toks.End)
430                     .First(NonWhitespace)
431                     .Assert("Invalid template declaration", t=>t.Value=="<")
432                     .GetMatchingRangeIn(toks);
433 
434                 actor.templateFormals = SplitParameterList(templateParams, ",")
435                     .Select(p => ParseVarDeclaration(p))    //< SOMEDAY: ?
436                     .ToArray();
437 
438                 toks = range(templateParams.End + 1, toks.End);
439             }
440 
441             var staticKeyword = toks.First(NonWhitespace);
442             if (staticKeyword.Value == "static")
443             {
444                 actor.isStatic = true;
445                 toks = range(staticKeyword.Position + 1, toks.End);
446             }
447 
448             var uncancellableKeyword = toks.First(NonWhitespace);
449             if (uncancellableKeyword.Value == "UNCANCELLABLE")
450             {
451                 actor.isUncancellable = true;
452                 toks = range(uncancellableKeyword.Position + 1, toks.End);
453             }
454 
455             // Find the parameter list
456             TokenRange paramRange = toks.Last(NonWhitespace)
457                 .Assert("Unexpected tokens after actor parameter list.",
458                         t => t.Value == ")" && t.ParenDepth == toks.First().ParenDepth)
459                 .GetMatchingRangeIn(toks);
460             actor.parameters = SplitParameterList(paramRange, ",")
461                 .Select(p => ParseVarDeclaration(p))
462                 .ToArray();
463 
464             var name = range(toks.Begin,paramRange.Begin-1).Last(NonWhitespace);
465             actor.name = name.Value;
466 
467             // SOMEDAY: refactor?
468             var returnType = range(toks.First().Position + 1, name.Position).SkipWhile(Whitespace);
469             var retToken = returnType.First();
470             if (retToken.Value == "Future")
471             {
472                 var ofType = returnType.Skip(1).First(NonWhitespace).Assert("Expected <", tok => tok.Value == "<").GetMatchingRangeIn(returnType);
473                 actor.returnType = str(NormalizeWhitespace(ofType));
474                 toks = range(ofType.End + 1, returnType.End);
475             }
476             else if (retToken.Value == "void"/* && !returnType.Skip(1).Any(NonWhitespace)*/)
477             {
478                 actor.returnType = null;
479                 toks = returnType.Skip(1);
480             }
481             else
482                 throw new Error(actor.SourceLine, "Actor apparently does not return Future<T>");
483 
484             toks = toks.SkipWhile(Whitespace);
485             if (!toks.IsEmpty)
486             {
487                 if (toks.Last().Value == "::")
488                 {
489                     actor.nameSpace = str(range(toks.Begin, toks.End - 1));
490                 }
491                 else
492                 {
493                     Console.WriteLine("Tokens: '{0}' {1} '{2}'", str(toks), toks.Count(), toks.Last().Value);
494                     throw new Error(actor.SourceLine, "Unrecognized tokens preceding parameter list in actor declaration");
495                 }
496             }
497         }
498 
ParseLoopStatement(TokenRange toks)499         LoopStatement ParseLoopStatement(TokenRange toks)
500         {
501             return new LoopStatement {
502                 body = ParseCompoundStatement( toks.Consume("loop") )
503             };
504         }
505 
ParseChooseStatement(TokenRange toks)506         ChooseStatement ParseChooseStatement(TokenRange toks)
507         {
508             return new ChooseStatement
509             {
510                 body = ParseCompoundStatement(toks.Consume("choose"))
511             };
512         }
513 
ParseWhenStatement(TokenRange toks)514         WhenStatement ParseWhenStatement(TokenRange toks)
515         {
516             var expr = toks.Consume("when")
517                            .SkipWhile(Whitespace)
518                            .First()
519                            .Assert("Expected (", t => t.Value == "(")
520                            .GetMatchingRangeIn(toks)
521                            .SkipWhile(Whitespace);
522 
523             return new WhenStatement {
524                 wait = ParseWaitStatement(expr),
525                 body = ParseCompoundStatement(range(expr.End+1, toks.End))
526             };
527         }
528 
ParseStateDeclaration(TokenRange toks)529         StateDeclarationStatement ParseStateDeclaration(TokenRange toks)
530         {
531             toks = toks.Consume("state").RevSkipWhile(t => t.Value == ";");
532             return new StateDeclarationStatement {
533                 decl = ParseVarDeclaration(toks)
534             };
535         }
536 
ParseReturnStatement(TokenRange toks)537         ReturnStatement ParseReturnStatement(TokenRange toks)
538         {
539             toks = toks.Consume("return").RevSkipWhile(t => t.Value == ";");
540             return new ReturnStatement
541             {
542                 expression = str(NormalizeWhitespace(toks))
543             };
544         }
545 
ParseThrowStatement(TokenRange toks)546         ThrowStatement ParseThrowStatement(TokenRange toks)
547         {
548             toks = toks.Consume("throw").RevSkipWhile(t => t.Value == ";");
549             return new ThrowStatement
550             {
551                 expression = str(NormalizeWhitespace(toks))
552             };
553         }
554 
ParseWaitStatement(TokenRange toks)555         WaitStatement ParseWaitStatement(TokenRange toks)
556         {
557             WaitStatement ws = new WaitStatement();
558             ws.FirstSourceLine = toks.First().SourceLine;
559             if (toks.First().Value == "state")
560             {
561                 ws.resultIsState = true;
562                 toks = toks.Consume("state");
563             }
564             TokenRange initializer;
565             if (toks.First().Value == "wait" || toks.First().Value == "waitNext")
566             {
567                 initializer = toks.RevSkipWhile(t=>t.Value==";");
568                 ws.result = new VarDeclaration {
569                         name = "_",
570                         type = "Void",
571                         initializer = "",
572                         initializerConstructorSyntax = false
573                 };
574             } else {
575                 Token name;
576                 TokenRange type;
577                 bool constructorSyntax;
578                 ParseDeclaration( toks.RevSkipWhile(t=>t.Value==";"), out name, out type, out initializer, out constructorSyntax );
579 
580                 string typestring = str(NormalizeWhitespace(type));
581                 if (typestring == "Void") {
582                     throw new Error(ws.FirstSourceLine, "Assigning the result of a Void wait is not allowed.  Just use a standalone wait statement.");
583                 }
584 
585                 ws.result = new VarDeclaration
586                 {
587                     name = name.Value,
588                     type = str(NormalizeWhitespace(type)),
589                     initializer = "",
590                     initializerConstructorSyntax = false
591                 };
592             }
593 
594             if (initializer == null) throw new Error(ws.FirstSourceLine, "Wait statement must be a declaration or standalone statement");
595 
596             var waitParams = initializer
597                 .SkipWhile(Whitespace).Consume("Statement contains a wait, but is not a valid wait statement or a supported compound statement.1",
598                         t=> {
599                             if (t.Value=="wait") return true;
600                             if (t.Value=="waitNext") { ws.isWaitNext = true; return true; }
601                             return false;
602                         })
603                 .SkipWhile(Whitespace).First().Assert("Expected (", t => t.Value == "(")
604                 .GetMatchingRangeIn(initializer);
605             if (!range(waitParams.End, initializer.End).Consume(")").All(Whitespace)) {
606                 throw new Error(toks.First().SourceLine, "Statement contains a wait, but is not a valid wait statement or a supported compound statement.2");
607             }
608 
609             ws.futureExpression = str(NormalizeWhitespace(waitParams));
610             return ws;
611         }
612 
ParseWhileStatement(TokenRange toks)613         WhileStatement ParseWhileStatement(TokenRange toks)
614         {
615             var expr = toks.Consume("while")
616                            .First(NonWhitespace)
617                            .Assert("Expected (", t => t.Value == "(")
618                            .GetMatchingRangeIn(toks);
619             return new WhileStatement
620             {
621                 expression = str(NormalizeWhitespace(expr)),
622                 body = ParseCompoundStatement(range(expr.End + 1, toks.End))
623             };
624         }
625 
ParseForStatement(TokenRange toks)626         Statement ParseForStatement(TokenRange toks)
627         {
628             var head =
629                 toks.Consume("for")
630                     .First(NonWhitespace)
631                     .Assert("Expected (", t => t.Value == "(")
632                     .GetMatchingRangeIn(toks);
633 
634             Token[] delim =
635                 head.Where(
636                     t => t.ParenDepth == head.First().ParenDepth &&
637                          t.BraceDepth == head.First().BraceDepth &&
638                          t.Value==";"
639                     ).ToArray();
640             if (delim.Length == 2)
641             {
642                 var init = range(head.Begin, delim[0].Position);
643                 var cond = range(delim[0].Position + 1, delim[1].Position);
644                 var next = range(delim[1].Position + 1, head.End);
645                 var body = range(head.End + 1, toks.End);
646 
647                 return new ForStatement
648                 {
649                     initExpression = str(NormalizeWhitespace(init)),
650                     condExpression = str(NormalizeWhitespace(cond)),
651                     nextExpression = str(NormalizeWhitespace(next)),
652                     body = ParseCompoundStatement(body)
653                 };
654             }
655 
656             delim =
657                 head.Where(
658                     t => t.ParenDepth == head.First().ParenDepth &&
659                             t.BraceDepth == head.First().BraceDepth &&
660                             t.Value == ":"
661                     ).ToArray();
662             if (delim.Length != 1)
663             {
664                 throw new Error(head.First().SourceLine, "for statement must be 3-arg style or c++11 2-arg style");
665             }
666 
667             return new RangeForStatement
668             {
669                 // The container over which to iterate
670                 rangeExpression = str(NormalizeWhitespace(range(delim[0].Position + 1, head.End).SkipWhile(Whitespace))),
671                 // Type and name of the variable assigned in each iteration
672                 rangeDecl = str(NormalizeWhitespace(range(head.Begin, delim[0].Position - 1).SkipWhile(Whitespace))),
673                 // The body of the for loop
674                 body = ParseCompoundStatement(range(head.End + 1, toks.End))
675             };
676         }
677 
ParseIfStatement(TokenRange toks)678         Statement ParseIfStatement(TokenRange toks)
679         {
680             var expr = toks.Consume("if")
681                            .First(NonWhitespace)
682                            .Assert("Expected (", t => t.Value == "(")
683                            .GetMatchingRangeIn(toks);
684             return new IfStatement {
685                 expression = str(NormalizeWhitespace(expr)),
686                 ifBody = ParseCompoundStatement(range(expr.End+1, toks.End))
687                 // elseBody will be filled in later if necessary by ParseElseStatement
688             };
689         }
ParseElseStatement(TokenRange toks, Statement prevStatement)690         void ParseElseStatement(TokenRange toks, Statement prevStatement)
691         {
692             var ifStatement = prevStatement as IfStatement;
693             while (ifStatement != null && ifStatement.elseBody != null)
694                 ifStatement = ifStatement.elseBody as IfStatement;
695             if (ifStatement == null)
696                 throw new Error(toks.First().SourceLine, "else without matching if");
697             ifStatement.elseBody = ParseCompoundStatement(toks.Consume("else"));
698         }
699 
ParseTryStatement(TokenRange toks)700         Statement ParseTryStatement(TokenRange toks)
701         {
702             return new TryStatement
703             {
704                 tryBody = ParseCompoundStatement(toks.Consume("try")),
705                 catches = new List<TryStatement.Catch>()    // will be filled in later by ParseCatchStatement
706             };
707         }
ParseCatchStatement(TokenRange toks, Statement prevStatement)708         void ParseCatchStatement(TokenRange toks, Statement prevStatement)
709         {
710             var tryStatement = prevStatement as TryStatement;
711             if (tryStatement == null)
712                 throw new Error(toks.First().SourceLine, "catch without matching try");
713             var expr = toks.Consume("catch")
714                 .First(NonWhitespace)
715                 .Assert("Expected (", t => t.Value == "(")
716                 .GetMatchingRangeIn(toks);
717             tryStatement.catches.Add(
718                 new TryStatement.Catch
719                 {
720                     expression = str(NormalizeWhitespace(expr)),
721                     body = ParseCompoundStatement(range(expr.End + 1, toks.End)),
722                     FirstSourceLine = expr.First().SourceLine
723                 });
724         }
725 
726         static readonly HashSet<string> IllegalKeywords = new HashSet<string> { "goto", "do", "finally", "__if_exists", "__if_not_exists" };
727 
ParseDeclaration(TokenRange toks, List<Declaration> declarations)728         void ParseDeclaration(TokenRange toks, List<Declaration> declarations)
729         {
730             Declaration dec = new Declaration();
731 
732             Token delim = toks.First(t => t.Value == ";");
733             var nameRange = range(toks.Begin, delim.Position).RevSkipWhile(Whitespace).RevTakeWhile(NonWhitespace);
734             var typeRange = range(toks.Begin, nameRange.Begin);
735             var commentRange = range(delim.Position + 1, toks.End);
736 
737             dec.name = str(nameRange).Trim();
738             dec.type = str(typeRange).Trim();
739             dec.comment = str(commentRange).Trim().TrimStart('/');
740 
741             declarations.Add(dec);
742         }
743 
ParseStatement(TokenRange toks, List<Statement> statements)744         void ParseStatement(TokenRange toks, List<Statement> statements)
745         {
746             toks = toks.SkipWhile(Whitespace);
747 
748             Action<Statement> Add = stmt =>
749             {
750                 stmt.FirstSourceLine = toks.First().SourceLine;
751                 statements.Add(stmt);
752             };
753 
754             switch (toks.First().Value)
755             {
756                 case "loop": Add(ParseLoopStatement(toks)); break;
757                 case "while": Add(ParseWhileStatement(toks)); break;
758                 case "for": Add(ParseForStatement(toks)); break;
759                 case "break": Add(new BreakStatement()); break;
760                 case "continue": Add(new ContinueStatement()); break;
761                 case "return": Add(ParseReturnStatement(toks)); break;
762                 case "{": Add(ParseCompoundStatement(toks)); break;
763                 case "if": Add(ParseIfStatement(toks)); break;
764                 case "else": ParseElseStatement(toks, statements[statements.Count - 1]); break;
765                 case "choose": Add(ParseChooseStatement(toks)); break;
766                 case "when": Add(ParseWhenStatement(toks)); break;
767                 case "try": Add(ParseTryStatement(toks)); break;
768                 case "catch": ParseCatchStatement(toks, statements[statements.Count - 1]); break;
769                 case "throw": Add(ParseThrowStatement(toks)); break;
770                 default:
771                     if (IllegalKeywords.Contains(toks.First().Value))
772                         throw new Error(toks.First().SourceLine, "Statement '{0}' not supported in actors.", toks.First().Value);
773                     if (toks.Any(t => t.Value == "wait" || t.Value == "waitNext"))
774                         Add(ParseWaitStatement(toks));
775                     else if (toks.First().Value == "state")
776                         Add(ParseStateDeclaration(toks));
777                     else if (toks.First().Value == "switch" && toks.Any(t => t.Value == "return"))
778                         throw new Error(toks.First().SourceLine, "Unsupported compound statement containing return.");
779                     else if (toks.RevSkipWhile(t => t.Value == ";").Any(NonWhitespace))
780                         Add(new PlainOldCodeStatement
781                         {
782                             code = str(NormalizeWhitespace(toks.RevSkipWhile(t => t.Value == ";"))) + ";"
783                         });
784                     break;
785             };
786         }
787 
ParseCompoundStatement(TokenRange toks)788         Statement ParseCompoundStatement(TokenRange toks)
789         {
790             var first = toks.First(NonWhitespace);
791             if (first.Value == "{") {
792                 var inBraces = first.GetMatchingRangeIn(toks);
793                 if (!range(inBraces.End, toks.End).Consume("}").All(Whitespace))
794                     throw new Error(inBraces.Last().SourceLine, "Unexpected tokens after compound statement");
795                 return ParseCodeBlock(inBraces);
796             } else {
797                 List<Statement> statements = new List<Statement>();
798                 ParseStatement( toks.Skip(1), statements );
799                 return statements[0];
800             }
801         }
802 
ParseDescrCodeBlock(TokenRange toks)803         List<Declaration> ParseDescrCodeBlock(TokenRange toks)
804         {
805             List<Declaration> declarations = new List<Declaration>();
806             while (true)
807             {
808                 Token delim = toks.FirstOrDefault(t => t.Value == ";");
809                 if (delim == null)
810                     break;
811 
812                 int pos = delim.Position + 1;
813                 var potentialComment = range(pos, toks.End).SkipWhile(t => t.Value == "\t" || t.Value == " ");
814                 if (!potentialComment.IsEmpty && potentialComment.First().Value.StartsWith("//"))
815                 {
816                     pos = potentialComment.First().Position + 1;
817                 }
818 
819                 ParseDeclaration(range(toks.Begin, pos), declarations);
820 
821                 toks = range(pos, toks.End);
822             }
823             if (!toks.All(Whitespace))
824                 throw new Error(toks.First(NonWhitespace).SourceLine, "Trailing unterminated statement in code block");
825             return declarations;
826         }
827 
ParseCodeBlock(TokenRange toks)828         CodeBlock ParseCodeBlock(TokenRange toks)
829         {
830             List<Statement> statements = new List<Statement>();
831             while (true)
832             {
833                 Token delim = toks
834                     .FirstOrDefault(
835                         t=> t.ParenDepth == toks.First().ParenDepth &&
836                             t.BraceDepth == toks.First().BraceDepth &&
837                             (t.Value==";" || t.Value == "}")
838                             );
839                 if (delim == null)
840                     break;
841                 ParseStatement(range(toks.Begin, delim.Position + 1), statements);
842                 toks = range(delim.Position + 1, toks.End);
843             }
844             if (!toks.All(Whitespace))
845                 throw new Error(toks.First(NonWhitespace).SourceLine, "Trailing unterminated statement in code block");
846             return new CodeBlock { statements = statements.ToArray() };
847         }
848 
range(int beginPos, int endPos)849         TokenRange range(int beginPos, int endPos)
850         {
851             return new TokenRange(tokens, beginPos, endPos);
852         }
853 
ParseDescr(int pos, out int end)854         Descr ParseDescr(int pos, out int end)
855         {
856             var descr = new Descr();
857             var toks = range(pos + 1, tokens.Length);
858             var heading = toks.TakeWhile(t => t.Value != "{");
859             var body = range(heading.End + 1, tokens.Length)
860                 .TakeWhile(t => t.BraceDepth > toks.First().BraceDepth || t.Value == ";" ); //assumes no whitespace between the last "}" and the ";"
861 
862             ParseDescrHeading(descr, heading);
863             descr.body = ParseDescrCodeBlock(body);
864 
865             end = body.End + 1;
866             return descr;
867         }
868 
ParseActor( int pos, out int end )869         Actor ParseActor( int pos, out int end ) {
870             var actor = new Actor();
871             var head_token = tokens[pos];
872             actor.SourceLine = head_token.SourceLine;
873 
874             var toks = range(pos+1, tokens.Length);
875             var heading = toks.TakeWhile(t => t.Value != "{");
876             var toSemicolon = toks.TakeWhile(t => t.Value != ";");
877             actor.isForwardDeclaration = toSemicolon.Length < heading.Length;
878             if (actor.isForwardDeclaration) {
879                 heading = toSemicolon;
880                 if (head_token.Value == "ACTOR") {
881                     ParseActorHeading(actor, heading);
882                 } else {
883                     head_token.Assert("ACTOR expected!", t => false);
884                 }
885                 end = heading.End + 1;
886             } else {
887                 var body = range(heading.End+1, tokens.Length)
888                     .TakeWhile(t => t.BraceDepth > toks.First().BraceDepth);
889 
890                 if (head_token.Value == "ACTOR")
891                 {
892                     ParseActorHeading(actor, heading);
893                 }
894                 else if (head_token.Value == "TEST_CASE") {
895                     ParseTestCaseHeading(actor, heading);
896                     actor.isTestCase = true;
897                 }
898                 else
899                     head_token.Assert("ACTOR or TEST_CASE expected!", t => false);
900 
901                 actor.body = ParseCodeBlock(body);
902 
903                 if (!actor.body.containsWait())
904                     this.errorMessagePolicy.HandleActorWithoutWait(sourceFile, actor);
905 
906                 end = body.End + 1;
907             }
908             return actor;
909         }
910 
str(IEnumerable<Token> tokens)911         string str(IEnumerable<Token> tokens)
912         {
913             return string.Join("", tokens.Select(x => x.Value).ToArray());
914         }
str(int begin, int end)915         string str(int begin, int end)
916         {
917             return str(range(begin,end));
918         }
919 
CountParens()920         void CountParens()
921         {
922             int BraceDepth = 0, ParenDepth = 0, LineCount = 1;
923             Token lastParen = null, lastBrace = null;
924             for (int i = 0; i < tokens.Length; i++)
925             {
926                 switch (tokens[i].Value)
927                 {
928                     case "}": BraceDepth--; break;
929                     case ")": ParenDepth--; break;
930                     case "\r\n": LineCount++; break;
931                     case "\n": LineCount++; break;
932                 }
933                 if (BraceDepth < 0) throw new Error(LineCount, "Mismatched braces");
934                 if (ParenDepth < 0) throw new Error(LineCount, "Mismatched parenthesis");
935                 tokens[i].Position = i;
936                 tokens[i].SourceLine = LineCount;
937                 tokens[i].BraceDepth = BraceDepth;
938                 tokens[i].ParenDepth = ParenDepth;
939                 if (tokens[i].Value.StartsWith("/*")) LineCount += tokens[i].Value.Count(c=>c=='\n');
940                 switch (tokens[i].Value)
941                 {
942                     case "{": BraceDepth++; if (BraceDepth==1) lastBrace = tokens[i]; break;
943                     case "(": ParenDepth++; if (ParenDepth==1) lastParen = tokens[i]; break;
944                 }
945             }
946             if (BraceDepth != 0) throw new Error(lastBrace.SourceLine, "Unmatched brace");
947             if (ParenDepth != 0) throw new Error(lastParen.SourceLine, "Unmatched parenthesis");
948         }
949 
showTokens()950         void showTokens()
951         {
952             foreach (var t in tokens)
953             {
954                 if (t.Value == "\r\n")
955                     Console.WriteLine();
956                 else if (t.Value.Length == 1)
957                     Console.Write(t.Value);
958                 else
959                     Console.Write("|{0}|", t.Value);
960             }
961         }
962 
963         readonly Regex[] tokenExpressions = (new string[] {
964             @"\{",
965             @"\}",
966             @"\(",
967             @"\)",
968             @"//[^\n]*",
969             @"/[*]([*][^/]|[^*])*[*]/",
970             @"'(\\.|[^\'\n])*'",    //< SOMEDAY: Not fully restrictive
971             @"""(\\.|[^\""\n])*""",
972             @"[a-zA-Z_][a-zA-Z_0-9]*",
973             @"\r\n",
974             @"\n",
975             @"::",
976             @"."
977         }).Select( x=>new Regex(@"\G"+x, RegexOptions.Singleline) ).ToArray();
978 
979         IEnumerable<string> Tokenize(string text)
980         {
981             int pos = 0;
982             while (pos < text.Length)
983             {
984                 bool ok = false;
985                 foreach (var re in tokenExpressions)
986                 {
987                     var m = re.Match(text, pos);
988                     if (m.Success)
989                     {
990                         yield return m.Value;
991                         pos += m.Value.Length;
992                         ok = true;
993                         break;
994                     }
995                 }
996                 if (!ok)
997                     throw new Exception( String.Format("Can't tokenize! {0}", pos));
998             }
999         }
1000     }
1001 }
1002