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 :) -- http://mail.python.org/mailman/listinfo/python-list