# An alternative specification for the Cif Parser, based on Yapps2
# by Amit Patel (http://theory.stanford.edu/~amitp/Yapps)
#
# helper code: we define our match tokens
lastval = ''
def monitor(location,value):
    global lastval
    #print 'At %s: %s' % (location,`value`)
    lastval = `value`
    return value

def stripextras(value):
    # we get rid of semicolons and leading/trailing terminators etc.
     import re
     jj = re.compile("[\n\r\f \t\v]*")
     semis = re.compile("[\n\r\f \t\v]*[\n\r\f]\n*;")
     cut = semis.match(value)
     if cut:
          nv = value[cut.end():len(value)-2]
          if nv[-1]=='\r': nv = nv[:-1]
     else: nv = value
     cut = jj.match(nv)
     if cut:
          return stripstring(nv[cut.end():])
     return nv

# helper function to get rid of inverted commas etc.

def stripstring(value):
     if value:
         if value[0]== '\'' and value[-1]=='\'':
           return value[1:-1]
         if value[0]=='"' and value[-1]=='"':
           return value[1:-1]
     return value

# helper function to create a dictionary given a set of
# looped datanames and data values

def makeloop(namelist,itemlist,context):
    noitems = len(namelist)
    nopoints = divmod(len(itemlist),noitems)
    if nopoints[1]!=0:    #mismatch
        raise "CifError:loop item mismatch"
    nopoints = nopoints[0]
    newdict = {}
    for i in range(0,noitems):
        templist = []
        for j in range(0,nopoints):
            templist.append(itemlist[j*noitems + i])
        newdict.update({namelist[i]:templist})
    context.append(newdict)
    #print 'Contructed loop with items: '+`newdict`
    return {}    # to keep things easy

#

from string import *
import re
from yappsrt import *

class CifParserScanner(Scanner):
    patterns = [
        ('([ \t\n\r](?!;))|[ \t]', re.compile('([ \t\n\r](?!;))|[ \t]')),
        ('#.*[\n\r](?!;)', re.compile('#.*[\n\r](?!;)')),
        ('#.*', re.compile('#.*')),
        ('LBLOCK', re.compile('(L|l)(O|o)(O|o)(P|p)_')),
        ('save_heading', re.compile('(S|s)(A|a)(V|v)(E|e)_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_-]+')),
        ('save_end', re.compile('(S|s)(A|a)(V|v)(E|e)_')),
        ('RESERVED', re.compile('((G|g)(L|l)(O|o)(B|b)(A|a)(L|l)_)|((S|s)(T|t)(O|o)(P|p)_)')),
        ('data_name', re.compile('_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_-]+')),
        ('data_heading', re.compile('(D|d)(A|a)(T|t)(A|a)_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_-]+')),
        ('start_sc_line', re.compile('(\n|\r\n);([^\n\r])*(\r\n|\r|\n)+')),
        ('sc_line_of_text', re.compile('[^;\r\n]([^\r\n])*(\r\n|\r|\n)+')),
        ('end_sc_line', re.compile(';')),
        ('data_value_1', re.compile('((?!(((S|s)(A|a)(V|v)(E|e)_[^\\s]*)|((G|g)(L|l)(O|o)(B|b)(A|a)(L|l)_[^\\s]*)|((S|s)(T|t)(O|o)(P|p)_[^\\s]*)|((D|d)(A|a)(T|t)(A|a)_[^\\s]*)))[^\\s"#$\'_\\[\\]][^\\s]*)|\'((\'(?=\\S))|([^\n\r\x0c\']))*\'+|"(("(?=\\S))|([^\n\r"]))*"+')),
        ('END', re.compile('$')),
    ]
    def __init__(self, str):
        Scanner.__init__(self,None,['([ \t\n\r](?!;))|[ \t]', '#.*[\n\r](?!;)', '#.*'],str)

class CifParser(Parser):
    def input(self):
        _token_ = self._peek('END', 'data_heading')
        if _token_ == 'data_heading':
            dblock = self.dblock()
            maindict = dblock
            while self._peek('END', 'data_heading') == 'data_heading':
                dblock = self.dblock()
                maindict.update(monitor('input',dblock))
            END = self._scan('END')
        else: # == 'END'
            END = self._scan('END')
            maindict = {}
        return maindict

    def dblock(self):
        data_heading = self._scan('data_heading')
        dict={data_heading[5:]:{"loops":[]} }
        while self._peek('save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading') not in ['END', 'data_heading']:
            _token_ = self._peek('save_heading', 'LBLOCK', 'data_name')
            if _token_ != 'save_heading':
                dataseq = self.dataseq(dict[data_heading[5:]]["loops"])
                dict[data_heading[5:]].update(dataseq)
            else: # == 'save_heading'
                save_frame = self.save_frame()
        return monitor('dblock',dict)

    def dataseq(self, loop_array):
        data = self.data(loop_array)
        datadict=data
        while self._peek('LBLOCK', 'data_name', 'save_heading', 'END', 'data_heading') in ['LBLOCK', 'data_name']:
            data = self.data(loop_array)
            datadict.update(data)
        return monitor('dataseq',datadict)

    def data(self, loop_array):
        _token_ = self._peek('LBLOCK', 'data_name')
        if _token_ == 'LBLOCK':
            data_loop = self.data_loop(loop_array)
            return data_loop
        else: # == 'data_name'
            datakvpair = self.datakvpair()
            return datakvpair

    def datakvpair(self):
        data_name = self._scan('data_name')
        data_value = self.data_value()
        return {data_name:data_value}

    def data_value(self):
        _token_ = self._peek('data_value_1', 'start_sc_line')
        if _token_ == 'data_value_1':
            data_value_1 = self._scan('data_value_1')
            thisval = stripstring(data_value_1)
        else: # == 'start_sc_line'
            sc_lines_of_text = self.sc_lines_of_text()
            thisval = stripextras(sc_lines_of_text)
        return monitor('data_value',thisval)

    def sc_lines_of_text(self):
        start_sc_line = self._scan('start_sc_line')
        lines = start_sc_line
        while self._peek('sc_line_of_text', 'end_sc_line') == 'sc_line_of_text':
            sc_line_of_text = self._scan('sc_line_of_text')
            lines = lines+sc_line_of_text
        end_sc_line = self._scan('end_sc_line')
        return monitor('sc_line_of_text',lines+end_sc_line)

    def data_loop(self, loop_array):
        LBLOCK = self._scan('LBLOCK')
        loopfield = self.loopfield()
        loopvalues = self.loopvalues()
        return makeloop(loopfield,loopvalues,loop_array)

    def loopfield(self):
        data_name = self._scan('data_name')
        loop=[data_name]
        while self._peek('data_name', 'data_value_1', 'start_sc_line') == 'data_name':
            data_name = self._scan('data_name')
            loop.append(data_name)
        return loop

    def loopvalues(self):
        data_value = self.data_value()
        loop=[data_value]
        while self._peek('data_value_1', 'start_sc_line', 'save_end', 'LBLOCK', 'data_name', 'save_heading', 'END', 'data_heading') in ['data_value_1', 'start_sc_line']:
            data_value = self.data_value()
            loop.append(monitor('loopval',data_value))
        return loop

    def save_frame(self):
        save_heading = self._scan('save_heading')
        while self._peek('save_end', 'LBLOCK', 'data_name', 'save_heading', 'END', 'data_heading') in ['LBLOCK', 'data_name']:
            data = self.data({"dummydict":0})
        save_end = self._scan('save_end')


def parse(rule, text):
    P = CifParser(CifParserScanner(text))
    return wrap_error_reporter(P, rule)



