# gamesequence.py import re as _re class SequenceError(RuntimeError): pass class ScriptBreak(object): def __init__(self, value): self.value = value def getValueFromString(arg: str, env: dict): #if env == None: #env = self.customValues val = None validIdent = _re.match(r'[_A-Za-z][_0-9A-Za-z]*', arg) if arg[0] in '"\'' and arg[-1] == arg[0]: # assume it's a string val = arg[1:-1] elif _re.match(r'[+-]?(?:[0-9]*[.])?[0-9]+', arg) != None: if '.' in arg: val = float(arg) else: val = int(arg) elif arg.casefold() == 'true': val = True elif arg.casefold() == 'false': val = False #elif arg.casefold() == 'playerx' and self.player != None: # val = self.player.x #elif arg.casefold() == 'playery' and self.player != None: # val = self.player.y #elif arg.casefold() == 'playername' and self.player != None: # val = self.player.name #elif arg.casefold() == 'playerinv' and self.player != None: # val = self.player.inventory #elif arg.casefold() == 'playerdesc' and self.player != None: # val = self.player.description elif validIdent != None and env != None: group = validIdent.group() if 'scriptLocal' in env and group in env['scriptLocal']: val = env['scriptLocal'][group] elif group in env['global']: val = env['global'][group] else: return False # for if statements; if a variable doesn't exist, should evaluate to False # evaluate all values of all indecies openBracket = validIdent.end() if openBracket < len(arg): if arg[openBracket] == '[': ptr = openBracket depth = 0 while ptr < len(arg): if depth == 0 and arg[ptr] != '[': raise SequenceError('Invalid value syntax: {}'.format(arg)) if arg[ptr] == '[': depth += 1 elif arg[ptr] == ']': depth -= 1 if depth == 0: index = getValueFromString(arg[openBracket+1:ptr], env) if index in val: val = val[index] else: return False openBracket = ptr + 1 ptr += 1 else: raise SequenceError('Invalid value syntax: {}'.format(arg)) else: raise SequenceError('Invalid argument to getValueFromString: {}'.format(arg)) return val def compareValues(args: list, env: dict): """Generalized comparisons, may eventually be extended to other operators""" if len(args) == 1: return bool(getValueFromString(args[0]), env) elif len(args) == 3 and args[1] in ('==', '!=', '<=', '>=', '<', '>', 'in'): lval = getValueFromString(args[0], env) operator = args[1] rval = getValueFromString(args[2], env) if operator == '==': return lval == rval elif operator == '!=': return lval != rval elif operator == '<=': return lval <= rval elif operator == '>=': return lval >= rval elif operator == '<': return lval < rval elif operator == '>': return lval > rval elif operator == 'in': #if args[2].casefold() == 'playerinv': # return lval in self.player.thingNames #else: return lval in rval else: raise SequenceError("Condition cannot be evaluated: {}".format(' '.join(args))) def ifScript(args, env: dict, externalScripts: dict): """If statement: if [not] value [op value] script""" if len(args) < 2: raise GameError('Incomplete If statement: if {}'.format(' '.join(args))) inverse = False ret = False if args[0] == 'not': inverse = True args.pop(0) if len(args) < 2: raise GameError('Incomplete If statement: if {}'.format(' '.join(args))) # evaluate condition if len(args) > 1 and args[1] in ('==', '!=', '<=', '>=', '<', '>', 'in'): if len(args) < 4: raise SequenceError('Incomplete If statement: if {}'.format(' '.join(args))) ret = compareValues(args[:3], env) args = args[3:] else: ret = bool(getValueFromString(args[0]), env) args = args[1:] if inverse: ret = not ret # if condition is true, evaluate further if ret: if isinstance(args[-1], list): return runScript(args[-1], env, externalScripts) else: return runScript([args], env, externalScripts) def getCustomValue(args, env: dict): if env == None: raise SequenceError("Cannot get a value from an empty environment.") return getValueFromString(args[0], env) def setCustomValue(args, env: dict): """takes [customValue, op, value]""" if env == None: raise SequenceError("Cannot set a value from an empty environment.") scope = env['scriptLocal'] if len(args) > 0 and args[0] == 'global': scope = env['global'] args.pop(0) if len(args) < 3 or args[1] not in ('=', '+=', '-=', '*=', '/=', '%=', '//=', '**=', 'b=', '!=', '|=', '&=', '^='): raise SequenceError('Arguments are not fit for the setCustomValue script.') # This next line allows cvmod to use values in the thing's customValues dict, # but doing this means that accessing a game CV requires setting it to a local var. val = getValueFromString(args[2], env) # set the value to a default value (0, 0.0, '', etc.) if not yet assigned if args[0] not in scope and args[1] != '=': if isinstance(val, int): scope[args[0]] = 0 elif isinstance(val, float): scope[args[0]] = 0.0 elif isinstance(val, str): scope[args[0]] = '' elif isinstance(val, bool): scope[args[0]] = False # Beyond this point are types that can only be found from other customValues elif isinstance(val, tuple): scope[args[0]] = () elif isinstance(val, list): scope[args[0]] = [] elif isinstance(val, dict): scope[args[0]] = {} else: raise SequenceError("Operation not supported for unassigned custom values of type {}: {}" .format(type(val), args[1])) # done parsing, evaluate if args[1] == '=': scope[args[0]] = val elif args[1] == '+=': scope[args[0]] += val elif args[1] == '-=': scope[args[0]] -= val elif args[1] == '*=': scope[args[0]] *= val elif args[1] == '/=': scope[args[0]] /= val elif args[1] == '%=': scope[args[0]] %= val elif args[1] == '//=': scope[args[0]] //= val elif args[1] == '**=': scope[args[0]] **= val elif args[1] == 'b=': scope[args[0]] = bool(val) elif args[1] == '!=': scope[args[0]] = not bool(val) elif args[1] == '|=': scope[args[0]] |= val elif args[1] == '&=': scope[args[0]] &= val elif args[1] == '^=': scope[args[0]] ^= val return scope[args[0]] def delCustomValue(args, env: dict): """To clean up after a map.""" if env == None: raise SequenceError("Cannot delete a value from an empty environment.") for i in args: if i in env['global']: del(env['global'][i]) _requireEnv = {'get' : getCustomValue, 'set' : setCustomValue, 'del' : delCustomValue} _requireScripts = {'if' : ifScript} def runScript(script: list, env: dict, externalScripts: dict): """run a script""" ret = False for line in script: if len(line) == 0: # empty line continue elif line[0].casefold() == 'playercmd': # run a player command self.getIO('playercmd')(line[1:]) elif line[0].casefold() in _requireScripts: ret = _requireScripts[line[0]](line[1:], env, externalScripts) elif line[0].casefold() in _requireEnv: ret = _requireEnv[line[0]](line[1:], env) elif line[0].casefold() == 'break': # exit early return _ScriptBreak(ret) elif line[0] in externalScripts: # run a script ret = externalScripts[line[0]](line[1:]) else: # conditional evaluation compareValues(line, env) if isinstance(ret, ScriptBreak): return ret # We specifically want the return value of the last line executed. return ret def parseScript(instr: str, envGlobal: dict, externalScripts: dict): """parses then runs a script.""" #print('parseScript called with {}.'.format(instr)) if instr == '': return # nothing to be done. literalStr = list(instr) inQuotes = False script = [] stack = [] ret = [] argStart = 0 c = 0 l = 0 while c < len(instr): if inQuotes: if instr[c] == '"': if instr[c-1] == '\\': del literalStr[l-1] l -= 1 else: inQuotes = False del literalStr[l] l -= 1 else: if instr[c] == '"': # quoted string if l > 0 and instr[c-1] == '\\': del literalStr[l-1] l -= 1 else: inQuotes = True del literalStr[l] l -= 1 elif instr[c] in ' \t\n;}{': if l > 0 and instr[c-1] == '\\': del literalStr[l-1] l -= 1 else: if argStart != l: ret.append(''.join(literalStr[argStart:l])) if instr[c] == ';': if len(ret) > 0: script.append(ret) ret = [] elif instr[c] == '{': # block stack.append(script) script.append(ret) script = [] ret.append(script) ret = [] del literalStr[l] l -= 1 elif instr[c] == '}': # close block if len(ret) > 0: script.append(ret) ret = [] script = stack.pop() del literalStr[l] l -= 1 argStart = l + 1 c += 1 l += 1 if argStart != l: ret.append(''.join(literalStr[argStart:l])) if len(ret) > 0: script.append(ret) #print('After parsing: {}'.format(script)) env = {'global' : envGlobal, 'scriptLocal' : {}} #self.customValues['scriptLocal'] = {} ret = runScript(script, env, externalScripts) if isinstance(ret, ScriptBreak): ret = ret.value return ret