# This file is part of the Coral project.
# Please read the copyright notice in the LICENSE file.

#
# Simple BNF syntax tree handling
# Used for creating mappings between metamodels and BNF languages
#
# Main class is BNF, which collects a set of Tokens, ProductionRules
# and the start Token. Every ProductionRule consists of a source nonterminal
# and a Sequence of Targets. Each Target has a Token and multiplicities.
# Tokens can be Terminals, Nonterminals or Groups, which give you a choice
# between several Sequences.
#
#
#
#

class Sequence:
    def __init__(self):
        self.targets = []

    def __str__(self):
        s = ""
        s += " ".join([ i.__str__() for i in self.targets ])
        return s
        
class Target:
    def __init__(self, token, multiplicity_lower = 1, multiplicity_upper = 1):
        self.token = token
        self.mult_lower = multiplicity_lower
        self.mult_upper = multiplicity_upper

    def __str__(self):
        s = ""
        L = self.mult_lower
        U = self.mult_upper
        if L == 0 and U == 1:
            s += "?"
        elif L == 1 and U == 1:
            pass
        elif L == 0 and U == -1:
            s += "*"
        elif L == 1 and U == -1:
            s += "+"
        else:
            s += "{%d,%d}" % (L, U)
        return self.token.__str__() + s

class Token:
    def __init__(self):
        pass


class Group(Token):
    def __init__(self):
        self.choices = []

    def addSequence(self, sequence):
        self.choices.append(sequence)

    def __str__(self):
        s = "( "
        s += " | ".join([ i.__str__() for i in self.choices ])
        s += " )"
        return s

class Nonterminal(Token):
    def __init__(self, name):
        Token.__init__(self)
        self.name = name
        self.seq = Sequence()

    def __str__(self):
        return self.name
    
class Terminal(Token):
    def __init__(self, value = ""):
        Token.__init__(self)
        self.value = value

    def __str__(self):
        return "\"%s\"" % self.value

class BuiltinNonterminal(Nonterminal):
    def __init__(self, name):
        Nonterminal.__init__(self, name)

class BNF:
    def __init__(self):
        self.start_token = None

    def __c(self, targets, sofar):
        s = ""
        for target in targets:
            if isinstance(target.token, Nonterminal):
                s += self.__s(target.token, sofar)
            elif isinstance(target.token, Group):
                for seq in target.token.choices:
                    s += self.__c(seq.targets, sofar)
        return s

    def __s(self, nonterminal, sofar):
        assert isinstance(nonterminal, Nonterminal)
        if sofar.has_key(nonterminal):
            return ""
        if isinstance(nonterminal, BuiltinNonterminal):
            return ""
        sofar[nonterminal] = 1
        s = ""
        s += nonterminal.__str__() + " : " + nonterminal.seq.__str__() + "\n"
        s += self.__c(nonterminal.seq.targets, sofar)
        return s
    
    def __str__(self):
        s = ""
        s += "BNF Grammar\n"
        sofar = {}
        s += self.__s(self.start_token, sofar)
        s += "---\n"
        return s


#
# Some default terminals
#

Integer = BuiltinNonterminal("[0-9]+")
String = BuiltinNonterminal("[a-zA-Z0-9]+")
Real = BuiltinNonterminal("[0-9]+\.[0-9]+")
