Package gavo :: Package dm :: Module sil
[frames] | no frames]

Source Code for Module gavo.dm.sil

  1  """ 
  2  SIL, the Simple Instance Language, is an attempt to allow 
  3  data model instances written in a simple, JSON-like language. 
  4  """ 
  5   
  6  #c Copyright 2008-2019, the GAVO project 
  7  #c 
  8  #c This program is free software, covered by the GNU GPL.  See the 
  9  #c COPYING file in the source distribution. 
 10   
 11   
 12  from __future__ import print_function 
 13   
 14  import re 
 15   
 16  from gavo import utils 
 17  from gavo.dm import common 
18 19 20 # sentinels for further processing 21 -class Atom(unicode):
22 """a sentinel class for atomic values of roles 23 """ 24 noQuotesOkRE = re.compile("[\w_.]+$") 25
26 - def asSIL(self):
27 if self.noQuotesOkRE.match(self): 28 return unicode(self) 29 else: 30 return '"%s"'%(self.replace('"', '""'))
31
32 - def __repr__(self):
33 return "a"+unicode.__repr__(self).lstrip("u")
34
35 36 -class Reference(unicode):
37 """a sentinel class for roles referencing something else. 38 """
39 - def asSIL(self):
40 return "@%s"%self
41
42 43 # parse methods, used by getGrammar, by nonterminal name there 44 -def _pa_attributeDef(s, p, toks):
45 return ("attr", toks[0], toks[2])
46
47 -def _pa_typeAnnotation(s, p, toks):
48 return toks[1]
49
50 -def _pa_collection(s, p, toks):
51 if len(toks)==1: 52 # no explicit type annotation; we return None as type. 53 return ("coll", None, toks[0]) 54 else: 55 return ("coll", toks[0], toks[1])
56
57 -def _pa_obj(s, p, toks):
58 if len(toks)==2: 59 # with type annotation 60 return ("obj", toks[0], toks[1][2]) 61 else: 62 # no type annotation; we should later add an annotation based on 63 # the default for the DM 64 return ("obj", None, toks[0][2])
65
66 -def _pa_objectBody(s, p, toks):
67 return ("uobj", None, toks[1].asList())
68
69 -def _pa_sequenceBody(s, p, toks):
70 return [toks[1].asList()]
71
72 -def _pa_reference(s, p, toks):
73 return Reference(toks[1])
74
75 -def _pa_simpleImmediate(s, p, toks):
76 return Atom(toks[0])
77
78 79 -class getGrammar(utils.CachedResource):
80 """returns a grammar for parsing a SIL object description. 81 """ 82 @classmethod
83 - def impl(cls):
84 from pyparsing import (Word, Literal, alphas, alphanums, 85 QuotedString, Forward, ZeroOrMore, Group, Optional, cStyleComment) 86 87 with utils.pyparsingWhitechars("\t\n\r "): 88 qualifiedIdentifier = Word(alphas+"_:", alphanums+"-._:") 89 plainIdentifier = Word(alphas+"_", alphanums+"-._") 90 externalIdentifier = Word(alphas+"_", alphanums+"._/#-") 91 plainLiteral = Word(alphanums+"_-.") 92 quotedLiteral = QuotedString(quoteChar='"', escQuote='""') 93 reference = (Literal('@') + externalIdentifier) 94 95 complexImmediate = Forward() 96 simpleImmediate = plainLiteral | quotedLiteral 97 value = (reference | complexImmediate | simpleImmediate) 98 99 attributeDef = (plainIdentifier 100 + Literal(":") 101 + value) 102 typeAnnotation = (Literal('(') 103 + qualifiedIdentifier 104 + Literal(')')) 105 objectBody = (Literal('{') 106 + Group(ZeroOrMore( attributeDef )) 107 + Literal('}')) 108 obj = Optional(typeAnnotation) + objectBody 109 110 sequenceBody = (Literal('[') 111 + Group(ZeroOrMore(value | objectBody)) 112 + Literal(']')) 113 collection = Optional(typeAnnotation) + sequenceBody 114 115 complexImmediate << ( obj | collection ) 116 117 for sym in [complexImmediate, collection, sequenceBody, 118 objectBody, typeAnnotation, attributeDef]: 119 sym.ignore(cStyleComment) 120 121 for n, func in globals().iteritems(): 122 if n.startswith("_pa_"): 123 locals()[n[4:]].setParseAction(func) 124 125 cls.symbols = locals() 126 return obj
127 128 @classmethod
129 - def enableDebuggingOutput(cls):
130 """(not user-servicable) 131 """ 132 from pyparsing import ParserElement 133 for name, sym in cls.symbols.iteritems(): 134 if isinstance(sym, ParserElement): 135 sym.setDebug(True) 136 sym.setName(name)
137
138 139 -def _iterAttrs(node, seqType, roleName):
140 """generates parse events for nodes with attribute children. 141 142 (see _parseTreeToEvents). 143 """ 144 for child in node[2]: 145 assert child[0]=='attr' 146 if isinstance(child[2], (Reference, Atom)): 147 yield ('attr', child[1], child[2]) 148 elif isinstance(child[2], tuple): 149 for grandchild in _parseTreeToEvents(child[2], roleName=child[1]): 150 yield grandchild 151 else: 152 assert False, "Bad object as parsed value: %s"%repr(child[2])
153
154 155 -def _iterObjs(node, seqType, roleName):
156 for child in node[2]: 157 if isinstance(child, (Reference, Atom)): 158 yield ('item', child, None) 159 160 else: 161 # complex child -- yield events 162 for grandchild in _parseTreeToEvents(child, seqType=seqType, 163 roleName=roleName): 164 yield grandchild
165 166 167 _PARSER_EVENT_MAPPING = { 168 # -> (iterparse ev name, type source, child parser) 169 'obj': ('obj', 'fromNode', _iterAttrs), 170 'uobj': ('obj', 'seqType', _iterAttrs), 171 'coll': ('coll', 'fromNode', _iterObjs) 172 }
173 174 -def _parseTreeToEvents(node, seqType=None, roleName=None):
175 """helps iterparse by interpreting the parser events in evStream. 176 """ 177 opener, typeSource, childParser = _PARSER_EVENT_MAPPING[node[0]] 178 if typeSource=='fromNode': 179 nodeType = node[1] 180 elif typeSource=='seqType': 181 nodeType = seqType 182 else: 183 assert False 184 yield (opener, roleName, nodeType) 185 186 for child in childParser(node, nodeType, roleName): 187 yield child 188 189 yield ('pop', None, None)
190
191 192 -def iterparse(silLiteral):
193 """yields parse events for a SIL literal in a string. 194 195 The parse events are triples of one of the forms: 196 197 * ('attr', roleName, value) add an attribute to the current annotation 198 * ('obj', roleName, type) create a new object object of type 199 * ('coll', type, None) create a new collection annotation (type can be None) 200 * ('item', val, None) add an atomic value to the current collection 201 * ('pop', None, None) finish current annotation and add it to its container 202 """ 203 root = getGrammar().parseString(silLiteral, parseAll=True)[0] 204 return _parseTreeToEvents(root)
205
206 207 -def getAnnotation(silLiteral, annotationFactory):
208 """returns an annotation object parsed from silLiteral. 209 210 annotationFactory is a callable that takes attributeName/attributeValue 211 pairs and returns annotations; attributeValue is either an Atom or 212 a Reference in these cases. 213 """ 214 obStack, result = [], None 215 iterator = iterparse(silLiteral) 216 217 # make the root of the DM instance tree 218 evType, arg1, arg2 = iterator.next() 219 assert evType=='obj' 220 root = common.ObjectAnnotation(arg1, arg2, None) 221 obStack.append(root) 222 223 for evType, arg1, arg2 in iterator: 224 if evType=='obj': 225 obStack.append(common.ObjectAnnotation(arg1, arg2, root)) 226 227 elif evType=='coll': 228 obStack.append(common.CollectionAnnotation(arg1, arg2, root)) 229 230 elif evType=='pop': 231 newRole = obStack.pop() 232 if obStack: 233 obStack[-1].add(newRole) 234 else: 235 # we've just popped the total result. Make sure 236 # any furher operations fail. 237 del obStack 238 result = newRole 239 240 elif evType=='attr': 241 obStack[-1].add( #noflake: the del obStack up there is conditional 242 annotationFactory(root, arg1, arg2)) 243 244 elif evType=='item': 245 collection = obStack[-1] #noflake: see above 246 assert isinstance(collection, common.CollectionAnnotation) 247 collection.add( 248 annotationFactory(root, collection.name, arg1)) 249 250 else: 251 assert False 252 253 if result is None: 254 raise utils.StructureError("Data model annotation yielded no result.") 255 if result.type is None: 256 raise utils.StructureError("Root of Data Model annotation must" 257 " have a type.") 258 259 return result
260 261 262 if __name__=="__main__": 263 g = getGrammar() 264 getGrammar.enableDebuggingOutput() 265 res = g.parseString( 266 """ 267 (:testclass) { 268 seq: [a "b c d" @e]}""", parseAll=True)[0] 269 print(res) 270