Fixed most input validation bugs, and did too many improvements to remember. Did I mention that I am typing these words with my hands?
This commit is contained in:
parent
ee5c4da549
commit
4a398cc2a3
11 changed files with 546 additions and 253 deletions
116
gamesequence.py
116
gamesequence.py
|
@ -1,48 +1,92 @@
|
|||
# gamesequence.py
|
||||
"""This contains the functions and classes necessary to parse and execute the scripting language.
|
||||
|
||||
Classes:
|
||||
- SequenceError: Derived from RuntimeError.
|
||||
- ScriptBreak: Represent a 'break' statement.
|
||||
- ScriptEnvironment: Represent the environment of a script.
|
||||
|
||||
Functions:
|
||||
- getValueFromString: Translate a literal or variable to a value.
|
||||
- compareValues: compare values for a conditional statement.
|
||||
- ifScript: Execute an 'if' statement.
|
||||
- getCustomValue: Get the value of a global variable.
|
||||
- setCustomValue: Set the value of a global variable.
|
||||
- delCustomValue: Delete a global variable.
|
||||
- runScript: Execute a script.
|
||||
- parseScript: Parse a script.
|
||||
"""
|
||||
|
||||
import re as _re
|
||||
|
||||
class SequenceError(RuntimeError):
|
||||
"""Derived from RuntimeError.
|
||||
|
||||
Raise whenever there is a runtime execution problem while parsing or executing a script.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class ScriptBreak(object):
|
||||
"""Class created when a 'break' statement is read."""
|
||||
|
||||
def __init__(self, value):
|
||||
"""Create a script break object with the value returned from the script broken from."""
|
||||
self.value = value
|
||||
|
||||
|
||||
def getValueFromString(arg: str, env: dict):
|
||||
#if env == None:
|
||||
#env = self.customValues
|
||||
class ScriptEnvironment(object):
|
||||
"""Represent the environment of a script.
|
||||
|
||||
This contains a dictionary of all global variables, as well as local variables."""
|
||||
|
||||
def __init__(self, globalVars: dict, localVars: dict):
|
||||
"""Create a script environment.
|
||||
|
||||
Global vars will survive beyond the end of the script.
|
||||
Local vars are all dropped at the end of a script."""
|
||||
|
||||
if not isinstance(globalVars, dict):
|
||||
raise TypeError("Global variables must be in a dictionary.")
|
||||
if not isinstance(globalVars, dict):
|
||||
raise TypeError("Local variables must be in a dictionary.")
|
||||
self.globalVars = globalVars
|
||||
self.localVars = localVars
|
||||
|
||||
def getValueFromString(arg: str, env: ScriptEnvironment):
|
||||
"""Translate a literal or variable name into a value.
|
||||
|
||||
arg should be a string representing a literal or identifier.
|
||||
env should be a ScriptEnvironment.
|
||||
Return the value described by the first literal or variable name."""
|
||||
val = None
|
||||
# We test for a valid identifier here, before all the if-elif starts.
|
||||
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
|
||||
if arg[0] in '"\'' and arg[-1] == arg[0]:
|
||||
# The argument is a string literal.
|
||||
val = arg[1:-1]
|
||||
elif _re.match(r'[+-]?(?:[0-9]*[.])?[0-9]+', arg) != None:
|
||||
# The argument is a number.
|
||||
if '.' in arg:
|
||||
# The argument is a float.
|
||||
val = float(arg)
|
||||
else:
|
||||
# The argument is an int.
|
||||
val = int(arg)
|
||||
elif arg.casefold() == 'true':
|
||||
# The argument is the boolean value 'true'.
|
||||
val = True
|
||||
elif arg.casefold() == 'false':
|
||||
# The argument is the boolean value '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:
|
||||
# The argument is a variable name.
|
||||
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]
|
||||
if group in env.localVars:
|
||||
# The variable is local.
|
||||
val = env.localVars[group]
|
||||
elif group in env.globalVars:
|
||||
# The variable is global.
|
||||
val = env.globalVars[group]
|
||||
else:
|
||||
return False # for if statements; if a variable doesn't exist, should evaluate to False
|
||||
# evaluate all values of all indecies
|
||||
|
@ -67,16 +111,19 @@ def getValueFromString(arg: str, env: dict):
|
|||
return False
|
||||
openBracket = ptr + 1
|
||||
ptr += 1
|
||||
if depth != 0:
|
||||
#depth should always == 0 at this point
|
||||
raise SequenceError('Mismatched number of open and close brackets: {}'.format(arg))
|
||||
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):
|
||||
def compareValues(args: list, env: ScriptEnvironment):
|
||||
"""Generalized comparisons, may eventually be extended to other operators"""
|
||||
if len(args) == 1:
|
||||
return bool(getValueFromString(args[0]), env)
|
||||
return bool(getValueFromString(args[0], env))
|
||||
elif len(args) == 3 and args[1] in ('==', '!=', '<=', '>=', '<', '>', 'in'):
|
||||
lval = getValueFromString(args[0], env)
|
||||
operator = args[1]
|
||||
|
@ -94,14 +141,11 @@ def compareValues(args: list, env: dict):
|
|||
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):
|
||||
def ifScript(args, env: ScriptEnvironment, externalScripts: dict):
|
||||
"""If statement: if [not] value [op value] script"""
|
||||
if len(args) < 2:
|
||||
raise GameError('Incomplete If statement: if {}'.format(' '.join(args)))
|
||||
|
@ -120,7 +164,7 @@ def ifScript(args, env: dict, externalScripts: dict):
|
|||
ret = compareValues(args[:3], env)
|
||||
args = args[3:]
|
||||
else:
|
||||
ret = bool(getValueFromString(args[0]), env)
|
||||
ret = bool(getValueFromString(args[0], env))
|
||||
args = args[1:]
|
||||
if inverse:
|
||||
ret = not ret
|
||||
|
@ -141,9 +185,9 @@ 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']
|
||||
scope = env.localVars
|
||||
if len(args) > 0 and args[0] == 'global':
|
||||
scope = env['global']
|
||||
scope = env.globalVars
|
||||
args.pop(0)
|
||||
if len(args) < 3 or args[1] not in ('=', '+=', '-=', '*=', '/=', '%=', '//=', '**=', 'b=', '!=', '|=', '&=', '^='):
|
||||
raise SequenceError('Arguments are not fit for the setCustomValue script.')
|
||||
|
@ -201,18 +245,18 @@ def setCustomValue(args, env: dict):
|
|||
scope[args[0]] ^= val
|
||||
return scope[args[0]]
|
||||
|
||||
def delCustomValue(args, env: dict):
|
||||
def delCustomValue(args, env: ScriptEnvironment):
|
||||
"""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])
|
||||
if i in env.globalVars:
|
||||
del(env.globalVars[i])
|
||||
|
||||
_requireEnv = {'get' : getCustomValue, 'set' : setCustomValue, 'del' : delCustomValue}
|
||||
_requireScripts = {'if' : ifScript}
|
||||
|
||||
def runScript(script: list, env: dict, externalScripts: dict):
|
||||
def runScript(script: list, env: ScriptEnvironment, externalScripts: dict):
|
||||
"""run a script"""
|
||||
ret = False
|
||||
for line in script:
|
||||
|
@ -228,13 +272,13 @@ def runScript(script: list, env: dict, externalScripts: dict):
|
|||
ret = _requireEnv[line[0]](line[1:], env)
|
||||
elif line[0].casefold() == 'break':
|
||||
# exit early
|
||||
return _ScriptBreak(ret)
|
||||
return ScriptBreak(ret)
|
||||
elif line[0] in externalScripts:
|
||||
# run a script
|
||||
ret = externalScripts[line[0]](line[1:])
|
||||
else:
|
||||
# conditional evaluation
|
||||
compareValues(line, env)
|
||||
return compareValues(line, env)
|
||||
|
||||
if isinstance(ret, ScriptBreak):
|
||||
return ret
|
||||
|
@ -306,9 +350,7 @@ def parseScript(instr: str, envGlobal: dict, externalScripts: dict):
|
|||
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'] = {}
|
||||
env = ScriptEnvironment(envGlobal, {})
|
||||
ret = runScript(script, env, externalScripts)
|
||||
if isinstance(ret, ScriptBreak):
|
||||
ret = ret.value
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue