#!/usr/bin/python2
# -*-coding:utf-8 -*

# Copyright (c) 2011-2014, Intel Corporation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



import re
import sys
import copy
from itertools import izip
from itertools import imap

# =====================================================================
""" Context classes, used during propagation and the "to PFW script" step """
# =====================================================================

class PropagationContextItem(list) :
    """Handle an item during the propagation step"""
    def __copy__(self):
        """C.__copy__() -> a shallow copy of C"""
        return self.__class__(self)

class PropagationContextElement(PropagationContextItem) :
    """Handle an Element during the propagation step"""
    def getElementsFromName(self, name):
        matchingElements = []
        for element in self :
            if element.getName() == name :
                matchingElements.append(element)
        return matchingElements


class PropagationContextOption(PropagationContextItem) :
    """Handle an Option during the propagation step"""
    def getOptionItems (self, itemName):
        items = []
        for options in self :
            items.append(options.getOption(itemName))
        return items


class PropagationContext() :
    """Handle the context during the propagation step"""
    def __init__(self, propagationContext=None) :

        if propagationContext == None :
            self._context = {
                "DomainOptions" : PropagationContextOption() ,
                "Configurations" : PropagationContextElement() ,
                "ConfigurationOptions" : PropagationContextOption() ,
                "Rules" : PropagationContextElement() ,
                "PathOptions" : PropagationContextOption() ,
        }
        else :
            self._context = propagationContext

    def copy(self):
        """return a copy of the context"""
        contextCopy = self._context.copy()

        for key in iter(self._context) :
            contextCopy[key] = contextCopy[key].__copy__()

        return self.__class__(contextCopy)

    def getDomainOptions (self):
        return self._context["DomainOptions"]

    def getConfigurations (self):
        return self._context["Configurations"]

    def getConfigurationOptions (self):
        return self._context["ConfigurationOptions"]

    def getRules (self):
        return self._context["Rules"]

    def getPathOptions (self):
        return self._context["PathOptions"]


# =====================================================
"""Element option container"""
# =====================================================

class Options () :
    """handle element options"""
    def __init__(self, options=[], optionNames=[]) :
        self.options = dict(izip(optionNames, options))
        # print(options,optionNames,self.options)


    def __str__(self) :
        ops2str = []
        for name, argument in self.options.items() :
            ops2str.append(str(name) + "=\"" + str(argument) + "\"")

        return " ".join(ops2str)

    def getOption(self, name):
        """get option by its name, if it does not exist return empty string"""
        return self.options.get(name, "")

    def setOption(self, name, newOption):
        """set option by its name"""
        self.options[name] = newOption

    def copy (self):
        """D.copy() -> a shallow copy of D"""
        copy = Options()
        copy.options = self.options.copy()
        return copy

# ====================================================
"""Definition of all element class"""
# ====================================================

class Element(object):
    """ implement a basic element

    It is the class base for all other elements as Domain, Configuration..."""
    tag = "unknown"
    optionNames = ["Name"]
    childWhiteList = []
    optionDelimiter = " "

    def __init__(self, line=None) :

        if line == None :
            self.option = Options([], self.optionNames)
        else :
            self.option = self.optionFromLine(line)

        self.children = []

    def optionFromLine(self, line) :
        # get ride of spaces
        line = line.strip()

        options = self.extractOptions(line)

        return Options(options, self.optionNames)

    def extractOptions(self, line) :
        """return the line splited by the optionDelimiter atribute

        Option list length is less or equal to the optionNames list length
        """
        options = line.split(self.optionDelimiter, len(self.optionNames) - 1)

        # get ride of leftover spaces
        optionsStrip = list(imap(str.strip, options))

        return optionsStrip

    def addChild(self, child, append=True) :
        """ A.addChid(B) -> add B to A child list if B class name is in A white List"""
        try:
            # Will raise an exception if this child is not in the white list
            self.childWhiteList.index(child.__class__.__name__)
            # If no exception was raised, add child to child list

            if append :
                self.children.append(child)
            else :
                self.children.insert(0, child)

        except ValueError:
            # the child class is not in the white list
            raise ChildNotPermitedError("", self, child)

    def addChildren(self, children, append=True) :
        """Add a list of child"""
        if append:
            # Add children at the end of the child list
            self.children.extend(children)
        else:
            # Add children at the begining of the child list
            self.children = children + self.children

    def childrenToString(self, prefix=""):
        """return raw printed children """
        body = ""
        for child in self.children :
            body = body + child.__str__(prefix)

        return body

    def __str__(self, prefix="") :
        """return raw printed element"""
        selfToString = prefix + " " + self.tag + " " + str(self.option)
        return selfToString + "\n" + self.childrenToString(prefix + "\t")

    def extractChildrenByClass(self, classTypeList) :
        """return all children whose class is in the list argument

        return a list of all children whose class in the list "classTypeList" (second arguments)"""
        selectedChildren = []

        for child in  self.children :
            for classtype in classTypeList :
                if child.__class__ == classtype :
                    selectedChildren.append(child)
                    break
        return selectedChildren

    def propagate (self, context=PropagationContext()):
        """call the propagate method of all children"""
        for child in  self.children :
            child.propagate(context)

    def getName(self):
        """return name option value. If none return "" """
        return self.option.getOption("Name")

    def setName(self, name):
        self.option.setOption("Name", name)

    def translate(self, translator):
        for child in self.children:
            child.translate(translator)

# ----------------------------------------------------------

class ElementWithTag (Element):
    """Element of this class are declared with a tag  => line == "tag: .*" """
    def extractOptions(self, line) :
        lineWithoutTag = line.split(":", 1)[-1].strip()
        options = super(ElementWithTag, self).extractOptions(lineWithoutTag)
        return options

# ----------------------------------------------------------

class ElementWithInheritance(Element):
    def propagate (self, context=PropagationContext) :
        """propagate some proprieties to children"""

        # copy the context so that everything that hapend next will only affect
        # children
        contextCopy = context.copy()

        # check for inheritance
        self.Inheritance(contextCopy)

        # call the propagate method of all children
        super(ElementWithInheritance, self).propagate(contextCopy)


class ElementWithRuleInheritance(ElementWithInheritance):
    """class that will give to its children its rules"""
    def ruleInheritance(self, context):
        """Add its rules to the context and get context rules"""

        # extract all children rule and operator
        childRules = self.extractChildrenByClass([Operator, Rule])

        # get context rules
        contextRules = context.getRules()

        # adopt rules of the beginning of the context
        self.addChildren(contextRules, append=False)

        # add previously extract rules to the context
        contextRules += childRules


# ----------------------------------------------------------

class EmptyLine (Element) :
    """This class represents an empty line.

    Will raise "EmptyLineWarning" exception at instanciation."""

    tag = "emptyLine"
    match = re.compile(r"[ \t]*\n?$").match
    def __init__ (self, line):
       raise EmptyLineWarning(line)

# ----------------------------------------------------------

class Commentary(Element):
    """This class represents a commentary.

    Will raise "CommentWarning" exception at instanciation."""

    tag = "commentary"
    optionNames = ["comment"]
    match = re.compile(r"#").match
    def __init__ (self, line):
       raise CommentWarning(line)

# ----------------------------------------------------------

class Path (ElementWithInheritance) :
    """class implementing the "path = value" concept"""
    tag = "path"
    optionNames = ["Name", "value"]
    match = re.compile(r".+=").match
    optionDelimiter = "="

    def translate(self, translator):
        translator.setParameter(self.getName(), self.option.getOption("value"))

    def Inheritance (self, context) :
        """check for path name inheritance"""
        self.OptionsInheritance(context)

    def OptionsInheritance (self, context) :
        """make configuration name inheritance """

        context.getPathOptions().append(self.option.copy())
        self.setName("/".join(context.getPathOptions().getOptionItems("Name")))


class GroupPath (Path, ElementWithTag) :
    tag = "component"
    match = re.compile(tag + r" *:").match
    optionNames = ["Name"]
    childWhiteList = ["Path", "GroupPath"]

    def getPathNames (self) :
        """Return the list of all path child name"""

        pathNames = []

        paths = self.extractChildrenByClass([Path])
        for path in paths :
            pathNames.append(path.getName())

        groupPaths = self.extractChildrenByClass([GroupPath])
        for groupPath in groupPaths :
            pathNames += groupPath.getPathNames()

        return pathNames

    def translate(self, translator):
        for child in self.extractChildrenByClass([Path, GroupPath]):
            child.translate(translator)

# ----------------------------------------------------------

class Rule (Element) :
    """class implementing the rule concept

    A rule is composed of a criterion, a rule type and an criterion state.
    It should not have any child and is propagated to all configuration in parent descendants.
    """

    tag = "rule"
    optionNames = ["criterion", "type", "element"]
    match = re.compile(r"[a-zA-Z0-9_.]+ +(Is|IsNot|Includes|Excludes) +[a-zA-Z0-9_.]+").match
    childWhiteList = []

    def PFWSyntax (self, prefix=""):

        script = prefix + \
                    self.option.getOption("criterion") + " " + \
                    self.option.getOption("type") + " " + \
                    self.option.getOption("element")

        return script


class Operator (Rule) :
    """class implementing the operator concept

    An operator contains rules and other operators
    It is as rules propagated to all configuration children in parent descendants.
    It should only have the name ANY or ALL to be understood by PFW.
    """

    tag = "operator"
    optionNames = ["Name"]
    match = re.compile(r"ANY|ALL").match
    childWhiteList = ["Rule", "Operator"]

    syntax = { "ANY" : "Any" , "ALL" : "All"}

    def PFWSyntax (self, prefix=""):
        """ return a pfw rule (ex : "Any{criterion1 is state1}") generated from "self" and its children options"""
        script = ""

        script += prefix + \
                    self.syntax[self.getName()] + "{ "

        rules = self.extractChildrenByClass([Rule, Operator])

        PFWRules = []
        for rule in rules :
            PFWRules.append(rule.PFWSyntax(prefix + "    "))

        script += (" , ").join(PFWRules)

        script += prefix + " }"

        return script

# ----------------------------------------------------------

class Configuration (ElementWithRuleInheritance, ElementWithTag) :
    tag = "configuration"
    optionNames = ["Name"]
    match = re.compile(r"conf *:").match
    childWhiteList = ["Rule", "Operator", "Path", "GroupPath"]

    def composition (self, context):
        """make all needed composition

        Composition is the fact that group configuration with the same name defined
        in a parent will give their rule children to this configuration
        """

        name = self.getName()
        sameNameConf = context.getConfigurations().getElementsFromName(name)

        sameNameConf.reverse()

        for configuration in sameNameConf :
            # add same name configuration rule children to self child list
            self.addChildren(configuration.extractChildrenByClass([Operator, Rule]), append=False)


    def propagate (self, context=PropagationContext) :
        """propagate proprieties to children

        make needed compositions, join ancestor name to its name,
        and add rules previously defined rules"""

        # make all needed composition
        self.composition(context)

        super(Configuration, self).propagate(context)

    def Inheritance (self, context) :
        """make configuration name and rule inheritance"""
        # check for configuration name inheritance
        self.OptionsInheritance(context)

        # check for rule inheritance
        self.ruleInheritance(context)

    def OptionsInheritance (self, context) :
        """make configuration name inheritance """

        context.getConfigurationOptions().append(self.option.copy())
        self.setName(".".join(context.getConfigurationOptions().getOptionItems("Name")))


    def getRootPath (self) :

        paths = self.extractChildrenByClass([Path, GroupPath])

        rootPath = GroupPath()
        rootPath.addChildren(paths)

        return rootPath

    def getConfigurableElements (self) :
        """return all path name defined in this configuration"""

        return self.getRootPath().getPathNames()

    def getRuleString(self):
        """Output this configuration's rule as a string"""

        # Create a rootRule
        ruleChildren = self.extractChildrenByClass([Rule, Operator])

        # Do not create a root rule if there is only one fist level Operator rule
        if len(ruleChildren) == 1 and ruleChildren[0].__class__ == Operator :
            ruleroot = ruleChildren[0]

        else :
            ruleroot = Operator()
            ruleroot.setName("ALL")
            ruleroot.addChildren(ruleChildren)

        return ruleroot.PFWSyntax()

    def translate(self, translator):
        translator.createConfiguration(self.getName())
        translator.setRule(self.getRuleString())

        paths = self.extractChildrenByClass([Path, GroupPath])
        translator.setElementSequence(self.getConfigurableElements())
        for path in paths:
            path.translate(translator)

    def copy (self) :
        """return a shallow copy of the configuration"""

        # create configuration or subclass copy
        confCopy = self.__class__()

        # add children
        confCopy.children = list(self.children)

        # add option
        confCopy.option = self.option.copy()

        return confCopy

class GroupConfiguration (Configuration) :
    tag = "GroupConfiguration"
    optionNames = ["Name"]
    match = re.compile(r"(supConf|confGroup|confType) *:").match
    childWhiteList = ["Rule", "Operator", "GroupConfiguration", "Configuration", "GroupPath"]

    def composition (self, context) :
        """add itself in context for configuration composition

        Composition is the fact that group configuration with the same name defined
        in a parent will give their rule children to this configuration
        """

        # copyItself
        selfCopy = self.copy()

        # make all needed composition
        super(GroupConfiguration, self).composition(context)

        # add the copy in context for futur configuration composition
        context.getConfigurations().append(selfCopy)


    def getConfigurableElements (self) :
        """return a list. Each elements consist of a list of configurable element of a configuration

        return a list consisting of all configurable elements for each configuration.
        These configurable elements are organized in a list"""
        configurableElements = []

        configurations = self.extractChildrenByClass([Configuration])
        for configuration in configurations :
            configurableElements.append(configuration.getConfigurableElements())

        groudeConfigurations = self.extractChildrenByClass([GroupConfiguration])
        for groudeConfiguration in groudeConfigurations :
            configurableElements += groudeConfiguration.getConfigurableElements()

        return configurableElements

    def translate(self, translator):
        for child in self.extractChildrenByClass([Configuration, GroupConfiguration]):
            child.translate(translator)

# ----------------------------------------------------------

class Domain (ElementWithRuleInheritance, ElementWithTag) :
    tag = "domain"
    sequenceAwareKeyword = "sequenceAware"

    match = re.compile(r"domain *:").match
    optionNames = ["Name", sequenceAwareKeyword]
    childWhiteList = ["Configuration", "GroupConfiguration", "Rule", "Operator"]

    def propagate (self, context=PropagationContext) :
        """ propagate name, sequenceAwareness and rule to children"""

        # call the propagate method of all children
        super(Domain, self).propagate(context)

        self.checkConfigurableElementUnicity()

    def Inheritance (self, context) :
        """check for domain name, sequence awarness and rules inheritance"""
        # check for domain name and sequence awarness inheritance
        self.OptionsInheritance(context)

        # check for rule inheritance
        self.ruleInheritance(context)

    def OptionsInheritance(self, context) :
        """ make domain name and sequence awareness inheritance

        join to the domain name all domain names defined in context and
        if any domain in context is sequence aware, set sequenceAwareness to True"""

        # add domain options to context
        context.getDomainOptions().append(self.option.copy())

        # set name to the junction of all domain name in context
        self.setName(".".join(context.getDomainOptions().getOptionItems("Name")))

        # get sequenceAwareness of all domains in context
        sequenceAwareList = context.getDomainOptions().getOptionItems(self.sequenceAwareKeyword)
        # or operation on all booleans in sequenceAwareList
        sequenceAwareness = False
        for sequenceAware in sequenceAwareList :
            sequenceAwareness = sequenceAwareness or sequenceAware
        # current domain sequenceAwareness = sequenceAwareness
        self.option.setOption(self.sequenceAwareKeyword, sequenceAwareness)


    def extractOptions(self, line) :
        """Extract options from the definition line"""
        options = super(Domain, self).extractOptions(line)

        sequenceAwareIndex = self.optionNames.index(self.sequenceAwareKeyword)

        # translate the keyword self.sequenceAwareKeyword if specified to boolean True,
        # to False otherwise
        try :
            if options[sequenceAwareIndex] == self.sequenceAwareKeyword :
               options[sequenceAwareIndex] = True
            else:
               options[sequenceAwareIndex] = False
        except IndexError :
            options = options + [None] * (sequenceAwareIndex - len(options)) + [False]
        return options

    def getRootConfiguration (self) :
        """return the root configuration group"""
        configurations = self.extractChildrenByClass([Configuration, GroupConfiguration])

        configurationRoot = GroupConfiguration()

        configurationRoot.addChildren(configurations)

        return configurationRoot

    # TODO: don't do that in the parser, let the PFW tell you that
    def checkConfigurableElementUnicity (self):
        """ check that all configurable elements defined in child configuration are the sames"""

        # get a list. Each elements of is the configurable element list of a configuration
        configurableElementsList = self.getRootConfiguration().getConfigurableElements()

        # if at least two configurations in the domain
        if len(configurableElementsList) > 1 :

            # get first configuration configurable element list sort
            configurableElementsList0 = list(configurableElementsList[0])
            configurableElementsList0.sort()

            for configurableElements in configurableElementsList :
                # sort current configurable element list
                auxConfigurableElements = list(configurableElements)
                auxConfigurableElements.sort()

                if auxConfigurableElements != configurableElementsList0 :
                    # if different, 2 configurations those not have the same configurable element list
                    # => one or more configurable element is missing in one of the 2 configuration
                    raise UndefinedParameter(self.getName())


    def translate(self, translator):
        sequence_aware = self.option.getOption(self.sequenceAwareKeyword)
        translator.createDomain(self.getName(), sequence_aware)

        configurations = self.getRootConfiguration()
        configurableElementsList = configurations.getConfigurableElements()

        # add configurable elements
        if len(configurableElementsList) != 0 :
            for configurableElement in configurableElementsList[0] :
                translator.addElement(configurableElement)

        configurations.translate(translator)

class GroupDomain (Domain) :
    tag = "groupDomain"
    match = re.compile(r"(supDomain|domainGroup) *:").match
    childWhiteList = ["GroupDomain", "Domain", "GroupConfiguration", "Rule", "Operator"]

    def translate(self, translator):
        for child in self.extractChildrenByClass([Domain, GroupDomain]):
            child.translate(translator)

# ----------------------------------------------------------

class Root(Element):
    tag = "root"
    childWhiteList = ["Domain", "GroupDomain"]


# ===========================================
""" Syntax error Exceptions"""
# ===========================================

class MySyntaxProblems(SyntaxError) :
    comment = "syntax error in %(line)s "

    def __init__(self, line=None, num=None):
        self.setLine(line, num)

    def __str__(self):

        if self.line :
            self.comment = self.comment % {"line" : repr(self.line)}
        if self.num :
            self.comment = "Line " + str(self.num) + ", " + self.comment
        return self.comment

    def setLine (self, line, num):
        self.line = str(line)
        self.num = num


# ---------------------------------------------------------

class MyPropagationError(MySyntaxProblems) :
    """ Syntax error Exceptions used in the propagation step"""
    pass

class UndefinedParameter(MyPropagationError) :
    comment = "Configurations in domain '%(domainName)s' do not all set the same parameters "
    def __init__ (self, domainName):
        self.domainName = domainName
    def __str__ (self):
        return self.comment % { "domainName" : self.domainName }


# -----------------------------------------------------
""" Syntax error Exceptions used by parser"""

class MySyntaxError(MySyntaxProblems) :
    """ Syntax error Exceptions used by parser"""
    pass

class MySyntaxWarning(MySyntaxProblems) :
    """ Syntax warning Exceptions used by parser"""
    pass

class IndentationSyntaxError(MySyntaxError) :
    comment = """syntax error in %(line)s has no father element.
    You can only increment indentation by one tabutation per line")"""

class EmptyLineWarning(MySyntaxWarning):
    comment = "warning : %(line)s is an empty line and has been ommited"

class CommentWarning(MySyntaxWarning):
    comment = "warning : %(line)s is a commentary and has been ommited"

class ChildNotPermitedError(MySyntaxError):
    def __init__(self, line, fatherElement, childElement):
        self.comment = "syntax error in %(line)s, " + fatherElement.tag + " should not have a " + childElement.tag + " child."
        super(ChildNotPermitedError, self).__init__(line)


class UnknownElementTypeError(MySyntaxError):
    comment = " error in line %(line)s , not known element type were matched "

class SpaceInIndentationError(MySyntaxError):
    comment = " error in ,%(line)s space is not permited in indentation"


# ============================================
"""Class creating the DOM elements from a stream"""
# ============================================

class ElementsFactory(object)  :
    """Element factory, return an instance of the first matching element

    Test each element list in elementClass and instanciate it if it's methode match returns True
    The method match is called with input line as argument
    """
    def __init__ (self):
        self.elementClass = [
        EmptyLine ,
        Commentary,
        GroupDomain,
        Domain,
        Path,
        GroupConfiguration,
        Configuration,
        Operator,
        Rule,
        GroupPath
        ]

    def createElementFromLine (self, line) :
        """return an instance of the first matching element

        Test each element list in elementClass and instanciate it if it's methode match returns True
        The method match is called with the argument line.
        Raise UnknownElementTypeError if no element matched.
        """
        for element in self.elementClass :
            if element.match(line) :
                # print (line + element.__class__.__name__)
                return element(line)
        # if we have not find any
        raise UnknownElementTypeError(line)

#------------------------------------------------------

class Parser(object) :
    """Class implementing the parser"""
    def __init__(self):
        self.rankPattern = re.compile(r"^([\t ]*)(.*)")
        self.elementFactory = ElementsFactory()
        self.previousRank = 0

    def __parseLine__(self, line):

        rank, rest = self.__getRank__(line)

        # instanciate the coresponding element
        element = self.elementFactory.createElementFromLine(rest)

        self.__checkIndentation__(rank)

        return rank, element

    def __getRank__(self, line):
        """return the rank, the name and the option of the input line

the rank is the number of tabulation (\t) at the line beginning.
the rest is the rest of the line."""
        # split line in rank and rest
        rank = self.rankPattern.match(line)
        if rank :
            rank, rest = rank.group(1, 2)
        else :
            raise MySyntaxError(line)

        # check for empty line
        if rest == "" :
            raise EmptyLineWarning(line)

        # check for space in indentation
        if rank.find(" ") > -1 :
            raise SpaceInIndentationError(line)

        rank = len (rank) + 1  # rank starts at 1


        return rank, rest


    def __checkIndentation__(self, rank):
        """check if indentation > previous indentation + 1. If so, raise IndentationSyntaxError"""
        if (rank > self.previousRank + 1) :
            raise IndentationSyntaxError()
        self.previousRank = rank

    def parse(self, stream, verbose=False):
        """parse a stream, usually a opened file"""
        myroot = Root("root")
        context = [myroot]  # root is element of rank 0
        warnings = ""

        for num, line in enumerate(stream):
            try:
                rank, myelement = self.__parseLine__(line)

                while len(context) > rank :
                    context.pop()
                context.append(myelement)
                context[-2].addChild(myelement)

            except MySyntaxWarning, ex:
                ex.setLine(line, num + 1)
                if verbose :
                    print >>sys.stderr, ex

            except MySyntaxError, ex :
                ex.setLine(line, num + 1)
                raise

        return myroot