diff --git a/capnpc-csharp/Model/SchemaModel.cs b/capnpc-csharp/Model/SchemaModel.cs index 0e6b5be..23e0906 100644 --- a/capnpc-csharp/Model/SchemaModel.cs +++ b/capnpc-csharp/Model/SchemaModel.cs @@ -9,19 +9,18 @@ namespace CapnpC.Model class SchemaModel { readonly Schema.CodeGeneratorRequest.Reader _request; - readonly List _files = new List(); - readonly Stack _typeNest = new Stack(); + readonly List _generatedFiles = new List(); + Dictionary _allDefinitions = new Dictionary(); readonly TypeDefinitionManager _typeDefMgr = new TypeDefinitionManager(); - Method _currentMethod; - Dictionary _id2node; + readonly Dictionary _id2node = new Dictionary(); public SchemaModel(Schema.CodeGeneratorRequest.Reader request) { _request = request; } - public IReadOnlyList FilesToGenerate => _files; + public IReadOnlyList FilesToGenerate => _generatedFiles; Schema.Node.Reader IdToNode(ulong id) { @@ -42,69 +41,196 @@ namespace CapnpC.Model throw new InvalidSchemaException("No nodes, nothing to generate"); } - try + foreach (var node in _request.Nodes) { - _id2node = _request.Nodes.ToDictionary(n => n.Id); - } - catch (ArgumentException) - { - throw new InvalidSchemaException("Nodes with duplicate IDs detected"); - } - - foreach (var reqFile in _request.RequestedFiles) - { - var file = new GenFile() + if (_id2node.TryGetValue(node.Id, out var existingNode)) { - Name = reqFile.Filename - }; + throw new InvalidSchemaException($"Node {node.StrId()} \"{node.DisplayName}\" has a duplicate ID, prior node was \"{existingNode.DisplayName}\""); + } + _id2node[node.Id] = node; + } - _files.Add(file); - _typeNest.Push(file); + BuildPass1(); + BuildPass2(); + } - var fileNode = IdToNode(reqFile.Id); + // First pass: create type definitions for each node. - if (!fileNode.IsFile) - throw new InvalidSchemaException("Expected a file node"); + struct Pass1State + { + public HashSet unprocessedNodes; + public bool isGenerated; + public IHasNestedDefinitions parent; + } - ProcessFile(fileNode); - - _typeNest.Pop(); + void BuildPass1() + { + Pass1State state = new Pass1State() + { + unprocessedNodes = new HashSet(_id2node.Keys) + }; + var requestedFiles = _request.RequestedFiles.ToDictionary(req => req.Id); + foreach (var node in _id2node.Values.Where(n => n.IsFile)) + { + GenFile file; + state.isGenerated = requestedFiles.TryGetValue(node.Id, out var req); + state.parent = null; + if (state.isGenerated) + { + file = (GenFile)ProcessNodePass1(node.Id, req.Filename, state); + _generatedFiles.Add(file); + } + else + { + file = (GenFile)ProcessNodePass1(node.Id, node.DisplayName, state); + } + _allDefinitions.Add(node.Id, file); + } + if (state.unprocessedNodes.Count != 0) + { + throw new InvalidSchemaException("Unreferenced nodes were present in the schema."); } } - void ProcessFile(Schema.Node.Reader fileReader) + TypeDefinition CreateTypeDef(Schema.Node.Reader node, IHasNestedDefinitions parent) { - foreach (var annotation in fileReader.Annotations) + var kind = node.GetKind(); + var def = _typeDefMgr.Create(node.Id, kind.GetTypeTag()); + def.DeclaringElement = parent; + if (kind == NodeKind.Group) + ((TypeDefinition)parent).NestedGroups.Add(def); + else + parent.NestedTypes.Add(def); + return def; + } + + IHasNestedDefinitions ProcessNodePass1(ulong id, string name, Pass1State state) + { + if (!_id2node.TryGetValue(id, out var node)) + { + if (!state.isGenerated) return null; + throw new InvalidSchemaException($"The node {id.StrId()} was necessary for backend codegen but is missing."); + } + + if (!state.unprocessedNodes.Remove(id)) + throw new InvalidSchemaException($"The node \"{node.DisplayName}\" {node.StrId()} has been declared recursively."); + + GenFile file = null; + bool processNestedNodes = false; + bool processFields = false; + bool processInterfaceMethods = false; + + switch (node.GetKind()) + { + case NodeKind.Annotation: + case NodeKind.Const: + // A dummy TypeDefinition is created to node hierarchy + break; + case NodeKind.File: + if (state.parent != null) + throw new InvalidSchemaException("Did not expect file nodes to appear as nested nodes"); + file = new GenFile(); + file.Namespace = null; // TODO + processNestedNodes = true; + break; + case NodeKind.Enum: + break; + case NodeKind.Interface: + processNestedNodes = true; + processFields = true; + processInterfaceMethods = true; + break; + case NodeKind.Struct: + case NodeKind.Group: + processNestedNodes = true; + processFields = true; + break; + default: + throw new InvalidSchemaException($"Don't know how to process node {node.StrId()} \"{node.DisplayName}\""); + } + + TypeDefinition def = null; + if (file != null) + { + state.parent = file; + file.Name = name; + } + else + { + state.parent = def = CreateTypeDef(node, state.parent); + def.Name = name; + } + + if (processNestedNodes && node.NestedNodes != null) + foreach (var nested in node.NestedNodes) + { + ProcessNodePass1(nested.Id, nested.Name, state); + } + if (processFields && node.Fields != null) + foreach (var field in node.Fields.Where(f => f.IsGroup)) + { + var group = IdToNode(field.Group_TypeId); + if (!group.IsStruct || !group.Struct_IsGroup) + { + throw new InvalidSchemaException($"Expected node with id {group.StrId()} to be a struct definition"); + } + ProcessNodePass1(field.Group_TypeId, field.Name, state); + } + if (processInterfaceMethods && node.Interface_Methods != null) + foreach (var method in node.Interface_Methods) + { + var pnode = IdToNode(method.ParamStructType); + if (pnode.ScopeId == 0) ProcessNodePass1(pnode.Id, null, state); // Anonymous generated type + pnode = IdToNode(method.ResultStructType); + if (pnode.ScopeId == 0) ProcessNodePass1(pnode.Id, null, state); // Anonymous generated type + } + return state.parent; + } + + // 2nd pass: Generate types based on definitions + + struct Pass2State + { + public Method currentMethod; + public HashSet processedNodes; + } + + void BuildPass2() + { + var files = _allDefinitions.Select(d => (Id: d.Key, File: d.Value as GenFile)).Where(d => d.File != null); + var state = new Pass2State() { processedNodes = new HashSet() }; + foreach (var file in files) + { + var node = IdToNode(file.Id); + ProcessFileAnnotations(node, file.File); + ProcessNestedNodes(node.NestedNodes, state); + } + } + + void ProcessNestedNodes(IEnumerable nestedNodes, Pass2State state) + { + foreach (var nestedNode in nestedNodes) + { + ProcessNode(nestedNode.Id, state); + } + } + + void ProcessFileAnnotations(Schema.Node.Reader fileNode, GenFile fileElement) + { + foreach (var annotation in fileNode.Annotations) { if (annotation.Id == 0xb9c6f99ebf805f2c) // Cxx namespace { - ((GenFile)_typeNest.Peek()).Namespace = annotation.Value.Text.Split("::"); + fileElement.Namespace = annotation.Value.Text.Split(new string[1] { "::" }, default); } } - - foreach (var nestedNode in fileReader.NestedNodes) - { - var node = IdToNode(nestedNode.Id); - - ProcessNode(node, nestedNode.Name); - } } - TypeDefinition GetOrCreateTypeDef(ulong id, TypeTag tag) => _typeDefMgr.GetOrCreate(id, tag); - TypeDefinition GetGroupTypeDef(ulong id, string name) - { - var nodeReader = _id2node[id]; - - if (!nodeReader.IsStruct) - throw new InvalidSchemaException($"Expected node with id {id} to be a struct definition"); - - return ProcessStruct(nodeReader, name); - } - void ProcessBrand(Schema.Brand.Reader brandReader, Type type) + void ProcessBrand(Schema.Brand.Reader brandReader, Type type, Pass2State state) { foreach (var scopeReader in brandReader.Scopes) { - var whatToBind = GetOrCreateTypeDef(scopeReader.ScopeId, TypeTag.Unknown); + var whatToBind = ProcessNode(scopeReader.ScopeId, state); int index = 0; switch (0) @@ -121,7 +247,7 @@ namespace CapnpC.Model switch (0) { case 0 when bindingReader.IsType: - type.BindGenericParameter(typeParameter, ProcessType(bindingReader.Type)); + type.BindGenericParameter(typeParameter, ProcessType(bindingReader.Type, state)); break; case 0 when bindingReader.IsUnbound: @@ -146,7 +272,8 @@ namespace CapnpC.Model } } } - Type ProcessType(Schema.Type.Reader typeReader) + + Type ProcessType(Schema.Type.Reader typeReader, Pass2State state) { Type result; @@ -159,7 +286,7 @@ namespace CapnpC.Model return Types.FromParameter( new GenericParameter() { - DeclaringEntity = GetOrCreateTypeDef(typeReader.AnyPointer_Parameter_ScopeId, TypeTag.Unknown), + DeclaringEntity = ProcessNode(typeReader.AnyPointer_Parameter_ScopeId, state), Index = typeReader.AnyPointer_Parameter_ParameterIndex }); @@ -167,7 +294,7 @@ namespace CapnpC.Model return Types.FromParameter( new GenericParameter() { - DeclaringEntity = _currentMethod ?? throw new InvalidOperationException("current method not set"), + DeclaringEntity = state.currentMethod ?? throw new InvalidOperationException("current method not set"), Index = typeReader.AnyPointer_ImplicitMethodParameter_ParameterIndex }); @@ -205,7 +332,7 @@ namespace CapnpC.Model return Types.F64; case 0 when typeReader.IsEnum: - return Types.FromDefinition(GetOrCreateTypeDef(typeReader.Enum_TypeId, TypeTag.Enum)); + return Types.FromDefinition(ProcessNode(typeReader.Enum_TypeId, state, TypeTag.Enum)); case 0 when typeReader.IsFloat32: return Types.F32; @@ -223,16 +350,16 @@ namespace CapnpC.Model return Types.S8; case 0 when typeReader.IsInterface: - result = Types.FromDefinition(GetOrCreateTypeDef(typeReader.Interface_TypeId, TypeTag.Interface)); - ProcessBrand(typeReader.Interface_Brand, result); + result = Types.FromDefinition(ProcessNode(typeReader.Interface_TypeId, state, TypeTag.Interface)); + ProcessBrand(typeReader.Interface_Brand, result, state); return result; case 0 when typeReader.IsList: - return Types.List(ProcessType(typeReader.List_ElementType)); + return Types.List(ProcessType(typeReader.List_ElementType, state)); case 0 when typeReader.IsStruct: - result = Types.FromDefinition(GetOrCreateTypeDef(typeReader.Struct_TypeId, TypeTag.Struct)); - ProcessBrand(typeReader.Struct_Brand, result); + result = Types.FromDefinition(ProcessNode(typeReader.Struct_TypeId, state, TypeTag.Struct)); + ProcessBrand(typeReader.Struct_Brand, result, state); return result; case 0 when typeReader.IsText: @@ -365,7 +492,7 @@ namespace CapnpC.Model return value; } - void ProcessFields(Schema.Node.Reader reader, TypeDefinition declaringType, List fields) + void ProcessFields(Schema.Node.Reader reader, TypeDefinition declaringType, List fields, Pass2State state) { if (reader.Fields == null) { @@ -389,15 +516,15 @@ namespace CapnpC.Model switch (0) { case 0 when fieldReader.IsGroup: - field.Type = Types.FromDefinition(GetGroupTypeDef( - fieldReader.Group_TypeId, fieldReader.Name)); + var def = ProcessNode(fieldReader.Group_TypeId, state, TypeTag.Group); + field.Type = Types.FromDefinition(def); break; case 0 when fieldReader.IsSlot: field.DefaultValue = ProcessValue(fieldReader.Slot_DefaultValue); field.DefaultValueIsExplicit = fieldReader.Slot_HadExplicitDefault; field.Offset = fieldReader.Slot_Offset; - field.Type = ProcessType(fieldReader.Slot_Type); + field.Type = ProcessType(fieldReader.Slot_Type, state); field.DefaultValue.Type = field.Type; break; @@ -411,7 +538,7 @@ namespace CapnpC.Model } } - void ProcessInterfaceOrStructTail(TypeDefinition def, Schema.Node.Reader reader) + TypeDefinition ProcessInterfaceOrStructTail(TypeDefinition def, Schema.Node.Reader reader, Pass2State state) { def.IsGeneric = reader.IsGeneric; @@ -423,18 +550,9 @@ namespace CapnpC.Model } } - _typeNest.Push(def); + ProcessNestedNodes(reader.NestedNodes, state); - if (reader.NestedNodes != null) - { - foreach (var nestedReader in reader.NestedNodes) - { - var node = IdToNode(nestedReader.Id); - ProcessNode(node, nestedReader.Name); - } - } - - ProcessFields(reader, def, def.Fields); + ProcessFields(reader, def, def.Fields, state); if (reader.IsInterface) { @@ -450,12 +568,12 @@ namespace CapnpC.Model { method.GenericParameters.Add(implicitParameterReader.Name); } - _currentMethod = method; + state.currentMethod = method; def.Methods.Add(method); var paramNode = IdToNode(methodReader.ParamStructType); - var paramType = ProcessParameterList(paramNode, methodReader.ParamBrand, method.Params); + var paramType = ProcessParameterList(paramNode, methodReader.ParamBrand, method.Params, state); if (paramType != null) { paramType.SpecialName = SpecialName.MethodParamsStruct; @@ -470,7 +588,7 @@ namespace CapnpC.Model method.ParamsStruct.InheritFreeParameters(method); var resultNode = IdToNode(methodReader.ResultStructType); - var resultType = ProcessParameterList(resultNode, methodReader.ResultBrand, method.Results); + var resultType = ProcessParameterList(resultNode, methodReader.ResultBrand, method.Results, state); if (resultType != null) { resultType.SpecialName = SpecialName.MethodResultStruct; @@ -485,24 +603,13 @@ namespace CapnpC.Model method.ResultStruct.InheritFreeParameters(method); } - _currentMethod = null; + state.currentMethod = null; } - - _typeNest.Pop(); + return def; } - TypeDefinition ProcessStruct(Schema.Node.Reader structReader, string name) + TypeDefinition ProcessStruct(Schema.Node.Reader structReader, TypeDefinition def, Pass2State state) { - var def = GetOrCreateTypeDef( - structReader.Id, - structReader.Struct_IsGroup ? TypeTag.Group : TypeTag.Struct); - - def.DeclaringElement = _typeNest.Peek(); - if (structReader.Struct_IsGroup) - ((TypeDefinition)def.DeclaringElement).NestedGroups.Add(def); - else - def.DeclaringElement.NestedTypes.Add(def); - def.Name = name; def.StructDataWordCount = structReader.Struct_DataWordCount; def.StructPointerCount = structReader.Struct_PointerCount; @@ -513,12 +620,10 @@ namespace CapnpC.Model 16u * structReader.Struct_DiscriminantOffset); } - ProcessInterfaceOrStructTail(def, structReader); - - return def; + return ProcessInterfaceOrStructTail(def, structReader, state); } - TypeDefinition ProcessParameterList(Schema.Node.Reader reader, Schema.Brand.Reader brandReader, List list) + TypeDefinition ProcessParameterList(Schema.Node.Reader reader, Schema.Brand.Reader brandReader, List list, Pass2State state) { //# If a named parameter list was specified in the method //# declaration (rather than a single struct parameter type) then a corresponding struct type is @@ -533,58 +638,38 @@ namespace CapnpC.Model throw new InvalidSchemaException("Expected a struct"); } + var def = ProcessNode(reader.Id, state, TypeTag.Struct); + if (reader.ScopeId == 0) { // Auto-generated => Named parameter list - ProcessFields(reader, null, list); - return ProcessStruct(reader, null); + foreach (var field in def.Fields) list.Add(field); + return def; } else { // Single, anonymous, struct-typed parameter - var def = GetOrCreateTypeDef(reader.Id, TypeTag.Struct); var type = Types.FromDefinition(def); - ProcessBrand(brandReader, type); + ProcessBrand(brandReader, type, state); var anon = new Field() { Type = type }; list.Add(anon); return null; } } - TypeDefinition ProcessInterface(Schema.Node.Reader ifaceReader, string name) + TypeDefinition ProcessInterface(Schema.Node.Reader ifaceReader, TypeDefinition def, Pass2State state) { - var def = GetOrCreateTypeDef( - ifaceReader.Id, - TypeTag.Interface); - - def.DeclaringElement = _typeNest.Peek(); - def.DeclaringElement.NestedTypes.Add(def); - def.Name = name; - foreach (var superClassReader in ifaceReader.Interface_Superclasses) { - var superClass = GetOrCreateTypeDef( - superClassReader.Id, - TypeTag.Interface); - + var superClass = ProcessNode(superClassReader.Id, state, TypeTag.Interface); def.Superclasses.Add(Types.FromDefinition(superClass)); } - ProcessInterfaceOrStructTail(def, ifaceReader); - - return def; + return ProcessInterfaceOrStructTail(def, ifaceReader, state); } - void ProcessEnum(Schema.Node.Reader enumReader, string name) + TypeDefinition ProcessEnum(Schema.Node.Reader enumReader, TypeDefinition def, Pass2State state) { - var def = GetOrCreateTypeDef(enumReader.Id, TypeTag.Enum); - - def.DeclaringElement = _typeNest.Peek(); - def.DeclaringElement.NestedTypes.Add(def); - def.Name = name; - - _typeNest.Push(def); - foreach (var fieldReader in enumReader.Enumerants) { var field = new Enumerant() @@ -601,46 +686,41 @@ namespace CapnpC.Model def.Enumerants.Add(field); } - - _typeNest.Pop(); + return def; } - void ProcessConst(Schema.Node.Reader constReader, string name) + Value ProcessConst(Schema.Node.Reader constReader, Pass2State state) { var value = ProcessValue(constReader.Const_Value); - value.Type = ProcessType(constReader.Const_Type); - - _typeNest.Peek().Constants.Add(value); + value.Type = ProcessType(constReader.Const_Type, state); + return value; } - void ProcessNode(Schema.Node.Reader node, string name) + TypeDefinition ProcessNode(ulong id, Pass2State state, TypeTag tag = default) { - switch (0) + var node = IdToNode(id); + var kind = node.GetKind(); + if (tag == TypeTag.Unknown) tag = kind.GetTypeTag(); + var def = _typeDefMgr.GetExisting(id, tag); + if (state.processedNodes.Contains(id)) return def; + state.processedNodes.Add(id); + + switch (kind) { - case 0 when node.IsAnnotation: - break; - - case 0 when node.IsConst: - ProcessConst(node, name); - break; - - case 0 when node.IsEnum: - ProcessEnum(node, name); - break; - - case 0 when node.IsFile: - throw new InvalidSchemaException("Did not expect file nodes to appear as nested nodes"); - - case 0 when node.IsInterface: - ProcessInterface(node, name); - break; - - case 0 when node.IsStruct: - ProcessStruct(node, name); - break; - + case NodeKind.Annotation: + return def; + case NodeKind.Const: + def.DeclaringElement.Constants.Add(ProcessConst(node, state)); + return def; + case NodeKind.Enum: + return ProcessEnum(node, def, state); + case NodeKind.Interface: + return ProcessInterface(node, def, state); + case NodeKind.Struct: + case NodeKind.Group: + return ProcessStruct(node, def, state); default: - throw new InvalidSchemaException($"Don't know how to process node {node.DisplayName}"); + throw new InvalidProgramException($"An unexpected node {node.StrId()} was found during the 2nd schema model building pass."); } } @@ -651,4 +731,55 @@ namespace CapnpC.Model return model; } } + + public enum NodeKind + { + Unknown, + Annotation, + Const, + Enum, + File, + Interface, + Struct, + Group + } + + public static class SchemaExtensions + { + public static string GetName(this Schema.Node.Reader node) + => node.DisplayName.Substring((int)node.DisplayNamePrefixLength); + + public static string StrId(this Schema.Node.Reader node) + => $"0x{node.Id.ToString("X")}"; + + public static string StrId(this ulong nodeId) + => $"0x{nodeId.ToString("X")}"; + + public static NodeKind GetKind(this Schema.Node.Reader node) + { + if (node.IsStruct) + return node.Struct_IsGroup ? NodeKind.Group : NodeKind.Struct; + if (node.IsInterface) return NodeKind.Interface; + if (node.IsEnum) return NodeKind.Enum; + if (node.IsConst) return NodeKind.Const; + if (node.IsAnnotation) return NodeKind.Annotation; + if (node.IsFile) return NodeKind.File; + return NodeKind.Unknown; + } + + internal static TypeTag GetTypeTag(this NodeKind kind) + { + switch (kind) + { + case NodeKind.Enum: return TypeTag.Enum; + case NodeKind.Interface: return TypeTag.Interface; + case NodeKind.Struct: return TypeTag.Struct; + case NodeKind.Group: return TypeTag.Group; + default: return TypeTag.Unknown; + } + } + + internal static TypeTag GetTypeTag(this Schema.Node.Reader node) + => node.GetKind().GetTypeTag(); + } } diff --git a/capnpc-csharp/Model/Type.cs b/capnpc-csharp/Model/Type.cs index 3512b2b..c1017df 100644 --- a/capnpc-csharp/Model/Type.cs +++ b/capnpc-csharp/Model/Type.cs @@ -9,9 +9,16 @@ namespace CapnpC.Model { class Type: AbstractType { + // Representation of a type expression in the schema language + public TypeDefinition Definition { get; set; } + // The model for all nodes that are not file nodes - they define types + public GenericParameter Parameter { get; set; } + // A reference to type parameter in this scope + public Type ElementType { get; set; } + // The type of a list element, if this is a list. readonly Dictionary _parameterBindings = new Dictionary(); diff --git a/capnpc-csharp/Model/TypeDefinitionManager.cs b/capnpc-csharp/Model/TypeDefinitionManager.cs index 7c1562e..a734026 100644 --- a/capnpc-csharp/Model/TypeDefinitionManager.cs +++ b/capnpc-csharp/Model/TypeDefinitionManager.cs @@ -8,7 +8,19 @@ namespace CapnpC.Model readonly Dictionary _id2def = new Dictionary(); - public TypeDefinition GetOrCreate(ulong id, TypeTag tag) + public TypeDefinition Create(ulong id, TypeTag tag) + { + if (_id2def.ContainsKey(id)) + { + throw new ArgumentException(nameof(id), $"Attempting to redefine {tag.ToString()} {id.StrId()}."); + } + + var def = new TypeDefinition(tag, id); + _id2def.Add(id, def); + return def; + } + + public TypeDefinition GetExisting(ulong id, TypeTag tag) { if (_id2def.TryGetValue(id, out var def)) { @@ -20,19 +32,9 @@ namespace CapnpC.Model { throw new ArgumentOutOfRangeException(nameof(tag), "Type tag does not match existing type"); } + return def; } - else - { - def = new TypeDefinition(tag, id); - _id2def.Add(id, def); - } - - return def; - } - - public TypeDefinition GetExisting(ulong id) - { - return _id2def[id]; + throw new ArgumentOutOfRangeException($"Attempting to retrieve nonexistend node {id.StrId()}."); } } }