316 lines
11 KiB
Python
316 lines
11 KiB
Python
|
# 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
|