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}