Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

#!/usr/bin/env python 

 

""" 

@file ion/integration/ais/validate_data_resource/data_resource_parser.py 

@author Ian Katz 

@brief Classes to parse a url for its metadata  

""" 

 

 

 

class ParseException(Exception): 

    pass 

 

# lexer and parser from Python Ply 

class Lexer(object): 

    #regex's for the tokens we expect to read 

    t_OPEN        = r"\{" 

    t_CLOSE       = r"\}" 

    t_SEMI        = r";" 

    t_COMMA       = r"," 

    #t_NAME        = r'[a-zA-Z_][a-zA-Z0-9_]*' 

    t_LITERAL     = r'"((\\")|[^"])*"' 

    t_ignore      = ' \t' 

 

    def t_NAME(self, t): 

        r'[a-zA-Z_][a-zA-Z0-9_]*' 

        if "NaN" == t.value: 

            t.type = "NUMBER" 

            t.value = float("NaN") 

        #print t 

        return t 

 

    #numbers that can be negative and/or decimal 

    def t_NUMBER(self, t): 

        r"NaN|(-?\d+(\.\d+)?(E-?\d+)?)" 

        #print t.value, "becomes", float(t.value) 

        val = float(t.value) 

        if str(int(val)) == t.value: 

            t.value = int(t.value) 

        else: 

            t.value = float(t.value) 

        return t 

 

    # Define a rule so we can track line numbers 

    def t_newline(self, t): 

        r'\n+' 

        t.lexer.lineno += len(t.value) 

 

    tokens = "OPEN CLOSE SEMI COMMA NUMBER NAME LITERAL".split() 

 

    def t_error(self, t): 

        raise ParseException(t) 

 

 

class Parser(object): 

    """parsing rules are contained in the comment to each function""" 

 

    starting = "dasfile" # rule to start parsing on... default is first method 

 

    def p_dasfile(self, p): 

        "dasfile : NAME OPEN sections CLOSE" 

        #  p[0]  : p[1] p[2] p[3]     p[4]    is how you read this 

 

        output = {} 

        #list of single-entry dictionaries for sections 

        for asection in p[3]: 

            for k, v in asection.iteritems(): 

                output[k] = v 

 

        p[0] = output 

 

    # recursive case for building a list... base case follows below 

    def p_sections(self, p): 

        "sections : section sections" 

        p[2].append(p[1]) 

        p[0] = p[2] 

 

    def p_sections_term(self, p): 

        "sections : section" 

        p[0] = [p[1]] 

 

 

    def p_section(self, p): 

        "section : NAME OPEN lineitems CLOSE" 

        theitems = {} 

 

        # lineitems is a list of single-entry dictionaries for lines... collapse it 

        for aline in p[3]: 

            for k, v in aline.iteritems(): 

                theitems[k] = v 

 

        p[0] = {p[1] : theitems} 

 

 

    # recursive case for building a list... base case follows below 

    def p_lineitems(self, p): 

        "lineitems : lineitem lineitems" 

        p[2].append(p[1]) 

        p[0] = p[2] 

 

    def p_lineitems_term(self, p): 

        "lineitems : lineitem" 

        p[0] = [p[1]] 

 

 

    def p_lineitem(self, p): 

        """lineitem : NAME NAME meat SEMI 

                |   NAME NAME NAME SEMI  

                |   section """ 

 

        #subsections are ok i think 

        if 2 == len(p): 

            p[0] = p[1] 

        else: 

            val = p[3] 

 

            #collapse number non-lists to just the number 

            if isinstance(val, type([])) and 1 == len(val): 

                val = val[0] 

 

            ret = {p[2] : {"TYPE" : p[1], "VALUE" : val}} 

            p[0] = ret 

 

 

    def p_meat(self, p): 

        """meat : LITERAL 

              |   numberlist""" 

 

        if type([]) == type(p[1]): 

            p[0] = p[1]       # use the numberlist as-is 

        else: 

            p[0] = p[1][1:-1] # strip quotes from literal 

 

    def p_numberlist(self, p): 

        """numberlist : numberlist COMMA NUMBER""" 

        p[1].append(p[3]) 

        p[0] = p[1] 

 

    def p_numberlist_term(self, p): 

        """numberlist : NUMBER""" 

        p[0] = [p[1]] 

 

    def p_error(self, p): 

        raise Exception(p) 

 

    tokens = Lexer.tokens