# GNU Solfege - eartraining for GNOME
# Copyright (C) 2001  Tom Cato Amundsen
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import sys, os, os.path
if __name__ == "__main__":
    sys.path.append(".")
import gettext
from i18n import _
import re, string
tokens = (
 'NAME', 'EQUALS', 'STRING', 'I18NSTRING', 'INTEGER', 'LBRACE',
 'RBRACE', 'COMMENT', 'PLUS', 'COMMA', 'PERCENT', 'DIVIDE',
 'BLOCKSTART', 'EQUALS_EXP', 'PLUS_EXP', 'DIVIDE_EXP', 'PERCENT_EXP',
 'COMMA_EXP', 'TEMPO', 'INCLUDE')

for t in tokens:
    globals()[t] = t

re_INCLUDE = re.compile("include*\(([\w-]*)\)")
re_ALL = re.compile(r"""
            (?P<keyword>[a-zA-Z]+[\w_-]*(\(.*?\))?)|
        ###### xstring
          (
           \"\"\"(?P<xstring>(.*?))\"\"\"
           )|
        ###### string
            ((?P<i18npre>_\()?\"
            (?P<string>[^\"]*)
            \"(?P<i18npost>\))?)|
        ###### integer
            (?P<integer>-?\d+)|
        ###### rational
            (?P<rat_num>\d+)/(?P<rat_den>\d+)|
        ###### comment
            (?P<comment>\#.*?$)
            """, re.VERBOSE|re.MULTILINE|re.DOTALL)

class DataparserException:
    def __init__(self, filename, lineno, badcode):
        self.filename = filename
        self.lineno = lineno
        self.badcode = badcode
    def __str__(self):
        return "dataparser-error, lineno: %i\nThis code is invalid: %s: %s" % (self.filename, self.lineno, self.badcode)


class Dataparser:
    def __init__(self, globals={}, gd=[]):
        self.m_globals = globals
        self.m_gd = gd
    def parse_file(self, filename, only_header=0):
        self.m_filename = [filename]
        v = []
        self.m_blocks = []
        context = self.m_globals
        v = self.get_tokens(filename, only_header)
        v = self.do_include(v)
        for op in (PERCENT, PLUS, DIVIDE, COMMA, EQUALS):
            v = self.do_simple_op(v, op)
        v = self.do_blockstart(v)
        for ex in v:
            if ex[0] == EQUALS_EXP:
                context[ex[1][1]] = self.evaluate(ex[2])
            elif ex[0] == BLOCKSTART:
                assert context == self.m_globals
                self.m_blocks.append({})
                context = self.m_blocks[-1]
                context['blocktype'] = ex[1][1]
            elif ex[0] == RBRACE:
                for n in self.m_gd:
                    if not context.has_key(n):
                        context[n] = self.m_globals[n]
                context = self.m_globals
            elif ex[0] == STRING:
                # the shortcut that interprets a single string
                # "this is a string" as
                # music = "this is a string"
                context['music'] = ex[1]
            elif ex[0] == PERCENT_EXP:
                # the shortcut that interprets a single string
                # "this is a string" as
                # music = "this is a string"
                context['music'] = self.evaluate(ex)
            elif ex[0] == PLUS_EXP:
                # the shortcut that interprets a single string
                # "this is a string" as
                # music = "this is a string"
                context['music'] = self.evaluate(ex)
            else:
                raise "Parse error in '%s', line %i" % (ex[2], ex[3]), ex[1]
    def get_tokens(self, filename, first_block_only):
        f = open(filename, "r")
        s = f.read()
        f.close()
        linecount = 1
        p = 0
        v = []
        slen = len(s)
        while p < slen:
            # doing simple comparations is faster than using a
            # regular expression
            if s[p] in " \n\t{=}%+,/":
                if s[p] == ' ':
                    p += 1
                    continue
                if s[p] == '\n':
                    p += 1
                    linecount += 1
                    continue
                if s[p] == '\t':
                    p += 1
                    continue
                if s[p] == '{':
                    p += 1
                    v.append((LBRACE, '{', filename, linecount))
                    continue
                elif s[p] == '=':
                    p += 1
                    v.append((EQUALS, '=', filename, linecount))
                    continue
                elif s[p] == '}':
                    p += 1
                    v.append((RBRACE, '}', filename, linecount))
                    if first_block_only:
                        break
                    continue
                elif s[p] == '%':
                    p += 1
                    v.append((PERCENT, '%', filename, linecount))
                    continue
                elif s[p] == '+':
                    p += 1
                    v.append((PLUS, '+', filename, linecount))
                    continue
                elif s[p] == ',':
                    p += 1
                    v.append((COMMA, ',', filename, linecount))
                    continue
                elif s[p] == '/':
                    p += 1
                    v.append((DIVIDE, '/', filename, linecount))
                    continue
            m = re_ALL.match(s[p:])
            if m:
                if m.group('integer'):
                    p += m.end()
                    v.append((INTEGER, int(m.group('integer')),
                              filename, linecount))
                    continue
                if m.group('string'):
                    p += m.end()
                    linecount = linecount + string.count(m.group(), '\n')
                    if m.group('i18npre'):
                        v.append((I18NSTRING, m.group('string'),
                                  filename, linecount))
                    else:
                        v.append((STRING, m.group('string'),
                                  filename, linecount))
                    continue
                elif m.group('xstring'):
                    #FIXME can't xstring and string be joined in the regex?
                    p += m.end()
                    linecount += string.count(m.group(), '\n')
                    v.append((STRING, m.group('xstring'), filename, linecount))
                    continue
                elif m.group('keyword'):
                    p += m.end()
                    im = re_INCLUDE.match(m.group('keyword'))
                    if im:
                        v.append((INCLUDE, im.groups()[0], filename, linecount))
                    else:
                        v.append((NAME, m.group('keyword'), filename, linecount))
                    continue
                elif m.group('comment'):
                    p += m.end()
                    continue
                elif m.group('rat_num'):
                    p += m.end()
                    v.append((TEMPO, (int(m.group('rat_num')),
                                      int(m.group('rat_den')))))
                    continue
                else:
                    raise "unmatched", m.groups()
            else:
                raise "miss '%s'" % s[p:p+10]
        return v
    def do_include(self, list):
        x = 0
        while x < len(list):
            if list[x][0] == INCLUDE:
                fn = os.path.join(os.path.dirname(self.m_filename[-1]),
                                  list[x][1])
                list[x:x+1] = self.get_tokens(fn, 0)
            x = x + 1
        return list
    def do_simple_op(self, list, op):
        x = 1
        s = '%s_EXP' % op
        while x + 1 < len(list):
            if list[x][0] is op:
                list[x-1:x+2]=[(s, list[x-1], list[x+1])]
            else:
                x += 1
        return list
    def do_blockstart(self, list):
        x = 0
        while x < len(list):
            if list[x][0] is NAME and list[x+1][0] is LBRACE:
                list[x:x+2]=[('BLOCKSTART', list[x])]
            else:
                x += 1
        return list
    def evaluate(self, exp):
        if exp[0] == DIVIDE_EXP:
            return int(exp[1][1]), int(exp[2][1])
        elif exp[0] == STRING:
            return exp[1]
        elif exp[0] == I18NSTRING:
            return _(exp[1])
        elif exp[0] == INTEGER:
            return exp[1]
        elif exp[0] == PLUS_EXP:
            return self.evaluate(exp[1]) + self.evaluate(exp[2])
        elif exp[0] == PERCENT_EXP:
            if exp[1][0] is NAME:
                A = self.evaluate(exp[1])
            else:
                A = exp[1][1]
            return A % exp[2][1]
        elif exp[0] == COMMA_EXP:
            A = self.evaluate(exp[1])
            B = self.evaluate(exp[2])
            if type(A) == type([]):
                return A + [B]
            else:
                return [A, B]
        elif exp[0] == NAME:
            return self.m_globals[exp[1]]
        elif exp[0] == TEMPO:
            return exp[1]
        else:
            raise "unfinished", exp

def get_translated_string(dict, name):
    for n in gettext.lang:
        if dict.has_key("%s(%s)" % (name, n)):
            return dict["%s(%s)" % (name, n)]
    return dict[name]

def __f():
    if len(sys.argv) == 1:
        print "Give the file to parse as command line argument."
        sys.exit(-1)
    import time
    t1 = time.clock()
    for fn in sys.argv[1:]:
        if (not os.path.isfile(fn)) or (fn == 'lesson-files/Makefile'):
            continue
        p = Dataparser({'dictation': 'dictation',
                      'progression': 'progression',
                      'harmony': 'harmony',
                      'sing-chord': 'sing-chord',
                      'chord': 'chord',
                      'id-by-name': 'id-by-name',
                      'satb': 'satb',
                      'horiz': 'horiz',
                      'vertic': 'vertic',
                      'yes': 1,
                      'no': 0,
                      'tempo': (160, 4)})
        for x in range(10):
            p.parse_file(fn)
       #print p.m_blocks
    print time.clock() - t1

def _benchmark_tokenizer():
    import time
    n = 40
    p = Dataparser()
    t1 = time.clock()
    for fn in sys.argv[1:]:
        if os.path.isfile(fn) and (os.path.basename(fn) != 'Makefile'):
            for x in range(n):
                v = p.get_tokens(fn, 0)
    t2 = time.clock()
    return t2-t1

if __name__ == "__main__":
    import i18n
    i18n.initialise("share/locale")
    from i18n import _
    #print _benchmark_tokenizer()
    __f()
    #import profile, pstats
    #profile.run("__f()", "profile.txt")
    #s = pstats.Stats("profile.txt")
    #s.strip_dirs().sort_stats('cumulative').print_stats(100)
