001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.util.struct;
006
007import edu.wpi.first.util.struct.parser.ParseException;
008import edu.wpi.first.util.struct.parser.ParsedDeclaration;
009import edu.wpi.first.util.struct.parser.ParsedSchema;
010import edu.wpi.first.util.struct.parser.Parser;
011import java.util.HashMap;
012import java.util.Map;
013import java.util.Stack;
014
015/** Database of raw struct dynamic descriptors. */
016public class StructDescriptorDatabase {
017  /** Default constructor. */
018  public StructDescriptorDatabase() {}
019
020  /**
021   * Adds a structure schema to the database. If the struct references other structs that have not
022   * yet been added, it will not be valid until those structs are also added.
023   *
024   * @param name structure name
025   * @param schema structure schema
026   * @return Added struct dynamic descriptor
027   * @throws BadSchemaException if schema invalid
028   */
029  public StructDescriptor add(String name, String schema) throws BadSchemaException {
030    Parser parser = new Parser(schema);
031    ParsedSchema parsed;
032    try {
033      parsed = parser.parse();
034    } catch (ParseException e) {
035      throw new BadSchemaException("parse error", e);
036    }
037
038    // turn parsed schema into descriptors
039    StructDescriptor theStruct = m_structs.computeIfAbsent(name, StructDescriptor::new);
040    theStruct.m_schema = schema;
041    theStruct.m_fields.clear();
042    boolean isValid = true;
043    for (ParsedDeclaration decl : parsed.declarations) {
044      StructFieldType type = StructFieldType.fromString(decl.typeString);
045      int size = type.size;
046
047      // bitfield checks
048      if (decl.bitWidth != 0) {
049        // only integer or boolean types are allowed
050        if (!type.isInt && !type.isUint && type != StructFieldType.kBool) {
051          throw new BadSchemaException(
052              decl.name, "type " + decl.typeString + " cannot be bitfield");
053        }
054
055        // bit width cannot be larger than field size
056        if (decl.bitWidth > (size * 8)) {
057          throw new BadSchemaException(
058              decl.name, "bit width " + decl.bitWidth + " exceeds type size");
059        }
060
061        // bit width must be 1 for booleans
062        if (type == StructFieldType.kBool && decl.bitWidth != 1) {
063          throw new BadSchemaException(decl.name, "bit width must be 1 for bool type");
064        }
065
066        // cannot combine array and bitfield (shouldn't parse, but double-check)
067        if (decl.arraySize > 1) {
068          throw new BadSchemaException(decl.name, "cannot combine array and bitfield");
069        }
070      }
071
072      // struct handling
073      StructDescriptor structDesc = null;
074      if (type == StructFieldType.kStruct) {
075        // recursive definitions are not allowed
076        if (decl.typeString.equals(name)) {
077          throw new BadSchemaException(decl.name, "recursive struct reference");
078        }
079
080        // cross-reference struct, creating a placeholder if necessary
081        StructDescriptor aStruct =
082            m_structs.computeIfAbsent(decl.typeString, StructDescriptor::new);
083
084        // if the struct isn't valid, we can't be valid either
085        if (aStruct.isValid()) {
086          size = aStruct.getSize();
087        } else {
088          isValid = false;
089        }
090
091        // add to cross-references for when the struct does become valid
092        aStruct.m_references.add(theStruct);
093        structDesc = aStruct;
094      }
095
096      // create field
097      StructFieldDescriptor fieldDesc =
098          new StructFieldDescriptor(
099              theStruct,
100              decl.name,
101              type,
102              size,
103              decl.arraySize,
104              decl.bitWidth,
105              decl.enumValues,
106              structDesc);
107      if (theStruct.m_fieldsByName.put(decl.name, fieldDesc) != null) {
108        throw new BadSchemaException(decl.name, "duplicate field name");
109      }
110
111      theStruct.m_fields.add(fieldDesc);
112    }
113
114    theStruct.m_valid = isValid;
115    Stack<StructDescriptor> stack = new Stack<>();
116    if (isValid) {
117      // we have all the info needed, so calculate field offset & shift
118      theStruct.calculateOffsets(stack);
119    } else {
120      // check for circular reference
121      if (!theStruct.checkCircular(stack)) {
122        StringBuilder builder = new StringBuilder();
123        builder.append("circular struct reference: ");
124        boolean first = true;
125        for (StructDescriptor elem : stack) {
126          if (!first) {
127            builder.append(" <- ");
128          } else {
129            first = false;
130          }
131          builder.append(elem.getName());
132        }
133        throw new BadSchemaException(builder.toString());
134      }
135    }
136
137    return theStruct;
138  }
139
140  /**
141   * Finds a structure in the database by name.
142   *
143   * @param name structure name
144   * @return struct descriptor, or null if not found
145   */
146  public StructDescriptor find(String name) {
147    return m_structs.get(name);
148  }
149
150  private final Map<String, StructDescriptor> m_structs = new HashMap<>();
151}