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
7
8
9
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
21 -class Atom(unicode):
22 """a sentinel class for atomic values of roles
23 """
24 noQuotesOkRE = re.compile("[\w_.]+$")
25
31
33 return "a"+unicode.__repr__(self).lstrip("u")
34
37 """a sentinel class for roles referencing something else.
38 """
41
45 return ("attr", toks[0], toks[2])
46
49
51 if len(toks)==1:
52
53 return ("coll", None, toks[0])
54 else:
55 return ("coll", toks[0], toks[1])
56
58 if len(toks)==2:
59
60 return ("obj", toks[0], toks[1][2])
61 else:
62
63
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
74
77
80 """returns a grammar for parsing a SIL object description.
81 """
82 @classmethod
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
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
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
165
166
167 _PARSER_EVENT_MAPPING = {
168
169 'obj': ('obj', 'fromNode', _iterAttrs),
170 'uobj': ('obj', 'seqType', _iterAttrs),
171 'coll': ('coll', 'fromNode', _iterObjs)
172 }
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
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
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
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
236
237 del obStack
238 result = newRole
239
240 elif evType=='attr':
241 obStack[-1].add(
242 annotationFactory(root, arg1, arg2))
243
244 elif evType=='item':
245 collection = obStack[-1]
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