1322 lines
49 KiB
Python
1322 lines
49 KiB
Python
# -*- coding: UTF-8 -*-
|
|
#/**
|
|
# * Software Name : pycrate
|
|
# * Version : 0.4
|
|
# *
|
|
# * Copyright 2017. Benoit Michau. ANSSI. P1sec.
|
|
# *
|
|
# * This library is free software; you can redistribute it and/or
|
|
# * modify it under the terms of the GNU Lesser General Public
|
|
# * License as published by the Free Software Foundation; either
|
|
# * version 2.1 of the License, or (at your option) any later version.
|
|
# *
|
|
# * This library is distributed in the hope that it will be useful,
|
|
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# * Lesser General Public License for more details.
|
|
# *
|
|
# * You should have received a copy of the GNU Lesser General Public
|
|
# * License along with this library; if not, write to the Free Software
|
|
# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
# * MA 02110-1301 USA
|
|
# *
|
|
# *--------------------------------------------------------
|
|
# * File Name : pycrate_csn1/trans.py
|
|
# * Created : 2017-06-12
|
|
# * Authors : Benoit Michau
|
|
# *--------------------------------------------------------
|
|
#*/
|
|
|
|
from .utils import *
|
|
from .utils import _RE_NAME, _RE_VAL
|
|
|
|
|
|
#------------------------------------------------------------------------------#
|
|
# CSN.1 object parser
|
|
#------------------------------------------------------------------------------#
|
|
# WNG: this parser is not implementing the whole CSN.1 language
|
|
# but only what is required in order to parse 3GPP specifications
|
|
|
|
class CSN1Obj(object):
|
|
"""Class to handle CSN.1 object parsing and definition
|
|
|
|
internal attributes:
|
|
- name: str, named of the object,
|
|
can be a null-string in case of anonymous object
|
|
- num :
|
|
int, static number of repetition for the object
|
|
default is 1, it can be >= 0
|
|
or -1, for an undefined number of repetitions
|
|
(x:int, (a:int, b:int)) is an alternate possible value in case
|
|
the number of repetitions is dynamic and depends on another
|
|
object:
|
|
x: backward reference to a field into the parent object which
|
|
has to be a list
|
|
a, b: transform in the form x: a*(x+b) to be applied to the
|
|
value of the backward reference to get the length of self
|
|
- lref: enforces a limitation to the length in bits during the decoding
|
|
and potentially the encoding, used for lists and alternatives
|
|
None, no limitation
|
|
int, static limitation to the number of bits
|
|
(x:int, (a:int, b:int)) is an alternate possible value in case the
|
|
limitation of number of bits is dynamic and depends on another
|
|
object, handling is similar to num
|
|
- ref : set of str, list all reference to external CSN.1 objects
|
|
|
|
In case the CSN.1 object is a bit-field:
|
|
- bit :
|
|
int, static number of bits for the object
|
|
default is 1, it can be >= 0
|
|
or -1, for an undefined number of bits
|
|
(x:int, (a:int, b:int)) is an alternate possible value in case
|
|
the number of bits is dynamic and depends on another
|
|
object, handling is similar to num
|
|
- excl: None or CSN1Val, list of excluded values
|
|
|
|
In case the CSN.1 object is a list of objects:
|
|
- list: list, list of contained objects
|
|
|
|
In case the CSN.1 object is a list of alternatives:
|
|
- alt : list, list of alternative lists of contained objects
|
|
"""
|
|
# CSN.1 default type is unsigned integer
|
|
TYPE = CSN1T_UINT
|
|
# the syntax does not give hint on how to handle an object (like an integer,
|
|
# signed, unsigned, ... or like a bit string ...)
|
|
# hence, the type has to be set by hand, if not handled like this default CSN1T_UINT
|
|
# the only supported alternative yet is CSN1T_BSTR
|
|
|
|
def __init__(self, name, text='', EOT=None):
|
|
# A CSN.1 object is named or anonymous
|
|
self._name = name
|
|
# A CSN.1 object can be repeated multiple times (self._num >= 0)
|
|
# or until there is no more buffer to consume (self._num == -1)
|
|
self._num = 1
|
|
# reference to a backward field enforcing the length in bits
|
|
self._lref = None
|
|
# reference to a parent object
|
|
self._par = None
|
|
|
|
# external object's reference(s) tracker, required for regenerating source code
|
|
self._ref = set()
|
|
#
|
|
# A CSN.1 object is a bit-field (self._bit),
|
|
# a list of CSN.1 objects (self._list)
|
|
# or a list of alternative of list of CSN.1 objects (self._alt)
|
|
self._bit = None # in case object is a single bit-field
|
|
self._excl = None # in case object is a single bit-field with excluded value
|
|
self._list = None # in case object is a list of objects
|
|
self._alt = None # in case object is a choice between mutliple alternatives
|
|
#
|
|
if text:
|
|
text = self.parse_def(text, EOT)
|
|
|
|
def reduce(self):
|
|
"""
|
|
optimizes the content of self in various ways
|
|
|
|
returns a new CSN1Obj if modified, or self
|
|
"""
|
|
#
|
|
ret = self
|
|
#
|
|
if hasattr(self, '_alt') and self._alt is not None:
|
|
# processing alternatives
|
|
if hasattr(self, '_err'):
|
|
# ignoring error branch at this time
|
|
# TODO: handle error branch
|
|
del self._alt[self._err]
|
|
del self._err
|
|
#
|
|
if len(self._alt) == 1:
|
|
# if this is an alternative with a single possibility
|
|
# we convert it back to a list
|
|
if isinstance(self._alt[0], list):
|
|
self._list = self._alt[0]
|
|
else:
|
|
# this list with a single element will get reduced below
|
|
self._list = [self._alt[0]]
|
|
self._alt = None
|
|
#
|
|
elif hasattr(self, '_ref') and not self._ref and \
|
|
self._num == 1 and self._lref is None and \
|
|
all([isinstance(Obj, CSN1Val) for Obj in self._alt]):
|
|
# if this is an alternative between multiple values
|
|
# we convert it to a CSN1Val with the list of given values
|
|
New = CSN1Val(name=self._name)
|
|
for alt in self._alt:
|
|
New._val.extend( alt._val )
|
|
ret = New
|
|
#
|
|
if hasattr(self, '_list') and self._list is not None:
|
|
# processing list
|
|
if len(self._list) == 1:
|
|
# if this is a list with a single object
|
|
# we return this single object
|
|
# WNG: this could assert in some extrem cases
|
|
New = self._list[0]
|
|
if self._num != 1:
|
|
assert( New._num == 1 )
|
|
New._num = self._num
|
|
if self._lref is not None:
|
|
assert( New._lref is None )
|
|
New._lref = self._lref
|
|
if hasattr(self, '_trunc'):
|
|
assert( hasattr(New, '_list') and New._list is not None )
|
|
New._trunc = True
|
|
# reattribute self._name to the inner object New
|
|
if self._name:
|
|
New._name = self._name
|
|
ret = New
|
|
#
|
|
if hasattr(self, '_alt') and not self._alt and \
|
|
hasattr(self, '_list') and not self._list:
|
|
# processing bit / octet
|
|
if isinstance(self._bit, integer_types) and \
|
|
isinstance(self._num, integer_types) and self._num > 1:
|
|
self._bit *= self._num
|
|
self._num = 1
|
|
#
|
|
if isinstance(self, CSN1Ref):
|
|
# processing reference
|
|
if self._obj == 'null' and self._num == 1 and self._lref is None:
|
|
# if this is a ref to null
|
|
# we convert it to a CSN1Val with a single null value
|
|
New = CSN1Val(name=self._name)
|
|
New._val = ['null']
|
|
ret = New
|
|
#
|
|
return ret
|
|
|
|
def resolve_refs(self):
|
|
"""
|
|
resolves all references between internal objects
|
|
|
|
need to be done after all parents' objects have been reduce() properly
|
|
"""
|
|
if isinstance(self._lref, tuple):
|
|
assert( isinstance(self._lref[0], str_types) )
|
|
self._resolve_ref('_lref')
|
|
elif isinstance(self._num, tuple):
|
|
assert( isinstance(self._num[0], str_types) )
|
|
self._resolve_ref('_num')
|
|
#
|
|
if hasattr(self, '_bit') and isinstance(self._bit, tuple):
|
|
# CSN1Bit
|
|
assert( isinstance(self._bit[0], str_types) )
|
|
self._resolve_ref('_bit')
|
|
elif hasattr(self, '_list') and self._list:
|
|
# CSN1List: process internal list objects recursively
|
|
for child in self._list:
|
|
child._par = (self, self._list)
|
|
child.resolve_refs()
|
|
elif hasattr(self, '_alt') and self._alt:
|
|
# CSN1Alt: process internal alternatives objects recursively
|
|
#for alt in [alt for alt in self._alt if isinstance(alt, list)]:
|
|
# for child in alt[1:]:
|
|
for alt in self._alt:
|
|
if isinstance(alt, list):
|
|
for child in alt:
|
|
child._par = (self, alt)
|
|
child.resolve_refs()
|
|
elif isinstance(alt, CSN1Obj):
|
|
alt._par = (self, alt)
|
|
alt.resolve_refs()
|
|
else:
|
|
assert()
|
|
|
|
def _resolve_ref(self, attr):
|
|
ref = getattr(self, attr)
|
|
if ref[0][0] == '#':
|
|
# manual conversion is required due to special arithmetic
|
|
setattr(self, attr, (ref[0], (None, None)))
|
|
return
|
|
#
|
|
# check within parents' iteratively to find the reference
|
|
par, path = self._par, []
|
|
while True:
|
|
if par is None:
|
|
# error while searching for the reference
|
|
log('WARN: unable to resolve reference, %s' % ref[0])
|
|
log(' do it by hand within the Python file generated')
|
|
setattr(self, attr, ('# unresolved: %s' % ref[0], (None, None)))
|
|
return
|
|
#
|
|
names = [obj._name for obj in par[1]]
|
|
if ref[0] not in names:
|
|
path.append(-1)
|
|
par = par[0]._par
|
|
else:
|
|
path.append( names.index(ref[0]) )
|
|
break
|
|
setattr(self, attr, (path, ref[1]))
|
|
|
|
def __repr__(self):
|
|
if self._bit is not None:
|
|
suf = 'Bit'
|
|
elif self._list:
|
|
suf = 'List'
|
|
elif self._alt:
|
|
suf = 'Alt'
|
|
else:
|
|
suf = ''
|
|
return '%s%s(%s)' % (self.__class__.__name__, suf, self._name)
|
|
|
|
def __continue(self, text, EOT):
|
|
if text:
|
|
if EOT is None:
|
|
return True
|
|
elif text[0:1] not in EOT:
|
|
return True
|
|
else:
|
|
return False
|
|
else:
|
|
return False
|
|
|
|
def parse_def(self, text, EOT=None):
|
|
"""
|
|
parses the text corresponding to the CSN.1 object definition
|
|
|
|
object definition can be:
|
|
- a bit definition, e.g. "bit (10)" or "bit == 1"
|
|
- a fixed bit value, e.g. "10110" or "HL"
|
|
- a reference to another object, e.g. "< OtherObject >"
|
|
- an inlined object definition, e.g. "< OtherObject: bit(10) >"
|
|
- a list of alternatives, e.g.
|
|
"{ 00 | 11 | 01 < Object-1 > | 10 < Object-2 > }"
|
|
"""
|
|
self._text_def = text
|
|
while self.__continue(text, EOT):
|
|
#
|
|
if text[0:3] == 'bit':
|
|
# bit definition
|
|
assert( self._bit is None )
|
|
if self._list is None:
|
|
self._list = []
|
|
text = self._parse_bit(text)
|
|
#
|
|
elif text[0:5] == 'octet':
|
|
# octet definition
|
|
assert( self._bit is None )
|
|
if self._list is None:
|
|
self._list = []
|
|
text = self._parse_octet(text)
|
|
#
|
|
elif text and text[0:1] in '01LHn':
|
|
# fixed anonymous bit value
|
|
assert( self._bit is None )
|
|
if self._list is None:
|
|
self._list = []
|
|
text = self._parse_anon_val(text)
|
|
#
|
|
elif text[0:1] == '<':
|
|
# reference to another object or inlined object definition
|
|
assert( self._bit is None )
|
|
if self._list is None:
|
|
self._list = []
|
|
text = self._parse_obj(text[1:].lstrip())
|
|
#
|
|
elif text[0:1] == '{':
|
|
# precedence token, subgroup handled as an anonymous object
|
|
assert( self._bit is None )
|
|
if self._list is None:
|
|
self._list = []
|
|
text = self._parse_grp(text[1:].lstrip())
|
|
#
|
|
elif text[0:1] in ('|', '!'):
|
|
assert( self._bit is None and self._list is not None )
|
|
# put the previous list of object as a potential alternative
|
|
if self._alt is None:
|
|
self._alt = []
|
|
self.__insert_alt()
|
|
#
|
|
if text[0:1] == '!':
|
|
# the next list correspond to an error case
|
|
self._err = len(self._alt)
|
|
#
|
|
text = text[1:].lstrip()
|
|
#
|
|
elif text[0:2] == '//':
|
|
# TODO: handle truncation somewhere
|
|
self._trunc = True
|
|
text = text[2:].lstrip()
|
|
#
|
|
elif text[0:1] == '&':
|
|
lenl, bit, obj = len(self._list), None, None
|
|
assert(lenl >= 1)
|
|
if self._list[-1]._name == '' and self._list[-1]._bit:
|
|
bit = self._list[-1]
|
|
off = -2
|
|
else:
|
|
obj = self._list[-1]
|
|
off = -1
|
|
#
|
|
# this should happen only within a < ... > struct
|
|
text = self.parse_def(text[1:].lstrip(), '>')
|
|
assert(len(self._list) == lenl + 1)
|
|
# 1 of the 2 objects must be bit
|
|
if bit is None:
|
|
assert( obj is not None )
|
|
bit = self._list[-1]
|
|
assert( bit._name == '' and bit._bit )
|
|
else:
|
|
assert( bit is not None )
|
|
obj = self._list[-1]
|
|
#
|
|
assert( isinstance(obj, CSN1Ref) or obj._list or obj._alt )
|
|
obj._lref = bit._bit
|
|
del self._list[off]
|
|
assert(not text or text[0:1] == '>')
|
|
text = text[1:].lstrip()
|
|
#
|
|
else:
|
|
m = re.match(_RE_NAME, text)
|
|
if m:
|
|
# simple reference
|
|
assert( self._bit is None and self._list is None and self._alt is None )
|
|
Ref = CSN1Ref('', m.group())
|
|
Ref = Ref.reduce()
|
|
self._list = [Ref]
|
|
text = text[m.end():].lstrip()
|
|
if text[0:1] == '>':
|
|
break
|
|
raise(CSN1Err('{0}: invalid text definition, {1}'\
|
|
.format(self._name, text)))
|
|
#
|
|
if EOT is None:
|
|
if self._alt is not None:
|
|
# in case self is an alternative object outside of any subgroups
|
|
# we need to transfer the last list within alt
|
|
self.__insert_alt()
|
|
#
|
|
return text
|
|
|
|
def __insert_list(self, Obj):
|
|
assert( self._list is not None )
|
|
self._list.append( Obj )
|
|
#
|
|
if isinstance(Obj, CSN1Val):
|
|
pass
|
|
elif isinstance(Obj, CSN1Ref):
|
|
self._ref.add(Obj._obj)
|
|
else:
|
|
self._ref.update(Obj._ref)
|
|
|
|
def __insert_alt(self):
|
|
assert( self._list )
|
|
# optimization to remove unneeded wrapping list of length 1
|
|
if len(self._list) == 1:
|
|
# if this is a list of a single object
|
|
# we keep only this single object
|
|
self._alt.append( self._list[0] )
|
|
else:
|
|
self._alt.append( self._list )
|
|
self._list = None
|
|
|
|
def _parse_bit(self, text):
|
|
"""
|
|
parses the text corresponding to a bit definition,
|
|
e.g. "bit (10)" or "bit == 1"
|
|
"""
|
|
text = text[3:].lstrip()
|
|
if text and text[0:1] in '(*':
|
|
# Obj._bit can be of fixed length, or of dynamic length with a ref
|
|
Obj = CSN1Obj('')
|
|
Obj._bit, text = CSN1Obj._parse_repet(text)
|
|
else:
|
|
Obj = CSN1Obj('')
|
|
Obj._bit = 1
|
|
if text and text[0:1] == '=':
|
|
text = CSN1Obj._parse_nostr(text)
|
|
#
|
|
if text and text[0:2] == '==':
|
|
# this is actually not an object, but a fixed value
|
|
# self (CSN1Obj) will be converted to a CSN1Val afterward
|
|
text = text[2:].lstrip()
|
|
Val = CSN1Val('')
|
|
text = Val.parse_val(text)
|
|
if isinstance(Obj._bit, tuple) or \
|
|
isinstance(Obj._bit, integer_types) and \
|
|
not all([len(v) == Obj._bit for v in Val._val]):
|
|
raise(CSN1Err('%: invalid mix of value and length constraint'\
|
|
% self._name))
|
|
else:
|
|
Obj = Val
|
|
elif text[0:7] == 'exclude':
|
|
excl, text = CSN1Obj._parse_excl(text[7:].lstrip())
|
|
for val in excl._val:
|
|
assert( len(val) == Obj._bit )
|
|
Obj._excl = excl
|
|
#
|
|
self.__insert_list( Obj.reduce() )
|
|
return text
|
|
|
|
def _parse_octet(self, text):
|
|
"""
|
|
parses the text corresponding to an octet definition,
|
|
e.g. "octet (2)"
|
|
"""
|
|
text = text[5:].lstrip()
|
|
if text and text[0:1] in '(*':
|
|
# Obj._bit can be of fixed length, or of dynamic length with a ref
|
|
Obj = CSN1Obj('')
|
|
Obj._bit, text = CSN1Obj._parse_repet(text)
|
|
Obj._num = Obj._bit
|
|
Obj._bit = 8
|
|
else:
|
|
Obj = CSN1Obj('')
|
|
Obj._bit = 8
|
|
#
|
|
self.__insert_list( Obj.reduce() )
|
|
return text
|
|
|
|
def _parse_anon_val(self, text):
|
|
"""
|
|
parses the text corresponding to an anonymous value, e.g. "010"
|
|
"""
|
|
Val = CSN1Val('')
|
|
text = Val.parse_val(text)
|
|
self.__insert_list( Val.reduce() )
|
|
return text
|
|
|
|
def _parse_obj(self, text):
|
|
"""
|
|
parses the text corresponding to the definition of another object,
|
|
e.g. "< AnotherObject ... >"
|
|
"""
|
|
m = re.match(_RE_NAME, text)
|
|
if not m:
|
|
raise(CSN1Err('{0}: invalid object name, {1}'\
|
|
.format(self._name, text)))
|
|
name = m.group().strip()
|
|
if name == 'bit':
|
|
text = self._parse_bit(text)
|
|
if text[0:1] == '>':
|
|
text = text[1:].lstrip()
|
|
else:
|
|
assert( text[0:1] == '&' )
|
|
else:
|
|
text = text[m.end():].lstrip()
|
|
if text[0:1] == ':':
|
|
# inlined CSN.1 object bit definition
|
|
Obj = CSN1Obj(name)
|
|
text = Obj.parse_def(text[1:].lstrip(), EOT='>')
|
|
text = text[1:].lstrip()
|
|
Obj = Obj.reduce()
|
|
if text[0:7] == 'exclude':
|
|
excl, text = CSN1Obj._parse_excl(text[7:].lstrip())
|
|
assert( Obj._bit is not None )
|
|
for val in excl._val:
|
|
assert( len(val) == Obj._bit )
|
|
Obj._excl = excl
|
|
if text and text[0:1] in '(*':
|
|
assert( Obj._num == 1 )
|
|
Obj._num, text = CSN1Obj._parse_repet(text)
|
|
self.__insert_list( Obj )
|
|
elif text[0:1] == '>':
|
|
# new ref to another object
|
|
Obj = CSN1Ref('', name)
|
|
text = text[1:].lstrip()
|
|
if text and text[0:1] in '(*':
|
|
Obj._num, text = CSN1Obj._parse_repet(text)
|
|
self.__insert_list( Obj.reduce() )
|
|
else:
|
|
raise(CSN1Err('{0}: invalid internal object definition, {1}'\
|
|
.format(self._name, text)))
|
|
#
|
|
return text
|
|
|
|
def _parse_grp(self, text):
|
|
"""
|
|
parses the text corresponding to the definition of an embedded object
|
|
up to the terminating "}"
|
|
"""
|
|
Obj = CSN1Obj('')
|
|
text = Obj.parse_def(text, EOT='}')
|
|
text = text[1:].lstrip()
|
|
# in case this is an alternative, transfer the last list to alt
|
|
if Obj._alt is not None:
|
|
Obj.__insert_alt()
|
|
if text and text[0:1] in '(*':
|
|
Obj._num, text = CSN1Obj._parse_repet(text)
|
|
self.__insert_list( Obj.reduce() )
|
|
return text
|
|
|
|
@classmethod
|
|
def _parse_repet(cls, text):
|
|
# parses the arithmetic expression for a length or number of iterations
|
|
# can be a fixed integer value
|
|
# or a dynamic value referencing a value from an upper field
|
|
# eventually transformed through a function x -> a*(x+b)
|
|
if text[0:1] == '*':
|
|
text = text[1:].lstrip()
|
|
if text[0:1] == '*':
|
|
# indefinite number of iterations
|
|
text = CSN1Obj._parse_nostr(text[1:].lstrip())
|
|
return -1, text
|
|
elif text[0:1] == '(':
|
|
# fixed of dynamic arithmetic expr
|
|
expr_arithm, text = CSN1Obj._parse_arithm(text)
|
|
text = CSN1Obj._parse_nostr(text)
|
|
return expr_arithm, text
|
|
else:
|
|
# must be an unsigned integer
|
|
m = re.match(r'[0-9\s]{1,}', text)
|
|
if not m:
|
|
raise(CSN1Err('CSN1Obj._parse_repet: invalid definition, %s'\
|
|
% text))
|
|
text = CSN1Obj._parse_nostr(text[m.end():].lstrip())
|
|
return int(re.sub(r'\s', '', m.group())), text
|
|
else:
|
|
assert(text[0:1] == '(')
|
|
text_tmp = text[1:].lstrip()
|
|
if text_tmp[0:1] == '*':
|
|
text_tmp = text_tmp[1:].lstrip()
|
|
if text_tmp[0:1] == ')':
|
|
text = CSN1Obj._parse_nostr(text_tmp[1:].lstrip())
|
|
return -1, text
|
|
expr_arithm, text = CSN1Obj._parse_arithm(text)
|
|
text = CSN1Obj._parse_nostr(text)
|
|
return expr_arithm, text
|
|
|
|
@classmethod
|
|
def _parse_nostr(cls, text):
|
|
m = SYNT_RE_NOSTR.match(text)
|
|
if m:
|
|
# TODO: handle this encoding directive, i.e. send construction.
|
|
return text[m.end():].lstrip()
|
|
else:
|
|
return text
|
|
|
|
_scanner_arithm = [
|
|
('[0-9\s]{1,}', TOK_UINT, lambda m: int(re.sub(r'\s', '', m.group()))),
|
|
(_RE_VAL, TOK_REF, lambda m: m.group(1).strip()),
|
|
('\+', TOK_ADD, arithm_get_expr),
|
|
('\-', TOK_SUB, arithm_get_expr),
|
|
('\*', TOK_MUL, arithm_get_expr),
|
|
('\(', TOK_OPEN, arithm_get_expr),
|
|
('\)', TOK_CLOS, arithm_get_expr)
|
|
]
|
|
|
|
@classmethod
|
|
def _parse_arithm(cls, text):
|
|
# parses the arithmetic expression between ( and )
|
|
# 1) extract all possible arithm token up to the closure )
|
|
text = text[1:].lstrip()
|
|
op_clo, toks, err = [1, 0], [], False
|
|
while op_clo[1] < op_clo[0]:
|
|
match = False
|
|
for expr, tok, trans in cls._scanner_arithm:
|
|
m = re.match(expr, text)
|
|
if m:
|
|
toks.append( (tok, trans(m)) )
|
|
if tok == TOK_OPEN:
|
|
op_clo[0] += 1
|
|
elif tok == TOK_CLOS:
|
|
op_clo[1] += 1
|
|
# exit the for loop
|
|
match = True
|
|
text = text[m.end():].lstrip()
|
|
break
|
|
if not match:
|
|
# in case of invalid token, we warns and skip the whole expr
|
|
err = True
|
|
break
|
|
#
|
|
if err:
|
|
toks = [str(t[1]) for t in toks]
|
|
# just for counting "(" and ")" to jump over the arithmetic expr
|
|
toks.insert(0, '(')
|
|
toks.append(text[0:1])
|
|
text = text[1:]
|
|
while text and op_clo[1] < op_clo[0]:
|
|
if text[0:1] == '(':
|
|
op_clo[0] += 1
|
|
elif text[0:1] == ')':
|
|
op_clo[1] += 1
|
|
toks.append(text[0:1])
|
|
text = text[1:]
|
|
log('WARN: unable to process arithmetic expression, %s' % ''.join(toks))
|
|
log(' do it by hand within the Python file generated')
|
|
return ('# unprocessed: %s' % ''.join(toks),
|
|
(None, None)), text.lstrip()
|
|
#
|
|
# remove last closing parenthesis
|
|
del toks[-1]
|
|
# remove unneeded parenthesis
|
|
try:
|
|
ind = [t[0] for t in toks].index(TOK_REF)
|
|
except:
|
|
pass
|
|
else:
|
|
if len(toks) >= 3 and toks[ind-1][0] == TOK_OPEN and toks[ind+1][0] == TOK_CLOS:
|
|
del toks[ind+1]
|
|
del toks[ind-1]
|
|
try:
|
|
return CSN1Obj._process_arithm(toks), text
|
|
except:
|
|
log('WARN: unable to process arithmetic expression, (%s)'\
|
|
% ''.join([str(t[1]) for t in toks]))
|
|
assert()
|
|
log(' do it by hand within the Python file generated')
|
|
return ('# unprocessed: %s' % ''.join([str(t[1]) for t in toks]),
|
|
(None, None)), text.lstrip()
|
|
|
|
@classmethod
|
|
def _process_arithm(cls, toks):
|
|
# process the list of tokens into a single fixed uint or
|
|
# an lref-style tuple for dynamic value
|
|
if len(toks) == 1:
|
|
if toks[0][0] == TOK_UINT:
|
|
return toks[0][1]
|
|
elif toks[0][0]:
|
|
return (toks[0][1], (1, 0))
|
|
else:
|
|
raise(CSN1Err('unsupported'))
|
|
else:
|
|
# WARNING: this is a very crude and incomplete way to get
|
|
# an affine-like function from the list of tokens...
|
|
# this code is really crappy
|
|
if len(toks) == 3:
|
|
return cls._process_arithm_grp(*toks)
|
|
elif len(toks) == 7:
|
|
if toks[0][0] == TOK_OPEN and toks[4][0] == TOK_CLOS \
|
|
and toks[5][0] == TOK_MUL or toks[6][0] == TOK_UINT:
|
|
tok_ref, (a, b) = cls._process_arithm_grp(*toks[1:4])
|
|
if a != 1:
|
|
raise(CSN1Err('unsupported'))
|
|
return (tok_ref, (toks[6][1], b))
|
|
elif toks[0][0] == TOK_UINT and toks[1][0] == TOK_MUL \
|
|
and toks[2][0] == TOK_OPEN and toks[6][0] == TOK_CLOS:
|
|
tok_ref, (a, b) = cls._process_arithm_grp(*toks[3:6])
|
|
if a != 1:
|
|
raise(CSN1Err('unsupported'))
|
|
return (tok_ref, (toks[0][1], b))
|
|
else:
|
|
raise(CSN1Err('unsupported'))
|
|
else:
|
|
raise(CSN1Err('unsupported'))
|
|
|
|
@classmethod
|
|
def _process_arithm_grp(cls, tok0, tok1, tok2):
|
|
# get fixed operand and variable ref
|
|
if tok0[0] == TOK_REF:
|
|
tok_ref = tok0[1]
|
|
if tok2[0] == TOK_UINT:
|
|
tok_op = tok2[1]
|
|
else:
|
|
raise(CSN1Err('unsupported'))
|
|
elif tok0[0] == TOK_UINT:
|
|
tok_op = tok0[1]
|
|
if tok2[0] == TOK_REF:
|
|
tok_ref = tok2[1]
|
|
else:
|
|
raise(CSN1Err('unsupported'))
|
|
else:
|
|
raise(CSN1Err('unsupported'))
|
|
# get operation
|
|
if tok1[0] == TOK_ADD:
|
|
return tok_ref, (1, tok_op)
|
|
elif tok1[0] == TOK_SUB:
|
|
#return tok_ref, (-1, -tok_op)
|
|
if tok0[0] == TOK_REF:
|
|
return tok_ref, (1, -tok_op)
|
|
else:
|
|
return tok_ref, (-1, -tok_op)
|
|
elif tok1[0] == TOK_MUL:
|
|
return tok_ref, (tok_op, 0)
|
|
else:
|
|
raise(CSN1Err('unsupported'))
|
|
|
|
@classmethod
|
|
def _parse_excl(cls, text):
|
|
if text and text[0:1] in '01LHn':
|
|
m = SYNT_RE_VALUE.match(text)
|
|
if not m:
|
|
raise(CSN1Err('CSN1Obj._parse_excl: invalid value definition, {1}'\
|
|
.format(text)))
|
|
text_val = re.sub('\s', '', m.group())
|
|
text = text[m.end():].lstrip()
|
|
return CSN1Val('', text_val), text
|
|
elif text[0:1] == '{':
|
|
text = text[1:].lstrip()
|
|
Val = CSN1Val('')
|
|
while text[0:1] != '}':
|
|
text = Val.parse_val(text)
|
|
if text[0:1] == '|':
|
|
text = text[1:].lstrip()
|
|
return Val, text[1:].lstrip()
|
|
|
|
|
|
class CSN1Ref(CSN1Obj):
|
|
"""Class to handle simple reference to other CSN.1 object
|
|
"""
|
|
def __init__(self, name, obj):
|
|
self._name = name
|
|
self._obj = obj
|
|
self._num = 1
|
|
self._lref = None
|
|
self._par = None
|
|
self._ref = set( [obj] )
|
|
|
|
def __repr__(self):
|
|
if self._name:
|
|
return 'CSN1Ref(%s, obj=%s)' % (self._name, self._obj)
|
|
else:
|
|
return 'CSN1Ref(obj=%s)' % self._obj
|
|
|
|
|
|
class CSN1Val(CSN1Obj):
|
|
"""Class to handle CSN.1 value, as used within choice
|
|
"""
|
|
def __init__(self, name='', text=''):
|
|
self._name = name
|
|
self._val = []
|
|
self._num = 1
|
|
self._lref = None
|
|
self._par = None
|
|
self._ref = set()
|
|
if text:
|
|
text = self.parse_val(text)
|
|
|
|
def __repr__(self):
|
|
if self._name:
|
|
return 'CSN1Val(%s: %r)' % (self._name, self._val)
|
|
else:
|
|
return 'CSN1Val(%r)' % self._val
|
|
|
|
def parse_val(self, text):
|
|
self._text_val = text
|
|
m = SYNT_RE_VALUE.match(text)
|
|
if not m:
|
|
raise(CSN1Err('{0}: invalid value definition, {1}'\
|
|
.format(self._name, text)))
|
|
text_val = re.sub('\s', '', m.group())
|
|
text = text[m.end():].lstrip()
|
|
if text and text[0:1] in '(*':
|
|
num, text = CSN1Obj._parse_repet(text)
|
|
if num == -1:
|
|
text_val += '**'
|
|
else:
|
|
text_val = num*text_val
|
|
self._val.append( text_val )
|
|
return text
|
|
|
|
|
|
#------------------------------------------------------------------------------#
|
|
# CSN.1 predefined objects
|
|
#------------------------------------------------------------------------------#
|
|
|
|
spare_bit = CSN1Obj('spare bit')
|
|
spare_bit._bit = 1
|
|
|
|
spare_bits = CSN1Obj('spare bits')
|
|
spare_bits._bit = 1
|
|
spare_bits._num = -1
|
|
|
|
spare_padding = CSN1Val('spare padding')
|
|
spare_padding._val = ['L**']
|
|
|
|
octet = CSN1Obj('octet')
|
|
octet._bit = 8
|
|
|
|
def get_predef_obj(bit=True, bits=True, padding=True, octet=True):
|
|
"""Returns a dict with the following predefined objects:
|
|
<spare bit>
|
|
<spare bits>
|
|
<spare padding>
|
|
"""
|
|
ret = {}
|
|
if bit:
|
|
ret['spare_bit'] = spare_bit
|
|
ret['Spare_bit'] = spare_bit
|
|
ret['Spare_Bit'] = spare_bit
|
|
if bits:
|
|
ret['spare_bits'] = spare_bits
|
|
ret['Spare_bits'] = spare_bits
|
|
ret['Spare_Bits'] = spare_bits
|
|
if padding:
|
|
ret['spare_padding'] = spare_padding
|
|
ret['Spare_padding'] = spare_padding
|
|
ret['Spare_Padding'] = spare_padding
|
|
if octet:
|
|
ret['octet'] = octet
|
|
ret['Octet'] = octet
|
|
return ret
|
|
|
|
def gen_predef_obj(bit=True, bits=True, padding=True, octet=True):
|
|
"""Returns the textual definition of the following predifined objects:
|
|
<spare bit>
|
|
<spare bits>
|
|
<spare padding>
|
|
"""
|
|
ret = []
|
|
if bit:
|
|
ret.append('''
|
|
spare_bit = CSN1Bit(name='spare_bit')
|
|
Spare_bit = spare_bit
|
|
Spare_Bit = spare_bit
|
|
''')
|
|
if bits:
|
|
ret.append('''
|
|
spare_bits = CSN1Bit(name='spare_bits', num=-1)
|
|
Spare_bits = spare_bits
|
|
Spare_Bits = spare_bits
|
|
''')
|
|
if padding:
|
|
ret.append('''
|
|
spare_padding = CSN1Val(name='spare_padding', val='L', num=-1)
|
|
Spare_padding = spare_padding
|
|
Spare_Padding = spare_padding
|
|
''')
|
|
if octet:
|
|
ret.append('''
|
|
octet = CSN1Bit(name='octet', num=8)
|
|
Octet = octet
|
|
''')
|
|
return ''.join(ret) + '\n'
|
|
|
|
def set_predef_arg(Obj, parg):
|
|
for dep in Obj._ref:
|
|
if pythonize_name(dep) in ('spare_bit', 'Spare_bit', 'Spare_Bit'):
|
|
parg[0] = True
|
|
elif pythonize_name(dep) in ('spare_bits', 'Spare_bits', 'Spare_Bits'):
|
|
parg[1] = True
|
|
elif pythonize_name(dep) in ('spare_padding', 'Spare_padding', 'Spare_Padding'):
|
|
parg[2] = True
|
|
elif pythonize_name(dep) in ('octet', 'Octet'):
|
|
parg[3] = True
|
|
|
|
#------------------------------------------------------------------------------#
|
|
# CSN.1 definition translator
|
|
#------------------------------------------------------------------------------#
|
|
|
|
global obj_name
|
|
|
|
def translate_text(text=u'', **kwargs):
|
|
"""Scans a text (or list of texts) for CSN.1 object definitions
|
|
|
|
translate them and return prototype Python definitions, to be used with
|
|
the CSN.1 runtime
|
|
|
|
Args:
|
|
text (str, or iterable of str): textual CSN.1 definition(s)
|
|
|
|
Returns:
|
|
result (3-tuple): a dict with generated Python objects,
|
|
a set of external references and
|
|
a text str containing the corresponding Python source code
|
|
"""
|
|
if isinstance(text, (list, tuple)):
|
|
text = u'\n\n'.join(text)
|
|
elif not isinstance(text, str_types):
|
|
raise(CSN1Err('[proc] need some textual definition'))
|
|
text = clean_text(text)
|
|
Objs, ret = {}, []
|
|
global obj_name
|
|
#
|
|
# 0) add all predifined objects
|
|
predef = get_predef_obj(True, True, True, True)
|
|
predef_name = list(predef.keys())
|
|
predef_args = [False, False, False, False]
|
|
#
|
|
# 1) process definition until the end of the text
|
|
while text:
|
|
text = process_definition(text, Objs, **kwargs)
|
|
#
|
|
# 2) sort local / external references
|
|
ObjList = list(Objs.keys())
|
|
# collect all references
|
|
RefList = set()
|
|
for Obj in Objs.values():
|
|
for ref in Obj._ref:
|
|
RefList.add(pythonize_name(ref))
|
|
# identify the set of external references
|
|
ExtSet = set()
|
|
for ref in RefList:
|
|
if ref not in ObjList and ref not in predef:
|
|
ExtSet.add(ref)
|
|
#
|
|
# 3) translate objects in the required local referencing order
|
|
while ObjList:
|
|
obj_len = len(ObjList)
|
|
for pyname in ObjList[:]:
|
|
Obj = Objs[pyname]
|
|
if isinstance(Obj, CSN1Val) or not Obj._ref:
|
|
# object without dependency
|
|
obj_name = pyname
|
|
Obj._root = True
|
|
ret.append( '%s = %s\n' % (pyname, translate_object(Obj)) )
|
|
ObjList.remove( pythonize_name(Obj._name) )
|
|
else:
|
|
if all([pythonize_name(dep) not in ObjList or \
|
|
pythonize_name(dep) == pyname or \
|
|
pythonize_name(dep) in predef_name \
|
|
for dep in Obj._ref]):
|
|
# object with all local dependencies already defined
|
|
obj_name = pyname
|
|
Obj._root = True
|
|
ret.append( '%s = %s\n' % (pyname, translate_object(Obj)) )
|
|
ObjList.remove( pythonize_name(Obj._name) )
|
|
set_predef_arg(Obj, predef_args)
|
|
# ensure we keep defining objects
|
|
if len(ObjList) == obj_len:
|
|
log('ERR: unable to define objects, %r' % ObjList)
|
|
break
|
|
#l
|
|
# 3) return python source
|
|
return Objs, ExtSet, \
|
|
'# code automatically generated by pycrate_csn1\n' + \
|
|
'# change object type with type=CSN1T_BSTR (default type is CSN1T_UINT) in init\n' + \
|
|
'# add dict for value interpretation with dic={...} in CSN1Bit init\n' + \
|
|
'# add dict for key interpretation with kdic={...} in CSN1Alt init\n\n' + \
|
|
'from pycrate_csn1.csnobj import *\n' + \
|
|
gen_predef_obj(*predef_args) + \
|
|
'\n'.join(ret)
|
|
|
|
|
|
def process_definition(text, Objs, **kwargs):
|
|
"""Processes a single CSN.1 definition and returns a Python object
|
|
"""
|
|
# look for the assignment sign ::=
|
|
m = re.search('::=', text)
|
|
if not m:
|
|
log('CSN.1 object assignment sign ::= not found')
|
|
return ''
|
|
# get the object name
|
|
text_name = text[:m.start()].strip()
|
|
text = text[m.end():].strip()
|
|
m = SYNT_RE_NAME.match(text_name)
|
|
if not m:
|
|
raise(CSN1Err('missing CSN.1 object name'))
|
|
name = m.group(1).strip()
|
|
# scan the object definition up to the 1st semi-colon
|
|
m = re.search(';', text)
|
|
if not m:
|
|
raise(CSN1Err('missing CSN.1 object end-of-definition sign ;'))
|
|
text_def = text[:m.start()].strip()
|
|
text = text[m.end():].strip()
|
|
# create a pythonic name
|
|
pyname = pythonize_name(name)
|
|
# populate the object dict
|
|
Obj = CSN1Obj(name, text_def)
|
|
Obj = Obj.reduce()
|
|
Obj.resolve_refs()
|
|
Objs[pyname] = Obj
|
|
return text
|
|
|
|
|
|
def translate_object(Obj):
|
|
if isinstance(Obj, CSN1Ref):
|
|
return translate_ref(Obj)
|
|
elif isinstance(Obj, CSN1Val):
|
|
return translate_val(Obj)
|
|
elif Obj._bit is not None:
|
|
return translate_bit(Obj)
|
|
elif Obj._list is not None:
|
|
return translate_list(Obj)
|
|
elif Obj._alt is not None:
|
|
return translate_alt(Obj)
|
|
else:
|
|
assert()
|
|
|
|
|
|
def _translate_arithm(val):
|
|
if isinstance(val, integer_types):
|
|
return '%i' % val
|
|
else:
|
|
if isinstance(val[1], tuple) and len(val[1]) == 2 and \
|
|
isinstance(val[1][0], integer_types) and \
|
|
isinstance(val[1][1], integer_types):
|
|
if val[1][1] != 0:
|
|
if val[1][0] == 1:
|
|
trans = 'lambda x: x + %i' % val[1][1]
|
|
else:
|
|
trans = 'lambda x: %i * (x + %i)' % val[1]
|
|
else:
|
|
if val[1][0] == 1:
|
|
trans = 'lambda x: x'
|
|
else:
|
|
trans = 'lambda x: %i * x' % val[1][0]
|
|
else:
|
|
trans = 'lambda: 0'
|
|
if isinstance(val[0], str_types):
|
|
return '(%r, %s)' % (val[0], trans)
|
|
else:
|
|
return '(%s, %s)' % (val[0], trans)
|
|
|
|
|
|
def translate_ref(Obj):
|
|
global obj_name
|
|
name, obj, args = pythonize_name(Obj._name), Obj._obj, []
|
|
if pythonize_name(obj) == obj_name:
|
|
# self reference
|
|
cla = 'CSN1SelfRef'
|
|
elif obj[:11] == '__pycrate__':
|
|
cla = 'CSN1ExtRef'
|
|
if Obj._name:
|
|
args.append( 'name=%r' % name )
|
|
args.append( 'obj=%s' % repr(tuple(obj[12:].split('.'))) )
|
|
else:
|
|
cla = 'CSN1Ref'
|
|
if Obj._name:
|
|
args.append('name=%r' % name)
|
|
args.append( 'obj=%s' % pythonize_name(obj) )
|
|
if Obj._num != 1:
|
|
args.append( 'num=%s' % _translate_arithm(Obj._num) )
|
|
if Obj._lref is not None:
|
|
args.append( 'lref=%s' % _translate_arithm(Obj._lref) )
|
|
return '%s(%s)' % (cla, ', '.join(args))
|
|
|
|
|
|
def translate_val(Obj):
|
|
if len(Obj._val) == 1:
|
|
if Obj._num == 1:
|
|
return 'CSN1Val(name=%r, val=%r)' \
|
|
% (pythonize_name(Obj._name), Obj._val[0])
|
|
else:
|
|
return 'CSN1Val(name=%r, val=%r, num=%s)' \
|
|
% (pythonize_name(Obj._name), Obj._val[0],
|
|
_translate_arithm(Obj._num))
|
|
else:
|
|
# convert to a list of alternatives
|
|
Alt = CSN1Obj(name=Obj._name)
|
|
Alt._alt = [CSN1Val('', v) for v in Obj._val]
|
|
if hasattr(Obj, '_root') and Obj._root:
|
|
Alt._root = True
|
|
return translate_alt(Alt)
|
|
|
|
|
|
def translate_bit(Obj):
|
|
args = []
|
|
if Obj._name:
|
|
args.append( 'name=%r' % pythonize_name(Obj._name) )
|
|
if Obj._bit != 1:
|
|
if isinstance(Obj._bit, integer_types) and \
|
|
isinstance(Obj._num, integer_types) and Obj._num > 1:
|
|
# quick optimization
|
|
Obj._bit *= Obj._num
|
|
Obj._num = 1
|
|
args.append( 'bit=%s' % _translate_arithm(Obj._bit) )
|
|
if Obj._num != 1:
|
|
args.append( 'num=%s' % _translate_arithm(Obj._num) )
|
|
if Obj._lref is not None:
|
|
args.append( 'lref=%s' % _translate_arithm(Obj._lref) )
|
|
if Obj._excl is not None:
|
|
# we only keep raw bit-strings
|
|
args.append( 'excl=%r' % Obj._excl._val )
|
|
return 'CSN1Bit(%s)' % ', '.join(args)
|
|
|
|
|
|
def translate_list(Obj):
|
|
# translate each list internal object on a new line
|
|
args = []
|
|
if Obj._name:
|
|
args.append( 'name=%r' % pythonize_name(Obj._name) )
|
|
if Obj._num != 1:
|
|
args.append( 'num=%s' % _translate_arithm(Obj._num) )
|
|
if Obj._lref is not None:
|
|
args.append( 'lref=%s' % _translate_arithm(Obj._lref) )
|
|
if hasattr(Obj, '_trunc'):
|
|
args.append( 'trunc=True' )
|
|
if Obj._list:
|
|
args.append( 'list=' + _translate_objlist(Obj._list) )
|
|
return 'CSN1List(%s)' % ', '.join(args)
|
|
|
|
|
|
def translate_alt(Obj):
|
|
selec = build_alt_selector(Obj)
|
|
args = []
|
|
if Obj._name:
|
|
args.append( 'name=%r' % pythonize_name(Obj._name) )
|
|
if Obj._num != 1:
|
|
args.append( 'num=%s' % _translate_arithm(Obj._num) )
|
|
if Obj._lref is not None:
|
|
args.append( 'lref=%s' % _translate_arithm(Obj._lref) )
|
|
if hasattr(Obj, '_trunc'):
|
|
args.append( 'trunc=True' )
|
|
#
|
|
arg_alt = []
|
|
for (val, (val_name, objs)) in selec.items():
|
|
arg_alt.append( ' %r: (%r, %s),\n' % (val, val_name, _translate_objlist(objs)) )
|
|
# sorting alternatives
|
|
arg_alt.sort()
|
|
if len(arg_alt):
|
|
arg_alt = 'alt={\n' + ''.join(arg_alt)[:-2] + '}'
|
|
else:
|
|
arg_alt = 'alt={}'
|
|
args.append( arg_alt )
|
|
return 'CSN1Alt(%s)' % ', '.join(args)
|
|
|
|
|
|
def _translate_objlist(ol):
|
|
ret = ['[\n']
|
|
for obj in ol:
|
|
ret.append( ' %s,\n' % translate_object(obj).replace('\n', '\n ') )
|
|
if len(ret) > 1:
|
|
ret[-1] = ret[-1][:-2] + ']'
|
|
else:
|
|
ret[0] = ret[0][:-1] + ']'
|
|
return ''.join(ret)
|
|
|
|
|
|
def build_alt_selector(Obj):
|
|
# define a discriminant for each branch and build a dict according to
|
|
# determined selectors
|
|
selec, has_null, req_null = [], [], []
|
|
for alt in Obj._alt:
|
|
# check the 1st field of each alternative
|
|
if isinstance(alt, CSN1Val):
|
|
# single value that means that we exit the object
|
|
if len(alt._val) > 1:
|
|
# multiple values
|
|
for val in alt._val:
|
|
if val == 'null':
|
|
has_null.append( True )
|
|
else:
|
|
# potential alternative
|
|
if val[-2:] == '**':
|
|
# take care of repeated value (e.g. 0**)
|
|
nv = ASN1Val()
|
|
nv.parse_val(val)
|
|
selec.append( (val[:-2], (pythonize_name(alt._name), [nv])) )
|
|
else:
|
|
selec.append( (val, (pythonize_name(alt._name), [])) )
|
|
has_null.append( False )
|
|
else:
|
|
if alt._val[0] == 'null':
|
|
# no more bit to consume, exiting the structure
|
|
has_null.append(True)
|
|
else:
|
|
# potential alternative
|
|
if alt._val[0][-2:] == '**':
|
|
# take care of repeated value (e.g. 0**)
|
|
selec.append( (alt._val[0][:-2], (pythonize_name(alt._name), [alt])) )
|
|
else:
|
|
selec.append( (alt._val[0], (pythonize_name(alt._name), [])) )
|
|
has_null.append( False )
|
|
#
|
|
elif isinstance(alt, CSN1Ref):
|
|
selec.append( ('', (pythonize_name(alt._name), [alt])) )
|
|
req_null.append( True )
|
|
#
|
|
elif isinstance(alt, list):
|
|
# list of fields
|
|
if isinstance(alt[0], CSN1Val):
|
|
if len(alt[0]._val) > 1:
|
|
# multiple values
|
|
# associate all of them with the rest of the objects' list
|
|
rest = alt[1:]
|
|
for val in alt[0]._val:
|
|
if val[-2:] == '**':
|
|
raise(CSN1Err('unsupported alternative key, %s' % val))
|
|
selec.append( (val, (pythonize_name(alt[0]._name), rest)) )
|
|
has_null.append( False )
|
|
else:
|
|
if alt[0]._val[0][-2:] == '**':
|
|
raise(CSN1Err('unsupported alternative key, %s' % alt[0]._val[0]))
|
|
selec.append( (alt[0]._val[0], (pythonize_name(alt[0]._name), alt[1:])) )
|
|
has_null.append( False )
|
|
elif alt[0]._bit and alt[0]._excl:
|
|
# generate all the possible values and remove the excluded one(s)
|
|
vals = list(map(lambda x: '{0:0>{1}}'.format(bin(x)[2:], alt[0]._bit),
|
|
range(0, (2<<(alt[0]._bit-1)))))
|
|
for val in alt[0]._excl._val:
|
|
assert( val in vals )
|
|
vals.remove(val)
|
|
# and associate all of them to the rest of the objects' list
|
|
rest = alt[1:]
|
|
for val in vals:
|
|
selec.append( (val, (pythonize_name(alt[0]._name), rest)) )
|
|
has_null.append( False )
|
|
else:
|
|
# this means that we have a null to exit the structure as
|
|
# the only other alternative
|
|
selec.append( ('', ('', alt)) )
|
|
req_null.append( True )
|
|
#
|
|
elif isinstance(alt, CSN1Obj) and alt._list \
|
|
and alt._num == 1 and alt._lref is None:
|
|
# list object
|
|
if alt._list[0]._name:
|
|
alt_name = alt._list[0]._name
|
|
elif alt._name:
|
|
alt_name = alt._name
|
|
else:
|
|
alt_name = ''
|
|
if isinstance(alt._list[0], CSN1Val):
|
|
if len(alt._list[0]._val) > 1:
|
|
# multiple values
|
|
# associate all of them with the rest of the objects' list
|
|
rest = alt._list[1:]
|
|
for val in alt._list[0]._val:
|
|
if val[-2:] == '**':
|
|
raise(CSN1Err('unsupported alternative key, %s' % val))
|
|
selec.append( (val, (pythonize_name(alt_name), rest)) )
|
|
has_null.append( False )
|
|
else:
|
|
if alt._list[0]._val[0][-2:] == '**':
|
|
raise(CSN1Err('unsupported alternative key, %s' % alt._list[0]._val[0]))
|
|
selec.append( (alt._list[0]._val[0], (pythonize_name(alt_name), alt._list[1:])) )
|
|
has_null.append( False )
|
|
elif alt._list[0]._bit and alt._list[0]._excl:
|
|
# generate all the possible values and remove the excluded one(s)
|
|
vals = list(map(lambda x: '{0:0>{1}}'.format(bin(x)[2:], alt._list[0]._bit),
|
|
range(0, (2<<(alt._list[0]._bit-1)))))
|
|
for val in alt._list[0]._excl._val:
|
|
assert( val in vals )
|
|
vals.remove(val)
|
|
# and associate all of them to the rest of the objects' list
|
|
rest = alt._list[1:]
|
|
for val in vals:
|
|
selec.append( (val, (pythonize_name(alt_name), rest)) )
|
|
has_null.append( False )
|
|
else:
|
|
# this means that we have a null to exit the structure as
|
|
# the only other alternative
|
|
selec.append( ('', ('', alt)) )
|
|
req_null.append( True )
|
|
#
|
|
else:
|
|
assert()
|
|
#
|
|
# ensure everything is solvable
|
|
if not req_null:
|
|
# OK, every alternative starts with a value or is null
|
|
d = _build_dict_from_alt(selec)
|
|
elif len(req_null) == 1:
|
|
# one alternative starts with an undetermined value
|
|
if has_null != [True]:
|
|
# the only possible other alternative is a single null field
|
|
raise(CSN1Err('an alternative starts with undetermined value'))
|
|
else:
|
|
# OK
|
|
d = _build_dict_from_alt(selec)
|
|
else:
|
|
# several alternatives starts with an undetermined value
|
|
raise(CSN1Err('mutliple alternatives start with undetermined values'))
|
|
#
|
|
if any(has_null):
|
|
d[None] = ('', [])
|
|
return d
|
|
|
|
|
|
def _build_dict_from_alt(selec):
|
|
d = {}
|
|
for (k, l) in selec:
|
|
# get the list of keys, enventually truncated to the length of k
|
|
if d and not all([_check_keys(k, key) for key in d.keys() if len(key) > 0]):
|
|
raise(CSN1Err('multiple alternatives start with the same value, {0!r}'\
|
|
.format(k)))
|
|
d[k] = l
|
|
return d
|
|
|
|
|
|
def _check_keys(k1, k2):
|
|
# ensure two CSN1Alt keys are not colliding
|
|
if k1 == k2[:len(k1)] or k1[:len(k2)] == k2:
|
|
return False
|
|
else:
|
|
return True
|
|
|