Commit f1e29439 authored by Maxime Perrotin's avatar Maxime Perrotin
Browse files

Add VDM value notation parser

parent 926013c4
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
ASN.1 Value Editor - Parse VDM Value notation
Mapping of ASN.1 types:
----------------------
Boolean : true, false
Integer: numbers
Real: numbers dot numbers
Sequence: mk_typename(val1, val2..)
Set of: { a, b. c }
Sequence of: [a, b, c]
Enum: <value>
Choice: ?
Bit string literal : ?
Octet string literal : ?
String Literal : ?
Copyright (c) 2012-2015 European Space Agency
Designed and implemented by Maxime Perrotin
Contact: maxime.perrotin@esa.int
License is LGPLv3 - Check the LICENSE file
"""
from pyparsing import(Word, QuotedString, Literal, Combine, Optional,
oneOf, OneOrMore, srange, nums, nestedExpr, Forward,
delimitedList, Keyword, ParseException)
import string
# TBC in VDM - Keep ASN.1 Value Notation
BitStringLiteral = ('"' + Word("01") + '"B').setResultsName('BITSTRING')
# TBC in VDM - Keep ASN.1 Value Notation
OctetStringLiteral=('"' + Word(string.hexdigits) + '"H').setResultsName('OCTETSTRING')
# QuotedString's first parameter = quote type..+multiline to support \n => OK
# Support embedded quotes as per GSER standard (""embedded quoted text"")
StringLiteral = QuotedString('"', escQuote='""', multiline=True,).setResultsName('QUOTEDSTRING')
TRUE = Literal("true").setResultsName('TRUE')
FALSE = Literal("false").setResultsName('FALSE')
# TBC in VDM
NULL = Literal("NULL").setResultsName('NULL')
COMMA = Literal(',').suppress()
# TBC in VDM
PLUS_INFINITY = Literal("PLUS-INFINITY").setResultsName('PLUS-INFINITY')
MINUS_INFINITY = Literal("MINUS-INFINITY").setResultsName('MINUS-INFINITY')
# EMPTY_LIST:
LBRACKET = Literal('{').suppress()
RBRACKET = Literal('}').suppress()
# Square brackets
LSBRACKET = Literal('[').suppress()
RSBRACKET = Literal(']').suppress()
# Parenthesis
LPAREN = Literal('(').suppress()
RPAREN = Literal(')').suppress()
# INTEGER
INT = Combine(Optional(oneOf("+ -")) + (Word(srange("[1-9]"), nums)|Literal('0'))).setResultsName('INTEGER')
# This one is correct:
# a lowercase followed by a-zA-z and -
#LID = Word(string.lowercase, string.letters+string.digits+'-')
# Update MP 21/11/12: made it tolerant to uppercase for the first character
LID = Word(string.letters, string.letters+string.digits+'-')
identifier = LID.setResultsName('IDENTIFIER')
# Value reference is in practice used to identify enumerated values
valuereference = LID.setResultsName('VALUE_REF')
# FloatingPointLiteral
DOT = Literal('.')
DOUBLE_DOT = Literal('..')
# Exponent (e.g. e+10)
Exponent = oneOf("e E") + Optional(oneOf("+ -")) + OneOrMore(Word(nums))
# Thefirst (INT + DOUBLE_DOT) is unclear (e.g. "5..").
FloatingPointLiteral = (INT + DOUBLE_DOT
| Combine(INT + DOT + Optional(Word(nums)) + Optional(Exponent))
| INT).setResultsName('REAL1')
# Numeric value2 (eg. { mantissa 3, base 4, exponent 5 })
# Does this noataion exist in VDM ?
MANTISSA = Literal('mantissa').suppress()
BASE = Literal('base').suppress()
EXPONENT = Literal('exponent').suppress()
NUMERIC_VALUE2 = (nestedExpr('{', '}', MANTISSA + INT + COMMA + BASE + INT + COMMA + EXPONENT + INT)).setResultsName('REAL2')
# Forward allows to use recursive constructs
NAMED_VALUE_LIST = Forward()
SETOF = Forward()
VALUE_LIST = Forward()
choiceValue = Forward()
# Support for "Any" value (using a star)
AnyValueLiteral = Keyword('*').setResultsName('ANY')
# ASN.1 Value Notation: only missing construct are OID references
# Ambiguity: FloatingPointLiteral contains INT -> INT will never be parsed as REAL
value = (BitStringLiteral
| OctetStringLiteral
| TRUE
| FALSE
| StringLiteral
| NULL
| PLUS_INFINITY
| MINUS_INFINITY
| NAMED_VALUE_LIST
| VALUE_LIST
| SETOF
| (valuereference ^ choiceValue)
| (INT ^ FloatingPointLiteral)
| NUMERIC_VALUE2
| AnyValueLiteral)
# ASN.1 CHOICE --> TBC in VDM
choiceValue << (identifier + ':' + value).setResultsName('CHOICE')
# SEQUENCE
NAMED_VALUE_LIST << LPAREN + delimitedList(value) + RPAREN
# VDM SEQUENCE OF
VALUE_LIST << LSBRACKET + Optional(delimitedList(value)) + RSBRACKET
# SET OF
SETOF << LBRACKET + Optional(delimitedList(value)) + RBRACKET
# Parse actions allow to modify the AST to be compliant with the ASN.1 Editor input
NAMED_VALUE_LIST.setParseAction(lambda s, l, t: [t.asList()])
VALUE_LIST.setParseAction(lambda s, l, t: [t.asList()])
SETOF.setParseAction(lambda s, l, t: [t.asList()])
choiceValue.setParseAction(lambda s, l, t: {'Choice': t[0].replace('-', '_'), t[0].replace('-', '_'): t[2]})
valuereference.setParseAction(lambda s, l, t: {'Enum': t[0].replace('-', '_')})
FloatingPointLiteral.setParseAction(lambda s, l, t: float(t[0]))
INT.setParseAction(lambda s, l, t: int(t[0]))
TRUE.setParseAction(lambda s, l, t: True)
FALSE.setParseAction(lambda s, l, t: False)
AnyValueLiteral.setParseAction(lambda s, l, t: {'Any': t[0]})
OctetStringLiteral.setParseAction(lambda s, l, t: ''.join([chr(int(t[1][i:i+2], 16)) for i in range(0, len(t[1]), 2)]))
# BitString Warning: will only work with series of 8 bits
BitStringLiteral.setParseAction(lambda s, l, t: ''.join([chr(int(t[1][i:i+9], 2)) for i in range(0, len(t[1]), 2)]))
def parse_vdm(varName='vdm', string=''):
''' Return a variable compatible with the ASN.1 Editor from a VDM string'''
try:
return {varName: value.parseString(string, True)[0]}
except ParseException as err:
print '[parse_vdm] Parsing error:', string
raise
def toASN1ValueNotation(val):
''' Create a GSER reprentation of the Python variable '''
result = ''
if isinstance(val, dict):
if 'Choice' in val:
result += val['Choice'].replace('_', '-').strip() + ':'
result += toASN1ValueNotation(val[val['Choice']])
elif 'Enum' in val:
result += unicode(val['Enum']).replace('_', '-').strip()
else: # SEQUENCE and SET
result += ' { '
needsComa = False
for seqElem in val:
if needsComa:
result += ', '
result += seqElem.replace('_', '-').strip() + ' '
result += toASN1ValueNotation(val[seqElem])
needsComa = True
result += ' }'
elif isinstance(val, list): # SEQUENCE OF or SET OF
result += ' { '
needsComa = False
for seqOfElem in val:
if needsComa:
result += ', '
result += toASN1ValueNotation(seqOfElem)
needsComa = True
result += ' }'
elif isinstance(val, bool):
# boolean are in upper case in ASN.1
result += str(val).upper()
elif isinstance(val, basestring):
# Strings: add quotes, and if string contain non-displayable chars, use repr() to get a visible string
if all(c in string.printable for c in val):
result = '"' + val + '"'
else:
result = '"'
for c in val:
result += '%02x' % ord(c)
result += '"H'
else:
result = str(val) # INTEGER and REAL
return result
def valueNotationToSwig(gser, dest, sort, ASN1Swig, ASN1_AST, var=None):
''' Parse a GSER value and fill a SWIG variable with it
Inputs:
gser : input GSER string
dest : output SWIG instance to be filled
ASN1Swig : python module containing SWIG DV access
sort: ASN1 typename, with dashes, no underscores
ASN1_AST : AST generated by ASN1SCC
var : optional already pyside-converted GSER string
Outputs:
none - "dest" is modified by this function
'''
var = var or value.parseString(gser, True)[0]
def reach(field, orig, idx=True):
''' Helper: move swig pointer to the next field, and optionaly
index (if idx=True)
Inputs: field is a string with optional index (e.g. "a[0]")
orig is the swig pointer
idx: set to true if you want the index to be reached
'''
split = field.strip(']').split('[')
ptr = getattr(orig, split[0]) if split else orig
if len(split) > 1 and idx:
# SEQOF index
ptr = ptr[int(split[1])]
def rec(inp, outp, sort):
''' Recursively fill up the value '''
if sort.kind == 'ReferenceType':
sort = ASN1_AST[sort.ReferencedTypeName]
if isinstance(inp, list):
# SEQUENCE OF
# get the path to the sequence of
_, params, path = outp.GetState()
if path:
path = path.strip('.').split('.')
for i in range(len(inp)):
outp.Reset()
for each in path:
reach(each, outp)
# Follow the ASN.1 type in the AST from ASN1SCC
rec(inp[i], outp[i], sort.type.type)
if sort.type.Min != sort.type.Max:
outp.Reset()
for each in path:
reach(each, outp)
# The ASN1SCC AST only knows if the list has a fixed length
outp.SetLength(len(inp))
elif isinstance(inp, (int, float, bool)):
outp.Set(inp)
elif isinstance(inp, dict):
if 'Enum' in inp:
# Get proper enum id from the ASN1SCC AST
enum_id = sort.type.EnumValues[
inp['Enum'].replace('_', '-')].EnumID
val = getattr(ASN1Swig.DV, enum_id)
outp.Set(val)
elif 'Choice' in inp:
child_name = inp['Choice']
ch_ty = sort.type.Children[child_name.replace('_', '-')]
enum_val = getattr(ASN1Swig.DV, ch_ty.EnumID)
outp.kind.Set(enum_val)
rec(inp[child_name],
getattr(outp,
child_name.replace('-', '_')),
ch_ty.type)
else:
# SEQUENCE
# get the path to the sequence
_, params, path = outp.GetState()
if path:
path = path.strip('.').split('.')
for field, data in inp.viewitems():
outp.Reset()
for each in path:
# Reach the path, including indexes
reach(each, outp)
# Then get the field itself
reach(field.replace('-', '_'), outp)
field_ty = sort.type.Children[field.replace('_', '-')]
rec(data, outp, field_ty.type)
# move back to original path
outp.Reset()
if len(path):
reach(path[0], outp)
else:
# Unsupported type
pass
rec(var, dest, sort)
if __name__ == '__main__':
''' Test application '''
import sys
if len(sys.argv)==2:
print parse_vdm(string=sys.argv[1])
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment