public Token NextToken()
        {
            // if we had a token waiting, just return it
            Token ans = null;

            // otherwise... process some text.
            skipWS();
            if (!OutOfChars)
            {
                char c = nextChar();
                switch (c)
                {
                    case '#':   // comment
                        getToEOL();
                        ans = new Token(TokenType.TOK_EOL, null);
                        break;
                    case '\n':  // end of line/statement
                        ans = new Token(TokenType.TOK_EOL, null);
                        break;
                    case ':':   // start of free-form string arg
                        ans = new Token(TokenType.TOK_STRING, getToEOL());
                        break;
                    default:    // must be a keyword or identifier...
                        ans = processIdentifier(c);
                        break;
                }
            }
            return ans;
        }
        private static void parseIdentifier(Tokenizer t, Diagram d, Token first)
        {
            // OK, we got an identifier... let's make sure the Diagram knows about it...
            Actor left = d.MaybeNewActor(first.data);

            //  Valid continuations are:
            //   EOL          ... just define the actor so we have a good order
            //   STRING EOL   ... define the actor with a display name
            //   TO ID [DASHED] [STRING] EOL  ... define an arrow
            Token second = t.NextToken();
            switch (second.type)
            {
                case TokenType.TOK_EOL:  // no problem here...
                    break;
                case TokenType.TOK_STRING: // giving a display name...
                    left.DisplayName = second.data;
                    break;
                case TokenType.TOK_TO:  // defining an arrow...
                    parseArrow(t, d, left);
                    break;
                default:   // something went wrong here...
                    d.HasErrors = true;
                    break;
            }
        }
        // process characters and figure out if it's a keyword or not.
        private Token processIdentifier(char first)
        {
            Token ans = null;
            var sb = new StringBuilder();
            sb.Append(first);

            char c = nextChar();
            while (c != ':' && !Char.IsWhiteSpace(c))
            {
                sb.Append(c);
                c = nextChar();
            }
            ungetChar(); // push back whatever char we stopped on.

            // now we have an identifier... we need to see if it's a keyword...
            string ident = sb.ToString();
            switch (ident.ToUpper())
            {
                case "TO":
                    ans = new Token(TokenType.TOK_TO, ident);
                    break;
                case "SELF":
                    ans = new Token(TokenType.TOK_SELF, ident);
                    break;
                case "DASHED":
                    ans = new Token(TokenType.TOK_DASHED, ident);
                    break;
                case "TITLE":
                    ans = new Token(TokenType.TOK_TITLE, ident);
                    break;
                case "NOTE":
                    ans = new Token(TokenType.TOK_NOTE, ident);
                    break;
                default:
                    ans = new Token(TokenType.TOK_IDENTIFIER, ident);
                    break;
            }
            return ans;
        }