jsonbp 0.9.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
jsonbp/parser.py ADDED
@@ -0,0 +1,549 @@
1
+
2
+ from decimal import Decimal
3
+ from .ply import lex as plyLex
4
+ from .ply import yacc as plyYacc
5
+
6
+ from . import field_type
7
+ from .types import primitive_types
8
+ from .exception import SchemaViolation
9
+ from .blueprint import JsonBlueprint
10
+ from .declaration import createDeclaration
11
+ from .field import createField
12
+ from .array import makeArray
13
+
14
+ reserved = (
15
+ 'root',
16
+ 'node',
17
+ 'type',
18
+ 'enum',
19
+ 'optional',
20
+ 'extends',
21
+ 'include'
22
+ )
23
+
24
+ literals = (
25
+ '{',
26
+ '}',
27
+ '(',
28
+ ')',
29
+ '[',
30
+ ']',
31
+ ',',
32
+ '=',
33
+ ':'
34
+ )
35
+
36
+ # List of tokens
37
+
38
+ types = (
39
+ 'STRING',
40
+ 'BOOLEAN',
41
+ 'FLOAT_AMOUNT',
42
+ 'INTEGER_AMOUNT',
43
+ 'IDENTIFIER',
44
+ )
45
+
46
+ tokens = list(types) + [token.upper() for token in reserved]
47
+
48
+ #------------------------------------------------------------
49
+
50
+ def t_STRING(t):
51
+ r'"[^"]*"'
52
+ t.value = t.value[1:-1]
53
+ return t
54
+
55
+ def t_BOOLEAN(t):
56
+ r'true|false'
57
+ t.value = (t.value == 'true')
58
+ return t
59
+
60
+ def t_FLOAT_AMOUNT(t):
61
+ r'[-+]?\d+(\.(\d+)?([eE][-+]?\d+)?|[eE][-+]?\d+)'
62
+ t.value = float(t.value)
63
+ return t
64
+
65
+ def t_INTEGER_AMOUNT(t):
66
+ r'[+-]?\d+'
67
+ t.value = int(t.value)
68
+ return t
69
+
70
+ def t_IDENTIFIER(t):
71
+ r'\w+'
72
+
73
+ identifier = t.value
74
+ if identifier in reserved:
75
+ t.type = identifier.upper()
76
+
77
+ return t
78
+
79
+ #----------------------------------------------------------------
80
+
81
+ def t_newline(t):
82
+ r'\n+'
83
+ t.lexer.lineno += len(t.value)
84
+
85
+ def t_comment(t):
86
+ r'[#][^\n]*'
87
+ pass
88
+
89
+ t_ignore = ' \t\r'
90
+
91
+ #---------------------------------------------------------------
92
+
93
+ def t_error(t):
94
+ sentence = t.value.split()[0]
95
+ msg = f"Syntax error: Unexpected sentence '{sentence}' on line {t.lineno}"
96
+ raise SchemaViolation(msg)
97
+
98
+ def p_error(p):
99
+ if p == None: raise SchemaViolation("Empty blueprint")
100
+ msg = f"Error parsing line {p.lineno}: token '{p.value}' misplaced"
101
+ raise SchemaViolation(msg)
102
+
103
+ #---------------------------------------------------------------
104
+
105
+ def typeExists(typeName, excluded=None):
106
+ excluded = excluded or set()
107
+
108
+ found = (
109
+ blueprint.find_element_declaration(typeName, excluded.copy()) or
110
+ blueprint.find_node_declaration(typeName, excluded.copy()) or
111
+ blueprint.find_enum_declaration(typeName, excluded.copy()))
112
+
113
+ return found != None
114
+
115
+
116
+ adhoc_counter = 0
117
+ def getNextAdhoc():
118
+ global adhoc_counter
119
+ adhoc_counter += 1
120
+ return adhoc_counter
121
+
122
+ #---------------- general structure -----------------------------
123
+
124
+ def p_schema(p):
125
+ '''
126
+ schema : schema construction
127
+ | construction
128
+ '''
129
+
130
+ def p_construction(p):
131
+ '''
132
+ construction : type
133
+ | enum
134
+ | node
135
+ | root
136
+ | include
137
+ '''
138
+
139
+ #---------------- constructs -----------------------------
140
+
141
+
142
+ def p_include(p):
143
+ '''
144
+ include : INCLUDE STRING
145
+ '''
146
+
147
+ inclusionFile = p[2]
148
+ inclusionPath = os.path.join(currentPath, inclusionFile)
149
+ pushEnv(); loadedBlueprint = load(inclusionPath); popEnv()
150
+
151
+ if None == loadedBlueprint:
152
+ msg = f'Unable to open file "{inclusionFile}"'
153
+ raise SchemaViolation(msg)
154
+
155
+ for typeName in loadedBlueprint.collectTypes():
156
+ if typeExists(typeName, excluded=loadedBlueprint.collectSources()):
157
+ msg = f"Error including '{inclusionFile}': type '{typeName}' already defined"
158
+ raise SchemaViolation(msg)
159
+
160
+ blueprint.includes.append(loadedBlueprint)
161
+
162
+
163
+ def p_root(p):
164
+ '''
165
+ root : ROOT array_declaration
166
+ | ROOT single_declaration
167
+
168
+ '''
169
+
170
+ if None != blueprint.root:
171
+ msg = 'Only one root can be defined'
172
+ raise SchemaViolation(msg)
173
+
174
+ blueprint.root = p[2]
175
+
176
+
177
+ def p_node(p):
178
+ '''
179
+ node : NODE IDENTIFIER EXTENDS IDENTIFIER node_declaration
180
+ | NODE IDENTIFIER node_declaration
181
+ '''
182
+
183
+ nodeName = p[2]
184
+ if typeExists(nodeName):
185
+ msg = f"Duplicated type '{nodeName}'"
186
+ raise SchemaViolation(msg)
187
+
188
+ if len(p) == 6:
189
+ baseNode = p[4]
190
+ baseFields = blueprint.find_node_declaration(baseNode)
191
+
192
+ if None == baseFields:
193
+ msg = f"Node '{baseNode}' is not defined"
194
+ raise SchemaViolation(msg)
195
+
196
+ nodeFields = p[5]
197
+ for fieldName, fieldDeclaration in nodeFields.items():
198
+ if fieldName in baseFields:
199
+ msg = f"Field '{fieldName}' in node '{nodeName}' is already defined in base node '{baseNode}'"
200
+ raise SchemaViolation(msg)
201
+
202
+ nodeFields.update(baseFields)
203
+ blueprint.nodes[nodeName] = nodeFields
204
+
205
+ else:
206
+ nodeFields = p[3]
207
+ blueprint.nodes[nodeName] = nodeFields
208
+
209
+
210
+ def p_node_specs(p):
211
+ '''
212
+ node_declaration : '{' attributes '}'
213
+ '''
214
+
215
+ decls = p[2]
216
+ newNode = dict()
217
+ for decl in decls:
218
+ newNode[decl[0]] = decl[1]
219
+
220
+ p[0] = newNode
221
+
222
+
223
+ def createType(newTypeName, declaration):
224
+ base_type = declaration.typeName
225
+ origin = blueprint.find_element_declaration(
226
+ base_type)
227
+
228
+ newType = dict()
229
+ for specName, value in origin.items():
230
+ newType[specName] = value
231
+
232
+ for spec in declaration.specs:
233
+ specName, value = spec
234
+ if not specName in origin:
235
+ msg = f"Type '{newTypeName}' ({declaration.typeName}) has no attribute '{specName}'"
236
+ raise SchemaViolation(msg)
237
+
238
+ oldValue = newType[specName]
239
+ if type(oldValue) == Decimal and type(value) == float: value = Decimal(value)
240
+ if type(oldValue) == float and type(value) == int: value = float(value)
241
+
242
+ if type(value) != type(oldValue):
243
+ msg = f"New value for specificity '{specName}' is {type(value).__name__}, but it expects {type(oldValue).__name__}"
244
+ raise SchemaViolation(msg)
245
+
246
+ newType[specName] = value
247
+
248
+ while not base_type in primitive_types:
249
+ parent_type = blueprint.derived_types[base_type]
250
+ base_type = parent_type['__baseType__']
251
+
252
+ newType['__baseType__'] = base_type
253
+ blueprint.derived_types[newTypeName] = newType
254
+ return newType
255
+
256
+
257
+ def p_type(p):
258
+ '''
259
+ type : TYPE IDENTIFIER ':' element_declaration
260
+ '''
261
+
262
+ typeName = p[2]
263
+ if typeExists(typeName):
264
+ msg = f"Duplicated type '{typeName}'"
265
+ raise SchemaViolation(msg)
266
+
267
+ declaration = p[4]
268
+ createType(typeName, declaration)
269
+
270
+
271
+ #---------------- attributes ----------------------------
272
+
273
+ def p_attributes(p):
274
+ '''
275
+ attributes : attributes ',' attribute
276
+ | attribute
277
+ '''
278
+
279
+ if len(p) == 4: p[1].append(p[3])
280
+ if len(p) == 2: p[1] = [p[1]]
281
+ p[0] = p[1]
282
+
283
+
284
+ def p_attribute(p):
285
+ '''
286
+ attribute : OPTIONAL attribution
287
+ | attribution
288
+ '''
289
+
290
+ if len(p) == 3:
291
+ fieldName, fieldData = p[2]
292
+ fieldData.setOptional()
293
+ p[0] = p[2]
294
+
295
+ else:
296
+ p[0] = p[1]
297
+
298
+
299
+ def p_attribution(p):
300
+ '''
301
+ attribution : IDENTIFIER ':' array_declaration
302
+ | IDENTIFIER ':' single_declaration
303
+ '''
304
+
305
+ p[0] = (p[1], p[3])
306
+
307
+
308
+ def p_array_declaration(p):
309
+ '''
310
+ array_declaration : single_declaration '[' specificities ']'
311
+ | single_declaration '[' ']'
312
+ '''
313
+
314
+ jArray = makeArray(p[1])
315
+
316
+ if len(p) == 5:
317
+ for specificity in p[3]:
318
+ spec, value = specificity
319
+ jArray.applySpec(spec, value)
320
+
321
+ p[0] = jArray
322
+
323
+
324
+ def p_single_declaration(p):
325
+ '''
326
+ single_declaration : node_declaration
327
+ | enum_declaration
328
+ | element_declaration
329
+ '''
330
+
331
+ declaration = p[1]
332
+ if isinstance(declaration, dict):
333
+ adhoc_node = '_node_type_' + str(getNextAdhoc()) + '_'
334
+ blueprint.nodes[adhoc_node] = declaration
335
+ fieldKind = field_type.NODE
336
+ fieldId = adhoc_node
337
+
338
+ elif isinstance(declaration, list):
339
+ adhoc_enum = '_enum_type_' + str(getNextAdhoc()) + '_'
340
+ blueprint.enums[adhoc_enum] = declaration
341
+ fieldKind = field_type.ENUM
342
+ fieldId = adhoc_enum
343
+
344
+ else:
345
+ declType = declaration.typeName
346
+ if blueprint.find_element_declaration(declType):
347
+ fieldKind = field_type.SIMPLE
348
+
349
+ if not declaration.isCustomized():
350
+ fieldId = declaration.typeName
351
+
352
+ else:
353
+ adhoc_type = '_simple_type_' + str(getNextAdhoc()) + '_'
354
+ createType(adhoc_type, declaration)
355
+ fieldId = adhoc_type
356
+
357
+ elif blueprint.find_enum_declaration(declType):
358
+ fieldKind = field_type.ENUM
359
+ fieldId = declType
360
+
361
+ elif blueprint.find_node_declaration(declType):
362
+ fieldKind = field_type.NODE
363
+ fieldId = declType
364
+
365
+ p[0] = createField(fieldKind, fieldId)
366
+
367
+
368
+ def p_element_declaration(p):
369
+ '''
370
+ element_declaration : IDENTIFIER '(' specificities ')'
371
+ | IDENTIFIER
372
+ '''
373
+
374
+ typeName = p[1]
375
+
376
+ if len(p) == 5:
377
+ not_simple = (
378
+ blueprint.find_node_declaration(typeName) or
379
+ blueprint.find_enum_declaration(typeName))
380
+
381
+ if not_simple:
382
+ msg = f"Unable to apply specificities to '{typeName}', only simple types can be specialized"
383
+ raise SchemaViolation(msg)
384
+
385
+ if not blueprint.find_element_declaration(typeName):
386
+ msg = f"Unknown simple type '{typeName}'"
387
+ raise SchemaViolation(msg)
388
+
389
+ specs = p[3]
390
+
391
+ else:
392
+ if not typeExists(typeName):
393
+ msg = f"Type not declared: '{typeName}'"
394
+ raise SchemaViolation(msg)
395
+
396
+ specs = list()
397
+
398
+ p[0] = createDeclaration(typeName, specs)
399
+
400
+
401
+ def p_specificities(p):
402
+ '''
403
+ specificities : specificities ',' specificity
404
+ | specificity
405
+ '''
406
+
407
+ if len(p) == 4: p[1].append(p[3])
408
+ if len(p) == 2: p[1] = [p[1]]
409
+ p[0] = p[1]
410
+
411
+
412
+ def p_specificity(p):
413
+ '''
414
+ specificity : IDENTIFIER '=' specified_value
415
+ '''
416
+
417
+ p[0] = (p[1], p[3])
418
+
419
+
420
+ def p_specified_value(p):
421
+ '''
422
+ specified_value : STRING
423
+ | FLOAT_AMOUNT
424
+ | INTEGER_AMOUNT
425
+ | BOOLEAN
426
+ '''
427
+
428
+ p[0] = p[1]
429
+
430
+ #---------------- enums -----------------------------
431
+
432
+ def p_enum(p):
433
+ '''
434
+ enum : ENUM IDENTIFIER enum_declaration
435
+ '''
436
+
437
+ enum_name = p[2]
438
+ if typeExists(enum_name):
439
+ msg = f"Duplicated type '{enum_name}'"
440
+ raise SchemaViolation(msg)
441
+
442
+ blueprint.enums[enum_name] = p[3]
443
+
444
+
445
+ def p_enum_declaration(p):
446
+ '''
447
+ enum_declaration : '{' constants '}'
448
+ '''
449
+
450
+ p[0] = p[2]
451
+
452
+
453
+ def p_constants(p):
454
+ '''
455
+ constants : constants ',' IDENTIFIER
456
+ | IDENTIFIER
457
+ '''
458
+
459
+ if len(p) == 4:
460
+ p[1].append(p[3])
461
+ p[0] = p[1]
462
+
463
+ else:
464
+ p[0] = [p[1]]
465
+
466
+ #---------------------------------------------------------------
467
+
468
+ from threading import Lock
469
+
470
+ _mutex = Lock()
471
+ _loadedFiles = dict()
472
+ _pushedEnvs = list()
473
+
474
+ def setupEnv(contentPath, output):
475
+ global blueprint, currentPath
476
+ currentPath = contentPath
477
+ blueprint = output
478
+
479
+
480
+ def pushEnv():
481
+ env = (currentPath, blueprint)
482
+ _pushedEnvs.append(env)
483
+ _mutex.release()
484
+
485
+
486
+ def popEnv():
487
+ global currentPath, blueprint
488
+
489
+ _mutex.acquire()
490
+ env = _pushedEnvs.pop()
491
+ (currentPath, blueprint) = env
492
+
493
+ #---------------------------------------------------------------
494
+
495
+ import os
496
+
497
+ def load(filepath):
498
+ abspath = os.path.abspath(filepath)
499
+ if abspath in _loadedFiles:
500
+ return _loadedFiles[abspath]
501
+
502
+ try:
503
+ with open(filepath, "r") as fd:
504
+ contents = fd.read()
505
+
506
+ except FileNotFoundError:
507
+ return None
508
+
509
+ return loads(contents,
510
+ os.path.dirname(filepath),
511
+ os.path.basename(filepath))
512
+
513
+
514
+ def loads(contents, contentPath='.', contentName=None):
515
+ lexer = plyLex.lex()
516
+ parser = plyYacc.yacc()
517
+
518
+ try:
519
+ _mutex.acquire()
520
+ result = JsonBlueprint()
521
+ setupEnv(contentPath, result)
522
+ parser.parse(contents)
523
+
524
+ if None == blueprint.root and len(_pushedEnvs) == 0:
525
+ msg = 'No root defined at topmost level'
526
+ raise SchemaViolation(msg)
527
+
528
+ if None != contentName:
529
+ contentFullpath = os.path.join(contentPath, contentName)
530
+ abspath = os.path.abspath(contentFullpath)
531
+ _loadedFiles[abspath] = result
532
+
533
+ return result
534
+
535
+ finally:
536
+ _mutex.release()
537
+
538
+ #------------------------------------------------------------------------------
539
+
540
+ def invalidateCache():
541
+ global _loadedFiles
542
+
543
+ try:
544
+ _mutex.acquire()
545
+ _loadedFiles = dict()
546
+
547
+ finally:
548
+ _mutex.release()
549
+
jsonbp/ply/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ # PLY package
2
+ # Author: David Beazley (dave@dabeaz.com)
3
+ # https://github.com/dabeaz/ply
4
+
5
+ __version__ = '2022_01_02'