CORD-1165: Support native Protobufs for specifying XOS models

Change-Id: I81abda44d164b6adb34e664680cf69a6ba74f353
diff --git a/xos/genx/targets/xproto.xtarget b/xos/genx/targets/xproto.xtarget
new file mode 100644
index 0000000..843d741
--- /dev/null
+++ b/xos/genx/targets/xproto.xtarget
@@ -0,0 +1,18 @@
+{% for k,v in options.iteritems() %}
+option {{ k }} = {{ v }};
+{% endfor %}
+
+{% for m in proto.messages %}
+message {{ m.name }} ({{ m.bases | join(",") }}){
+  {%- for f in m.fields %}
+  {% if not f.link %}
+  {{ f.modifier }} {{f.type}} {{f.name}} = {{ f.id }}{% if f.options %} [{% for k,v in f.options.iteritems() %} {{ k }} = {{ v }}{% if not loop.last %},{% endif %} {% endfor %}]{% endif %};
+  {% endif %}
+  {%- endfor %}
+  
+  {%- for l in m.links %}
+  {{ l }}
+  {{ l.modifier }} {{ l.link_type }} {{ l.name }} -> {{ l.peer }}:{{ l.dst_port }} = 1 {% if l.options %} [{% for k,v in l.options.iteritems() %} {{ k }} = {{ v }}{% if not loop.last %},{% endif %} {% endfor %}]{% endif %};
+  {%- endfor -%}
+}
+{% endfor %}
diff --git a/xos/genx/tool/lib.py b/xos/genx/tool/lib.py
index 1c5200e..2230d5c 100644
--- a/xos/genx/tool/lib.py
+++ b/xos/genx/tool/lib.py
@@ -154,3 +154,4 @@
         return '_'
 
     return match
+
diff --git a/xos/genx/tool/proto2xproto.py b/xos/genx/tool/proto2xproto.py
index 413ff0a..ad6a21e 100644
--- a/xos/genx/tool/proto2xproto.py
+++ b/xos/genx/tool/proto2xproto.py
@@ -11,14 +11,59 @@
     def push(self,x):
         self.append(x)
 
-''' Proto2XProto overrides the underlying visitor pattern to transform the tree
-    in addition to traversing it '''
+def replace_link(obj):
+        try:
+            link = obj.link
+            try:
+                through = link['through']
+            except KeyError:
+                through = None
+
+            try:
+                through_str = through[1:-1]
+            except TypeError:
+                through_str = None
+
+            ls = m.LinkSpec(obj, m.LinkDefinition(link['link'][1:-1],obj.name,link['model'][1:-1],link['port'][1:-1],through_str))
+            return ls
+        except:
+            return obj
+
 class Proto2XProto(m.Visitor):
     stack = Stack()
     count_stack = Stack()
     content=""
     offset=0
     statementsChanged=0
+    message_options = {}
+    options = {}
+    current_message_name = None
+    
+    xproto_message_options = ['bases']
+    xproto_field_options = ['model']
+    
+    
+    def proto_to_xproto_field(self, obj):
+        try:
+            opts = {}
+            for fd in obj.fieldDirective:
+                k = fd.pval.name.value.pval
+                v = fd.pval.value.value.pval
+                opts[k]=v
+
+            if ('model' in opts and 'link' in opts and 'port' in opts):
+                obj.link = opts
+            pass
+        except KeyError:
+            raise
+
+    def proto_to_xproto_message(self, obj):
+        try:
+            bases = self.message_options['bases'].split(',')
+            bases = map(lambda x:x[1:-1], bases)
+            obj.bases = bases
+        except KeyError:
+            raise
 
     def map_field(self, obj, s):
         if 'model' in s:
@@ -48,7 +93,14 @@
         return True
 
     def visit_OptionStatement(self, obj):
-        '''Ignore'''
+        if (self.current_message_name):
+            k = obj.name.value.pval
+            self.message_options[k] = obj.value.value.pval
+            if (k in self.xproto_message_options):
+               obj.mark_for_deletion = True  
+        else:
+            self.options[obj.name.value.pval] = obj.value.value.pval
+
         return True
 
     def visit_LU(self, obj):
@@ -61,20 +113,6 @@
         return True
 
     def visit_FieldDirective_post(self, obj):
-        try:
-            name = obj.name.value.pval
-        except AttributeError:
-            name = obj.name.value
-
-        try:
-            value = obj.value.value.pval
-        except AttributeError:
-            try:
-                value = obj.value.value
-            except AttributeError:
-                value = obj.value.pval
-
-        self.stack.push([name,value])
         return True
 
     def visit_FieldType(self, obj):
@@ -84,19 +122,10 @@
         return True
 
     def visit_FieldDefinition(self, obj):
-        self.count_stack.push(len(obj.fieldDirective))
         return True
 
     def visit_FieldDefinition_post(self, obj):
-        opts = {}
-        n = self.count_stack.pop()
-        for i in range(0, n):
-            k,v = self.stack.pop()
-            opts[k] = v
-
-        f = self.map_field(obj, opts)
-        self.stack.push(f)
-        #self.stack::declare.push(f)
+        self.proto_to_xproto_field(obj)
         return True
 
     def visit_EnumFieldDefinition(self, obj):
@@ -106,17 +135,17 @@
         return True
 
     def visit_MessageDefinition(self, obj):
-        self.count_stack.push(len(obj.body))
+        self.current_message_name = obj.name.value.pval
+        self.message_options = {}
+
         return True
     
     def visit_MessageDefinition_post(self, obj):
-        stack_num = self.count_stack.pop()
-        lst = []
+        self.proto_to_xproto_message(obj)
+        obj.body = filter(lambda x:not hasattr(x, 'mark_for_deletion'), obj.body)
+        obj.body = map(replace_link, obj.body)
 
-        for n in range(0,stack_num):
-            lst.append(self.stack.pop())
-
-        obj.body = lst
+        self.current_message_name = None
         return True
 
     def visit_MessageExtension(self, obj):
diff --git a/xos/genx/tool/xos2jinja.py b/xos/genx/tool/xos2jinja.py
index 5af2272..3db2b41 100644
--- a/xos/genx/tool/xos2jinja.py
+++ b/xos/genx/tool/xos2jinja.py
@@ -34,8 +34,6 @@
     count_stack = Stack()
     content=""
     offset=0
-    doNameSanitization=False
-    statementsChanged=0
     prefix=""
     current_message_name = None
 
@@ -58,10 +56,11 @@
         return True
 
     def visit_OptionStatement(self, obj):
-        if (self.current_message_name):
-            self.message_options[obj.name.value.pval] = obj.value.value.pval
-        else:
-            self.options[obj.name.value.pval] = obj.value.value.pval
+        if not hasattr(obj,'mark_for_deletion'):
+            if (self.current_message_name):
+                self.message_options[obj.name.value.pval] = obj.value.value.pval
+            else:
+                self.options[obj.name.value.pval] = obj.value.value.pval
 
         return True
 
@@ -99,22 +98,30 @@
     def visit_LinkDefinition(self, obj):
         s={}
 
-        s['link_type'] = obj.link_type.pval
+        try:
+            s['link_type'] = obj.link_type.pval
+        except AttributeError:
+            s['link_type'] = obj.link_type
+
         s['src_port'] = obj.src_port.value.pval
         s['name'] = obj.src_port.value.pval
 
         try:
             s['dst_port'] = obj.dst_port.value.pval
         except AttributeError:
-            s['dst_port'] = ''
+            s['dst_port'] = obj.dst_port
 
 
         try:
             s['through'] = obj.through.pval
         except AttributeError:
-            s['through'] = ''
+            s['through'] = obj.through
 
-        s['peer'] = obj.name.pval
+        try:
+            s['peer'] = obj.name.pval
+        except AttributeError:
+            s['peer'] = obj.name
+
         s['_type'] = 'link'
         s['options'] = {'modifier':'optional'}
 
@@ -179,7 +186,10 @@
         fields = []
         links = []
         last_field = None
-        obj.bases = map(lambda x:x.pval, obj.bases)
+        try:
+            obj.bases = map(lambda x:x.pval, obj.bases)
+        except AttributeError:
+            pass
 
         for i in range(0,stack_num):
             f = self.stack.pop()
diff --git a/xos/genx/tool/xosgen b/xos/genx/tool/xosgen
index addb6ba..ca6f070 100755
--- a/xos/genx/tool/xosgen
+++ b/xos/genx/tool/xosgen
@@ -36,15 +36,16 @@
 
 def main():
     try:
-        if (not args.rev):
-            v = XOS2Jinja()
-        else:
-            v = Proto2XProto()
 
         parser = plyproto.ProtobufAnalyzer()
         input = open(args.input).read()
-
         ast = parser.parse_string(input,debug=0)
+
+        if (args.rev):
+            v = Proto2XProto()
+            ast.accept(v)
+        
+        v = XOS2Jinja()
         ast.accept(v)
         
         template_name = os.path.abspath(args.target)