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}