How to access object attributes given a string

Santiago Romero sromero at gmail.com
Tue Feb 12 16:05:39 EST 2008


 And the big functions:

 I imagine that the following is HORRIBLE in the pythonic-vision and
surely can be rewriten with a single map+reduce+filter + 200 lambdas
functions X-D, but I come from C and any advice on how to implement my
"simple scripting language" without using lex or yacc is welcome :)



#--------------------------------------------------------------------
def ExecParser_Parse( key, value, line, file ):
   """
   Parses an exec or exec2 line, generating a list of "opcodes".

   This function takes an exec= line from a OBJ file and parses it
   generating a list of "executable opcodes" in a format executable
   by ExecParser_Exec().

   The rules for this "small" language are:
   - Spaces and tabs are stripped.
   - Comments (starting by REM) are ignored.
   - A simple set of commands is available. Those commands modify
     some game variables or objects. As an example: PLAYSOUND(snd),
     plays the sound identified by the sound_tag "snd".
   - Commands must be separated by ";" characters.
   - Commands can receive parameters enclosed between ( and ) of
     types INT or STR.
   - Programmer can use "self" to refer to the current object in
     functions that accept an object or enemy text id.
   - Simple control flow is present with IF, ELIF, ELSE and ENDIF
     statements.
   - IF and ELIF statemens are followed by a BOOLEAN command, which
     will return 0 or 1.

   Example ('\' character wraps lines in the OBJ file):

      KILLOBJECT(coin);\
      REM Wait 5 seconds;\
      IF FLAG(5);\
         SLEEP(5);\
         PLAYERSAY(test,5);\
         SETMAP(10,10,top,5);\
         IF FLAG(6);\
            NOP();\
            SLEEP(7);\
         ELIF FLAG(7);\
            NOP();\
            SLEEP(9);\
         ELSE;\
            SLEEP(999);\
         ENDIF;\
         IF FLAG_VALUE(7,1);\
            CHANGESCREEN(start,10,100);\
            PLAYERFACING(0);\
         ENDIF;\
      ENDIF;\
      SLEEP(12);\
      SLEEP(11);

   This function will parse the exec line and produce as output
opcodes in
   this format:

   [ type_of_exec, if_level, opcode, parameters ]

   type_of_exec = 1 for exec= lines, and 2 for exec2= lines.
   if_level is the current code "level" or scope. IF statements
increase
            if_level and ENDIF statements decrease it.
   opcode and parameters are the function_name and the params for this
command.

   Example:

     ExecType  CodeLevel      Opcode and params
  ----------------------------------------------------------
      1        0        KILLOBJECT ['coin']
      0        0        REM ['Wait 5 seconds']
      1        0        IF FLAG [5]
      1        1        SLEEP [5]
      1        1        PLAYERSAY ['test', 5]
      1        1        SETMAP [10, 10, 'top', 5]
      1        1        IF FLAG [6]
      1        2        NOP
      1        2        SLEEP [7]
      1        1        ENDIF
      1        0        ENDIF
      1        0        END


   The function makes some small checkings, like:
count(IF)==count(ENDIFs),
   check number of arguments, argument type checking, validate command
   functions, and some others, but it does not cover all possible
syntax
   errors or typing mistakes.
   """

   reserved_words = {
   "SETMAP" : ( 4, "int", "int", "str", "int" ),
   "KILLOBJECT" : ( 1, "str" ),
   "ENABLEOBJECT" : ( 1, "str" ),
   "DISABLEOBJECT" : ( 1, "str" ),
   "PLAYSOUND" : ( 1, "str" ),
   "PLAYMUSIC" : ( 1, "str" ),
   "SLEEPCYCLES" : ( 1, "int" ),
   "SLEEP" : ( 1, "int" ),
   "SHOWTEXT" : ( 1, "str" ),
   "SHOWTEXTTIMED" : ( 2, "str", "int" ),
   "SHOWSCREEN" : ( 2, "str", "int" ),
   "CHANGESCREEN" : ( 3, "str", "int", "int" ),
   "ADDINVENTORY" : ( 1, "str" ),
   "REMOVEINVENTORY" : ( 1, "str" ),
   "OBJECTFACING" : ( 2, "str", "int" ),
   "PLAYERFACING" : ( 1, "int" ),
   "PLAYERSAY" : ( 2, "str", "int" ),
   "OBJECTSAY" : ( 3, "str", "str", "int" ),
   "SETFLAG" : (2, "int", "int" ),
   "INCFLAG" : (1, "int" ),
   "DECFLAG" : (1, "int" ),
   "DELEXEC" : (1, "int" ),
   "REM" : ( 0, ),
   "NOP" : ( 0, ),
   "END" : ( 0, ),
   "TRUE" : ( 0, ),
   "FALSE" : ( 0, ),
   "IF" : ( 0, ),
   "ELIF" : ( 0, ),
   "ELSE" : ( 0, ),
   "ENDIF" : ( 0, ),
   "FLAG" : ( 1, "int" ),
   "NOT_FLAG" : ( 1, "int" ),
   "FLAG_VALUE" : ( 2, "int", "int" ),
   "PLAYER_HAS" : ( 1, "str" ),
   "PLAYER_HAS_NOT" : ( 1, "str" ),
   "SCREEN_IS" : ( 1, "str" ),
   "SCREEN_IS_NOT" : ( 1, "str" )
   }


   #input is something like: "exec=COMMAND;COMMAND;COMMAND..."

   if key.upper() == "EXEC":  exec_type = 1
   code = []                                  # Resulting code

   if ";" in value:  commands = value.split(";")
   else:             commands = list( value )

   # Filter empty lines
   commands = filter( lambda x: len(x) > 1, commands )

   code_level = level_inc = 0                 # Current scope level
   found_if = found_elif = 0

   # Parse all commands in the input script
   for cmd_counter, cmd in enumerate(commands):

      if cmd == '': continue
      cmd_upper = cmd.upper()
      cmderr = "(line %d:%d) of <%s>." % (line,cmd_counter+1,file)

      pos = cmd.find(')')
      if pos != -1 and pos != len(cmd)-1:
         print "OBJ-exec: Command format error in %s %s" %
(cmd,cmderr)
         continue

      # Delete spaces, CR and LFs
      if not cmd_upper.startswith("REM ") and \
         not cmd_upper.startswith("IF ") and \
         not cmd_upper.startswith("ELIF "):
            for i in " \n\r\t": cmd = cmd.replace(i, "")
            cmd_upper = cmd.upper()

      # Check special commands (without parameters):

      # REM and NOP commands, just add it to the list
      if cmd_upper.startswith("REM "):
         code.append( ( 0, code_level, "REM", [cmd[4:]] ) )
         continue
      elif cmd_upper.startswith("NOP"):
         code.append( ( exec_type, code_level, "NOP", [] ) )
         continue
      elif cmd_upper == "END":
         code.append( ( exec_type, code_level, "END", [] ) )
         continue

      # IF commands: increase code score and save the IF statement
      # boolean function will be processed later
      if cmd_upper.startswith("IF "):
         level_inc = 1
         found_if = 1
         cmd = cmd[2:].strip()
      # ELIF: save ELIF statement in previous level and code in next
      # boolean function will be processed later
      elif cmd_upper.startswith("ELIF "):
         level_inc = 1
         found_elif = 1
         if code_level > 0:   code_level -= 1
         else:                print "OBJ-exec: ELIF without IF %s" %
(cmderr)
         cmd = cmd[4:].strip()
      # ELSE: same as ELIF, but don't process any boolean function
      elif cmd_upper.startswith("ELSE"):
         if code_level > 0:   code_level -= 1
         else:                print "OBJ-exec: ELSE without IF %s" %
(cmderr)
         code.append( ( exec_type, code_level, "ELSE", [] ) )
         code_level += 1
         continue
      # ENDIF: descend a "scope" level
      elif cmd_upper.startswith("ENDIF"):
         if code_level > 0:
            code_level -= 1
            code.append( ( exec_type, code_level, "ENDIF", [] ) )
         else:
            print "OBJ-exec: ENDIF without IF %s" % (cmderr)
         continue

      # Process IF, ELIF boolean checks or any other command:

      # Commands with parameters, check for () and a function name:
      if not '(' in cmd or not ')' in cmd or cmd.startswith("("):
         print "OBJ-exec: Unknown command '%s' %s" % (cmd,cmderr)
         continue

      # Try to get the function name and uppercase it.
      try:
         rows = cmd.split('(')
         function = rows[0].upper()
      except:
         print "OBJ-exec: command error '%s' %s" % (cmd,cmderr)
         continue

      # Check if the function name is a valid/existing one:
      if function not in reserved_words:
         print "OBJ-exec: unknown function call '%s' %s" %
(function,cmderr)
         continue

      params = []
      # Try to get all the present parameters
      try:
         # Only 1 parameter present:
         if not ',' in rows[1]:
            params.append(rows[1].split(')')[0])
         # More than 1 parameter present, get them:
         else:
            paramlist = rows[1].split(')')[0]
            params.extend(paramlist.split(','))
      except:
         print "OBJ-exec: error getting params in '%s' %s" %
(function,cmderr)
         continue

      # Check if the number of parameters is correct:
      rightnumparam = reserved_words[function][0]
      if len(params) != rightnumparam:
         print "OBJ-exec: Found %d parameter(s) in %s instead of %d
%s" \
             % (len(params),function,rightnumparam,cmderr)
         continue

      # Check if all the params are of the right type (int or str)
      # Try to convert ints to int, store str as str
      for num_param in range(len(params)):
         valid_type = reserved_words[function][num_param+1]
         if valid_type == "int":
            try:
               params[num_param] = int(params[num_param])
            except:
               print "OBJ-exec: Parameter %s should be INT in %s %s" \
                   % (num_param,function,cmderr)
               continue

      if found_if == 1:
         opcode = ( exec_type, code_level, "IF " + function,
(params) )
         found_if = found_elif = 0
      elif found_elif == 1:
         opcode = ( exec_type, code_level, "ELIF " + function,
(params) )
         found_if = found_elif = 0
      else:
         opcode = ( exec_type, code_level, function, (params) )

      code.append(opcode)

      # Check if next instructions must be in a different code scope
      if level_inc != 0:
         code_level += level_inc
         level_inc = 0

   # Check consistence IF / ENDIF
   ifs = len(filter(lambda l: l[2].startswith("IF "), code))
   endifs = len(filter(lambda l: l[2].startswith("ENDIF"), code))
   if ifs > endifs:
      print "OBJ-exec: missing %d ENDIFs in line %s of <%s>." % (ifs-
endifs,line,file)
   elif ifs < endifs:
      print "OBJ-exec: missing %d IFs in line %s of <%s>." % (endifs-
ifs,line,file)

   # Check if we got as many opcodes as commands
   num_opcodes = len(code)
   num_commands = len(commands)
   if num_opcodes != num_commands:
      print "OBJ-exec: COMPILE ERROR; got %s opcodes from %s commands
in line %s of <%s>." \
          % (num_opcodes,num_commands,line,file)

   code.append( (exec_type, 0, "END", []) )
   return code



 Thanks for any advice :)



More information about the Python-list mailing list