public Token ConsumeToken(StringWalker w)
        {
            CodePos pos     = w.Position;
            string  content = w.ConsumeWhile(char.IsLetterOrDigit);

            return(new Token(pos, TokenType.Keyword, content));
        }
        public Token ConsumeToken(StringWalker w)
        {
            CodePos pos         = w.Position;
            string  sign        = ConsumeSign();
            string  wholePart   = ConsumeWholePart();
            string  decimalPart = ConsumeDecimalPart();
            string  content     = $"{sign}{wholePart}{decimalPart}";

            return(new Token(pos, TokenType.Number, content));

            string ConsumeSign() => w.Peek() == '-'
        ? w.Consume(1)
        : "";

            string ConsumeWholePart() => w.ConsumeWhile(char.IsDigit);

            string ConsumeDecimalPart()
            {
                if (w.IsEmpty() || w.Peek() != '.')
                {
                    return("");
                }

                string decimalPoint = w.Consume(1);
                string digits       = w.ConsumeWhile(char.IsDigit);

                // If there is nothing after the decimal point, that's an error
                if (digits.Length == 0)
                {
                    throw new CompileErrorException(w.Position, "There must be digits after the decimal point");
                }

                // There can only be one decimal point in the number.  If there's
                // another, that's an error.
                if (!w.IsEmpty() && w.Peek() == '.')
                {
                    throw new CompileErrorException(w.Position, "A number may only have one decimal point");
                }

                return($".{digits}");
            }
        }