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