Repository: lark-parser/lark
Pull request #1356 (2023-10-22 04:30:45)

Standalone: Added support for interactive parser.
parse_interactive and on_error are now working


Patch:
diff --git a/lark/parsers/lalr_interactive_parser.py b/lark/parsers/lalr_interactive_parser.py
index 2a30d8e..d5a2152 100644
--- a/lark/parsers/lalr_interactive_parser.py
+++ b/lark/parsers/lalr_interactive_parser.py
@@ -7,6 +7,7 @@ import warnings
 from lark.exceptions import UnexpectedToken
 from lark.lexer import Token, LexerThread
 
+###{standalone
 
 class InteractiveParser:
     """InteractiveParser gives you advanced control over parsing and error handling when parsing with LALR.
@@ -152,3 +153,5 @@ class ImmutableInteractiveParser(InteractiveParser):
         """Convert to an ``InteractiveParser``."""
         p = copy(self)
         return InteractiveParser(p.parser, p.parser_state, p.lexer_thread)
+
+###}
diff --git a/lark/tools/standalone.py b/lark/tools/standalone.py
index 1901f71..9940ccb 100644
--- a/lark/tools/standalone.py
+++ b/lark/tools/standalone.py
@@ -24,6 +24,7 @@
 #
 #
 
+from copy import deepcopy
 from abc import ABC, abstractmethod
 from types import ModuleType
 from typing import (
@@ -65,6 +66,7 @@ EXTRACT_STANDALONE_FILES = [
     'parsers/lalr_analysis.py',
     'parsers/lalr_parser_state.py',
     'parsers/lalr_parser.py',
+    'parsers/lalr_interactive_parser.py',
     'parser_frontends.py',
     'lark.py',
     'indenter.py',


Test patch:
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 839e16a..7498501 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -2542,7 +2542,7 @@ def _make_parser_test(LEXER, PARSER):
 
             res = ip.feed_eof(ip.lexer_thread.state.last_token)
             self.assertEqual(res, Tree('start', ['a', 'b']))
-            self.assertRaises(UnexpectedToken ,ip.feed_eof)
+            self.assertRaises(UnexpectedToken, ip.feed_eof)
 
             self.assertRaises(UnexpectedToken, ip_copy.feed_token, Token('A', 'a'))
             ip_copy.feed_token(Token('B', 'b'))
diff --git a/tests/test_tools.py b/tests/test_tools.py
index deef929..4771545 100644
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -51,6 +51,46 @@ class TestStandalone(TestCase):
         l = _Lark()
         x = l.parse('12 elephants')
 
+    def test_interactive(self):
+        grammar = """
+                start: A+ B*
+                A: "a"
+                B: "b"
+        """
+        context = self._create_standalone(grammar)
+        parser: Lark = context['Lark_StandAlone']()
+
+        ip = parser.parse_interactive()
+
+        UnexpectedToken = context['UnexpectedToken']
+        Token = context['Token']
+
+        self.assertRaises(UnexpectedToken, ip.feed_eof)
+        self.assertRaises(TypeError, ip.exhaust_lexer)
+        ip.feed_token(Token('A', 'a'))
+        res = ip.feed_eof()
+        self.assertEqual(res, Tree('start', ['a']))
+
+        ip = parser.parse_interactive("ab")
+
+        ip.exhaust_lexer()
+
+        ip_copy = ip.copy()
+        self.assertEqual(ip_copy.parser_state, ip.parser_state)
+        self.assertEqual(ip_copy.lexer_thread.state, ip.lexer_thread.state)
+        self.assertIsNot(ip_copy.parser_state, ip.parser_state)
+        self.assertIsNot(ip_copy.lexer_thread.state, ip.lexer_thread.state)
+        self.assertIsNot(ip_copy.lexer_thread.state.line_ctr, ip.lexer_thread.state.line_ctr)
+
+        res = ip.feed_eof(ip.lexer_thread.state.last_token)
+        self.assertEqual(res, Tree('start', ['a', 'b']))
+        self.assertRaises(UnexpectedToken, ip.feed_eof)
+
+        self.assertRaises(UnexpectedToken, ip_copy.feed_token, Token('A', 'a'))
+        ip_copy.feed_token(Token('B', 'b'))
+        res = ip_copy.feed_eof()
+        self.assertEqual(res, Tree('start', ['a', 'b', 'b']))
+
     def test_contextual(self):
         grammar = """
         start: a b