Ver Fonte

Improving error messages

Sam Jaffe há 7 anos atrás
pai
commit
4a1c2cac92
3 ficheiros alterados com 60 adições e 29 exclusões
  1. 8 2
      include/exception.h
  2. 49 24
      src/die.cxx
  3. 3 3
      src/main.cxx

+ 8 - 2
include/exception.h

@@ -11,7 +11,13 @@
 #include <stdexcept>
 
 namespace dice {
-  struct unexpected_token : public std::runtime_error {
-    unexpected_token(long long position);
+  class unexpected_token : public std::runtime_error {
+  public:
+    unexpected_token(std::string const & reason, long long position);
+    
+    std::string pointer(long long backup_length = -1) const;
+    
+  private:
+    long long position;
   };
 }

+ 49 - 24
src/die.cxx

@@ -9,8 +9,7 @@
 #include "die.h"
 #include "exception.h"
 
-#include <boost/variant/get.hpp>
-
+#include <cmath>
 #include <iostream>
 
 static void advance_over_whitespace(std::istream & in, char const * also = "") {
@@ -21,7 +20,7 @@ static void advance_over_whitespace(std::istream & in, char const * also = "") {
 static std::string carrot(long long pos) {
   if (pos == -1) return "<END>";
   std::string out;
-  out.resize(pos, '~');
+  out.resize(std::max(pos, 1ll), '~');
   out.back() = '^';
   return out;
 }
@@ -44,10 +43,16 @@ namespace dice {
 }
 
 namespace dice {
-  unexpected_token::unexpected_token(long long pos) :
-    std::runtime_error(carrot(pos)) {
+  unexpected_token::unexpected_token(std::string const & reason,
+                                     long long pos) :
+    std::runtime_error(reason),
+    position(pos) {
     
   }
+  
+  std::string unexpected_token::pointer(long long backup_length) const {
+    return carrot(position == -1 ? backup_length : position);
+  }
     
   std::ostream & operator<<(std::ostream & out, dice const & d) {
     if (d.num != 1) out << d.num << '{';
@@ -60,59 +65,79 @@ namespace dice {
     if (d.num != 1) out << '}';
     return out;
   }
+}
+namespace dice { namespace {
+  struct parser {
+    void parse(sign s);
+    void parse_dN(sign s, int value);
+    void parse_const(sign s, int value);
+    
+    std::istream & in;
+    dice & d;
+  };
   
-  static void parse(std::istream & in, dice & d, sign s);
-  
-  static void parse_dN(std::istream & in, dice & d, die di) {
+  void parser::parse_dN(sign s, int value) {
     advance_over_whitespace(in, "dD");
-    d.of.push_back(di);
+    d.of.push_back({s, value, 0});
+    if (!isnumber(in.peek())) {
+      throw unexpected_token("Expected a number of sides", in.tellg());
+    }
     in >> d.of.back().sides;
-    parse(in, d, ZERO);
+    parse(ZERO);
+  }
+  
+  void parser::parse_const(sign s, int value) {
+    if (value) {
+      d.modifier.push_back({s, std::abs(value)});
+    }
   }
   
-  static void parse(std::istream & in, dice & d, sign s) {
+  void parser::parse(sign s) {
     // Allow implicit 1
     advance_over_whitespace(in);
     int value = 0;
     if (isnumber(in.peek())) { in >> value; }
+    else if (in.peek() == EOF && s != ZERO) {
+      throw unexpected_token("Unexpected EOF while parsing", -1);
+    }
     advance_over_whitespace(in);
     switch (in.peek()) {
       case 'd': case 'D':
-        return parse_dN(in, d, {s, value, 0});
+        return parse_dN(s, value);
       case '+': case '-':
-        if (value) {
-          d.modifier.push_back({s, std::abs(value)});
-        }
-        s = (in.get() == '+') ? PLUS : MINUS;
-        parse(in, d, s);
-        break;
-      case EOF:
+        parse_const(s, value);
+        parse((in.get() == '+') ? PLUS : MINUS);
         break;
       default:
-        d.modifier.push_back({s, std::abs(value)});
+        parse_const(s, value);
         break;
     }
   }
+}}
 
+namespace dice {
   std::istream & operator>>(std::istream & in, dice & d) {
     int value{1};
     advance_over_whitespace(in);
     if (isnumber(in.peek())) { in >> value; }
     switch (in.peek()) {
       case 'd': case 'D':
-        parse(in, d, ZERO);
+        parser{in, d}.parse(ZERO);
         d.of.front().num = value;
         break;
       case '{':
         in.get();
         d.num = value;
-        parse(in, d, ZERO);
+        parser{in, d}.parse(ZERO);
         if (in.get() != '}') {
-          throw unexpected_token(in.tellg());
+          throw unexpected_token("Expected closing '}' in repeated roll",
+                                 in.tellg());
         }
         break;
+      case EOF:
+        throw unexpected_token("No dice in expression", in.tellg());
       default:
-        throw unexpected_token(in.tellg());
+        throw unexpected_token("Unexpected token", in.tellg());
     }
    return in;
   }

+ 3 - 3
src/main.cxx

@@ -14,14 +14,14 @@
 #include <sstream>
 
 void eval(std::string const & str) {
-  std::stringstream ss(str + "$");
   try {
+    std::stringstream ss(str);
     dice::dice d;
     ss >> d;
     dice::roll(d);
   } catch (dice::unexpected_token const & ut) {
-    std::cerr << "Error in roll: '" << str << "'\n";
-    std::cerr << "                " << ut.what() << std::endl;
+    std::cerr << "Error in roll: '" << str << "': " << ut.what() << "\n";
+    std::cerr << "                " << ut.pointer(str.size()+1) << std::endl;
   }
 }