diff --git a/README.rst b/README.rst
index afa55a6..fab1b16 100644
--- a/README.rst
+++ b/README.rst
@@ -443,6 +443,7 @@ Version History
 ===============
 
 (Next release)
+  * Improve error message in left-recursive rules. (lucaswiman)
   * Add support for range ``{min,max}`` repetition expressions (righthandabacus)
   * Fix bug in ``*`` and ``+`` for token grammars (lucaswiman)
   * Add support for grammars on bytestrings (lucaswiman)
diff --git a/parsimonious/exceptions.py b/parsimonious/exceptions.py
index d0f9375..a03a549 100644
--- a/parsimonious/exceptions.py
+++ b/parsimonious/exceptions.py
@@ -1,3 +1,4 @@
+from textwrap import dedent
 
 from parsimonious.utils import StrAndRepr
 
@@ -44,6 +45,20 @@ class ParseError(StrAndRepr, Exception):
             return self.pos + 1
 
 
+class LeftRecursionError(ParseError):
+    def __str__(self):
+        rule_name = self.expr.name if self.expr.name else str(self.expr)
+        window = self.text[self.pos:self.pos + 20]
+        return dedent(f"""
+            Left recursion in rule {rule_name!r} at {window!r} (line {self.line()}, column {self.column()}).
+
+            Parsimonious is a packrat parser, so it can't handle left recursion.
+            See https://en.wikipedia.org/wiki/Parsing_expression_grammar#Indirect_left_recursion
+            for how to rewrite your grammar into a rule that does not use left-recursion.
+            """
+        ).strip()
+
+
 class IncompleteParseError(ParseError):
     """A call to ``parse()`` matched a whole Expression but did not consume the
     entire text."""
diff --git a/parsimonious/expressions.py b/parsimonious/expressions.py
index 9200365..f93f2c6 100644
--- a/parsimonious/expressions.py
+++ b/parsimonious/expressions.py
@@ -10,7 +10,7 @@ from collections import defaultdict
 from inspect import getfullargspec, isfunction, ismethod, ismethoddescriptor
 import regex as re
 
-from parsimonious.exceptions import ParseError, IncompleteParseError
+from parsimonious.exceptions import ParseError, IncompleteParseError, LeftRecursionError
 from parsimonious.nodes import Node, RegexNode
 from parsimonious.utils import StrAndRepr
 
@@ -96,6 +96,9 @@ def expression(callable, rule_name, grammar):
     return AdHocExpression(name=rule_name)
 
 
+IN_PROGRESS = object()
+
+
 class Expression(StrAndRepr):
     """A thing that can be matched against a piece of text"""
 
@@ -186,10 +189,10 @@ class Expression(StrAndRepr):
             node = expr_cache[pos]
         else:
             # TODO: Set default value to prevent infinite recursion in left-recursive rules.
-            node = expr_cache[pos] = self._uncached_match(text,
-                                                          pos,
-                                                          cache,
-                                                          error)
+            expr_cache[pos] = IN_PROGRESS  # Mark as in progress
+            node = expr_cache[pos] = self._uncached_match(text, pos, cache, error)
+        if node is IN_PROGRESS:
+            raise LeftRecursionError(text, pos=-1, expr=self)
 
         # Record progress for error reporting:
         if node is None and pos >= error.pos and (

diff --git a/parsimonious/tests/test_grammar.py b/parsimonious/tests/test_grammar.py
index b4ac7f7..2f979f6 100644
--- a/parsimonious/tests/test_grammar.py
+++ b/parsimonious/tests/test_grammar.py
@@ -4,9 +4,8 @@ from sys import version_info
 from unittest import TestCase
 
 import pytest
-import sys
 
-from parsimonious.exceptions import BadGrammar, UndefinedLabel, ParseError, VisitationError
+from parsimonious.exceptions import BadGrammar, LeftRecursionError, ParseError, UndefinedLabel, VisitationError
 from parsimonious.expressions import Literal, Lookahead, Regex, Sequence, TokenMatcher, is_callable
 from parsimonious.grammar import rule_grammar, RuleVisitor, Grammar, TokenGrammar, LazyReference
 from parsimonious.nodes import Node
@@ -649,3 +648,20 @@ def test_inconsistent_string_types_in_grammar():
         foo = "foo"
         bar = "bar"
     """)
+
+
+def test_left_associative():
+    # Regression test for https://github.com/erikrose/parsimonious/issues/209
+    language_grammar = r"""
+    expression = operator_expression / non_operator_expression
+    non_operator_expression = number_expression
+
+    operator_expression = expression "+" non_operator_expression
+
+    number_expression = ~"[0-9]+"
+    """
+
+    grammar = Grammar(language_grammar)
+    with pytest.raises(LeftRecursionError) as e:
+        grammar["operator_expression"].parse("1+2")
+    assert "Parsimonious is a packrat parser, so it can't handle left recursion." in str(e.value)

Don't allow recursion if no characters are being used up.
I was trying to write left associative operators in grammar, but an issue that kept coming up was infinite recursion. This could be solved by making every operator right associative, because the parser would cut out the option of just going down the same node over and over. Making it so that the parser can only visit one node twice if characters have been used up would completely solve this issue.
parsimonious/tests/test_grammar.py::BootstrappingGrammarTests::test_quantifier parsimonious/tests/test_grammar.py::BootstrappingGrammarTests::test_regex parsimonious/tests/test_grammar.py::BootstrappingGrammarTests::test_spaceless_literal parsimonious/tests/test_grammar.py::BootstrappingGrammarTests::test_successes parsimonious/tests/test_grammar.py::RuleVisitorTests::test_optional parsimonious/tests/test_grammar.py::RuleVisitorTests::test_round_trip parsimonious/tests/test_grammar.py::RuleVisitorTests::test_undefined_rule parsimonious/tests/test_grammar.py::GrammarTests::test_bad_grammar parsimonious/tests/test_grammar.py::GrammarTests::test_callability_custom_rules parsimonious/tests/test_grammar.py::GrammarTests::test_callability_of_routines parsimonious/tests/test_grammar.py::GrammarTests::test_circular_toplevel_reference parsimonious/tests/test_grammar.py::GrammarTests::test_comments parsimonious/tests/test_grammar.py::GrammarTests::test_complex_custom_rules parsimonious/tests/test_grammar.py::GrammarTests::test_expressions_from_rules parsimonious/tests/test_grammar.py::GrammarTests::test_immutable_grammar parsimonious/tests/test_grammar.py::GrammarTests::test_infinite_loop parsimonious/tests/test_grammar.py::GrammarTests::test_lazy_custom_rules parsimonious/tests/test_grammar.py::GrammarTests::test_lazy_default_rule parsimonious/tests/test_grammar.py::GrammarTests::test_lookahead parsimonious/tests/test_grammar.py::GrammarTests::test_match parsimonious/tests/test_grammar.py::GrammarTests::test_multi_line parsimonious/tests/test_grammar.py::GrammarTests::test_not parsimonious/tests/test_grammar.py::GrammarTests::test_parens parsimonious/tests/test_grammar.py::GrammarTests::test_parens_with_leading_whitespace parsimonious/tests/test_grammar.py::GrammarTests::test_repetitions parsimonious/tests/test_grammar.py::GrammarTests::test_repr parsimonious/tests/test_grammar.py::GrammarTests::test_resolve_refs_completeness parsimonious/tests/test_grammar.py::GrammarTests::test_resolve_refs_order parsimonious/tests/test_grammar.py::GrammarTests::test_right_recursive parsimonious/tests/test_grammar.py::GrammarTests::test_rule_ordering_is_preserved parsimonious/tests/test_grammar.py::GrammarTests::test_rule_ordering_is_preserved_on_shallow_copies parsimonious/tests/test_grammar.py::GrammarTests::test_simple_custom_rules parsimonious/tests/test_grammar.py::GrammarTests::test_single_quoted_literals parsimonious/tests/test_grammar.py::GrammarTests::test_unconnected_custom_rules parsimonious/tests/test_grammar.py::GrammarTests::test_unicode parsimonious/tests/test_grammar.py::TokenGrammarTests::test_parse_failure parsimonious/tests/test_grammar.py::TokenGrammarTests::test_parse_success parsimonious/tests/test_grammar.py::TokenGrammarTests::test_token_repr parsimonious/tests/test_grammar.py::TokenGrammarTests::test_token_star_plus_expressions parsimonious/tests/test_grammar.py::test_precedence_of_string_modifiers parsimonious/tests/test_grammar.py::test_binary_grammar parsimonious/tests/test_grammar.py::test_inconsistent_string_types_in_grammar parsimonious/tests/test_grammar.py::test_left_associative