All Downloads are FREE. Search and download functionalities are using the official Maven repository.

us.ihmc.scs2.session.mcap.OMGIDLSchemaParser Maven / Gradle / Ivy

The newest version!
package us.ihmc.scs2.session.mcap;

import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import us.ihmc.log.LogTools;
import us.ihmc.scs2.session.mcap.MCAPSchema.MCAPSchemaField;
import us.ihmc.scs2.session.mcap.omgidl_parser.IDLBaseListener;
import us.ihmc.scs2.session.mcap.omgidl_parser.IDLLexer;
import us.ihmc.scs2.session.mcap.omgidl_parser.IDLListener;
import us.ihmc.scs2.session.mcap.omgidl_parser.IDLParser;
import us.ihmc.scs2.session.mcap.omgidl_parser.IDLParser.Enum_typeContext;
import us.ihmc.scs2.session.mcap.specs.records.Schema;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;

public class OMGIDLSchemaParser
{
   public static MCAPSchema loadSchema(Schema mcapSchema) throws IOException
   {
      return loadSchema(mcapSchema.name(), mcapSchema.id(), new ByteBufferBackedInputStream(mcapSchema.data()));
   }

   public static MCAPSchema loadSchema(String name, int id, byte[] data) throws IOException
   {
      return loadSchema(name, id, new ByteArrayInputStream(data));
   }

   public static MCAPSchema loadSchema(String name, int id, InputStream is) throws IOException
   {
      MCAPSchema schema = new MCAPSchema(name, id, new ArrayList<>(), new HashMap<>());

      CharStream bytesAsChar = CharStreams.fromStream(is);

      IDLLexer lexer = new IDLLexer(bytesAsChar);
      CommonTokenStream tokens = new CommonTokenStream(lexer);
      IDLParser parser = new IDLParser(tokens);
      parser.setBuildParseTree(true);
      ParseTree tree = parser.specification();

      IDLListener schemaCreatorListener = new SchemaCreatorListener(schema);
      ParseTreeWalker.DEFAULT.walk(schemaCreatorListener, tree);

      return schema;
   }

   private static class SchemaCreatorListener extends IDLBaseListener
   {
      // Each top level struct gets its own schema
      private final MCAPSchema rootSchema;
      private MCAPSchema currentSchema = null;
      private MCAPSchema previousSchema = null;
      private final MemberInfo currentMemberInfo = new MemberInfo();

      // Keeping track of current member attributes
      // These can only be used in exitRule() functions

      private static class MemberInfo
      {
         private String type;
         private String name;
         private int maxLength;
         private boolean isArray;
         private boolean isSequence;
         private boolean isEnum;
         private boolean isComplexType;

         private String[] enumConstants;

         public MemberInfo()
         {
            reset();
         }

         private void reset()
         {
            this.type = null;
            this.name = null;
            this.maxLength = -1;
            this.isArray = false;
            this.isSequence = false;
            this.isEnum = false;
            this.isComplexType = false;
            this.enumConstants = null;
         }

         @Override
         public String toString()
         {
            return "MemberInfo{" + "type='" + type + '\'' + ", name='" + name + '\'' + ", maxLength=" + maxLength + ", isArray=" + isArray + ", isSequence="
                   + isSequence + ", isEnum=" + isEnum + ", isComplexType=" + isComplexType + ", enumConstants=" + Arrays.toString(enumConstants) + '}';
         }
      }

      public SchemaCreatorListener(MCAPSchema schema)
      {
         rootSchema = schema;
         this.currentSchema = schema;
      }

      @Override
      public void exitScoped_name(IDLParser.Scoped_nameContext ctx)
      {
         // TODO: (AM) Handle scoped type names properly, currently forcing global scope resolution
         //      String prefixScope = "";
         //      if (this.previousSchema.getName() != null)
         //      {
         //      prefixScope = this.previousSchema.getName().toString();
         //      }
         //      this.currentMemberType = prefixScope + "::" + ctx.ID(0).getText();
         currentMemberInfo.type = ctx.ID(0).getText();
         currentMemberInfo.isComplexType = true;
      }

      @Override
      public void exitConst_decl(IDLParser.Const_declContext ctx)
      {
         // constants have the type "const base_type"
         MCAPSchemaField newField = new MCAPSchemaField(ctx.identifier().getText(), ctx.const_type().getText(), false, false, -1, false);
         newField.setDefaultValue(ctx.const_exp().getText());
         this.currentSchema.getStaticFields().add(newField);
      }

      @Override
      public void exitBase_type_spec(IDLParser.Base_type_specContext ctx)
      {
         //TODO: (AM) does this always get overridden by the array size?
         currentMemberInfo.type = ctx.getText();
      }

      @Override
      public void exitSimple_declarator(IDLParser.Simple_declaratorContext ctx)
      {
         currentMemberInfo.name = ctx.ID().getText();
      }

      @Override
      public void enterStruct_type(IDLParser.Struct_typeContext ctx)
      {
         String structName = ctx.identifier().getText();

         if (ctx.scoped_name() != null)
            structName += (ctx.DOUBLE_COLON().getText() + ctx.scoped_name().getText());

         // Add a subschema whenever we enter a new struct
         if (Objects.equals(rootSchema.getName(), structName))
         {  // If this is the root schema, then we don't need to create a new schema
            this.previousSchema = this.currentSchema;
            this.currentSchema = rootSchema;
         }
         else
         {
            MCAPSchema newCurrentSchema = new MCAPSchema(structName, 0);
            this.currentSchema.getSubSchemaMap().put(structName, newCurrentSchema);

            this.previousSchema = this.currentSchema;
            this.currentSchema = newCurrentSchema;
         }
      }

      @Override
      public void exitStruct_type(IDLParser.Struct_typeContext ctx)
      {
         // The walker does a recursive dfs traversal, so it will only visit every node once
         this.currentSchema = previousSchema;
         this.previousSchema = null;
      }

      @Override
      public void exitMember(IDLParser.MemberContext ctx)
      {
         assert currentMemberInfo.type != null : String.format("Got a null member type for %s", ctx.type_spec().getText());
         assert currentMemberInfo.name != null : String.format("Got a null member name for %s", ctx.declarators().getText());

         //Create field here for each member
         MCAPSchemaField newField = new MCAPSchemaField(currentMemberInfo.name,
                                                        currentMemberInfo.type,
                                                        currentMemberInfo.isArray,
                                                        currentMemberInfo.isSequence,
                                                        currentMemberInfo.maxLength,
                                                        currentMemberInfo.isComplexType);
         this.currentSchema.getFields().add(newField);

         // reset Member stats after we are done visiting the member
         currentMemberInfo.reset();
      }

      @Override
      public void exitSequence_type(IDLParser.Sequence_typeContext ctx)
      {
         //TODO: (AM) support unbounded sequences
         if (ctx.positive_int_const() != null)
         {
            currentMemberInfo.maxLength = Integer.parseInt(ctx.positive_int_const().getText());
         }
         else
         {
            currentMemberInfo.maxLength = 255;
            LogTools.warn("Unbounded sequences are not supported, limiting max length to 255");
         }

         currentMemberInfo.isSequence = true;
         currentMemberInfo.isComplexType = true;
      }

      @Override
      public void exitEnum_type(Enum_typeContext ctx)
      {
         String name = ctx.identifier().getText();
         MCAPSchema enumSchema = new MCAPSchema(name, -1, ctx.enumerator().stream().map(it -> it.identifier().ID().getText()).toArray(String[]::new));
         this.currentSchema.getSubSchemaMap().put(name, enumSchema);
      }

      @Override
      public void exitString_type(IDLParser.String_typeContext ctx)
      {
         //TODO: (AM) treat unbounded strings properly, right now they are treated like a base type of -1 size
         //this.currentMemberMaxLength = Integer.parseInt(ctx.positive_int_const().getText());
         currentMemberInfo.type = "string";
      }

      @Override
      public void exitArray_declarator(IDLParser.Array_declaratorContext ctx)
      {
         currentMemberInfo.name = ctx.ID().getText();
      }

      @Override
      public void exitFixed_array_size(IDLParser.Fixed_array_sizeContext ctx)
      {
         currentMemberInfo.maxLength = Integer.parseInt(ctx.positive_int_const().getText());
         currentMemberInfo.isComplexType = true;
         currentMemberInfo.isArray = true;
      }
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy