1#!/usr/bin/python2
2# -*-coding:utf-8 -*
3
4# Copyright (c) 2011-2014, Intel Corporation
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without modification,
8# are permitted provided that the following conditions are met:
9#
10# 1. Redistributions of source code must retain the above copyright notice, this
11# list of conditions and the following disclaimer.
12#
13# 2. Redistributions in binary form must reproduce the above copyright notice,
14# this list of conditions and the following disclaimer in the documentation and/or
15# other materials provided with the distribution.
16#
17# 3. Neither the name of the copyright holder nor the names of its contributors
18# may be used to endorse or promote products derived from this software without
19# specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
25# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32
33
34import re
35import sys
36import copy
37from itertools import izip
38from itertools import imap
39
40# =====================================================================
41""" Context classes, used during propagation and the "to PFW script" step """
42# =====================================================================
43
44class PropagationContextItem(list) :
45    """Handle an item during the propagation step"""
46    def __copy__(self):
47        """C.__copy__() -> a shallow copy of C"""
48        return self.__class__(self)
49
50class PropagationContextElement(PropagationContextItem) :
51    """Handle an Element during the propagation step"""
52    def getElementsFromName(self, name):
53        matchingElements = []
54        for element in self :
55            if element.getName() == name :
56                matchingElements.append(element)
57        return matchingElements
58
59
60class PropagationContextOption(PropagationContextItem) :
61    """Handle an Option during the propagation step"""
62    def getOptionItems (self, itemName):
63        items = []
64        for options in self :
65            items.append(options.getOption(itemName))
66        return items
67
68
69class PropagationContext() :
70    """Handle the context during the propagation step"""
71    def __init__(self, propagationContext=None) :
72
73        if propagationContext == None :
74            self._context = {
75                "DomainOptions" : PropagationContextOption() ,
76                "Configurations" : PropagationContextElement() ,
77                "ConfigurationOptions" : PropagationContextOption() ,
78                "Rules" : PropagationContextElement() ,
79                "PathOptions" : PropagationContextOption() ,
80        }
81        else :
82            self._context = propagationContext
83
84    def copy(self):
85        """return a copy of the context"""
86        contextCopy = self._context.copy()
87
88        for key in iter(self._context) :
89            contextCopy[key] = contextCopy[key].__copy__()
90
91        return self.__class__(contextCopy)
92
93    def getDomainOptions (self):
94        return self._context["DomainOptions"]
95
96    def getConfigurations (self):
97        return self._context["Configurations"]
98
99    def getConfigurationOptions (self):
100        return self._context["ConfigurationOptions"]
101
102    def getRules (self):
103        return self._context["Rules"]
104
105    def getPathOptions (self):
106        return self._context["PathOptions"]
107
108
109# =====================================================
110"""Element option container"""
111# =====================================================
112
113class Options () :
114    """handle element options"""
115    def __init__(self, options=[], optionNames=[]) :
116        self.options = dict(izip(optionNames, options))
117        # print(options,optionNames,self.options)
118
119
120    def __str__(self) :
121        ops2str = []
122        for name, argument in self.options.items() :
123            ops2str.append(str(name) + "=\"" + str(argument) + "\"")
124
125        return " ".join(ops2str)
126
127    def getOption(self, name):
128        """get option by its name, if it does not exist return empty string"""
129        return self.options.get(name, "")
130
131    def setOption(self, name, newOption):
132        """set option by its name"""
133        self.options[name] = newOption
134
135    def copy (self):
136        """D.copy() -> a shallow copy of D"""
137        copy = Options()
138        copy.options = self.options.copy()
139        return copy
140
141# ====================================================
142"""Definition of all element class"""
143# ====================================================
144
145class Element(object):
146    """ implement a basic element
147
148    It is the class base for all other elements as Domain, Configuration..."""
149    tag = "unknown"
150    optionNames = ["Name"]
151    childWhiteList = []
152    optionDelimiter = " "
153
154    def __init__(self, line=None) :
155
156        if line == None :
157            self.option = Options([], self.optionNames)
158        else :
159            self.option = self.optionFromLine(line)
160
161        self.children = []
162
163    def optionFromLine(self, line) :
164        # get ride of spaces
165        line = line.strip()
166
167        options = self.extractOptions(line)
168
169        return Options(options, self.optionNames)
170
171    def extractOptions(self, line) :
172        """return the line splited by the optionDelimiter atribute
173
174        Option list length is less or equal to the optionNames list length
175        """
176        options = line.split(self.optionDelimiter, len(self.optionNames) - 1)
177
178        # get ride of leftover spaces
179        optionsStrip = list(imap(str.strip, options))
180
181        return optionsStrip
182
183    def addChild(self, child, append=True) :
184        """ A.addChid(B) -> add B to A child list if B class name is in A white List"""
185        try:
186            # Will raise an exception if this child is not in the white list
187            self.childWhiteList.index(child.__class__.__name__)
188            # If no exception was raised, add child to child list
189
190            if append :
191                self.children.append(child)
192            else :
193                self.children.insert(0, child)
194
195        except ValueError:
196            # the child class is not in the white list
197            raise ChildNotPermitedError("", self, child)
198
199    def addChildren(self, children, append=True) :
200        """Add a list of child"""
201        if append:
202            # Add children at the end of the child list
203            self.children.extend(children)
204        else:
205            # Add children at the begining of the child list
206            self.children = children + self.children
207
208    def childrenToString(self, prefix=""):
209        """return raw printed children """
210        body = ""
211        for child in self.children :
212            body = body + child.__str__(prefix)
213
214        return body
215
216    def __str__(self, prefix="") :
217        """return raw printed element"""
218        selfToString = prefix + " " + self.tag + " " + str(self.option)
219        return selfToString + "\n" + self.childrenToString(prefix + "\t")
220
221    def extractChildrenByClass(self, classTypeList) :
222        """return all children whose class is in the list argument
223
224        return a list of all children whose class in the list "classTypeList" (second arguments)"""
225        selectedChildren = []
226
227        for child in  self.children :
228            for classtype in classTypeList :
229                if child.__class__ == classtype :
230                    selectedChildren.append(child)
231                    break
232        return selectedChildren
233
234    def propagate (self, context=PropagationContext()):
235        """call the propagate method of all children"""
236        for child in  self.children :
237            child.propagate(context)
238
239    def getName(self):
240        """return name option value. If none return "" """
241        return self.option.getOption("Name")
242
243    def setName(self, name):
244        self.option.setOption("Name", name)
245
246    def translate(self, translator):
247        for child in self.children:
248            child.translate(translator)
249
250# ----------------------------------------------------------
251
252class ElementWithTag (Element):
253    """Element of this class are declared with a tag  => line == "tag: .*" """
254    def extractOptions(self, line) :
255        lineWithoutTag = line.split(":", 1)[-1].strip()
256        options = super(ElementWithTag, self).extractOptions(lineWithoutTag)
257        return options
258
259# ----------------------------------------------------------
260
261class ElementWithInheritance(Element):
262    def propagate (self, context=PropagationContext) :
263        """propagate some proprieties to children"""
264
265        # copy the context so that everything that hapend next will only affect
266        # children
267        contextCopy = context.copy()
268
269        # check for inheritance
270        self.Inheritance(contextCopy)
271
272        # call the propagate method of all children
273        super(ElementWithInheritance, self).propagate(contextCopy)
274
275
276class ElementWithRuleInheritance(ElementWithInheritance):
277    """class that will give to its children its rules"""
278    def ruleInheritance(self, context):
279        """Add its rules to the context and get context rules"""
280
281        # extract all children rule and operator
282        childRules = self.extractChildrenByClass([Operator, Rule])
283
284        # get context rules
285        contextRules = context.getRules()
286
287        # adopt rules of the beginning of the context
288        self.addChildren(contextRules, append=False)
289
290        # add previously extract rules to the context
291        contextRules += childRules
292
293
294# ----------------------------------------------------------
295
296class EmptyLine (Element) :
297    """This class represents an empty line.
298
299    Will raise "EmptyLineWarning" exception at instanciation."""
300
301    tag = "emptyLine"
302    match = re.compile(r"[ \t]*\n?$").match
303    def __init__ (self, line):
304       raise EmptyLineWarning(line)
305
306# ----------------------------------------------------------
307
308class Commentary(Element):
309    """This class represents a commentary.
310
311    Will raise "CommentWarning" exception at instanciation."""
312
313    tag = "commentary"
314    optionNames = ["comment"]
315    match = re.compile(r"#").match
316    def __init__ (self, line):
317       raise CommentWarning(line)
318
319# ----------------------------------------------------------
320
321class Path (ElementWithInheritance) :
322    """class implementing the "path = value" concept"""
323    tag = "path"
324    optionNames = ["Name", "value"]
325    match = re.compile(r".+=").match
326    optionDelimiter = "="
327
328    def translate(self, translator):
329        translator.setParameter(self.getName(), self.option.getOption("value"))
330
331    def Inheritance (self, context) :
332        """check for path name inheritance"""
333        self.OptionsInheritance(context)
334
335    def OptionsInheritance (self, context) :
336        """make configuration name inheritance """
337
338        context.getPathOptions().append(self.option.copy())
339        self.setName("/".join(context.getPathOptions().getOptionItems("Name")))
340
341
342class GroupPath (Path, ElementWithTag) :
343    tag = "component"
344    match = re.compile(tag + r" *:").match
345    optionNames = ["Name"]
346    childWhiteList = ["Path", "GroupPath"]
347
348    def getPathNames (self) :
349        """Return the list of all path child name"""
350
351        pathNames = []
352
353        paths = self.extractChildrenByClass([Path])
354        for path in paths :
355            pathNames.append(path.getName())
356
357        groupPaths = self.extractChildrenByClass([GroupPath])
358        for groupPath in groupPaths :
359            pathNames += groupPath.getPathNames()
360
361        return pathNames
362
363    def translate(self, translator):
364        for child in self.extractChildrenByClass([Path, GroupPath]):
365            child.translate(translator)
366
367# ----------------------------------------------------------
368
369class Rule (Element) :
370    """class implementing the rule concept
371
372    A rule is composed of a criterion, a rule type and an criterion state.
373    It should not have any child and is propagated to all configuration in parent descendants.
374    """
375
376    tag = "rule"
377    optionNames = ["criterion", "type", "element"]
378    match = re.compile(r"[a-zA-Z0-9_.]+ +(Is|IsNot|Includes|Excludes) +[a-zA-Z0-9_.]+").match
379    childWhiteList = []
380
381    def PFWSyntax (self, prefix=""):
382
383        script = prefix + \
384                    self.option.getOption("criterion") + " " + \
385                    self.option.getOption("type") + " " + \
386                    self.option.getOption("element")
387
388        return script
389
390
391class Operator (Rule) :
392    """class implementing the operator concept
393
394    An operator contains rules and other operators
395    It is as rules propagated to all configuration children in parent descendants.
396    It should only have the name ANY or ALL to be understood by PFW.
397    """
398
399    tag = "operator"
400    optionNames = ["Name"]
401    match = re.compile(r"ANY|ALL").match
402    childWhiteList = ["Rule", "Operator"]
403
404    syntax = { "ANY" : "Any" , "ALL" : "All"}
405
406    def PFWSyntax (self, prefix=""):
407        """ return a pfw rule (ex : "Any{criterion1 is state1}") generated from "self" and its children options"""
408        script = ""
409
410        script += prefix + \
411                    self.syntax[self.getName()] + "{ "
412
413        rules = self.extractChildrenByClass([Rule, Operator])
414
415        PFWRules = []
416        for rule in rules :
417            PFWRules.append(rule.PFWSyntax(prefix + "    "))
418
419        script += (" , ").join(PFWRules)
420
421        script += prefix + " }"
422
423        return script
424
425# ----------------------------------------------------------
426
427class Configuration (ElementWithRuleInheritance, ElementWithTag) :
428    tag = "configuration"
429    optionNames = ["Name"]
430    match = re.compile(r"conf *:").match
431    childWhiteList = ["Rule", "Operator", "Path", "GroupPath"]
432
433    def composition (self, context):
434        """make all needed composition
435
436        Composition is the fact that group configuration with the same name defined
437        in a parent will give their rule children to this configuration
438        """
439
440        name = self.getName()
441        sameNameConf = context.getConfigurations().getElementsFromName(name)
442
443        sameNameConf.reverse()
444
445        for configuration in sameNameConf :
446            # add same name configuration rule children to self child list
447            self.addChildren(configuration.extractChildrenByClass([Operator, Rule]), append=False)
448
449
450    def propagate (self, context=PropagationContext) :
451        """propagate proprieties to children
452
453        make needed compositions, join ancestor name to its name,
454        and add rules previously defined rules"""
455
456        # make all needed composition
457        self.composition(context)
458
459        super(Configuration, self).propagate(context)
460
461    def Inheritance (self, context) :
462        """make configuration name and rule inheritance"""
463        # check for configuration name inheritance
464        self.OptionsInheritance(context)
465
466        # check for rule inheritance
467        self.ruleInheritance(context)
468
469    def OptionsInheritance (self, context) :
470        """make configuration name inheritance """
471
472        context.getConfigurationOptions().append(self.option.copy())
473        self.setName(".".join(context.getConfigurationOptions().getOptionItems("Name")))
474
475
476    def getRootPath (self) :
477
478        paths = self.extractChildrenByClass([Path, GroupPath])
479
480        rootPath = GroupPath()
481        rootPath.addChildren(paths)
482
483        return rootPath
484
485    def getConfigurableElements (self) :
486        """return all path name defined in this configuration"""
487
488        return self.getRootPath().getPathNames()
489
490    def getRuleString(self):
491        """Output this configuration's rule as a string"""
492
493        # Create a rootRule
494        ruleChildren = self.extractChildrenByClass([Rule, Operator])
495
496        # Do not create a root rule if there is only one fist level Operator rule
497        if len(ruleChildren) == 1 and ruleChildren[0].__class__ == Operator :
498            ruleroot = ruleChildren[0]
499
500        else :
501            ruleroot = Operator()
502            ruleroot.setName("ALL")
503            ruleroot.addChildren(ruleChildren)
504
505        return ruleroot.PFWSyntax()
506
507    def translate(self, translator):
508        translator.createConfiguration(self.getName())
509        translator.setRule(self.getRuleString())
510
511        paths = self.extractChildrenByClass([Path, GroupPath])
512        translator.setElementSequence(self.getConfigurableElements())
513        for path in paths:
514            path.translate(translator)
515
516    def copy (self) :
517        """return a shallow copy of the configuration"""
518
519        # create configuration or subclass copy
520        confCopy = self.__class__()
521
522        # add children
523        confCopy.children = list(self.children)
524
525        # add option
526        confCopy.option = self.option.copy()
527
528        return confCopy
529
530class GroupConfiguration (Configuration) :
531    tag = "GroupConfiguration"
532    optionNames = ["Name"]
533    match = re.compile(r"(supConf|confGroup|confType) *:").match
534    childWhiteList = ["Rule", "Operator", "GroupConfiguration", "Configuration", "GroupPath"]
535
536    def composition (self, context) :
537        """add itself in context for configuration composition
538
539        Composition is the fact that group configuration with the same name defined
540        in a parent will give their rule children to this configuration
541        """
542
543        # copyItself
544        selfCopy = self.copy()
545
546        # make all needed composition
547        super(GroupConfiguration, self).composition(context)
548
549        # add the copy in context for futur configuration composition
550        context.getConfigurations().append(selfCopy)
551
552
553    def getConfigurableElements (self) :
554        """return a list. Each elements consist of a list of configurable element of a configuration
555
556        return a list consisting of all configurable elements for each configuration.
557        These configurable elements are organized in a list"""
558        configurableElements = []
559
560        configurations = self.extractChildrenByClass([Configuration])
561        for configuration in configurations :
562            configurableElements.append(configuration.getConfigurableElements())
563
564        groudeConfigurations = self.extractChildrenByClass([GroupConfiguration])
565        for groudeConfiguration in groudeConfigurations :
566            configurableElements += groudeConfiguration.getConfigurableElements()
567
568        return configurableElements
569
570    def translate(self, translator):
571        for child in self.extractChildrenByClass([Configuration, GroupConfiguration]):
572            child.translate(translator)
573
574# ----------------------------------------------------------
575
576class Domain (ElementWithRuleInheritance, ElementWithTag) :
577    tag = "domain"
578    sequenceAwareKeyword = "sequenceAware"
579
580    match = re.compile(r"domain *:").match
581    optionNames = ["Name", sequenceAwareKeyword]
582    childWhiteList = ["Configuration", "GroupConfiguration", "Rule", "Operator"]
583
584    def propagate (self, context=PropagationContext) :
585        """ propagate name, sequenceAwareness and rule to children"""
586
587        # call the propagate method of all children
588        super(Domain, self).propagate(context)
589
590        self.checkConfigurableElementUnicity()
591
592    def Inheritance (self, context) :
593        """check for domain name, sequence awarness and rules inheritance"""
594        # check for domain name and sequence awarness inheritance
595        self.OptionsInheritance(context)
596
597        # check for rule inheritance
598        self.ruleInheritance(context)
599
600    def OptionsInheritance(self, context) :
601        """ make domain name and sequence awareness inheritance
602
603        join to the domain name all domain names defined in context and
604        if any domain in context is sequence aware, set sequenceAwareness to True"""
605
606        # add domain options to context
607        context.getDomainOptions().append(self.option.copy())
608
609        # set name to the junction of all domain name in context
610        self.setName(".".join(context.getDomainOptions().getOptionItems("Name")))
611
612        # get sequenceAwareness of all domains in context
613        sequenceAwareList = context.getDomainOptions().getOptionItems(self.sequenceAwareKeyword)
614        # or operation on all booleans in sequenceAwareList
615        sequenceAwareness = False
616        for sequenceAware in sequenceAwareList :
617            sequenceAwareness = sequenceAwareness or sequenceAware
618        # current domain sequenceAwareness = sequenceAwareness
619        self.option.setOption(self.sequenceAwareKeyword, sequenceAwareness)
620
621
622    def extractOptions(self, line) :
623        """Extract options from the definition line"""
624        options = super(Domain, self).extractOptions(line)
625
626        sequenceAwareIndex = self.optionNames.index(self.sequenceAwareKeyword)
627
628        # translate the keyword self.sequenceAwareKeyword if specified to boolean True,
629        # to False otherwise
630        try :
631            if options[sequenceAwareIndex] == self.sequenceAwareKeyword :
632               options[sequenceAwareIndex] = True
633            else:
634               options[sequenceAwareIndex] = False
635        except IndexError :
636            options = options + [None] * (sequenceAwareIndex - len(options)) + [False]
637        return options
638
639    def getRootConfiguration (self) :
640        """return the root configuration group"""
641        configurations = self.extractChildrenByClass([Configuration, GroupConfiguration])
642
643        configurationRoot = GroupConfiguration()
644
645        configurationRoot.addChildren(configurations)
646
647        return configurationRoot
648
649    # TODO: don't do that in the parser, let the PFW tell you that
650    def checkConfigurableElementUnicity (self):
651        """ check that all configurable elements defined in child configuration are the sames"""
652
653        # get a list. Each elements of is the configurable element list of a configuration
654        configurableElementsList = self.getRootConfiguration().getConfigurableElements()
655
656        # if at least two configurations in the domain
657        if len(configurableElementsList) > 1 :
658
659            # get first configuration configurable element list sort
660            configurableElementsList0 = list(configurableElementsList[0])
661            configurableElementsList0.sort()
662
663            for configurableElements in configurableElementsList :
664                # sort current configurable element list
665                auxConfigurableElements = list(configurableElements)
666                auxConfigurableElements.sort()
667
668                if auxConfigurableElements != configurableElementsList0 :
669                    # if different, 2 configurations those not have the same configurable element list
670                    # => one or more configurable element is missing in one of the 2 configuration
671                    raise UndefinedParameter(self.getName())
672
673
674    def translate(self, translator):
675        sequence_aware = self.option.getOption(self.sequenceAwareKeyword)
676        translator.createDomain(self.getName(), sequence_aware)
677
678        configurations = self.getRootConfiguration()
679        configurableElementsList = configurations.getConfigurableElements()
680
681        # add configurable elements
682        if len(configurableElementsList) != 0 :
683            for configurableElement in configurableElementsList[0] :
684                translator.addElement(configurableElement)
685
686        configurations.translate(translator)
687
688class GroupDomain (Domain) :
689    tag = "groupDomain"
690    match = re.compile(r"(supDomain|domainGroup) *:").match
691    childWhiteList = ["GroupDomain", "Domain", "GroupConfiguration", "Rule", "Operator"]
692
693    def translate(self, translator):
694        for child in self.extractChildrenByClass([Domain, GroupDomain]):
695            child.translate(translator)
696
697# ----------------------------------------------------------
698
699class Root(Element):
700    tag = "root"
701    childWhiteList = ["Domain", "GroupDomain"]
702
703
704# ===========================================
705""" Syntax error Exceptions"""
706# ===========================================
707
708class MySyntaxProblems(SyntaxError) :
709    comment = "syntax error in %(line)s "
710
711    def __init__(self, line=None, num=None):
712        self.setLine(line, num)
713
714    def __str__(self):
715
716        if self.line :
717            self.comment = self.comment % {"line" : repr(self.line)}
718        if self.num :
719            self.comment = "Line " + str(self.num) + ", " + self.comment
720        return self.comment
721
722    def setLine (self, line, num):
723        self.line = str(line)
724        self.num = num
725
726
727# ---------------------------------------------------------
728
729class MyPropagationError(MySyntaxProblems) :
730    """ Syntax error Exceptions used in the propagation step"""
731    pass
732
733class UndefinedParameter(MyPropagationError) :
734    comment = "Configurations in domain '%(domainName)s' do not all set the same parameters "
735    def __init__ (self, domainName):
736        self.domainName = domainName
737    def __str__ (self):
738        return self.comment % { "domainName" : self.domainName }
739
740
741# -----------------------------------------------------
742""" Syntax error Exceptions used by parser"""
743
744class MySyntaxError(MySyntaxProblems) :
745    """ Syntax error Exceptions used by parser"""
746    pass
747
748class MySyntaxWarning(MySyntaxProblems) :
749    """ Syntax warning Exceptions used by parser"""
750    pass
751
752class IndentationSyntaxError(MySyntaxError) :
753    comment = """syntax error in %(line)s has no father element.
754    You can only increment indentation by one tabutation per line")"""
755
756class EmptyLineWarning(MySyntaxWarning):
757    comment = "warning : %(line)s is an empty line and has been ommited"
758
759class CommentWarning(MySyntaxWarning):
760    comment = "warning : %(line)s is a commentary and has been ommited"
761
762class ChildNotPermitedError(MySyntaxError):
763    def __init__(self, line, fatherElement, childElement):
764        self.comment = "syntax error in %(line)s, " + fatherElement.tag + " should not have a " + childElement.tag + " child."
765        super(ChildNotPermitedError, self).__init__(line)
766
767
768class UnknownElementTypeError(MySyntaxError):
769    comment = " error in line %(line)s , not known element type were matched "
770
771class SpaceInIndentationError(MySyntaxError):
772    comment = " error in ,%(line)s space is not permited in indentation"
773
774
775# ============================================
776"""Class creating the DOM elements from a stream"""
777# ============================================
778
779class ElementsFactory(object)  :
780    """Element factory, return an instance of the first matching element
781
782    Test each element list in elementClass and instanciate it if it's methode match returns True
783    The method match is called with input line as argument
784    """
785    def __init__ (self):
786        self.elementClass = [
787        EmptyLine ,
788        Commentary,
789        GroupDomain,
790        Domain,
791        Path,
792        GroupConfiguration,
793        Configuration,
794        Operator,
795        Rule,
796        GroupPath
797        ]
798
799    def createElementFromLine (self, line) :
800        """return an instance of the first matching element
801
802        Test each element list in elementClass and instanciate it if it's methode match returns True
803        The method match is called with the argument line.
804        Raise UnknownElementTypeError if no element matched.
805        """
806        for element in self.elementClass :
807            if element.match(line) :
808                # print (line + element.__class__.__name__)
809                return element(line)
810        # if we have not find any
811        raise UnknownElementTypeError(line)
812
813#------------------------------------------------------
814
815class Parser(object) :
816    """Class implementing the parser"""
817    def __init__(self):
818        self.rankPattern = re.compile(r"^([\t ]*)(.*)")
819        self.elementFactory = ElementsFactory()
820        self.previousRank = 0
821
822    def __parseLine__(self, line):
823
824        rank, rest = self.__getRank__(line)
825
826        # instanciate the coresponding element
827        element = self.elementFactory.createElementFromLine(rest)
828
829        self.__checkIndentation__(rank)
830
831        return rank, element
832
833    def __getRank__(self, line):
834        """return the rank, the name and the option of the input line
835
836the rank is the number of tabulation (\t) at the line beginning.
837the rest is the rest of the line."""
838        # split line in rank and rest
839        rank = self.rankPattern.match(line)
840        if rank :
841            rank, rest = rank.group(1, 2)
842        else :
843            raise MySyntaxError(line)
844
845        # check for empty line
846        if rest == "" :
847            raise EmptyLineWarning(line)
848
849        # check for space in indentation
850        if rank.find(" ") > -1 :
851            raise SpaceInIndentationError(line)
852
853        rank = len (rank) + 1  # rank starts at 1
854
855
856        return rank, rest
857
858
859    def __checkIndentation__(self, rank):
860        """check if indentation > previous indentation + 1. If so, raise IndentationSyntaxError"""
861        if (rank > self.previousRank + 1) :
862            raise IndentationSyntaxError()
863        self.previousRank = rank
864
865    def parse(self, stream, verbose=False):
866        """parse a stream, usually a opened file"""
867        myroot = Root("root")
868        context = [myroot]  # root is element of rank 0
869        warnings = ""
870
871        for num, line in enumerate(stream):
872            try:
873                rank, myelement = self.__parseLine__(line)
874
875                while len(context) > rank :
876                    context.pop()
877                context.append(myelement)
878                context[-2].addChild(myelement)
879
880            except MySyntaxWarning, ex:
881                ex.setLine(line, num + 1)
882                if verbose :
883                    print >>sys.stderr, ex
884
885            except MySyntaxError, ex :
886                ex.setLine(line, num + 1)
887                raise
888
889        return myroot
890
891