Przeglądaj źródła

refactor: improve printing

Sam Jaffe 1 rok temu
rodzic
commit
70b042d534

+ 23 - 49
curses_dice/main.cxx

@@ -14,6 +14,7 @@
 
 #include "dice-roll/die.h"
 #include "dice-roll/exception.h"
+#include "dice-roll/io.h"
 #include "dice-roll/roll.h"
 
 using namespace curses;
@@ -35,61 +36,34 @@ void print(curses::Window & window, int roll, int sides) {
     window.printf("%d", roll);
   }
 }
-
-void print(curses::Window & window, dice::dice_roll const & r) {
-  if (r.dc.comp != dice::difficulty_class::test::None) {
-    if (auto scope = window.with(color(int(r)))) {
-      window << (int(r) ? "PASS" : "FAIL");
-    }
-    window.printf(" (");
-  } else {
-    window.printf("%d (", int(r));
-  }
-  for (dice::die_roll const & dr : r.sub_rolls) {
-    window << dr.sign;
-    switch (dr.rolled.size()) {
-    case 0:
-      window.printf("0");
-      break;
-    case 1:
-      print(window, dr.rolled[0], dr.sides);
-      break;
-    default:
-      window.printf("[ ");
-      print(window, dr.rolled[0], dr.sides);
-      for (int i = 1; i < dr.rolled.size(); ++i) {
-        window.printf(", ");
-        print(window, dr.rolled[i], dr.sides);
-      }
-      window.printf(" ]");
-    }
+namespace dice {
+curses::Window & operator<<(curses::Window & window, outcome o) {
+  if (auto scope = window.with(color(o == outcome::PASS))) {
+    window.printf("%s", o == outcome::PASS ? "PASS" : "FAIL");
   }
-  for (dice::mod const & m : r.modifiers) {
-    window << m.sign;
-    window.printf("%d", m.value);
-  }
-  window.printf(")\n");
+  return window;
 }
 
-void print(curses::Window & window, std::vector<dice::dice_roll> const & rs) {
-  if (rs.size() != 1) {
-    window.printf("\n");
-    for (int i = 0; i < rs.size(); ++i) {
-      window.printf("  Result/%d: ", i);
-      print(window, rs[i]);
-    }
-  } else {
-    print(window, rs[0]);
+curses::Window & operator<<(curses::Window & window, die_outcome const & r) {
+  if (auto scope = window.with(color(r.roll == r.sides, r.roll == 1))) {
+    window.printf("%d", r.roll);
   }
+  return window;
+}
+
+}
+
+void eval(curses::Window & window, std::string line) try {
+  using ::dice::operator<<;
+  dice::dice const d = dice::from_string(line);
+  std::vector<dice::dice_roll> const rs = dice::roller()(d);
+  window << std::make_pair(d, rs) << '\n';
+} catch (dice::unexpected_token const & ut) {
+  window.printf("Error in roll: '%s': %s\n                %s\n", line.c_str(),
+                ut.what(), ut.pointer(line.size() + 1).c_str());
 }
 
 int main(int, const char **) {
-  Cli("> ", WithColor).loop([](curses::Window & window, std::string line) {
-    if (line.empty()) return;
-    auto d = dice::from_string(line);
-    auto rs = dice::roller()(d);
-    window << "Result of '" << d << "': ";
-    print(window, rs);
-  });
+  Cli("> ", WithColor).loop(&eval);
   return 0;
 }

+ 0 - 10
dice-roll.xcodeproj/project.pbxproj

@@ -15,9 +15,7 @@
 		CD38F53721C89493007A732C /* exception.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F53621C89493007A732C /* exception.cxx */; };
 		CD38F53921C922E2007A732C /* roll_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F53821C922E2007A732C /* roll_test.cxx */; };
 		CD38F53B21C928B4007A732C /* exception_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F53A21C928B4007A732C /* exception_test.cxx */; };
-		CD38F54F21C945C2007A732C /* terminal_helper.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F54E21C945C2007A732C /* terminal_helper.cxx */; };
 		CD38F55721C9482A007A732C /* main.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F55621C9482A007A732C /* main.cxx */; };
-		CD38F56221C94872007A732C /* terminal_helper.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F54E21C945C2007A732C /* terminal_helper.cxx */; };
 		CD38F56521C94A00007A732C /* libdice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD38F50921C83912007A732C /* libdice.a */; };
 		CD5B3FD229D8ED5D0028CD41 /* libshared_random_generator.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD5B3F6B29D8EBD90028CD41 /* libshared_random_generator.a */; };
 		CDC7489B25312DD6008D9D1D /* GoogleMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDC7489425312DBF008D9D1D /* GoogleMock.framework */; };
@@ -26,7 +24,6 @@
 		CDEE790225B336EC00F195F9 /* io.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDEE790125B336EC00F195F9 /* io.cxx */; };
 		CDEE7A6B25B34DAA00F195F9 /* die_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDEE7A6A25B34DAA00F195F9 /* die_test.cxx */; };
 		CDEE7A7525B35EEC00F195F9 /* io_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDEE7A7425B35EEC00F195F9 /* io_test.cxx */; };
-		CDEECC322C50497B000C4392 /* terminal_helper.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F54E21C945C2007A732C /* terminal_helper.cxx */; };
 		CDEECC342C50497B000C4392 /* libdice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD38F50921C83912007A732C /* libdice.a */; };
 		CDEECC452C5049DD000C4392 /* main.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDEECC442C5049DD000C4392 /* main.cxx */; };
 		CDEECC4A2C50515B000C4392 /* libncurses.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CDEECC482C50515B000C4392 /* libncurses.tbd */; };
@@ -208,7 +205,6 @@
 		CD38F53621C89493007A732C /* exception.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = exception.cxx; sourceTree = "<group>"; };
 		CD38F53821C922E2007A732C /* roll_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = roll_test.cxx; sourceTree = "<group>"; };
 		CD38F53A21C928B4007A732C /* exception_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = exception_test.cxx; sourceTree = "<group>"; };
-		CD38F54E21C945C2007A732C /* terminal_helper.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = terminal_helper.cxx; sourceTree = "<group>"; };
 		CD38F55421C9482A007A732C /* stateful_dice */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = stateful_dice; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD38F55621C9482A007A732C /* main.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = main.cxx; sourceTree = "<group>"; };
 		CD5B3F4E29D8EBD10028CD41 /* scope_guard.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scope_guard.xcodeproj; path = external/scope_guard/scope_guard.xcodeproj; sourceTree = "<group>"; };
@@ -224,7 +220,6 @@
 		CDEE78ED25B3350B00F195F9 /* xcode_gtest_helper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xcode_gtest_helper.h; sourceTree = "<group>"; };
 		CDEE78F625B336B000F195F9 /* parser.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = parser.cxx; sourceTree = "<group>"; };
 		CDEE790125B336EC00F195F9 /* io.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = io.cxx; sourceTree = "<group>"; };
-		CDEE7A5925B3437D00F195F9 /* terminal_helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = terminal_helper.h; sourceTree = "<group>"; };
 		CDEE7A6A25B34DAA00F195F9 /* die_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = die_test.cxx; sourceTree = "<group>"; };
 		CDEE7A7425B35EEC00F195F9 /* io_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = io_test.cxx; sourceTree = "<group>"; };
 		CDEECC392C50497B000C4392 /* curses_dice */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = curses_dice; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -380,8 +375,6 @@
 		CDED6A2521B2F28A00AB91D0 /* src */ = {
 			isa = PBXGroup;
 			children = (
-				CDEE7A5925B3437D00F195F9 /* terminal_helper.h */,
-				CD38F54E21C945C2007A732C /* terminal_helper.cxx */,
 				CDED6A3021B2F2DC00AB91D0 /* die.cxx */,
 				CDEE78F625B336B000F195F9 /* parser.cxx */,
 				CDEE790125B336EC00F195F9 /* io.cxx */,
@@ -724,7 +717,6 @@
 			buildActionMask = 2147483647;
 			files = (
 				CD38F55721C9482A007A732C /* main.cxx in Sources */,
-				CD38F56221C94872007A732C /* terminal_helper.cxx in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -733,7 +725,6 @@
 			buildActionMask = 2147483647;
 			files = (
 				CDED6A2721B2F28A00AB91D0 /* main.cxx in Sources */,
-				CD38F54F21C945C2007A732C /* terminal_helper.cxx in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -741,7 +732,6 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				CDEECC322C50497B000C4392 /* terminal_helper.cxx in Sources */,
 				CDEECC452C5049DD000C4392 /* main.cxx in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

+ 1 - 1
external/ncurses-wrapper

@@ -1 +1 @@
-Subproject commit 55e5d61fdc2116da36798c1a87aec36ecb4d96c4
+Subproject commit fb7dd23ddffb192363b5fe37000e846bcb153e9d

+ 132 - 0
include/dice-roll/io.h

@@ -0,0 +1,132 @@
+//
+//  io.h
+//  dice-roll
+//
+//  Created by Sam Jaffe on 7/24/24.
+//  Copyright © 2024 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <iostream>
+
+#include "dice-roll/die.h"
+#include "dice-roll/roll.h"
+
+namespace dice {
+template <typename OStream> OStream & operator<<(OStream & os, outcome e) {
+  switch (e) {
+  case outcome::PASS:
+    return os << "PASS";
+  case outcome::FAIL:
+    return os << "FAIL";
+  }
+}
+
+template <typename OStream> OStream & operator<<(OStream & os, sign s) {
+  switch (s) {
+  case sign::PLUS:
+    return os << '+';
+  case sign::MINUS:
+    return os << '-';
+  case sign::ZERO:
+    return os;
+  }
+}
+
+template <typename OStream>
+OStream & operator<<(OStream & os, difficulty_class::test t) {
+  switch (t) {
+  case difficulty_class::test::None:
+    return os;
+  case difficulty_class::test::Less:
+    return os << '<';
+  case difficulty_class::test::LessOrEqual:
+    return os << '<' << '=';
+  case difficulty_class::test::Greater:
+    return os << '>';
+  case difficulty_class::test::GreaterOrEqual:
+    return os << '>' << '=';
+  }
+}
+
+template <typename OStream> OStream & operator<<(OStream & os, keep const & k) {
+  switch (k.method) {
+  case keep::Highest:
+    return os << 'k' << 'h' << k.amount;
+  case keep::Lowest:
+    return os << 'k' << 'l' << k.amount;
+  default:
+    return os;
+  }
+}
+
+template <typename OStream> OStream & operator<<(OStream & os, dice const & d) {
+  if (d.num != 1) os << d.num << '{';
+  for (die const & di : d.of) {
+    os << di.sgn << di.num << 'd' << di.sides << di.keep;
+  }
+  for (mod m : d.modifier) {
+    os << m.sign << m.value;
+  }
+  if (d.dc.comp != difficulty_class::test::None) {
+    os << d.dc.comp << d.dc.against;
+  }
+  if (d.num != 1) os << '}';
+  return os;
+}
+
+template <typename OStream>
+OStream & operator<<(OStream & os, die_outcome const & r) {
+  return os << r.roll;
+}
+
+template <typename OStream>
+OStream & operator<<(OStream & os, die_roll const & r) {
+  os << r.sign;
+  switch (r.rolled.size()) {
+  case 0:
+    // Prevent crashes if we somehow get a 0dM expression
+    return os << "0";
+  case 1:
+    // Don't bother with braces if there's only a single roll,
+    // the braces are for grouping purposes.
+    return os << r.rolled[0];
+  default:
+    os << "[ ";
+    os << r.rolled[0];
+    for (int i = 1; i < r.rolled.size(); ++i) {
+      os << ", " << r.rolled[i];
+    }
+    return os << " ]";
+  }
+}
+
+template <typename OStream>
+OStream & operator<<(OStream & os, dice_roll const & r) {
+  for (die_roll const & dr : r.sub_rolls) {
+    os << dr;
+  }
+  for (mod const & m : r.modifiers) {
+    os << m.sign << m.value;
+  }
+  return os;
+}
+
+template <typename OStream>
+OStream & operator<<(OStream & os,
+                     std::pair<dice, std::vector<dice_roll>> const & pair) {
+  os << "Result of '" << pair.first << "':";
+  if (pair.second.size() == 1) {
+    os << ' ';
+    std::visit([&os](auto v) { os << v; }, pair.second[0].result());
+    return os << ' ' << '(' << pair.second[0] << ')';
+  }
+  for (int i = 0; i < pair.second.size(); ++i) {
+    os << "\n  Result/" << i << ": ";
+    std::visit([&os](auto v) { os << v; }, pair.second[i].result());
+    os << ' ' << '(' << pair.second[i] << ')';
+  }
+  return os;
+}
+}

+ 14 - 2
include/dice-roll/roll.h

@@ -14,6 +14,18 @@
 #include "random.h"
 
 namespace dice {
+enum class outcome {
+  PASS,
+  FAIL,
+};
+
+struct die_outcome {
+  friend auto operator<=>(die_outcome const &, die_outcome const &) = default;
+
+  int roll;
+  int sides;
+};
+
 // Describe the actual result of rolling (+/-)NdM
 struct die_roll {
   // Collapse this roll into its actual value
@@ -22,14 +34,14 @@ struct die_roll {
   sign sign;
   // Since this roll was composed on NdM, rolled.size() == N. Each element
   // of rolled is within the integer range [1, M].
-  std::vector<int> rolled;
-  int sides;
+  std::vector<die_outcome> rolled;
 };
 
 // Describe the actual result of rolling an arbitrary set of dice with mods
 struct dice_roll {
   // Collapse this roll into its actual value
   operator int() const;
+  std::variant<int, outcome> result() const;
   // A vector of component roll results, each on representing a single NdM
   // expression.
   std::vector<die_roll> sub_rolls;

+ 5 - 3
simple_dice/main.cxx

@@ -9,13 +9,15 @@
 #include <iostream>
 
 #include "dice-roll/exception.h"
-#include "terminal_helper.h"
 
 void eval(std::string const & str) {
   try {
-    terminal::process_dice_string(str);
+    auto d = dice::from_string(str);
+    auto rs = dice::roller()(d);
+    std::cout << std::make_pair(d, rs) << '\n';
   } catch (dice::unexpected_token const & ut) {
-    terminal::print_error_message(str, ut);
+    std::cerr << "Error in roll: '" << str << "': " << ut.what() << "\n";
+    std::cerr << "                " << ut.pointer(str.size() + 1) << std::endl;
   }
 }
 

+ 4 - 73
src/io.cxx

@@ -10,91 +10,22 @@
 #include <sstream>
 
 #include "dice-roll/die.h"
+#include "dice-roll/io.h"
 #include "dice-roll/parser.h"
 #include "dice-roll/roll.h"
 
 namespace dice {
 
 std::ostream & operator<<(std::ostream & out, sign s) {
-  switch (s) {
-  case sign::PLUS:
-    return out << '+';
-  case sign::MINUS:
-    return out << '-';
-  case sign::ZERO:
-    return out;
-  }
-}
-
-std::ostream & operator<<(std::ostream & out, difficulty_class::test t) {
-  switch (t) {
-  case difficulty_class::test::None:
-    return out;
-  case difficulty_class::test::Less:
-    return out << '<';
-  case difficulty_class::test::LessOrEqual:
-    return out << '<' << '=';
-  case difficulty_class::test::Greater:
-    return out << '>';
-  case difficulty_class::test::GreaterOrEqual:
-    return out << '>' << '=';
-  }
-}
-
-std::ostream & operator<<(std::ostream & out, keep const & k) {
-  switch (k.method) {
-  case keep::Highest:
-    return out << 'k' << 'h' << k.amount;
-  case keep::Lowest:
-    return out << 'k' << 'l' << k.amount;
-  default:
-    return out;
-  }
+  return ::dice::template operator<<(out, s);
 }
 
 std::ostream & operator<<(std::ostream & out, dice const & d) {
-  if (d.num != 1) out << d.num << '{';
-  for (die const & di : d.of) {
-    out << di.sgn << di.num << 'd' << di.sides << di.keep;
-  }
-  for (mod m : d.modifier) {
-    out << m.sign << m.value;
-  }
-  if (d.dc.comp != difficulty_class::test::None) {
-    out << d.dc.comp << d.dc.against;
-  }
-  if (d.num != 1) out << '}';
-  return out;
-}
-
-std::ostream & operator<<(std::ostream & out, die_roll const & r) {
-  out << r.sign;
-  switch (r.rolled.size()) {
-  case 0:
-    // Prevent crashes if we somehow get a 0dM expression
-    return out << "0";
-  case 1:
-    // Don't bother with braces if there's only a single roll,
-    // the braces are for grouping purposes.
-    return out << r.rolled[0];
-  default:
-    out << "[ ";
-    out << r.rolled[0];
-    for (int i = 1; i < r.rolled.size(); ++i) {
-      out << ", " << r.rolled[i];
-    }
-    return out << " ]";
-  }
+  return ::dice::template operator<<(out, d);
 }
 
 std::ostream & operator<<(std::ostream & out, dice_roll const & r) {
-  for (die_roll const & dr : r.sub_rolls) {
-    out << dr;
-  }
-  for (mod const & m : r.modifiers) {
-    out << m.sign << m.value;
-  }
-  return out;
+  return ::dice::template operator<<(out, r);
 }
 
 std::istream & operator>>(std::istream & in, dice & d) {

+ 16 - 4
src/roll.cxx

@@ -13,8 +13,11 @@
 #include "dice-roll/random.h"
 
 namespace dice {
+int add_outcome(int accum, die_outcome const & die) { return accum + die.roll; }
+
 die_roll::operator int() const {
-  return sgn(sign) * std::accumulate(rolled.begin(), rolled.end(), 0);
+  return sgn(sign) *
+         std::accumulate(rolled.begin(), rolled.end(), 0, &add_outcome);
 }
 
 dice_roll::operator int() const {
@@ -27,10 +30,19 @@ dice_roll::operator int() const {
   }
 }
 
+std::variant<int, outcome> dice_roll::result() const {
+  auto value = static_cast<int>(*this);
+  if (dc.comp != difficulty_class::test::None) {
+    return value ? outcome::PASS : outcome::FAIL;
+  }
+  return value;
+}
+
 die_roll roll_impl(die const & d, engine::random & gen) {
-  std::vector<int> hits;
+  std::vector<die_outcome> hits;
   for (int i = 0; i < d.num; ++i) {
-    hits.push_back(gen.roll(d.sides));
+    hits.push_back(
+        {.roll = static_cast<int>(gen.roll(d.sides)), .sides = d.sides});
   }
   switch (d.keep.method) {
   case keep::Highest:
@@ -46,7 +58,7 @@ die_roll roll_impl(die const & d, engine::random & gen) {
   default:
     break;
   }
-  return {d.sgn, hits, d.sides};
+  return {d.sgn, hits};
 }
 
 dice_roll roll_impl(dice const & d, engine::random & gen) {

+ 0 - 52
src/terminal_helper.cxx

@@ -1,52 +0,0 @@
-//
-//  terminal_helper.cxx
-//  simple_dice
-//
-//  Created by Sam Jaffe on 12/18/18.
-//  Copyright © 2018 Sam Jaffe. All rights reserved.
-//
-
-#include "terminal_helper.h"
-
-#include <iostream>
-
-#include "dice-roll/die.h"
-#include "dice-roll/exception.h"
-#include "dice-roll/roll.h"
-
-namespace terminal { namespace {
-void print(dice::dice_roll const & r) {
-  if (r.dc.comp != dice::difficulty_class::test::None) {
-    std::cout << (int(r) ? "PASS" : "FAIL") << " (" << r << ")\n";
-  } else {
-    std::cout << int(r) << " (" << r << ")\n";
-  }
-}
-
-void print(std::vector<dice::dice_roll> const & rs) {
-  if (rs.size() != 1) {
-    std::cout << '\n';
-    for (int i = 0; i < rs.size(); ++i) {
-      std::cout << "  Result/" << i << ": ";
-      print(rs[i]);
-    }
-  } else {
-    print(rs[0]);
-  }
-}
-}}
-
-namespace terminal {
-void process_dice_string(std::string const & str) {
-  auto d = dice::from_string(str);
-  auto rs = dice::roller()(d);
-  std::cout << "Result of '" << d << "': ";
-  print(rs);
-}
-
-void print_error_message(std::string const & str,
-                         dice::unexpected_token const & ut) {
-  std::cerr << "Error in roll: '" << str << "': " << ut.what() << "\n";
-  std::cerr << "                " << ut.pointer(str.size() + 1) << std::endl;
-}
-}

+ 0 - 24
src/terminal_helper.h

@@ -1,24 +0,0 @@
-//
-//  terminal_helper.hpp
-//  simple_dice
-//
-//  Created by Sam Jaffe on 12/18/18.
-//  Copyright © 2018 Sam Jaffe. All rights reserved.
-//
-
-#pragma once
-
-#include <string>
-
-namespace dice {
-class unexpected_token;
-}
-
-namespace terminal {
-/**
- * @param str {@see make_dice(std::string const &)}
- */
-void process_dice_string(std::string const & str);
-void print_error_message(std::string const & str,
-                         dice::unexpected_token const & ut);
-}

+ 5 - 3
stateful_dice/main.cxx

@@ -12,7 +12,6 @@
 #include <regex>
 
 #include "dice-roll/exception.h"
-#include "terminal_helper.h"
 
 void usage() {
   std::cout << "'help'      : Print this message\n";
@@ -100,10 +99,13 @@ void evaluator::operator()(std::string const & str) {
         curr.replace(match[1].first, match[1].second,
                      find_or(match[1].str().substr(1)).second);
       }
-      terminal::process_dice_string(curr);
+      auto d = dice::from_string(curr);
+      auto rs = dice::roller()(d);
+      std::cout << std::make_pair(d, rs) << '\n';
     }
   } catch (dice::unexpected_token const & ut) {
-    terminal::print_error_message(curr, ut);
+    std::cerr << "Error in roll: '" << curr << "': " << ut.what() << "\n";
+    std::cerr << "                " << ut.pointer(str.size() + 1) << std::endl;
   }
 }