-from functools import partial
-import unittest
-
-
-class ArgumentError(Exception):
- pass
-
-
-class ParseError(Exception):
- pass
-
-
-class Parser:
-
- def __init__(self, game):
- self.game = game
-
- def tokenize(self, msg):
- """Parse msg string into tokens.
-
- Separates by ' ' and '\n', but allows whitespace in tokens quoted by
- '"', and allows escaping within quoted tokens by a prefixed backslash.
- """
- tokens = []
- token = ''
- quoted = False
- escaped = False
- for c in msg:
- if quoted:
- if escaped:
- token += c
- escaped = False
- elif c == '\\':
- escaped = True
- elif c == '"':
- quoted = False
- else:
- token += c
- elif c == '"':
- quoted = True
- elif c in {' ', '\n'}:
- if len(token) > 0:
- tokens += [token]
- token = ''
- else:
- token += c
- if len(token) > 0:
- tokens += [token]
- return tokens
-
- def parse(self, msg):
- """Parse msg as call to self.game method, return method with arguments.
-
- Respects method signatures defined in methods' .argtypes attributes.
- """
- tokens = self.tokenize(msg)
- if len(tokens) == 0:
- return None
- method_candidate = 'cmd_' + tokens[0]
- if not hasattr(self.game, method_candidate):
- return None
- method = getattr(self.game, method_candidate)
- if len(tokens) == 1:
- if not hasattr(method, 'argtypes'):
- return method
- else:
- raise ParseError('Command expects argument(s).')
- args_candidates = tokens[1:]
- if not hasattr(method, 'argtypes'):
- raise ParseError('Command expects no argument(s).')
- args, kwargs = self.argsparse(method.argtypes, args_candidates)
- return partial(method, *args, **kwargs)
-
- def parse_yx_tuple(self, yx_string):
- """Parse yx_string as yx_tuple:nonneg argtype, return result."""
-
- def get_axis_position_from_argument(axis, token):
- if len(token) < 3 or token[:2] != axis + ':' or \
- not token[2:].isdigit():
- raise ParseError('Non-int arg for ' + axis + ' position.')
- n = int(token[2:])
- if n < 1:
- raise ParseError('Arg for ' + axis + ' position < 1.')
- return n
-
- tokens = yx_string.split(',')
- if len(tokens) != 2:
- raise ParseError('Wrong number of yx-tuple arguments.')
- y = get_axis_position_from_argument('Y', tokens[0])
- x = get_axis_position_from_argument('X', tokens[1])
- return (y, x)
-
- def argsparse(self, signature, args_tokens):
- """Parse into / return args_tokens as args/kwargs defined by signature.
-
- Expects signature to be a ' '-delimited sequence of any of the strings
- 'int:nonneg', 'yx_tuple:nonneg', 'string', defining the respective
- argument types.
- """
- tmpl_tokens = signature.split()
- if len(tmpl_tokens) != len(args_tokens):
- raise ParseError('Number of arguments (' + str(len(args_tokens)) +
- ') not expected number (' + str(len(tmpl_tokens))
- + ').')
- args = []
- for i in range(len(tmpl_tokens)):
- tmpl = tmpl_tokens[i]
- arg = args_tokens[i]
- if tmpl == 'int:nonneg':
- if not arg.isdigit():
- raise ParseError('Argument must be non-negative integer.')
- args += [int(arg)]
- elif tmpl == 'yx_tuple:nonneg':
- args += [self.parse_yx_tuple(arg)]
- elif tmpl == 'string':
- args += [arg]
- else:
- raise ParseError('Unknown argument type.')
- return args, {}