'''Parser dan compiler file isi halaman untuk situs AdhiHargo.net.

Sangat sederhana, dikodekan kurang dari satu jam.

Author          : Adhi Hargo
Last modified   : 21/04/2006 0:08:41
'''
__all__ = [ 'Parser',
            'ParserError',
            'Compiler',
            'CompilerError' ]

# Bila sedang dalam definisi sebuah label, semua karakter whitespace di bawah akan
# diganti dengan karakter spasi biasa.
WSPACE = [' ','\w','\r','\f','\v','\t']

# Pasangan LABEL--DEFINISI_LABEL harus berada dalam baris terpisah, namun karena
# baris kosong boleh berada dalam definisi label, harus ada penanda khusus.
# Penanda tersebut harus berada di satu baris tersendiri, dan hanya berisi satu
# karakter non-whitespace, yaitu...
DEFINITION_END = '%'

# Karakter ini memisahkan antara label dan definisi label.
MAK_COMBLANG = '='

# ==============================================================================
#                           Parser File Isi
# ==============================================================================
class Parser:

    def __init__(self):
        self.reset()

    def reset(self):
        self.tree = {}
        self.__in_def__ = False
        self.__current_label__ = []
        self.__current_def__ = []
        self.__current_line__ = ''
        self.__current_cidx__ = 0 # Indeks karakter yang terakhir diolah

    def feed(self,object):
        '''Memasukkan string input, secara implisit mengaktifkan parser.

        Argumen harus berupa string input atau struktur data iterable yang
        memuat string input tersebut. Hasil parse dapat diambil dalam atribut
        "tree" milik objek parser yang bersangkutan, bila proses parsing
        berjalan dengan baik.'''
        if type(object) is type(''):
            self.__parse_line__(object)
        elif hasattr(object,'__iter__'):
            for line in object:
                self.__parse_line__(line)
        else:
            raise TypeError, 'Objek yang dimasukkan harus string atau iterable.'

    def __parse_line__(self, line):
        self.__current_line__ = line
        self.__current_cidx__ = 0
        if line.strip() == DEFINITION_END:
            if self.__in_def__:
                label_name  = ''.join(self.__current_label__).strip()
                label_def   = ''.join(self.__current_def__)
                self.tree[label_name] = label_def
                self.__current_label__ = []
                self.__current_def__ = []
            self.__in_def__ = False
        elif self.__in_def__:
            self.__handle_def__()
        else:
            self.__handle_label__()

    def __handle_label__(self):
        line = self.__current_line__
        for i in range(self.__current_cidx__, len(line)):
            c = line[i]
            if c is MAK_COMBLANG:
                self.__current_cidx__ = i + 1
                self.__in_def__ = True
                break
            else:
                self.__current_label__.append(c)
        self.__handle_def__()

    def __handle_def__(self):
        line = self.__current_line__
        for c in line[self.__current_cidx__:]:
            if c in WSPACE:
                self.__current_def__.append(' ')
            else: self.__current_def__.append(c)

# ==============================================================================
#                           Kompiler File Isi
# ==============================================================================
class Compiler:
    def __init__(self):
        self.reset()
    def reset(self):
        self.string = ''
        self.strings = []

    def feed(self,object):
        '''Memasukkan struktur input, secara implisit mengaktifkan kompiler.

        Argumen harus berupa struktur data dictionary. Iterator untuk string
        keluaran diambil dari atribut "strings" dari objek kompiler yang
        bersangkutan.
        '''
        if not type(object) is type({}):
            raise CompilerError, 'Objek yang dimasukkan harus bertipe dictionary.'
        for label in object.keys():
            label_def = str(object[label])
            def_str = "%s %s %s\n%s\n" % (label, MAK_COMBLANG, label_def, DEFINITION_END)
            self.strings.append(def_str)

# ==============================================================================

class ParserError(StandardError):
    pass
class CompilerError(StandardError):
    pass

# ==============================================================================
if __name__ == '__main__':
    dummy = '''
Adhi Hargo = asdmkv;c adsdsa;lk das
dsajk;sd lasd ;lksa d
%

Gentur Prabowo = Nih, boleh ada spasi di atas label.
dsaodsapo

dsapodksaodksa
%
Marimutu = Halo, nama saya Marimutu. Perut saya gendut.

Namun bagaimanapun saya hanya pengusaha, bukan koruptor. Yang bisa korupsi kan
cuma pejabat?
%Ooooo. Kalau ada string non-whitespace di belakang separator, tetap ikut!
%
    '''.split('\n')
    parser = Parser()
    parser.feed(dummy)
    compiler = Compiler()
    compiler.feed(parser.tree)
    print "Pohon hasil parsing = \n%s\n" % parser.tree
    print "String hasil kompilasi pohon parse = \n%s\n" % ''.join(compiler.strings)