From f3e107445e4de3f4e79c3d369dd70f5bab8b7d0d Mon Sep 17 00:00:00 2001
From: Christian Heller <c.heller@plomlompom.de>
Date: Thu, 10 Jun 2021 22:54:48 +0200
Subject: [PATCH] Refactor parser code.

---
 plomrogue/game.py    | 19 ++++---------------
 plomrogue/io.py      | 12 ++++++++++++
 plomrogue/parser.py  | 16 ++++++++--------
 rogue_chat_curses.py | 21 +++++++++++++--------
 4 files changed, 37 insertions(+), 31 deletions(-)

diff --git a/plomrogue/game.py b/plomrogue/game.py
index b76860a..cb749cf 100755
--- a/plomrogue/game.py
+++ b/plomrogue/game.py
@@ -2,7 +2,6 @@ from plomrogue.errors import GameError, PlayError
 from plomrogue.io import GameIO
 from plomrogue.misc import quote
 from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex, Map
-import string
 import datetime
 
 
@@ -123,6 +122,7 @@ import os
 class Game(GameBase):
 
     def __init__(self, save_file, *args, **kwargs):
+        import string
         from plomrogue.misc import Terrain
         super().__init__(*args, **kwargs)
         self.changed = True
@@ -159,9 +159,11 @@ class Game(GameBase):
         if os.path.exists(self.io.save_file):
             if not os.path.isfile(self.io.save_file):
                 raise GameError('save file path refers to non-file')
+        self.io.train_parser()
 
     def register_thing_type(self, thing_type):
         self._register_object(thing_type, 'thing_type', 'Thing_')
+        self.io.train_parser()
 
     def register_task(self, task):
         self._register_object(task, 'task', 'Task_')
@@ -190,20 +192,6 @@ class Game(GameBase):
                 return False
         return True
 
-    def get_string_options(self, string_option_type):
-        if string_option_type == 'direction':
-            return self.map_geometry.directions
-        elif string_option_type == 'direction+here':
-            return ['HERE'] + self.map_geometry.directions
-        elif string_option_type == 'char':
-            return [c for c in
-                    string.digits + string.ascii_letters + string.punctuation + ' ']
-        elif string_option_type == 'map_geometry':
-            return ['Hex', 'Square']
-        elif string_option_type == 'thing_type':
-            return self.thing_types.keys()
-        return None
-
     def get_default_spawn_point(self):
         import random
         if len(self.spawn_points) == 0:
@@ -646,6 +634,7 @@ class Game(GameBase):
         self.portals = {}
         self.admin_passwords = []
         self.map_geometry = map_geometry
+        self.io.train_parser()
         self.map_control_passwords = {'X': 'secret'}
         self.get_map(YX(0, 0))
         self.get_map(YX(0, 0), 'control')
diff --git a/plomrogue/io.py b/plomrogue/io.py
index 438b8b6..490706e 100644
--- a/plomrogue/io.py
+++ b/plomrogue/io.py
@@ -13,6 +13,18 @@ class GameIO():
         self.save_file = save_file
         self.servers = []
 
+    def train_parser(self):
+        import string
+        self.parser.string_options = {
+            'map_geometry': {'Hex', 'Square'},
+            'char': [c for c in
+                     string.digits + string.ascii_letters + string.punctuation
+                     + ' '],
+            'direction': self.game.map_geometry.directions,
+            'direction+here': ['HERE'] + self.game.map_geometry.directions,
+            'thing_type': self.game.thing_types.keys()
+        }
+
     def loop(self, q):
         """Handle commands coming through queue q, run game, send results back.
 
diff --git a/plomrogue/parser.py b/plomrogue/parser.py
index 302733d..51f5cc2 100644
--- a/plomrogue/parser.py
+++ b/plomrogue/parser.py
@@ -6,6 +6,7 @@ class Parser:
 
     def __init__(self, game=None):
         self.game = game
+        self.string_options = {}
 
     def tokenize(self, msg):
         """Parse msg string into tokens.
@@ -73,16 +74,17 @@ class Parser:
         x = get_axis_position_from_argument('X', tokens[1])
         return YX(y, x)
 
-    def parse(self, msg):
+    def parse(self, msg, replace_newline=True):
         """Parse msg as call to function, return function with args tuple.
 
         Respects function signature defined in function's .argtypes attribute.
 
-        Throws out messages with any but a small list of acceptable characters.
+        Refuses messages with any but a small list of acceptable characters.
 
         """
         import string
-        msg = msg.replace('\n', ' ')  # Inserted by some tablet keyboards.
+        if replace_newline:
+            msg = msg.replace('\n', ' ')  # Inserted by some tablet keyboards.
         legal_chars = string.digits + string.ascii_letters +\
             string.punctuation + ' ' + 'ÄäÖöÜüߧ' + 'éèáàô' + '–…'
         for c in msg:
@@ -151,12 +153,10 @@ class Parser:
             elif tmpl == string_string:
                 args += [arg]
             elif tmpl[:len(string_string) + 1] == string_string + ':':
-                if not hasattr(self.game, 'get_string_options'):
-                    raise ArgError('No string option directory.')
                 string_option_type = tmpl[len(string_string) + 1:]
-                options = self.game.get_string_options(string_option_type)
-                if options is None:
-                    raise ArgError('Unknown string option type.')
+                if not string_option_type in self.string_options.keys():
+                    raise ArgError('Unknown string option type: %s' % string_option_type)
+                options = self.string_options[string_option_type]
                 if arg not in options:
                     msg = 'Argument #%s must be one of: %s' % (i + 1, options)
                     raise ArgError(msg)
diff --git a/rogue_chat_curses.py b/rogue_chat_curses.py
index 91418f6..4f6981d 100755
--- a/rogue_chat_curses.py
+++ b/rogue_chat_curses.py
@@ -302,6 +302,7 @@ cmd_TASKS.argtypes = 'string'
 
 def cmd_THING_TYPE(game, thing_type, symbol_hint):
     game.thing_types[thing_type] = symbol_hint
+    game.train_parser()
 cmd_THING_TYPE.argtypes = 'string char'
 
 def cmd_THING_INSTALLED(game, thing_id):
@@ -383,13 +384,14 @@ class Game(GameBase):
         self.portals_new = {}
         self.terrains = {}
         self.player = None
+        self.parser = Parser(self)
+        self.train_parser()
 
-    def get_string_options(self, string_option_type):
-        if string_option_type == 'map_geometry':
-            return ['Hex', 'Square']
-        elif string_option_type == 'thing_type':
-            return self.thing_types.keys()
-        return None
+    def train_parser(self):
+        self.parser.string_options = {
+            'map_geometry': {'Hex', 'Square'},
+            'thing_type': self.thing_types.keys()
+        }
 
     def get_command(self, command_name):
         from functools import partial
@@ -497,7 +499,6 @@ class RogueChatTUI(TUI):
         self.socket = ClientSocket(host, self.socket_log)
         self.game = Game()
         self.game.tui = self
-        self.parser = Parser(self.game)
         self.login_name = None
         self.map_mode = 'terrain + things'
         self.password = 'foo'
@@ -589,6 +590,10 @@ class RogueChatTUI(TUI):
         super().__init__(*args, **kwargs)
 
     def update_on_connect(self):
+        self.game.thing_types = {}
+        self.game.terrains = {}
+        self.game.train_parser()
+        self.is_admin = False
         self.socket.send('TASKS')
         self.socket.send('TERRAINS')
         self.socket.send('THING_TYPES')
@@ -1093,7 +1098,7 @@ class RogueChatTUI(TUI):
                 self.draw_face_popup()
 
     def handle_server_message(self, msg):
-        command, args = self.parser.parse(msg)
+        command, args = self.game.parser.parse(msg)
         command(*args)
 
     def on_each_loop_start(self):
-- 
2.30.2