home · contact · privacy
Refactor parser code.
[plomrogue2] / plomrogue / parser.py
index a1b56b571b650431d297badf3f0e4573da731359..51f5cc20390ac71c50b5b1baae7f53fcd7c12bdd 100644 (file)
@@ -1,4 +1,3 @@
-import unittest
 from plomrogue.errors import ArgError
 from plomrogue.mapping import YX
 
 from plomrogue.errors import ArgError
 from plomrogue.mapping import YX
 
@@ -7,6 +6,7 @@ class Parser:
 
     def __init__(self, game=None):
         self.game = game
 
     def __init__(self, game=None):
         self.game = game
+        self.string_options = {}
 
     def tokenize(self, msg):
         """Parse msg string into tokens.
 
     def tokenize(self, msg):
         """Parse msg string into tokens.
@@ -45,16 +45,23 @@ class Parser:
         """Parse yx_string as yx_tuple, return result.
 
         The range_ argument may be 'nonneg' (non-negative, including
         """Parse yx_string as yx_tuple, return result.
 
         The range_ argument may be 'nonneg' (non-negative, including
-        0) or 'pos' (positive, excluding 0).
+        0) or 'pos' (positive, excluding 0) or 'all'.
 
         """
 
         def get_axis_position_from_argument(axis, token):
 
         """
 
         def get_axis_position_from_argument(axis, token):
-            if len(token) < 3 or token[:2] != axis + ':' or \
-                    not (token[2:].isdigit() or token[2] == '-'):
-                raise ArgError('Non-int arg for ' + axis + ' position.')
-            n = int(token[2:])
-            if n < 1 and range_ == 'pos':
+            if token[:2] != axis + ':':
+                raise ArgError('invalid YX tuple formatting')
+            n_string = token[2:]
+            if n_string.strip() != n_string:
+                raise ArgError('invalid YX tuple formatting')
+            try:
+                n = int(n_string)
+            except ValueError:
+                raise ArgError('non-int value for ' + axis + ' position')
+            if range_ == 'all':
+                return n
+            if n < 1 and range == 'pos':
                 raise ArgError('Arg for ' + axis + ' position < 1.')
             elif n < 0 and range_ == 'nonneg':
                 raise ArgError('Arg for ' + axis + ' position < 0.')
                 raise ArgError('Arg for ' + axis + ' position < 1.')
             elif n < 0 and range_ == 'nonneg':
                 raise ArgError('Arg for ' + axis + ' position < 0.')
@@ -67,11 +74,23 @@ class Parser:
         x = get_axis_position_from_argument('X', tokens[1])
         return YX(y, x)
 
         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.
         """Parse msg as call to function, return function with args tuple.
 
         Respects function signature defined in function's .argtypes attribute.
+
+        Refuses messages with any but a small list of acceptable characters.
+
         """
         """
+        import string
+        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:
+            if not c in legal_chars:
+                raise ArgError('Command/message contains illegal character(s), '
+                               'may only contain ones of: %s' % legal_chars)
         tokens = self.tokenize(msg)
         if len(tokens) == 0:
             return None, ()
         tokens = self.tokenize(msg)
         if len(tokens) == 0:
             return None, ()
@@ -95,8 +114,8 @@ class Parser:
         tmpl_tokens = signature.split()
         if len(tmpl_tokens) != len(args_tokens):
             raise ArgError('Number of arguments (' + str(len(args_tokens)) +
         tmpl_tokens = signature.split()
         if len(tmpl_tokens) != len(args_tokens):
             raise ArgError('Number of arguments (' + str(len(args_tokens)) +
-                           ') not expected number (' + str(len(tmpl_tokens))
-                           ').')
+                           ') not expected number (' + str(len(tmpl_tokens)) +
+                           ').')
         args = []
         string_string = 'string'
         for i in range(len(tmpl_tokens)):
         args = []
         string_string = 'string'
         for i in range(len(tmpl_tokens)):
@@ -110,6 +129,15 @@ class Parser:
                 if not arg.isdigit() or int(arg) < 1:
                     raise ArgError('Argument must be positive integer.')
                 args += [int(arg)]
                 if not arg.isdigit() or int(arg) < 1:
                     raise ArgError('Argument must be positive integer.')
                 args += [int(arg)]
+            elif tmpl == 'int':
+                try:
+                    args += [int(arg)]
+                except ValueError:
+                    raise ArgError('Argument must be integer.')
+            elif tmpl == 'bool':
+                if not arg.isdigit() or int(arg) not in (0, 1):
+                    raise ArgError('Argument must be 0 or 1.')
+                args += [bool(int(arg))]
             elif tmpl == 'char':
                 try:
                     ord(arg)
             elif tmpl == 'char':
                 try:
                     ord(arg)
@@ -120,15 +148,15 @@ class Parser:
                 args += [self.parse_yx_tuple(arg, 'nonneg')]
             elif tmpl == 'yx_tuple:pos':
                 args += [self.parse_yx_tuple(arg, 'pos')]
                 args += [self.parse_yx_tuple(arg, 'nonneg')]
             elif tmpl == 'yx_tuple:pos':
                 args += [self.parse_yx_tuple(arg, 'pos')]
+            elif tmpl == 'yx_tuple':
+                args += [self.parse_yx_tuple(arg, 'all')]
             elif tmpl == string_string:
                 args += [arg]
             elif tmpl[:len(string_string) + 1] == string_string + ':':
             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:]
                 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)
                 if arg not in options:
                     msg = 'Argument #%s must be one of: %s' % (i + 1, options)
                     raise ArgError(msg)