Commit fce7eeb6 authored by dbarbera's avatar dbarbera
Browse files

Merge remote-tracking branch 'upstream/master'

Conflicts:
	AdaGenerator.py
	sdl92Lexer.py
	sdl92Parser.py
parents 183aa980 d0816dba
......@@ -639,29 +639,28 @@ def _task_forloop(task):
loop['range']['start'])
local_decl.extend(start_local)
stmt.extend(start_stmt)
# ASN.1 Integers are 64 bits - we need to convert to 32 bits
#if isinstance(loop['range']['start'], ogAST.PrimInteger):
# start_str = 'Integer({})'.format(start_str)
if loop['range']['step'] == 1:
start_str += '..'
stop_stmt, stop_str, stop_local = expression(loop['range']['stop'])
local_decl.extend(stop_local)
stmt.extend(stop_stmt)
#if isinstance(loop['range']['stop'], ogAST.PrimInteger):
# stop_str = 'Integer({})'.format(stop_str)
if loop['range']['step'] == 1:
if unicode.isnumeric(stop_str):
stop_str = unicode(int(stop_str) - 1)
else:
stop_str = u'{} - 1'.format(stop_str)
stmt.append(
'for {it} in {start}{stop} loop'
u'for {it} in {start}{stop} loop'
.format(it=loop['var'], start=start_str, stop=stop_str))
else:
# Step is not directly supported in Ada, we need to use 'while'
stmt.extend(['declare',
'{it} : Integer := {start};'
u'{it} : Integer := {start};'
.format(it=loop['var'],
start=start_str),
'',
'begin',
'while {it} < {stop} loop'.format(it=loop['var'],
u'while {it} < {stop} loop'.format(it=loop['var'],
stop=stop_str)])
else:
# case of form: FOR x in SEQUENCE OF
......@@ -724,7 +723,7 @@ def _primary_variable(prim):
if prim.exprType.__name__ == 'for_range':
# Ada iterator in FOR loops is an Integer - we must cast to 64 bits
ada_string = u'Asn1Int({})'.format(ada_string)
return [], ada_string, []
return [], unicode(ada_string), []
@expression.register(ogAST.PrimCall)
......@@ -801,7 +800,7 @@ def _prim_call(prim):
ada_string += ', '.join(list_of_params)
ada_string += ')'
return stmts, ada_string, local_decl
return stmts, unicode(ada_string), local_decl
@expression.register(ogAST.PrimIndex)
......@@ -824,7 +823,7 @@ def _prim_index(prim):
stmts.extend(idx_stmts)
local_decl.extend(idx_var)
return stmts, ada_string, local_decl
return stmts, unicode(ada_string), local_decl
@expression.register(ogAST.PrimSubstring)
......@@ -865,7 +864,7 @@ def _prim_substring(prim):
idx=prim.value[1]['tmpVar'], length=length, data=ada_string))
ada_string = 'tmp{idx}'.format(idx=prim.value[1]['tmpVar'])
return stmts, ada_string, local_decl
return stmts, unicode(ada_string), local_decl
@expression.register(ogAST.PrimSelector)
......@@ -889,7 +888,7 @@ def _prim_selector(prim):
else:
ada_string += '.' + field_name
return stmts, ada_string, local_decl
return stmts, unicode(ada_string), local_decl
@expression.register(ogAST.ExprPlus)
......@@ -916,7 +915,7 @@ def _basic_operators(expr):
code.extend(right_stmts)
local_decl.extend(left_local)
local_decl.extend(right_local)
return code, ada_string, local_decl
return code, unicode(ada_string), local_decl
@expression.register(ogAST.ExprOr)
......@@ -932,7 +931,7 @@ def _bitwise_operators(expr):
# Sequence of boolean or bit string
if expr.right.is_raw:
# Declare a temporary variable to store the raw value
tmp_string = 'tmp{}'.format(expr.right.tmpVar)
tmp_string = u'tmp{}'.format(expr.right.tmpVar)
local_decl.append(u'{tmp} : aliased asn1Scc{eType};'.format(
tmp=tmp_string,
eType=expr.right.exprType.ReferencedTypeName
......@@ -955,7 +954,7 @@ def _bitwise_operators(expr):
code.extend(right_stmts)
local_decl.extend(left_local)
local_decl.extend(right_local)
return code, ada_string, local_decl
return code, unicode(ada_string), local_decl
@expression.register(ogAST.ExprNot)
......@@ -967,7 +966,7 @@ def _unary_operator(expr):
ada_string = u'({op} {expr})'.format(op=expr.operand, expr=expr_str)
code.extend(expr_stmts)
local_decl.extend(expr_local)
return code, ada_string, local_decl
return code, unicode(ada_string), local_decl
@expression.register(ogAST.ExprAppend)
......@@ -1036,7 +1035,7 @@ def _append(expr):
rid=expr.right.sid, l2=expr.right.slen))
stmts.append('{res}.Length := {l1} + {l2};'.format(
res=ada_string, l1=expr.left.slen, l2=expr.right.slen))
return stmts, ada_string, local_decl
return stmts, unicode(ada_string), local_decl
@expression.register(ogAST.ExprIn)
......@@ -1067,7 +1066,7 @@ def _expr_in(expr):
stmts.append("exit in_loop_{tmp} when {tmp} = True;"
.format(tmp=ada_string))
stmts.append("end loop in_loop_{};".format(ada_string))
return stmts, ada_string, local_decl
return stmts, unicode(ada_string), local_decl
@expression.register(ogAST.PrimEnumeratedValue)
......@@ -1075,8 +1074,8 @@ def _enumerated_value(primary):
''' Generate code for an enumerated value '''
enumerant = primary.value[0].replace('_', '-')
basic = find_basic_type(primary.exprType)
ada_string = ('asn1Scc' + basic.EnumValues[enumerant].EnumID)
return [], ada_string, []
ada_string = (u'asn1Scc' + basic.EnumValues[enumerant].EnumID)
return [], unicode(ada_string), []
@expression.register(ogAST.PrimChoiceDeterminant)
......@@ -1084,7 +1083,7 @@ def _choice_determinant(primary):
''' Generate code for a choice determinant (enumerated) '''
enumerant = primary.value[0].replace('_', '-')
ada_string = primary.exprType.EnumValues[enumerant].EnumID
return [], ada_string, []
return [], unicode(ada_string), []
@expression.register(ogAST.PrimInteger)
......@@ -1092,26 +1091,27 @@ def _choice_determinant(primary):
def _integer(primary):
''' Generate code for a raw numerical value '''
if float(primary.value[0]) < 0:
# Parentesize negative integers for maintaining the precedence in the generated code
ada_string = '({})'.format(primary.value[0])
# Parentesize negative integers for maintaining
# the precedence in the generated code
ada_string = u'({})'.format(primary.value[0])
else:
ada_string = primary.value[0]
return [], ada_string, []
return [], unicode(ada_string), []
@expression.register(ogAST.PrimBoolean)
def _boolean(primary):
''' Generate code for a raw boolean value '''
ada_string = primary.value[0]
return [], ada_string, []
return [], unicode(ada_string), []
@expression.register(ogAST.PrimEmptyString)
def _empty_string(primary):
''' Generate code for an empty SEQUENCE OF: {} '''
ada_string = 'asn1Scc{typeRef}_Init'.format(
ada_string = u'asn1Scc{typeRef}_Init'.format(
typeRef=primary.exprType.ReferencedTypeName.replace('-', '_'))
return [], ada_string, []
return [], unicode(ada_string), []
@expression.register(ogAST.PrimStringLiteral)
......@@ -1122,27 +1122,28 @@ def _string_literal(primary):
# then convert the string to an array of unsigned_8 integers
# as expected by the Ada type corresponding to Octet String
unsigned_8 = [str(ord(val)) for val in primary.value[1:-1]]
ada_string = '(Data => (' + ', '.join(
ada_string = u'(Data => (' + ', '.join(
unsigned_8) + ', others => 0)'
if basic_type.Min != basic_type.Max:
# Non-fixed string size -> add Length field
ada_string += ', Length => {}'.format(
ada_string += u', Length => {}'.format(
str(len(primary.value[1:-1])))
ada_string += ')'
return [], ada_string, []
return [], unicode(ada_string), []
@expression.register(ogAST.PrimConstant)
def _constant(primary):
''' Generate code for a reference to an ASN.1 constant '''
return [], primary.value[0], []
return [], unicode(primary.value[0]), []
@expression.register(ogAST.PrimMantissaBaseExp)
def _mantissa_base_exp(primary):
''' Generate code for a Real with Mantissa-base-Exponent representation '''
# TODO
return [], '', []
return [], u'', []
@expression.register(ogAST.PrimIfThenElse)
......@@ -1187,7 +1188,7 @@ def _if_then_else(ifThenElse):
else_str=else_str))
stmts.append('end if;')
ada_string = u'tmp{idx}'.format(idx=ifThenElse.value['tmpVar'])
return stmts, ada_string, local_decl
return stmts, unicode(ada_string), local_decl
@expression.register(ogAST.PrimSequence)
......@@ -1211,7 +1212,7 @@ def _sequence(seq):
stmts.extend(value_stmts)
local_decl.extend(local_var)
ada_string += ')'
return stmts, ada_string, local_decl
return stmts, unicode(ada_string), local_decl
@expression.register(ogAST.PrimSequenceOf)
......@@ -1244,7 +1245,7 @@ def _sequence_of(seqof):
local_decl.extend(local_var)
ada_string += '{i} => {value}, '.format(i=i + 1, value=item_str)
ada_string += 'others => {anyVal}))'.format(anyVal=item_str)
return stmts, ada_string, local_decl
return stmts, unicode(ada_string), local_decl
@expression.register(ogAST.PrimChoiceItem)
......@@ -1259,7 +1260,7 @@ def _choiceitem(choice):
cType=actual_type,
opt=choice.value['choice'],
expr=choice_str)
return stmts, ada_string, local_decl
return stmts, unicode(ada_string), local_decl
@generate.register(ogAST.Decision)
......
......@@ -236,11 +236,29 @@ class CommentConnection(Connection):
class Channel(Connection):
''' Subclass of Connection used to draw channels between processes '''
in_sig = out_sig = None
def __init__(self, process):
''' Set generic parameters from Connection class '''
super(Channel, self).__init__(process, process)
self.text_label = None
self.label_in = QGraphicsTextItem('[]', parent=self)
self.label_out = QGraphicsTextItem('[]', parent=self)
if not Channel.in_sig:
# keep at class level as long as only one process is supported
# when copy-pasting a process the challel in/out signal lists
# are not parsed. Workaround is to keep the list "global"
# to allow a copy of both process and channel
# Needed for the image exporter, that copies the scene to a
# temporary one
Channel.in_sig = '[{}]'.format(',\n'.join(sig['name']
for sig in process.input_signals))
Channel.out_sig = '[{}]'.format(',\n'.join(sig['name']
for sig in process.output_signals))
font = QFont('Ubuntu', pointSize=8)
for each in (self.label_in, self.label_out):
each.setFont(font)
each.show()
self.process = process
self.reshape()
@property
def start_point(self):
......@@ -258,9 +276,23 @@ class Channel(Connection):
view.viewport().geometry()).boundingRect().topLeft()
scene_pos_x = self.mapFromScene(view_pos).x()
return QPointF(scene_pos_x, self.start_point.y())
except IndexError:
except (IndexError, AttributeError):
# In case there is no view (e.g. Export PNG from cmd line)
return QPointF(self.start_point.x() - 50, self.start_point.y())
return QPointF(self.start_point.x() - 250, self.start_point.y())
def reshape(self):
''' Redefine shape function to add the text areas '''
super(Channel, self).reshape()
self.label_in.setPlainText(self.in_sig)
self.label_out.setPlainText(self.out_sig)
width_in = self.label_in.boundingRect().width()
self.label_in.setX(self.start_point.x() - width_in)
self.label_in.setY(self.start_point.y() + 5)
self.label_out.setX(self.end_point.x() + 10)
self.label_out.setY(self.end_point.y() + 5)
class Controlpoint(QGraphicsPathItem, object):
......
......@@ -20,7 +20,7 @@ compile-all:
install: compile-all
mkdir -p opengeode
for f in AdaGenerator.py __init__.py genericSymbols.py icons.py ogAST.py ogParser.py opengeode.py Renderer.py samnmax.py sdl92Lexer.py sdl92Parser.py sdlSymbols.py undoCommands.py Clipboard.py Statechart.py LlvmGenerator.py Lander.py Helper.py Connectors.py; do echo Installing $$f && cp $$f opengeode; done
for f in AdaGenerator.py __init__.py genericSymbols.py icons.py ogAST.py ogParser.py opengeode.py Renderer.py samnmax.py sdl92Lexer.py sdl92Parser.py sdlSymbols.py undoCommands.py Clipboard.py Statechart.py LlvmGenerator.py Lander.py Helper.py Connectors.py Asn1scc.py; do echo Installing $$f && cp $$f opengeode; done
python setup.py install
publish: install
......
......@@ -49,8 +49,9 @@ def parse_scene(scene):
composite = set(scene.composite_states.keys())
for each in scene.states:
if each.is_composite():
statename = unicode(each).split()[0].lower() # Ignore via clause
try:
composite.remove(unicode(each).lower())
composite.remove(statename)
sub_state = generate(each, composite=True, nextstate=False)
if sub_state:
sub_state.reverse()
......@@ -105,8 +106,9 @@ def recursive_aligned(symbol):
def generate(symbol, *args, **kwargs):
''' Generate text for a symbol, recursively or not - return a list of
strings '''
_, _ = symbol, recursive
raise NotImplementedError('[PR Generator] Unsupported AST construct')
_ = symbol
raise NotImplementedError('Unsupported AST construct: {}'
.format(type(symbol)))
return Indent()
......@@ -267,8 +269,8 @@ def _state(symbol, recursive=True, nextstate=True, composite=False, **kwargs):
else:
# Generate code for a nested state
result = Indent()
result.extend(['STATE {};'.format(unicode(symbol)),
'SUBSTRUCTURE'])
result.append('STATE {};'.format(unicode(symbol).split()[0]))
result.append('SUBSTRUCTURE')
Indent.indent += 1
entry_points, exit_points = [], []
for each in symbol.nested_scene.start:
......
......@@ -145,6 +145,15 @@ The fonts are the fonts from Ubuntu, check licence in file FONT-LICENSE.TXT
Changelog
=========
0.993 (07/2014)
- Parser bugfixes
- Better support for nested states
- Ada generator improvements
- Support for unicode
- Indentation of PR code
- Copy-paste of procedures and nested states
- Improved regression testing
0.99 (04/2014)
- Refactoring of the backend engine, now using singledispatch
- Support of hierachical states
......
......@@ -327,6 +327,7 @@ def _terminator(ast, scene, parent, states):
for each in chain(state_ast.inputs, state_ast.connects):
render(each, scene=scene, parent=symbol, states=states)
break
symbol.nested_scene = ast.composite or ogAST.CompositeState()
elif ast.kind == 'join':
symbol = sdlSymbols.Join(parent, ast)
elif ast.kind in ('return', 'stop'):
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module is a free SDL editor.
It allows to graphically design state machines, using a formal, well-
defined language, and generate Ada code from the models.
"""
__version__ = "0.992"
from opengeode import opengeode
from opengeode import opengeode, __version__
......@@ -306,6 +306,25 @@ class EditableText(QGraphicsTextItem, object):
text_cursor = self.textCursor()
text_cursor.select(QTextCursor.WordUnderCursor)
self.completion_prefix = text_cursor.selectedText()
# Work in progress - to support advanced autocompletion
tmp = self.textCursor()
pos = tmp.positionInBlock()
tmp.select(QTextCursor.BlockUnderCursor)
try:
import string
line = tmp.selectedText()
if line[pos] in string.ascii_letters + '!' + '.' + '_':
last_word = line[slice(0, pos + 1)].split()[-1]
else:
last_word = ''
except IndexError:
pass
else:
pass
# print last_word.encode('utf-8')
# -- END
completion_count = self.completer.set_completion_prefix(
self.completion_prefix)
if event.key() in (Qt.Key_Period, Qt.Key_Exclam):
......
......@@ -2,7 +2,7 @@
 
# Resource object code
#
# Created: Wed Jul 2 08:06:50 2014
# Created: Sun Jul 13 22:01:20 2014
# by: The Resource Compiler for PySide (Qt v4.8.6)
#
# WARNING! All changes made in this file will be lost!
......@@ -456,6 +456,8 @@ class Terminator(object):
# There can be several if terminator follows a floating label
# Note, this field is updated by the Helper.flatten function
self.possible_states = []
# optional composite state content (type CompositeState)
self.composite = None
def trace(self):
''' Debug output for terminators '''
......@@ -783,7 +785,8 @@ class Process(object):
self.global_variables = {}
self.global_timers = []
# Set default coordinates and width/height
self.pos_x = self.pos_y = 150
self.pos_x = 250
self.pos_y = 150
self.width = 150
self.height = 75
# Optional hyperlink
......
......@@ -28,6 +28,8 @@ __author__ = 'Maxime Perrotin'
import sys
import os
import re
import fnmatch
import logging
import traceback
from itertools import chain, permutations
......@@ -1712,6 +1714,13 @@ def composite_state(root, parent=None, context=None):
errors.extend(err)
warnings.extend(warn)
comp.content.states.append(newstate)
# Post-processing: check that all NEXTSTATEs have a corresponding STATE
for ns in [t.inputString.lower() for t in comp.terminators
if t.kind == 'next_state']:
if not ns in [s.lower() for s in
comp.mapping.viewkeys()] + ['-']:
errors.append('In composite state "{}": missing definition '
'of substate "{}"'.format(comp.statename, ns.upper()))
return comp, errors, warnings
......@@ -2271,6 +2280,8 @@ def process_definition(root, parent=None, context=None):
process.composite_states.append(comp)
elif child.type == lexer.REFERENCED:
process.referenced = True
elif child.type == lexer.COMMENT:
process.comment, _, _ = end(child)
else:
warnings.append('Unsupported process definition child: ' +
sdl92Parser.tokenNames[child.type] +
......@@ -2838,8 +2849,10 @@ def decision(root, parent, context):
def nextstate(root, context):
''' Parse a NEXTSTATE [VIA State_Entry_Point] '''
''' Parse a NEXTSTATE [VIA State_Entry_Point] - detect various kinds of
errors when trying to enter a nested state '''
next_state_id, via, entrypoint = '', None, None
errors = []
for child in root.getChildren():
if child.type == lexer.ID:
next_state_id = child.text
......@@ -2855,25 +2868,34 @@ def nextstate(root, context):
if comp.statename.lower()
== next_state_id.lower())
except ValueError:
raise TypeError('State {} is not a composite state'
errors.append('State {} is not a composite state'
.format(next_state_id))
else:
if entrypoint.lower() not in composite.state_entrypoints:
raise TypeError('State {s} has no "{p}" entrypoint'
.format(s=next_state_id, p=entrypoint))
errors.append('State {s} has no "{p}" entrypoint'
.format(s=next_state_id, p=entrypoint))
for each in composite.content.named_start:
if each.inputString == entrypoint.lower() + '_START':
break
else:
raise TypeError('Entrypoint {p} in state {s} is '
'declared but not defined'.format
(s=next_state_id, p=entrypoint))
errors.append('Entrypoint {p} in state {s} is '
'declared but not defined'.format
(s=next_state_id, p=entrypoint))
else:
raise TypeError('"History" NEXTSTATE'
' cannot have a "via" clause')
errors.append('"History" NEXTSTATE cannot have a "via" clause')
else:
raise TypeError('NEXTSTATE undefined construct')
return next_state_id, via, entrypoint
errors.append('NEXTSTATE undefined construct')
if not via:
# check that if the nextstate is nested, it has a START symbol
try:
composite, = (comp for comp in context.composite_states
if comp.statename.lower() == next_state_id.lower())
if not composite.content.start:
errors.append('Composite state "{}" has no unnamed '
'START symbol'.format(composite.statename))
except ValueError:
pass
return next_state_id, via, entrypoint, errors
def terminator_statement(root, parent, context):
......@@ -2895,15 +2917,21 @@ def terminator_statement(root, parent, context):
lab.terminators = [t]
elif term.type == lexer.NEXTSTATE:
t.kind = 'next_state'
try:
t.inputString, t.via, t.entrypoint = nextstate(term, context)
except TypeError as err:
errors.append(str(err))
t.inputString, t.via, t.entrypoint, err = nextstate(term, context)
if err:
errors.extend(err)
t.line = term.getChild(0).getLine()
t.charPositionInLine = term.getChild(0).getCharPositionInLine()
# Add next state infos at process level
# Used in rendering backends to merge a NEXTSTATE with a STATE
context.terminators.append(t)
# post-processing: if nextatate is nested, add link to the content
# (normally handled at state level, but if state is not defined
# standalone, the nextstate must hold the composite content)
if t.inputString != '-':
for each in context.composite_states:
if each.statename.lower() == t.inputString.lower():
t.composite = each
elif term.type == lexer.JOIN:
t.kind = 'join'
t.inputString = term.getChild(0).toString()
......@@ -3048,7 +3076,8 @@ def assign(root, context):
expr.left.inputString + ', type= ' +
type_name(expr.left.exprType) + '), right (' +
expr.right.inputString + ', type= ' +
type_name(expr.right.exprType) + ') ' + str(err))
(type_name(expr.right.exprType)
if expr.right.exprType else 'Unknown') + ') ' + str(err))
else:
expr.right.exprType = expr.left.exprType
......@@ -3281,12 +3310,20 @@ def pr_file(root):
LOG.debug('USE clause')
# USE clauses can contain a CIF comment with the ASN.1 filename
use_clause_subs = child.getChildren()
asn1_filename = None
for clause in use_clause_subs:
if clause.type == lexer.ASN1:
asn1_filename = clause.getChild(0).text[1:-1]
ast.asn1_filenames.append(asn1_filename)
else:
ast.use_clauses.append(clause.text)
# if not asn1_filename:
# # Look for case insentitive pr file and add it to AST
# search = fnmatch.translate(clause.text + '.pr')
# searchobj = re.compile(search, re.IGNORECASE)
# for each in os.listdir('.'):
# if searchobj.match(each):
# print 'found', each
try:
DV = parse_asn1(tuple(ast.asn1_filenames),
ast_version=ASN1.UniqueEnumeratedNames,
......@@ -3299,6 +3336,7 @@ def pr_file(root):
# Can happen if DataView.py is not there
LOG.info('USE Clause did not contain ASN.1 filename')
LOG.debug(str(err))
for child in systems:
LOG.debug('found SYSTEM')
system, err, warn = system_definition(child, parent=ast)
......
......@@ -30,6 +30,7 @@ from itertools import chain
# Added to please py2exe - NOQA makes flake8 ignore the following lines:
# pylint: disable=W0611
import enum # NOQA
import fnmatch # NOQA
import operator # NOQA
import subprocess # NOQA
import distutils # NOQA
......@@ -97,7 +98,7 @@ except ImportError:
pass
__all__ = ['opengeode']
__version__ = '0.992'