Newer
Older
atlantis / src / interpreter.py
#!/usr/bin/python3
# 
# Atlantis is a framework for creating multi-user dungeon worlds.
# This module reads in a world file and parses it. This is where the
# structure of the Atlantis language is defined.
#
# Licensed under the terms of the GPLv3
# author: Daniel Vedder
# date: 02/05/2015
#

from world import World


class DefineCommand(object):
    '''
    This is the super class for all define commands. It should be extended
    for each individual command, like define-place, define-monster, etc.

    Each define command works by creating the relevant object, then fleshing
    it out as more options are passed to it. Finally, the finished object is
    returned.
    '''

    def __init__(self, name, doc_string):
        self.name = name
        self.doc_string = doc_string
        self.option_registry = dict()

    def init_object(self, object_name):
        '''
        Initialize the object this command creates
        '''
        raise NotImplementedError

    def return_object(self):
        '''
        Return the type of the game object ('place', 'npc', 'item' or
        'monster') and the object itself
        '''
        raise NotImplementedError

    def pass_option(self, option_name, option_argument):
        '''
        Pass this define command one of its options and the relevant argument
        '''
        self.option_registry[option_name]["function"](option_argument)

    def add_option(self, option_name, option_docstring, option_function):
        '''
        Add an option for this define command. Arguments:
        option_name: The name of this option as it would appear in
                     an atl source file
        option_docstring: A description of this option
        option_function: The function that this option calls
                     (this function should take exactly one string argument)
        '''
        option_dict = dict["docstring":option_docstring,
                           "function":option_function]
        self.option_registry[option_name] = option_dict


define_command_registry = dict()

def register_define_command(def_com):
    define_command_registry[def_com.name] = def_com


class Parser(object):
    '''
    The actual parser class. It reads in a world file and transforms it into
    a series of DefineCommands, which are then executed
    '''

    def __init__(self, world_file_name):
        self.line_command_registry = dict()
        self.world = World()
        self.add_line_commands()
        self.load(world_file_name)
        return self.world

    def load(self, world_file_name):
        '''
        Load a world file and pass it on to the interpreter
        '''
        try:
            world_file = open(world_file_name, 'r')
            atl_text = world_file.readlines()
            world_file.close()
        except IOError:
            print("Failed to load world file '"+world_file_name+"'!")
        self.interpret_atl(atl_text)

    def add_line_commands(self):
        '''
        Line commands are ATL constructs that only take up one line
        (unlike define commands). All line commands must be registered in
        this method by adding them to the line_command_registry and setting
        their key-value to the method to be called when their key appears
        in an ATL source file.
        '''
        self.line_command_registry["load"] = self.load
        self.line_command_registry["start-place"] = self.world.set_starting_place

    def interpret_atl(self, atl_source):
        '''
        This method interprets a list of atl source lines, passing them
        on to the relevant define commands.
        '''
        define_command = None # The define command currently being executed
        line_no = 0
        for line in atl_source:
            # TODO allow for linebreaks
            line_no = line_no + 1
            if line[0] == "#":
                pass #comments are ignored
            elif (line == "" or line == "\n") and define_command:
                # Empty lines finish off define blocks
                object_type, game_object = define_command.return_object()
                self.world.add_object(object_type, game_object)
                define_command = None
            else:
                print("Line: '"+line+"'")
                command = line.split()[0]
                # execute a line command
                if command in line_command_registry.keys():
                    line_command_registry[command](line[line.find(" ")+1:])
                # start of a define block
                elif command in define_command_registry.keys():
                    define_command = define_command_registry[command]
                    object_name = line[line.find(" ")+1:]
                    define_command.init_object(object_name)
                # parse an option command
                elif line[0] == " " or line[0] == "\t":
                    while line[0] == " " or line[0] == "\t":
                        line = line[1:]
                    if line and define_command:
                        option_command = line.split()[0]
                        option_arg = line[line.find(" ")+1:]
                        define_command.pass_option(option_command, option_arg)
                    else:
                        # XXX: What should be done here? Do nothing, raise a
                        # syntax error, or something else?
                        print("Unrecognized syntax in line "+str(line_no)+"!")