start xml implementation (see details)
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/push Build is failing
				
			This implementation works for trivial cases, and all xml.zig test cases pass. Using this with a real response (see describe_instances.xml.zig) triggers a compile error, captured in llvm-ir.log
This commit is contained in:
		
							parent
							
								
									86751f22e9
								
							
						
					
					
						commit
						d37aff40ae
					
				
					 5 changed files with 121373 additions and 19 deletions
				
			
		
							
								
								
									
										83
									
								
								src/aws.zig
									
										
									
									
									
								
							
							
						
						
									
										83
									
								
								src/aws.zig
									
										
									
									
									
								
							|  | @ -2,6 +2,7 @@ const std = @import("std"); | |||
| 
 | ||||
| const awshttp = @import("awshttp.zig"); | ||||
| const json = @import("json.zig"); | ||||
| const xml_shaper = @import("xml_shaper.zig"); | ||||
| const url = @import("url.zig"); | ||||
| const case = @import("case.zig"); | ||||
| const servicemodel = @import("servicemodel.zig"); | ||||
|  | @ -265,25 +266,69 @@ fn queryFieldTransformer(field_name: []const u8, encoding_options: url.EncodingO | |||
|     return try case.snakeToPascal(encoding_options.allocator.?, field_name); | ||||
| } | ||||
| 
 | ||||
| // Use for debugging json responses of specific requests | ||||
| // test "dummy request" { | ||||
| fn readFileToString(allocator: *std.mem.Allocator, file_name: []const u8, max_bytes: usize) ![]const u8 { | ||||
|     const file = try std.fs.cwd().openFile(file_name, std.fs.File.OpenFlags{}); | ||||
|     defer file.close(); | ||||
|     return file.readToEndAlloc(allocator, max_bytes); | ||||
| } | ||||
| var test_data: ?[]const u8 = null; | ||||
| 
 | ||||
| fn getTestData(_: *std.mem.Allocator) []const u8 { | ||||
|     if (test_data) |d| return d; | ||||
|     var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||||
|     test_data = readFileToString(&gpa.allocator, "describe_instances.xml", 150000) catch @panic("could not read describe_instances.xml"); | ||||
|     return test_data.?; | ||||
| } | ||||
| test "read file" { | ||||
|     var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||||
|     defer if (gpa.deinit()) @panic("leak"); | ||||
|     const allocator = &gpa.allocator; | ||||
|     _ = getTestData(allocator); | ||||
|     // test stuff | ||||
| } | ||||
| // Running this breaks the compiler | ||||
| // broken LLVM module found: Operand is null... | ||||
| //   br i1 %100, label %ErrRetReturn12, <null operand!>, !dbg !7881 | ||||
| // Operand is null | ||||
| //   br i1 %100, label %ErrRetReturn12, <null operand!>, !dbg !8012 | ||||
| // test "parse xml request (runtime)" { | ||||
| //     const allocator = std.testing.allocator; | ||||
| //     const svs = Services(.{.sts}){}; | ||||
| //     const request = svs.sts.get_session_token.Request{ | ||||
| //         .duration_seconds = 900, | ||||
| //     }; | ||||
| //     const FullR = FullResponse(request); | ||||
| //     const response = | ||||
| //     var stream = json.TokenStream.init(response); | ||||
| // | ||||
| //     const parser_options = json.ParseOptions{ | ||||
| //         .allocator = allocator, | ||||
| //         .allow_camel_case_conversion = true, // new option | ||||
| //         .allow_snake_case_conversion = true, // new option | ||||
| //         .allow_unknown_fields = true, // new option. Cannot yet handle non-struct fields though | ||||
| //         .allow_missing_fields = false, // new option. Cannot yet handle non-struct fields though | ||||
| //     }; | ||||
| //     const svs = Services(.{.ec2}){}; | ||||
| //     const request = svs.ec2.describe_instances.Request{}; | ||||
| //     const response = getTestData(allocator); | ||||
| //     const SResponse = ServerResponse(request); | ||||
| //     const r = try json.parse(SResponse, &stream, parser_options); | ||||
| //     json.parseFree(SResponse, r, parser_options); | ||||
| //     // const parser = xml_shaper.Parser(SResponse){}; | ||||
| //     const r = try xml_shaper.Parser(SResponse).parse("", .{ .allocator = allocator, .match_predicate = xml_shaper.fuzzyEqual }); | ||||
| //     // const r = parser.parse("", .{ .allocator = allocator, .match_predicate = xml_shaper}); | ||||
| //     // const r = try xml_shaper.parse(SResponse, "", .{ .allocator = allocator, .match_predicate = xml_shaper.fuzzyEqual }); | ||||
| //     // defer r.deinit(); | ||||
| //     try std.testing.expectEqualStrings( | ||||
| //         "37f07eb3-4a90-4166-b36f-225f79b6a0fe", | ||||
| //         r.parsed_value.DescribeInstancesResponse.ResponseMetadata.RequestId, | ||||
| //     ); | ||||
| // } | ||||
| 
 | ||||
| // Running this breaks the compiler | ||||
| // broken LLVM module found: Operand is nulltType... | ||||
| //   br i1 %100, label %ErrRetReturn12, <null operand!>, !dbg !7881 | ||||
| // Operand is null | ||||
| //   br i1 %100, label %ErrRetReturn12, <null operand!>, !dbg !8012 | ||||
| // | ||||
| // This is a bug in the Zig compiler. | ||||
| // test "parse xml request (comptime)" { | ||||
| //     const allocator = std.testing.allocator; | ||||
| //     const svs = Services(.{.ec2}){}; | ||||
| //     const request = svs.ec2.describe_instances.Request{}; | ||||
| //     // const FullR = FullResponse(request); | ||||
| //     // https://github.com/Snektron/vulkan-zig/commit/511211f03896b1becdb274c8ab4f2dacd2be4bf2 | ||||
| //     // https://raw.githubusercontent.com/Snektron/vulkan-zig/master/generator/xml.zig | ||||
| //     const response = @import("describe_instances.xml.zig").describe_instances_test_response; | ||||
| // | ||||
| //     const SResponse = ServerResponse(request); | ||||
| //     const r = try xml_shaper.parse(SResponse, response, .{ .allocator = allocator, .match_predicate = xml_shaper.fuzzyEqual }); | ||||
| //     // defer r.deinit(); | ||||
| //     try std.testing.expectEqualStrings( | ||||
| //         "37f07eb3-4a90-4166-b36f-225f79b6a0fe", | ||||
| //         r.parsed_value.DescribeInstancesResponse.ResponseMetadata.RequestId, | ||||
| //     ); | ||||
| // } | ||||
|  |  | |||
							
								
								
									
										453
									
								
								src/describe_instances.xml.zig
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										453
									
								
								src/describe_instances.xml.zig
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,453 @@ | |||
| pub const describe_instances_test_response = | ||||
|     \\<?xml version="1.0" encoding="UTF-8"?> | ||||
|     \\<DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/"> | ||||
|     \\    <requestId>37f07eb3-4a90-4166-b36f-225f79b6a0fe</requestId> | ||||
|     \\    <reservationSet> | ||||
|     \\        <item> | ||||
|     \\            <reservationId>r-0aa8dc779a7a3a76a</reservationId> | ||||
|     \\            <ownerId>550620852718</ownerId> | ||||
|     \\            <groupSet/> | ||||
|     \\            <instancesSet> | ||||
|     \\                <item> | ||||
|     \\                    <instanceId>i-07d9a800b9cf6cb02</instanceId> | ||||
|     \\                    <imageId>ami-0e9dbfecae1768f17</imageId> | ||||
|     \\                    <instanceState> | ||||
|     \\                        <code>80</code> | ||||
|     \\                        <name>stopped</name> | ||||
|     \\                    </instanceState> | ||||
|     \\                    <privateDnsName>ip-172-31-28-124.us-west-2.compute.internal</privateDnsName> | ||||
|     \\                    <dnsName/> | ||||
|     \\                    <reason>User initiated</reason> | ||||
|     \\                    <amiLaunchIndex>0</amiLaunchIndex> | ||||
|     \\                    <productCodes/> | ||||
|     \\                    <instanceType>t3.small</instanceType> | ||||
|     \\                    <launchTime>2021-07-16T18:49:06.000Z</launchTime> | ||||
|     \\                    <placement> | ||||
|     \\                        <availabilityZone>us-west-2b</availabilityZone> | ||||
|     \\                        <groupName/> | ||||
|     \\                        <tenancy>default</tenancy> | ||||
|     \\                    </placement> | ||||
|     \\                    <monitoring> | ||||
|     \\                        <state>disabled</state> | ||||
|     \\                    </monitoring> | ||||
|     \\                    <subnetId>subnet-1f3f4278</subnetId> | ||||
|     \\                    <vpcId>vpc-dc2402bb</vpcId> | ||||
|     \\                    <privateIpAddress>172.31.28.124</privateIpAddress> | ||||
|     \\                    <sourceDestCheck>true</sourceDestCheck> | ||||
|     \\                    <groupSet> | ||||
|     \\                        <item> | ||||
|     \\                            <groupId>sg-08f08fa2f77bc26f4</groupId> | ||||
|     \\                            <groupName>aws-cloud9-foo-394ec61bf1ca40188ec0701578b99e68-InstanceSecurityGroup-X63389OZJE9A</groupName> | ||||
|     \\                        </item> | ||||
|     \\                    </groupSet> | ||||
|     \\                    <stateReason> | ||||
|     \\                        <code>Client.InstanceInitiatedShutdown</code> | ||||
|     \\                        <message>Client.InstanceInitiatedShutdown: Instance initiated shutdown</message> | ||||
|     \\                    </stateReason> | ||||
|     \\                    <architecture>x86_64</architecture> | ||||
|     \\                    <rootDeviceType>ebs</rootDeviceType> | ||||
|     \\                    <rootDeviceName>/dev/xvda</rootDeviceName> | ||||
|     \\                    <blockDeviceMapping> | ||||
|     \\                        <item> | ||||
|     \\                            <deviceName>/dev/xvda</deviceName> | ||||
|     \\                            <ebs> | ||||
|     \\                                <volumeId>vol-00f20d00c3e7793db</volumeId> | ||||
|     \\                                <status>attached</status> | ||||
|     \\                                <attachTime>2021-07-14T16:56:39.000Z</attachTime> | ||||
|     \\                                <deleteOnTermination>true</deleteOnTermination> | ||||
|     \\                            </ebs> | ||||
|     \\                        </item> | ||||
|     \\                    </blockDeviceMapping> | ||||
|     \\                    <virtualizationType>hvm</virtualizationType> | ||||
|     \\                    <clientToken>aws-c-Insta-13W4473LX3CZH</clientToken> | ||||
|     \\                    <tagSet> | ||||
|     \\                        <item> | ||||
|     \\                            <key>aws:cloud9:environment</key> | ||||
|     \\                            <value>394ec61bf1ca40188ec0701578b99e68</value> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <key>aws:cloudformation:logical-id</key> | ||||
|     \\                            <value>Instance</value> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <key>Name</key> | ||||
|     \\                            <value>aws-cloud9-foo-394ec61bf1ca40188ec0701578b99e68</value> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <key>aws:cloud9:owner</key> | ||||
|     \\                            <value>550620852718:admin-nucman</value> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <key>aws:cloudformation:stack-name</key> | ||||
|     \\                            <value>aws-cloud9-foo-394ec61bf1ca40188ec0701578b99e68</value> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <key>aws:cloudformation:stack-id</key> | ||||
|     \\                            <value>arn:aws:cloudformation:us-west-2:550620852718:stack/aws-cloud9-foo-394ec61bf1ca40188ec0701578b99e68/6c4bec10-e4c4-11eb-8338-0266575e789f</value> | ||||
|     \\                        </item> | ||||
|     \\                    </tagSet> | ||||
|     \\                    <hypervisor>xen</hypervisor> | ||||
|     \\                    <networkInterfaceSet> | ||||
|     \\                        <item> | ||||
|     \\                            <networkInterfaceId>eni-0aea22ab476c131e8</networkInterfaceId> | ||||
|     \\                            <subnetId>subnet-1f3f4278</subnetId> | ||||
|     \\                            <vpcId>vpc-dc2402bb</vpcId> | ||||
|     \\                            <description/> | ||||
|     \\                            <ownerId>550620852718</ownerId> | ||||
|     \\                            <status>in-use</status> | ||||
|     \\                            <macAddress>02:e4:fe:61:88:ad</macAddress> | ||||
|     \\                            <privateIpAddress>172.31.28.124</privateIpAddress> | ||||
|     \\                            <privateDnsName>ip-172-31-28-124.us-west-2.compute.internal</privateDnsName> | ||||
|     \\                            <sourceDestCheck>true</sourceDestCheck> | ||||
|     \\                            <groupSet> | ||||
|     \\                                <item> | ||||
|     \\                                    <groupId>sg-08f08fa2f77bc26f4</groupId> | ||||
|     \\                                    <groupName>aws-cloud9-foo-394ec61bf1ca40188ec0701578b99e68-InstanceSecurityGroup-X63389OZJE9A</groupName> | ||||
|     \\                                </item> | ||||
|     \\                            </groupSet> | ||||
|     \\                            <attachment> | ||||
|     \\                                <attachmentId>eni-attach-031a23d21bd9a9675</attachmentId> | ||||
|     \\                                <deviceIndex>0</deviceIndex> | ||||
|     \\                                <status>attached</status> | ||||
|     \\                                <attachTime>2021-07-14T16:56:38.000Z</attachTime> | ||||
|     \\                                <deleteOnTermination>true</deleteOnTermination> | ||||
|     \\                                <networkCardIndex>0</networkCardIndex> | ||||
|     \\                            </attachment> | ||||
|     \\                            <privateIpAddressesSet> | ||||
|     \\                                <item> | ||||
|     \\                                    <privateIpAddress>172.31.28.124</privateIpAddress> | ||||
|     \\                                    <privateDnsName>ip-172-31-28-124.us-west-2.compute.internal</privateDnsName> | ||||
|     \\                                    <primary>true</primary> | ||||
|     \\                                </item> | ||||
|     \\                            </privateIpAddressesSet> | ||||
|     \\                            <ipv6AddressesSet/> | ||||
|     \\                            <interfaceType>interface</interfaceType> | ||||
|     \\                        </item> | ||||
|     \\                    </networkInterfaceSet> | ||||
|     \\                    <ebsOptimized>false</ebsOptimized> | ||||
|     \\                    <enaSupport>true</enaSupport> | ||||
|     \\                    <cpuOptions> | ||||
|     \\                        <coreCount>1</coreCount> | ||||
|     \\                        <threadsPerCore>2</threadsPerCore> | ||||
|     \\                    </cpuOptions> | ||||
|     \\                    <capacityReservationSpecification> | ||||
|     \\                        <capacityReservationPreference>open</capacityReservationPreference> | ||||
|     \\                    </capacityReservationSpecification> | ||||
|     \\                    <hibernationOptions> | ||||
|     \\                        <configured>false</configured> | ||||
|     \\                    </hibernationOptions> | ||||
|     \\                    <enclaveOptions> | ||||
|     \\                        <enabled>false</enabled> | ||||
|     \\                    </enclaveOptions> | ||||
|     \\                    <metadataOptions> | ||||
|     \\                        <state>applied</state> | ||||
|     \\                        <httpTokens>optional</httpTokens> | ||||
|     \\                        <httpPutResponseHopLimit>1</httpPutResponseHopLimit> | ||||
|     \\                        <httpEndpoint>enabled</httpEndpoint> | ||||
|     \\                    </metadataOptions> | ||||
|     \\                    <privateDnsNameOptions/> | ||||
|     \\                </item> | ||||
|     \\            </instancesSet> | ||||
|     \\            <requesterId>658754138699</requesterId> | ||||
|     \\        </item> | ||||
|     \\        <item> | ||||
|     \\            <reservationId>r-070573e68e0a31bab</reservationId> | ||||
|     \\            <ownerId>550620852718</ownerId> | ||||
|     \\            <groupSet/> | ||||
|     \\            <instancesSet> | ||||
|     \\                <item> | ||||
|     \\                    <instanceId>i-097eaf8bea0e9a1be</instanceId> | ||||
|     \\                    <imageId>ami-046b69c0f7b7428d5</imageId> | ||||
|     \\                    <instanceState> | ||||
|     \\                        <code>80</code> | ||||
|     \\                        <name>stopped</name> | ||||
|     \\                    </instanceState> | ||||
|     \\                    <privateDnsName>ip-172-31-27-214.us-west-2.compute.internal</privateDnsName> | ||||
|     \\                    <dnsName/> | ||||
|     \\                    <reason>User initiated</reason> | ||||
|     \\                    <amiLaunchIndex>0</amiLaunchIndex> | ||||
|     \\                    <productCodes/> | ||||
|     \\                    <instanceType>t3.small</instanceType> | ||||
|     \\                    <launchTime>2021-07-16T18:54:31.000Z</launchTime> | ||||
|     \\                    <placement> | ||||
|     \\                        <availabilityZone>us-west-2b</availabilityZone> | ||||
|     \\                        <groupName/> | ||||
|     \\                        <tenancy>default</tenancy> | ||||
|     \\                    </placement> | ||||
|     \\                    <monitoring> | ||||
|     \\                        <state>disabled</state> | ||||
|     \\                    </monitoring> | ||||
|     \\                    <subnetId>subnet-1f3f4278</subnetId> | ||||
|     \\                    <vpcId>vpc-dc2402bb</vpcId> | ||||
|     \\                    <privateIpAddress>172.31.27.214</privateIpAddress> | ||||
|     \\                    <sourceDestCheck>true</sourceDestCheck> | ||||
|     \\                    <groupSet> | ||||
|     \\                        <item> | ||||
|     \\                            <groupId>sg-05466b60f1a4ee056</groupId> | ||||
|     \\                            <groupName>aws-cloud9-bar-1994a98a468e445ba4b5ad91dd93475b-InstanceSecurityGroup-7FL7ACV848S1</groupName> | ||||
|     \\                        </item> | ||||
|     \\                    </groupSet> | ||||
|     \\                    <stateReason> | ||||
|     \\                        <code>Client.InstanceInitiatedShutdown</code> | ||||
|     \\                        <message>Client.InstanceInitiatedShutdown: Instance initiated shutdown</message> | ||||
|     \\                    </stateReason> | ||||
|     \\                    <architecture>x86_64</architecture> | ||||
|     \\                    <rootDeviceType>ebs</rootDeviceType> | ||||
|     \\                    <rootDeviceName>/dev/xvda</rootDeviceName> | ||||
|     \\                    <blockDeviceMapping> | ||||
|     \\                        <item> | ||||
|     \\                            <deviceName>/dev/xvda</deviceName> | ||||
|     \\                            <ebs> | ||||
|     \\                                <volumeId>vol-0e99d9d6867118030</volumeId> | ||||
|     \\                                <status>attached</status> | ||||
|     \\                                <attachTime>2021-07-16T18:54:32.000Z</attachTime> | ||||
|     \\                                <deleteOnTermination>true</deleteOnTermination> | ||||
|     \\                            </ebs> | ||||
|     \\                        </item> | ||||
|     \\                    </blockDeviceMapping> | ||||
|     \\                    <virtualizationType>hvm</virtualizationType> | ||||
|     \\                    <clientToken>aws-c-Insta-1H5LO8S4BU1CS</clientToken> | ||||
|     \\                    <tagSet> | ||||
|     \\                        <item> | ||||
|     \\                            <key>aws:cloudformation:stack-id</key> | ||||
|     \\                            <value>arn:aws:cloudformation:us-west-2:550620852718:stack/aws-cloud9-bar-1994a98a468e445ba4b5ad91dd93475b/39a8dd00-e667-11eb-a967-02d5fabd30ef</value> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <key>Name</key> | ||||
|     \\                            <value>aws-cloud9-bar-1994a98a468e445ba4b5ad91dd93475b</value> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <key>aws:cloud9:owner</key> | ||||
|     \\                            <value>550620852718:admin-nucman</value> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <key>aws:cloudformation:logical-id</key> | ||||
|     \\                            <value>Instance</value> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <key>aws:cloud9:environment</key> | ||||
|     \\                            <value>1994a98a468e445ba4b5ad91dd93475b</value> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <key>aws:cloudformation:stack-name</key> | ||||
|     \\                            <value>aws-cloud9-bar-1994a98a468e445ba4b5ad91dd93475b</value> | ||||
|     \\                        </item> | ||||
|     \\                    </tagSet> | ||||
|     \\                    <hypervisor>xen</hypervisor> | ||||
|     \\                    <networkInterfaceSet> | ||||
|     \\                        <item> | ||||
|     \\                            <networkInterfaceId>eni-0dd0360c91184a496</networkInterfaceId> | ||||
|     \\                            <subnetId>subnet-1f3f4278</subnetId> | ||||
|     \\                            <vpcId>vpc-dc2402bb</vpcId> | ||||
|     \\                            <description/> | ||||
|     \\                            <ownerId>550620852718</ownerId> | ||||
|     \\                            <status>in-use</status> | ||||
|     \\                            <macAddress>02:84:8b:39:43:7f</macAddress> | ||||
|     \\                            <privateIpAddress>172.31.27.214</privateIpAddress> | ||||
|     \\                            <privateDnsName>ip-172-31-27-214.us-west-2.compute.internal</privateDnsName> | ||||
|     \\                            <sourceDestCheck>true</sourceDestCheck> | ||||
|     \\                            <groupSet> | ||||
|     \\                                <item> | ||||
|     \\                                    <groupId>sg-05466b60f1a4ee056</groupId> | ||||
|     \\                                    <groupName>aws-cloud9-bar-1994a98a468e445ba4b5ad91dd93475b-InstanceSecurityGroup-7FL7ACV848S1</groupName> | ||||
|     \\                                </item> | ||||
|     \\                            </groupSet> | ||||
|     \\                            <attachment> | ||||
|     \\                                <attachmentId>eni-attach-00d320672fdf08bc2</attachmentId> | ||||
|     \\                                <deviceIndex>0</deviceIndex> | ||||
|     \\                                <status>attached</status> | ||||
|     \\                                <attachTime>2021-07-16T18:54:31.000Z</attachTime> | ||||
|     \\                                <deleteOnTermination>true</deleteOnTermination> | ||||
|     \\                                <networkCardIndex>0</networkCardIndex> | ||||
|     \\                            </attachment> | ||||
|     \\                            <privateIpAddressesSet> | ||||
|     \\                                <item> | ||||
|     \\                                    <privateIpAddress>172.31.27.214</privateIpAddress> | ||||
|     \\                                    <privateDnsName>ip-172-31-27-214.us-west-2.compute.internal</privateDnsName> | ||||
|     \\                                    <primary>true</primary> | ||||
|     \\                                </item> | ||||
|     \\                            </privateIpAddressesSet> | ||||
|     \\                            <ipv6AddressesSet/> | ||||
|     \\                            <interfaceType>interface</interfaceType> | ||||
|     \\                        </item> | ||||
|     \\                    </networkInterfaceSet> | ||||
|     \\                    <ebsOptimized>false</ebsOptimized> | ||||
|     \\                    <enaSupport>true</enaSupport> | ||||
|     \\                    <cpuOptions> | ||||
|     \\                        <coreCount>1</coreCount> | ||||
|     \\                        <threadsPerCore>2</threadsPerCore> | ||||
|     \\                    </cpuOptions> | ||||
|     \\                    <capacityReservationSpecification> | ||||
|     \\                        <capacityReservationPreference>open</capacityReservationPreference> | ||||
|     \\                    </capacityReservationSpecification> | ||||
|     \\                    <hibernationOptions> | ||||
|     \\                        <configured>false</configured> | ||||
|     \\                    </hibernationOptions> | ||||
|     \\                    <enclaveOptions> | ||||
|     \\                        <enabled>false</enabled> | ||||
|     \\                    </enclaveOptions> | ||||
|     \\                    <metadataOptions> | ||||
|     \\                        <state>applied</state> | ||||
|     \\                        <httpTokens>optional</httpTokens> | ||||
|     \\                        <httpPutResponseHopLimit>1</httpPutResponseHopLimit> | ||||
|     \\                        <httpEndpoint>enabled</httpEndpoint> | ||||
|     \\                    </metadataOptions> | ||||
|     \\                    <privateDnsNameOptions/> | ||||
|     \\                </item> | ||||
|     \\            </instancesSet> | ||||
|     \\            <requesterId>658754138699</requesterId> | ||||
|     \\        </item> | ||||
|     \\        <item> | ||||
|     \\            <reservationId>r-099bdc75a5ab8419b</reservationId> | ||||
|     \\            <ownerId>550620852718</ownerId> | ||||
|     \\            <groupSet/> | ||||
|     \\            <instancesSet> | ||||
|     \\                <item> | ||||
|     \\                    <instanceId>i-08a2569c63ff8e87e</instanceId> | ||||
|     \\                    <imageId>ami-067d3e2d50855f7d6</imageId> | ||||
|     \\                    <instanceState> | ||||
|     \\                        <code>16</code> | ||||
|     \\                        <name>running</name> | ||||
|     \\                    </instanceState> | ||||
|     \\                    <privateDnsName>ip-172-31-48-70.us-west-2.compute.internal</privateDnsName> | ||||
|     \\                    <dnsName>ec2-34-222-191-26.us-west-2.compute.amazonaws.com</dnsName> | ||||
|     \\                    <reason/> | ||||
|     \\                    <keyName>_gpg</keyName> | ||||
|     \\                    <amiLaunchIndex>0</amiLaunchIndex> | ||||
|     \\                    <productCodes/> | ||||
|     \\                    <instanceType>t3.xlarge</instanceType> | ||||
|     \\                    <launchTime>2021-06-21T17:43:54.000Z</launchTime> | ||||
|     \\                    <placement> | ||||
|     \\                        <availabilityZone>us-west-2d</availabilityZone> | ||||
|     \\                        <groupName/> | ||||
|     \\                        <tenancy>default</tenancy> | ||||
|     \\                    </placement> | ||||
|     \\                    <platform>windows</platform> | ||||
|     \\                    <monitoring> | ||||
|     \\                        <state>disabled</state> | ||||
|     \\                    </monitoring> | ||||
|     \\                    <subnetId>subnet-7ba0e853</subnetId> | ||||
|     \\                    <vpcId>vpc-dc2402bb</vpcId> | ||||
|     \\                    <privateIpAddress>172.31.48.70</privateIpAddress> | ||||
|     \\                    <ipAddress>34.222.191.26</ipAddress> | ||||
|     \\                    <sourceDestCheck>true</sourceDestCheck> | ||||
|     \\                    <groupSet> | ||||
|     \\                        <item> | ||||
|     \\                            <groupId>sg-00ffec335f6c35ce3</groupId> | ||||
|     \\                            <groupName>rdp</groupName> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <groupId>sg-0aab58c6b2bde2105</groupId> | ||||
|     \\                            <groupName>outbound</groupName> | ||||
|     \\                        </item> | ||||
|     \\                    </groupSet> | ||||
|     \\                    <architecture>x86_64</architecture> | ||||
|     \\                    <rootDeviceType>ebs</rootDeviceType> | ||||
|     \\                    <rootDeviceName>/dev/sda1</rootDeviceName> | ||||
|     \\                    <blockDeviceMapping> | ||||
|     \\                        <item> | ||||
|     \\                            <deviceName>/dev/sda1</deviceName> | ||||
|     \\                            <ebs> | ||||
|     \\                                <volumeId>vol-0b2093ed03d0fba04</volumeId> | ||||
|     \\                                <status>attached</status> | ||||
|     \\                                <attachTime>2021-06-21T17:43:55.000Z</attachTime> | ||||
|     \\                                <deleteOnTermination>true</deleteOnTermination> | ||||
|     \\                            </ebs> | ||||
|     \\                        </item> | ||||
|     \\                    </blockDeviceMapping> | ||||
|     \\                    <virtualizationType>hvm</virtualizationType> | ||||
|     \\                    <clientToken/> | ||||
|     \\                    <tagSet> | ||||
|     \\                        <item> | ||||
|     \\                            <key>Name</key> | ||||
|     \\                            <value>VS/Windows Dev</value> | ||||
|     \\                        </item> | ||||
|     \\                        <item> | ||||
|     \\                            <key>lerchdev</key> | ||||
|     \\                            <value>vs</value> | ||||
|     \\                        </item> | ||||
|     \\                    </tagSet> | ||||
|     \\                    <hypervisor>xen</hypervisor> | ||||
|     \\                    <networkInterfaceSet> | ||||
|     \\                        <item> | ||||
|     \\                            <networkInterfaceId>eni-0e46f76afdab4ea78</networkInterfaceId> | ||||
|     \\                            <subnetId>subnet-7ba0e853</subnetId> | ||||
|     \\                            <vpcId>vpc-dc2402bb</vpcId> | ||||
|     \\                            <description/> | ||||
|     \\                            <ownerId>550620852718</ownerId> | ||||
|     \\                            <status>in-use</status> | ||||
|     \\                            <macAddress>0e:5c:d9:6d:fa:01</macAddress> | ||||
|     \\                            <privateIpAddress>172.31.48.70</privateIpAddress> | ||||
|     \\                            <privateDnsName>ip-172-31-48-70.us-west-2.compute.internal</privateDnsName> | ||||
|     \\                            <sourceDestCheck>true</sourceDestCheck> | ||||
|     \\                            <groupSet> | ||||
|     \\                                <item> | ||||
|     \\                                    <groupId>sg-00ffec335f6c35ce3</groupId> | ||||
|     \\                                    <groupName>rdp</groupName> | ||||
|     \\                                </item> | ||||
|     \\                                <item> | ||||
|     \\                                    <groupId>sg-0aab58c6b2bde2105</groupId> | ||||
|     \\                                    <groupName>outbound</groupName> | ||||
|     \\                                </item> | ||||
|     \\                            </groupSet> | ||||
|     \\                            <attachment> | ||||
|     \\                                <attachmentId>eni-attach-0a793e5c67bf91ad3</attachmentId> | ||||
|     \\                                <deviceIndex>0</deviceIndex> | ||||
|     \\                                <status>attached</status> | ||||
|     \\                                <attachTime>2021-06-21T17:43:54.000Z</attachTime> | ||||
|     \\                                <deleteOnTermination>true</deleteOnTermination> | ||||
|     \\                                <networkCardIndex>0</networkCardIndex> | ||||
|     \\                            </attachment> | ||||
|     \\                            <association> | ||||
|     \\                                <publicIp>34.222.191.26</publicIp> | ||||
|     \\                                <publicDnsName>ec2-34-222-191-26.us-west-2.compute.amazonaws.com</publicDnsName> | ||||
|     \\                                <ipOwnerId>amazon</ipOwnerId> | ||||
|     \\                            </association> | ||||
|     \\                            <privateIpAddressesSet> | ||||
|     \\                                <item> | ||||
|     \\                                    <privateIpAddress>172.31.48.70</privateIpAddress> | ||||
|     \\                                    <privateDnsName>ip-172-31-48-70.us-west-2.compute.internal</privateDnsName> | ||||
|     \\                                    <primary>true</primary> | ||||
|     \\                                    <association> | ||||
|     \\                                    <publicIp>34.222.191.26</publicIp> | ||||
|     \\                                    <publicDnsName>ec2-34-222-191-26.us-west-2.compute.amazonaws.com</publicDnsName> | ||||
|     \\                                    <ipOwnerId>amazon</ipOwnerId> | ||||
|     \\                                    </association> | ||||
|     \\                                </item> | ||||
|     \\                            </privateIpAddressesSet> | ||||
|     \\                            <ipv6AddressesSet/> | ||||
|     \\                            <interfaceType>interface</interfaceType> | ||||
|     \\                        </item> | ||||
|     \\                    </networkInterfaceSet> | ||||
|     \\                    <iamInstanceProfile> | ||||
|     \\                        <arn>arn:aws:iam::550620852718:instance-profile/ec2-dev</arn> | ||||
|     \\                        <id>AIPAYAM4POHXCFNKZ7HU2</id> | ||||
|     \\                    </iamInstanceProfile> | ||||
|     \\                    <ebsOptimized>true</ebsOptimized> | ||||
|     \\                    <enaSupport>true</enaSupport> | ||||
|     \\                    <cpuOptions> | ||||
|     \\                        <coreCount>2</coreCount> | ||||
|     \\                        <threadsPerCore>2</threadsPerCore> | ||||
|     \\                    </cpuOptions> | ||||
|     \\                    <capacityReservationSpecification> | ||||
|     \\                        <capacityReservationPreference>open</capacityReservationPreference> | ||||
|     \\                    </capacityReservationSpecification> | ||||
|     \\                    <hibernationOptions> | ||||
|     \\                        <configured>true</configured> | ||||
|     \\                    </hibernationOptions> | ||||
|     \\                    <enclaveOptions> | ||||
|     \\                        <enabled>false</enabled> | ||||
|     \\                    </enclaveOptions> | ||||
|     \\                    <metadataOptions> | ||||
|     \\                        <state>applied</state> | ||||
|     \\                        <httpTokens>optional</httpTokens> | ||||
|     \\                        <httpPutResponseHopLimit>1</httpPutResponseHopLimit> | ||||
|     \\                        <httpEndpoint>enabled</httpEndpoint> | ||||
|     \\                    </metadataOptions> | ||||
|     \\                    <privateDnsNameOptions/> | ||||
|     \\                </item> | ||||
|     \\            </instancesSet> | ||||
|     \\        </item> | ||||
|     \\    </reservationSet> | ||||
|     \\</DescribeInstancesResponse> | ||||
| ; | ||||
							
								
								
									
										119775
									
								
								src/llvm-ir.log
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										119775
									
								
								src/llvm-ir.log
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										672
									
								
								src/xml.zig
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										672
									
								
								src/xml.zig
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,672 @@ | |||
| const std = @import("std"); | ||||
| const mem = std.mem; | ||||
| const testing = std.testing; | ||||
| const Allocator = mem.Allocator; | ||||
| const ArenaAllocator = std.heap.ArenaAllocator; | ||||
| const ArrayList = std.ArrayList; | ||||
| 
 | ||||
| pub const Attribute = struct { | ||||
|     name: []const u8, | ||||
|     value: []const u8, | ||||
| }; | ||||
| 
 | ||||
| pub const Content = union(enum) { | ||||
|     CharData: []const u8, | ||||
|     Comment: []const u8, | ||||
|     Element: *Element, | ||||
| }; | ||||
| 
 | ||||
| pub const Element = struct { | ||||
|     pub const AttributeList = ArrayList(*Attribute); | ||||
|     pub const ContentList = ArrayList(Content); | ||||
| 
 | ||||
|     tag: []const u8, | ||||
|     attributes: AttributeList, | ||||
|     children: ContentList, | ||||
| 
 | ||||
|     fn init(tag: []const u8, alloc: *Allocator) Element { | ||||
|         return .{ | ||||
|             .tag = tag, | ||||
|             .attributes = AttributeList.init(alloc), | ||||
|             .children = ContentList.init(alloc), | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     pub fn getAttribute(self: *Element, attrib_name: []const u8) ?[]const u8 { | ||||
|         for (self.attributes.items) |child| { | ||||
|             if (mem.eql(u8, child.name, attrib_name)) { | ||||
|                 return child.value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     pub fn getCharData(self: *Element, child_tag: []const u8) ?[]const u8 { | ||||
|         const child = self.findChildByTag(child_tag) orelse return null; | ||||
|         if (child.children.items.len != 1) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return switch (child.children.items[0]) { | ||||
|             .CharData => |char_data| char_data, | ||||
|             else => null, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     pub fn iterator(self: *Element) ChildIterator { | ||||
|         return .{ | ||||
|             .items = self.children.items, | ||||
|             .i = 0, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     pub fn elements(self: *Element) ChildElementIterator { | ||||
|         return .{ | ||||
|             .inner = self.iterator(), | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     pub fn findChildByTag(self: *Element, tag: []const u8) !?*Element { | ||||
|         return try self.findChildrenByTag(tag).next(); | ||||
|     } | ||||
| 
 | ||||
|     pub fn findChildrenByTag(self: *Element, tag: []const u8) FindChildrenByTagIterator { | ||||
|         return .{ | ||||
|             .inner = self.elements(), | ||||
|             .tag = tag, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     pub const ChildIterator = struct { | ||||
|         items: []Content, | ||||
|         i: usize, | ||||
| 
 | ||||
|         pub fn next(self: *ChildIterator) ?*Content { | ||||
|             if (self.i < self.items.len) { | ||||
|                 self.i += 1; | ||||
|                 return &self.items[self.i - 1]; | ||||
|             } | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     pub const ChildElementIterator = struct { | ||||
|         inner: ChildIterator, | ||||
| 
 | ||||
|         pub fn next(self: *ChildElementIterator) ?*Element { | ||||
|             while (self.inner.next()) |child| { | ||||
|                 if (child.* != .Element) { | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 return child.*.Element; | ||||
|             } | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     fn strictEqual(a: []const u8, b: []const u8, _: PredicateOptions) !bool { | ||||
|         return mem.eql(u8, a, b); | ||||
|     } | ||||
|     pub const FindChildrenByTagIterator = struct { | ||||
|         inner: ChildElementIterator, | ||||
|         tag: []const u8, | ||||
|         predicate: fn (a: []const u8, b: []const u8, options: PredicateOptions) anyerror!bool = strictEqual, | ||||
|         predicate_options: PredicateOptions = .{}, | ||||
| 
 | ||||
|         pub fn next(self: *FindChildrenByTagIterator) !?*Element { | ||||
|             while (self.inner.next()) |child| { | ||||
|                 if (!try self.predicate(child.tag, self.tag, self.predicate_options)) { | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 return child; | ||||
|             } | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| pub const PredicateOptions = struct { | ||||
|     allocator: ?*std.mem.Allocator = null, | ||||
| }; | ||||
| pub const XmlDecl = struct { | ||||
|     version: []const u8, | ||||
|     encoding: ?[]const u8, | ||||
|     standalone: ?bool, | ||||
| }; | ||||
| 
 | ||||
| pub const Document = struct { | ||||
|     arena: ArenaAllocator, | ||||
|     xml_decl: ?*XmlDecl, | ||||
|     root: *Element, | ||||
| 
 | ||||
|     pub fn deinit(self: Document) void { | ||||
|         var arena = self.arena; // Copy to stack so self can be taken by value. | ||||
|         arena.deinit(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| const ParseContext = struct { | ||||
|     source: []const u8, | ||||
|     offset: usize, | ||||
|     line: usize, | ||||
|     column: usize, | ||||
| 
 | ||||
|     fn init(source: []const u8) ParseContext { | ||||
|         return .{ | ||||
|             .source = source, | ||||
|             .offset = 0, | ||||
|             .line = 0, | ||||
|             .column = 0, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     fn peek(self: *ParseContext) ?u8 { | ||||
|         return if (self.offset < self.source.len) self.source[self.offset] else null; | ||||
|     } | ||||
| 
 | ||||
|     fn consume(self: *ParseContext) !u8 { | ||||
|         if (self.offset < self.source.len) { | ||||
|             return self.consumeNoEof(); | ||||
|         } | ||||
| 
 | ||||
|         return error.UnexpectedEof; | ||||
|     } | ||||
| 
 | ||||
|     fn consumeNoEof(self: *ParseContext) u8 { | ||||
|         std.debug.assert(self.offset < self.source.len); | ||||
|         const c = self.source[self.offset]; | ||||
|         self.offset += 1; | ||||
| 
 | ||||
|         if (c == '\n') { | ||||
|             self.line += 1; | ||||
|             self.column = 0; | ||||
|         } else { | ||||
|             self.column += 1; | ||||
|         } | ||||
| 
 | ||||
|         return c; | ||||
|     } | ||||
| 
 | ||||
|     fn eat(self: *ParseContext, char: u8) bool { | ||||
|         self.expect(char) catch return false; | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     fn expect(self: *ParseContext, expected: u8) !void { | ||||
|         if (self.peek()) |actual| { | ||||
|             if (expected != actual) { | ||||
|                 return error.UnexpectedCharacter; | ||||
|             } | ||||
| 
 | ||||
|             _ = self.consumeNoEof(); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         return error.UnexpectedEof; | ||||
|     } | ||||
| 
 | ||||
|     fn eatStr(self: *ParseContext, text: []const u8) bool { | ||||
|         self.expectStr(text) catch return false; | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     fn expectStr(self: *ParseContext, text: []const u8) !void { | ||||
|         if (self.source.len < self.offset + text.len) { | ||||
|             return error.UnexpectedEof; | ||||
|         } else if (std.mem.startsWith(u8, self.source[self.offset..], text)) { | ||||
|             var i: usize = 0; | ||||
|             while (i < text.len) : (i += 1) { | ||||
|                 _ = self.consumeNoEof(); | ||||
|             } | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         return error.UnexpectedCharacter; | ||||
|     } | ||||
| 
 | ||||
|     fn eatWs(self: *ParseContext) bool { | ||||
|         var ws = false; | ||||
| 
 | ||||
|         while (self.peek()) |ch| { | ||||
|             switch (ch) { | ||||
|                 ' ', '\t', '\n', '\r' => { | ||||
|                     ws = true; | ||||
|                     _ = self.consumeNoEof(); | ||||
|                 }, | ||||
|                 else => break, | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return ws; | ||||
|     } | ||||
| 
 | ||||
|     fn expectWs(self: *ParseContext) !void { | ||||
|         if (!self.eatWs()) return error.UnexpectedCharacter; | ||||
|     } | ||||
| 
 | ||||
|     fn currentLine(self: ParseContext) []const u8 { | ||||
|         var begin: usize = 0; | ||||
|         if (mem.lastIndexOfScalar(u8, self.source[0..self.offset], '\n')) |prev_nl| { | ||||
|             begin = prev_nl + 1; | ||||
|         } | ||||
| 
 | ||||
|         var end = mem.indexOfScalarPos(u8, self.source, self.offset, '\n') orelse self.source.len; | ||||
|         return self.source[begin..end]; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| test "ParseContext" { | ||||
|     { | ||||
|         var ctx = ParseContext.init("I like pythons"); | ||||
|         try testing.expectEqual(@as(?u8, 'I'), ctx.peek()); | ||||
|         try testing.expectEqual(@as(u8, 'I'), ctx.consumeNoEof()); | ||||
|         try testing.expectEqual(@as(?u8, ' '), ctx.peek()); | ||||
|         try testing.expectEqual(@as(u8, ' '), try ctx.consume()); | ||||
| 
 | ||||
|         try testing.expect(ctx.eat('l')); | ||||
|         try testing.expectEqual(@as(?u8, 'i'), ctx.peek()); | ||||
|         try testing.expectEqual(false, ctx.eat('a')); | ||||
|         try testing.expectEqual(@as(?u8, 'i'), ctx.peek()); | ||||
| 
 | ||||
|         try ctx.expect('i'); | ||||
|         try testing.expectEqual(@as(?u8, 'k'), ctx.peek()); | ||||
|         try testing.expectError(error.UnexpectedCharacter, ctx.expect('a')); | ||||
|         try testing.expectEqual(@as(?u8, 'k'), ctx.peek()); | ||||
| 
 | ||||
|         try testing.expect(ctx.eatStr("ke")); | ||||
|         try testing.expectEqual(@as(?u8, ' '), ctx.peek()); | ||||
| 
 | ||||
|         try testing.expect(ctx.eatWs()); | ||||
|         try testing.expectEqual(@as(?u8, 'p'), ctx.peek()); | ||||
|         try testing.expectEqual(false, ctx.eatWs()); | ||||
|         try testing.expectEqual(@as(?u8, 'p'), ctx.peek()); | ||||
| 
 | ||||
|         try testing.expectEqual(false, ctx.eatStr("aaaaaaaaa")); | ||||
|         try testing.expectEqual(@as(?u8, 'p'), ctx.peek()); | ||||
| 
 | ||||
|         try testing.expectError(error.UnexpectedEof, ctx.expectStr("aaaaaaaaa")); | ||||
|         try testing.expectEqual(@as(?u8, 'p'), ctx.peek()); | ||||
|         try testing.expectError(error.UnexpectedCharacter, ctx.expectStr("pytn")); | ||||
|         try testing.expectEqual(@as(?u8, 'p'), ctx.peek()); | ||||
|         try ctx.expectStr("python"); | ||||
|         try testing.expectEqual(@as(?u8, 's'), ctx.peek()); | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         var ctx = ParseContext.init(""); | ||||
|         try testing.expectEqual(ctx.peek(), null); | ||||
|         try testing.expectError(error.UnexpectedEof, ctx.consume()); | ||||
|         try testing.expectEqual(ctx.eat('p'), false); | ||||
|         try testing.expectError(error.UnexpectedEof, ctx.expect('p')); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub const ParseError = error{ | ||||
|     IllegalCharacter, | ||||
|     UnexpectedEof, | ||||
|     UnexpectedCharacter, | ||||
|     UnclosedValue, | ||||
|     UnclosedComment, | ||||
|     InvalidName, | ||||
|     InvalidEntity, | ||||
|     InvalidStandaloneValue, | ||||
|     NonMatchingClosingTag, | ||||
|     InvalidDocument, | ||||
|     OutOfMemory, | ||||
| }; | ||||
| 
 | ||||
| pub fn parse(backing_allocator: *Allocator, source: []const u8) !Document { | ||||
|     var ctx = ParseContext.init(source); | ||||
|     return try parseDocument(&ctx, backing_allocator); | ||||
| } | ||||
| 
 | ||||
| fn parseDocument(ctx: *ParseContext, backing_allocator: *Allocator) !Document { | ||||
|     var doc = Document{ | ||||
|         .arena = ArenaAllocator.init(backing_allocator), | ||||
|         .xml_decl = null, | ||||
|         .root = undefined, | ||||
|     }; | ||||
| 
 | ||||
|     errdefer doc.deinit(); | ||||
| 
 | ||||
|     try trySkipComments(ctx, &doc.arena.allocator); | ||||
| 
 | ||||
|     doc.xml_decl = try tryParseProlog(ctx, &doc.arena.allocator); | ||||
|     _ = ctx.eatWs(); | ||||
|     try trySkipComments(ctx, &doc.arena.allocator); | ||||
| 
 | ||||
|     doc.root = (try tryParseElement(ctx, &doc.arena.allocator)) orelse return error.InvalidDocument; | ||||
|     _ = ctx.eatWs(); | ||||
|     try trySkipComments(ctx, &doc.arena.allocator); | ||||
| 
 | ||||
|     if (ctx.peek() != null) return error.InvalidDocument; | ||||
| 
 | ||||
|     return doc; | ||||
| } | ||||
| 
 | ||||
| fn parseAttrValue(ctx: *ParseContext, alloc: *Allocator) ![]const u8 { | ||||
|     const quote = try ctx.consume(); | ||||
|     if (quote != '"' and quote != '\'') return error.UnexpectedCharacter; | ||||
| 
 | ||||
|     const begin = ctx.offset; | ||||
| 
 | ||||
|     while (true) { | ||||
|         const c = ctx.consume() catch return error.UnclosedValue; | ||||
|         if (c == quote) break; | ||||
|     } | ||||
| 
 | ||||
|     const end = ctx.offset - 1; | ||||
| 
 | ||||
|     return try dupeAndUnescape(alloc, ctx.source[begin..end]); | ||||
| } | ||||
| 
 | ||||
| fn parseEqAttrValue(ctx: *ParseContext, alloc: *Allocator) ![]const u8 { | ||||
|     _ = ctx.eatWs(); | ||||
|     try ctx.expect('='); | ||||
|     _ = ctx.eatWs(); | ||||
| 
 | ||||
|     return try parseAttrValue(ctx, alloc); | ||||
| } | ||||
| 
 | ||||
| fn parseNameNoDupe(ctx: *ParseContext) ![]const u8 { | ||||
|     // XML's spec on names is very long, so to make this easier | ||||
|     // we just take any character that is not special and not whitespace | ||||
|     const begin = ctx.offset; | ||||
| 
 | ||||
|     while (ctx.peek()) |ch| { | ||||
|         switch (ch) { | ||||
|             ' ', '\t', '\n', '\r' => break, | ||||
|             '&', '"', '\'', '<', '>', '?', '=', '/' => break, | ||||
|             else => _ = ctx.consumeNoEof(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const end = ctx.offset; | ||||
|     if (begin == end) return error.InvalidName; | ||||
| 
 | ||||
|     return ctx.source[begin..end]; | ||||
| } | ||||
| 
 | ||||
| fn tryParseCharData(ctx: *ParseContext, alloc: *Allocator) !?[]const u8 { | ||||
|     const begin = ctx.offset; | ||||
| 
 | ||||
|     while (ctx.peek()) |ch| { | ||||
|         switch (ch) { | ||||
|             '<' => break, | ||||
|             else => _ = ctx.consumeNoEof(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const end = ctx.offset; | ||||
|     if (begin == end) return null; | ||||
| 
 | ||||
|     return try dupeAndUnescape(alloc, ctx.source[begin..end]); | ||||
| } | ||||
| 
 | ||||
| fn parseContent(ctx: *ParseContext, alloc: *Allocator) ParseError!Content { | ||||
|     if (try tryParseCharData(ctx, alloc)) |cd| { | ||||
|         return Content{ .CharData = cd }; | ||||
|     } else if (try tryParseComment(ctx, alloc)) |comment| { | ||||
|         return Content{ .Comment = comment }; | ||||
|     } else if (try tryParseElement(ctx, alloc)) |elem| { | ||||
|         return Content{ .Element = elem }; | ||||
|     } else { | ||||
|         return error.UnexpectedCharacter; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn tryParseAttr(ctx: *ParseContext, alloc: *Allocator) !?*Attribute { | ||||
|     const name = parseNameNoDupe(ctx) catch return null; | ||||
|     _ = ctx.eatWs(); | ||||
|     try ctx.expect('='); | ||||
|     _ = ctx.eatWs(); | ||||
|     const value = try parseAttrValue(ctx, alloc); | ||||
| 
 | ||||
|     const attr = try alloc.create(Attribute); | ||||
|     attr.name = try mem.dupe(alloc, u8, name); | ||||
|     attr.value = value; | ||||
|     return attr; | ||||
| } | ||||
| 
 | ||||
| fn tryParseElement(ctx: *ParseContext, alloc: *Allocator) !?*Element { | ||||
|     const start = ctx.offset; | ||||
|     if (!ctx.eat('<')) return null; | ||||
|     const tag = parseNameNoDupe(ctx) catch { | ||||
|         ctx.offset = start; | ||||
|         return null; | ||||
|     }; | ||||
| 
 | ||||
|     const element = try alloc.create(Element); | ||||
|     element.* = Element.init(try std.mem.dupe(alloc, u8, tag), alloc); | ||||
| 
 | ||||
|     while (ctx.eatWs()) { | ||||
|         const attr = (try tryParseAttr(ctx, alloc)) orelse break; | ||||
|         try element.attributes.append(attr); | ||||
|     } | ||||
| 
 | ||||
|     if (ctx.eatStr("/>")) { | ||||
|         return element; | ||||
|     } | ||||
| 
 | ||||
|     try ctx.expect('>'); | ||||
| 
 | ||||
|     while (true) { | ||||
|         if (ctx.peek() == null) { | ||||
|             return error.UnexpectedEof; | ||||
|         } else if (ctx.eatStr("</")) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         const content = try parseContent(ctx, alloc); | ||||
|         try element.children.append(content); | ||||
|     } | ||||
| 
 | ||||
|     const closing_tag = try parseNameNoDupe(ctx); | ||||
|     if (!std.mem.eql(u8, tag, closing_tag)) { | ||||
|         return error.NonMatchingClosingTag; | ||||
|     } | ||||
| 
 | ||||
|     _ = ctx.eatWs(); | ||||
|     try ctx.expect('>'); | ||||
|     return element; | ||||
| } | ||||
| 
 | ||||
| test "tryParseElement" { | ||||
|     var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||||
|     defer arena.deinit(); | ||||
|     var alloc = &arena.allocator; | ||||
| 
 | ||||
|     { | ||||
|         var ctx = ParseContext.init("<= a='b'/>"); | ||||
|         try testing.expectEqual(@as(?*Element, null), try tryParseElement(&ctx, alloc)); | ||||
|         try testing.expectEqual(@as(?u8, '<'), ctx.peek()); | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         var ctx = ParseContext.init("<python size='15' color = \"green\"/>"); | ||||
|         const elem = try tryParseElement(&ctx, alloc); | ||||
|         try testing.expectEqualSlices(u8, elem.?.tag, "python"); | ||||
| 
 | ||||
|         const size_attr = elem.?.attributes.items[0]; | ||||
|         try testing.expectEqualSlices(u8, size_attr.name, "size"); | ||||
|         try testing.expectEqualSlices(u8, size_attr.value, "15"); | ||||
| 
 | ||||
|         const color_attr = elem.?.attributes.items[1]; | ||||
|         try testing.expectEqualSlices(u8, color_attr.name, "color"); | ||||
|         try testing.expectEqualSlices(u8, color_attr.value, "green"); | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         var ctx = ParseContext.init("<python>test</python>"); | ||||
|         const elem = try tryParseElement(&ctx, alloc); | ||||
|         try testing.expectEqualSlices(u8, elem.?.tag, "python"); | ||||
|         try testing.expectEqualSlices(u8, elem.?.children.items[0].CharData, "test"); | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         var ctx = ParseContext.init("<a>b<c/>d<e/>f<!--g--></a>"); | ||||
|         const elem = try tryParseElement(&ctx, alloc); | ||||
|         try testing.expectEqualSlices(u8, elem.?.tag, "a"); | ||||
|         try testing.expectEqualSlices(u8, elem.?.children.items[0].CharData, "b"); | ||||
|         try testing.expectEqualSlices(u8, elem.?.children.items[1].Element.tag, "c"); | ||||
|         try testing.expectEqualSlices(u8, elem.?.children.items[2].CharData, "d"); | ||||
|         try testing.expectEqualSlices(u8, elem.?.children.items[3].Element.tag, "e"); | ||||
|         try testing.expectEqualSlices(u8, elem.?.children.items[4].CharData, "f"); | ||||
|         try testing.expectEqualSlices(u8, elem.?.children.items[5].Comment, "g"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn tryParseProlog(ctx: *ParseContext, alloc: *Allocator) !?*XmlDecl { | ||||
|     const start = ctx.offset; | ||||
|     if (!ctx.eatStr("<?") or !mem.eql(u8, try parseNameNoDupe(ctx), "xml")) { | ||||
|         ctx.offset = start; | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     const decl = try alloc.create(XmlDecl); | ||||
|     decl.encoding = null; | ||||
|     decl.standalone = null; | ||||
| 
 | ||||
|     // Version info is mandatory | ||||
|     try ctx.expectWs(); | ||||
|     try ctx.expectStr("version"); | ||||
|     decl.version = try parseEqAttrValue(ctx, alloc); | ||||
| 
 | ||||
|     if (ctx.eatWs()) { | ||||
|         // Optional encoding and standalone info | ||||
|         var require_ws = false; | ||||
| 
 | ||||
|         if (ctx.eatStr("encoding")) { | ||||
|             decl.encoding = try parseEqAttrValue(ctx, alloc); | ||||
|             require_ws = true; | ||||
|         } | ||||
| 
 | ||||
|         if (require_ws == ctx.eatWs() and ctx.eatStr("standalone")) { | ||||
|             const standalone = try parseEqAttrValue(ctx, alloc); | ||||
|             if (std.mem.eql(u8, standalone, "yes")) { | ||||
|                 decl.standalone = true; | ||||
|             } else if (std.mem.eql(u8, standalone, "no")) { | ||||
|                 decl.standalone = false; | ||||
|             } else { | ||||
|                 return error.InvalidStandaloneValue; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         _ = ctx.eatWs(); | ||||
|     } | ||||
| 
 | ||||
|     try ctx.expectStr("?>"); | ||||
|     return decl; | ||||
| } | ||||
| 
 | ||||
| test "tryParseProlog" { | ||||
|     var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||||
|     defer arena.deinit(); | ||||
|     var alloc = &arena.allocator; | ||||
| 
 | ||||
|     { | ||||
|         var ctx = ParseContext.init("<?xmla version='aa'?>"); | ||||
|         try testing.expectEqual(@as(?*XmlDecl, null), try tryParseProlog(&ctx, alloc)); | ||||
|         try testing.expectEqual(@as(?u8, '<'), ctx.peek()); | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         var ctx = ParseContext.init("<?xml version='aa'?>"); | ||||
|         const decl = try tryParseProlog(&ctx, alloc); | ||||
|         try testing.expectEqualSlices(u8, "aa", decl.?.version); | ||||
|         try testing.expectEqual(@as(?[]const u8, null), decl.?.encoding); | ||||
|         try testing.expectEqual(@as(?bool, null), decl.?.standalone); | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         var ctx = ParseContext.init("<?xml version=\"aa\" encoding = 'bbb' standalone   \t =   'yes'?>"); | ||||
|         const decl = try tryParseProlog(&ctx, alloc); | ||||
|         try testing.expectEqualSlices(u8, "aa", decl.?.version); | ||||
|         try testing.expectEqualSlices(u8, "bbb", decl.?.encoding.?); | ||||
|         try testing.expectEqual(@as(?bool, true), decl.?.standalone.?); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn trySkipComments(ctx: *ParseContext, alloc: *Allocator) !void { | ||||
|     while (try tryParseComment(ctx, alloc)) |_| { | ||||
|         _ = ctx.eatWs(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn tryParseComment(ctx: *ParseContext, alloc: *Allocator) !?[]const u8 { | ||||
|     if (!ctx.eatStr("<!--")) return null; | ||||
| 
 | ||||
|     const begin = ctx.offset; | ||||
|     while (!ctx.eatStr("-->")) { | ||||
|         _ = ctx.consume() catch return error.UnclosedComment; | ||||
|     } | ||||
| 
 | ||||
|     const end = ctx.offset - "-->".len; | ||||
|     return try mem.dupe(alloc, u8, ctx.source[begin..end]); | ||||
| } | ||||
| 
 | ||||
| fn unescapeEntity(text: []const u8) !u8 { | ||||
|     const EntitySubstition = struct { text: []const u8, replacement: u8 }; | ||||
| 
 | ||||
|     const entities = [_]EntitySubstition{ | ||||
|         .{ .text = "<", .replacement = '<' }, | ||||
|         .{ .text = ">", .replacement = '>' }, | ||||
|         .{ .text = "&", .replacement = '&' }, | ||||
|         .{ .text = "'", .replacement = '\'' }, | ||||
|         .{ .text = """, .replacement = '"' }, | ||||
|     }; | ||||
| 
 | ||||
|     for (entities) |entity| { | ||||
|         if (std.mem.eql(u8, text, entity.text)) return entity.replacement; | ||||
|     } | ||||
| 
 | ||||
|     return error.InvalidEntity; | ||||
| } | ||||
| 
 | ||||
| fn dupeAndUnescape(alloc: *Allocator, text: []const u8) ![]const u8 { | ||||
|     const str = try alloc.alloc(u8, text.len); | ||||
| 
 | ||||
|     var j: usize = 0; | ||||
|     var i: usize = 0; | ||||
|     while (i < text.len) : (j += 1) { | ||||
|         if (text[i] == '&') { | ||||
|             const entity_end = 1 + (mem.indexOfScalarPos(u8, text, i, ';') orelse return error.InvalidEntity); | ||||
|             str[j] = try unescapeEntity(text[i..entity_end]); | ||||
|             i = entity_end; | ||||
|         } else { | ||||
|             str[j] = text[i]; | ||||
|             i += 1; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return alloc.shrink(str, j); | ||||
| } | ||||
| 
 | ||||
| test "dupeAndUnescape" { | ||||
|     var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||||
|     defer arena.deinit(); | ||||
|     var alloc = &arena.allocator; | ||||
| 
 | ||||
|     try testing.expectEqualSlices(u8, "test", try dupeAndUnescape(alloc, "test")); | ||||
|     try testing.expectEqualSlices(u8, "a<b&c>d\"e'f<", try dupeAndUnescape(alloc, "a<b&c>d"e'f<")); | ||||
|     try testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&")); | ||||
|     try testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&&")); | ||||
|     try testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&test;")); | ||||
|     try testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&boa")); | ||||
| } | ||||
| 
 | ||||
| test "Top level comments" { | ||||
|     var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||||
|     defer arena.deinit(); | ||||
|     var alloc = &arena.allocator; | ||||
| 
 | ||||
|     const doc = try parse(alloc, "<?xml version='aa'?><!--comment--><python color='green'/><!--another comment-->"); | ||||
|     try testing.expectEqualSlices(u8, "python", doc.root.tag); | ||||
| } | ||||
							
								
								
									
										409
									
								
								src/xml_shaper.zig
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										409
									
								
								src/xml_shaper.zig
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,409 @@ | |||
| const std = @import("std"); | ||||
| const xml = @import("xml.zig"); | ||||
| 
 | ||||
| fn Parsed(comptime T: type) type { | ||||
|     return struct { | ||||
|         allocator: *std.mem.Allocator, | ||||
|         parsed_value: T, | ||||
| 
 | ||||
|         const Self = @This(); | ||||
| 
 | ||||
|         pub fn init(allocator: *std.mem.Allocator, parsedObj: T) Self { | ||||
|             return .{ | ||||
|                 .allocator = allocator, | ||||
|                 .parsed_value = parsedObj, | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         pub fn deinit(self: Self) void { | ||||
|             deinitObject(self.allocator, self.parsed_value); | ||||
|         } | ||||
| 
 | ||||
|         fn deinitObject(allocator: *std.mem.Allocator, obj: anytype) void { | ||||
|             switch (@typeInfo(@TypeOf(obj))) { | ||||
|                 .Optional => if (obj) |o| deinitObject(allocator, o), | ||||
|                 .Union => |union_info| { | ||||
|                     inline for (union_info.fields) |field| { | ||||
|                         std.debug.print("{s}", field); // need to find active field and deinit it | ||||
|                     } | ||||
|                 }, | ||||
|                 .Struct => |struct_info| { | ||||
|                     inline for (struct_info.fields) |field| { | ||||
|                         deinitObject(allocator, @field(obj, field.name)); | ||||
|                     } | ||||
|                 }, | ||||
|                 .Array => {}, // Not implemented below | ||||
|                 .Pointer => |ptr_info| { | ||||
|                     switch (ptr_info.size) { | ||||
|                         .One => { | ||||
|                             deinitObject(allocator, obj.*); | ||||
|                             allocator.free(obj); | ||||
|                         }, | ||||
|                         .Many => {}, | ||||
|                         .C => {}, | ||||
|                         .Slice => { | ||||
|                             allocator.free(obj); | ||||
|                         }, | ||||
|                     } | ||||
|                 }, | ||||
|                 //.Bool, .Float, .ComptimeFloat, .Int, .ComptimeInt, .Enum, .Opaque => {}, // no allocations here | ||||
|                 else => {}, | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| pub fn Parser(comptime T: type) type { | ||||
|     return struct { | ||||
|         ParseType: type = T, | ||||
|         ReturnType: type = Parsed(T), | ||||
| 
 | ||||
|         const Self = @This(); | ||||
| 
 | ||||
|         pub fn parse(source: []const u8, options: ParseOptions) !Parsed(T) { | ||||
|             if (options.allocator == null) | ||||
|                 return error.AllocatorRequired; // we are only leaving it be null for compatibility with json | ||||
|             const allocator = options.allocator.?; | ||||
|             const parse_allocator = std.heap.ArenaAllocator.init(allocator); | ||||
|             const parsed = try xml.parse(allocator, source); | ||||
|             defer parsed.deinit(); | ||||
|             defer parse_allocator.deinit(); | ||||
|             return Parsed(T).init(allocator, try parseInternal(T, parsed.root, options)); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| // should we just use json parse options? | ||||
| pub const ParseOptions = struct { | ||||
|     allocator: ?*std.mem.Allocator = null, | ||||
|     match_predicate: ?fn (a: []const u8, b: []const u8, options: xml.PredicateOptions) anyerror!bool = null, | ||||
| }; | ||||
| 
 | ||||
| pub fn parse(comptime T: type, source: []const u8, options: ParseOptions) !Parsed(T) { | ||||
|     if (options.allocator == null) | ||||
|         return error.AllocatorRequired; // we are only leaving it be null for compatibility with json | ||||
|     const allocator = options.allocator.?; | ||||
|     const parse_allocator = std.heap.ArenaAllocator.init(allocator); | ||||
|     const parsed = try xml.parse(allocator, source); | ||||
|     defer parsed.deinit(); | ||||
|     defer parse_allocator.deinit(); | ||||
|     return Parsed(T).init(allocator, try parseInternal(T, parsed.root, options)); | ||||
| } | ||||
| 
 | ||||
| fn parseInternal(comptime T: type, element: *xml.Element, options: ParseOptions) !T { | ||||
|     switch (@typeInfo(T)) { | ||||
|         .Bool => { | ||||
|             if (std.ascii.eqlIgnoreCase("true", element.children.items[0].CharData)) | ||||
|                 return true; | ||||
|             if (std.ascii.eqlIgnoreCase("false", element.children.items[0].CharData)) | ||||
|                 return false; | ||||
|             return error.UnexpectedToken; | ||||
|         }, | ||||
|         .Float, .ComptimeFloat => { | ||||
|             return try std.fmt.parseFloat(T, element.children.items[0].CharData); | ||||
|         }, | ||||
|         .Int, .ComptimeInt => { | ||||
|             return try std.fmt.parseInt(T, element.children.items[0].CharData, 10); | ||||
|         }, | ||||
|         .Optional => |optional_info| { | ||||
|             if (element.children.items.len == 0) { | ||||
|                 // This is almost certainly incomplete. Empty strings? xsi:nil? | ||||
|                 return null; | ||||
|             } else { | ||||
|                 // return try parseInternal(optional_info.child, element.elements().next().?, options); | ||||
|                 return try parseInternal(optional_info.child, element, options); | ||||
|             } | ||||
|         }, | ||||
|         .Enum => |enum_info| { | ||||
|             const numeric: ?enum_info.tag_type = std.fmt.parseInt(enumInfo.tag_type, element.children.items[0].CharData, 10) catch null; | ||||
|             if (numeric) |num| { | ||||
|                 return std.meta.intToEnum(T, num); | ||||
|             } else { | ||||
|                 // json parser handles escaping - could this happen here or does chardata handle? | ||||
|                 return std.meta.stringToEnum(T, element.CharData); | ||||
|             } | ||||
|         }, | ||||
|         .Union => |union_info| { | ||||
|             if (union_info.tag_type) |_| { | ||||
|                 // try each of the union fields until we find one that matches | ||||
|                 inline for (union_info.fields) |u_field| { | ||||
|                     // take a copy of tokens so we can withhold mutations until success | ||||
|                     var tokens_copy = tokens.*; | ||||
|                     if (parseInternal(u_field.field_type, token, &tokens_copy, options)) |value| { | ||||
|                         tokens.* = tokens_copy; | ||||
|                         return @unionInit(T, u_field.name, value); | ||||
|                     } else |err| { | ||||
|                         // Bubble up error.OutOfMemory | ||||
|                         // Parsing some types won't have OutOfMemory in their | ||||
|                         // error-sets, for the condition to be valid, merge it in. | ||||
|                         if (@as(@TypeOf(err) || error{OutOfMemory}, err) == error.OutOfMemory) return err; | ||||
|                         // Bubble up AllocatorRequired, as it indicates missing option | ||||
|                         if (@as(@TypeOf(err) || error{AllocatorRequired}, err) == error.AllocatorRequired) return err; | ||||
|                         // otherwise continue through the `inline for` | ||||
|                     } | ||||
|                 } | ||||
|                 return error.NoUnionMembersMatched; | ||||
|             } else { | ||||
|                 @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'"); | ||||
|             } | ||||
|         }, | ||||
|         .Struct => |struct_info| { | ||||
|             var r: T = undefined; | ||||
|             var fields_seen = [_]bool{false} ** struct_info.fields.len; | ||||
|             var fields_set: u64 = 0; | ||||
|             // errdefer { | ||||
|             //     // TODO: why so high here? This was needed for ec2 describe instances | ||||
|             //     @setEvalBranchQuota(100000); | ||||
|             //     inline for (struct_info.fields) |field, i| { | ||||
|             //         if (fields_seen[i] and !field.is_comptime) { | ||||
|             //             parseFree(field.field_type, @field(r, field.name), options); | ||||
|             //         } | ||||
|             //     } | ||||
|             // } | ||||
| 
 | ||||
|             // XML parser provides CharData for whitespace around elements. | ||||
|             // We shall ignore extra data for the moment as a performance thing | ||||
|             // if (element.children.items.len > struct_info.fields.len) { | ||||
|             //     std.debug.print("element children: {d}, struct fields: {d}\n", .{ element.children.items.len, struct_info.fields.len }); | ||||
|             //     for (element.children.items) |child, i| { | ||||
|             //         switch (child) { | ||||
|             //             .CharData => std.debug.print("{d}: {s}\n", .{ i, child }), | ||||
|             //             .Comment => {}, | ||||
|             //             .Element => {}, | ||||
|             //         } | ||||
|             //     } | ||||
|             //     return error.MoreElementsThanFields; | ||||
|             // } | ||||
| 
 | ||||
|             inline for (struct_info.fields) |field, i| { | ||||
|                 // std.debug.print("Field name: {s}, Element: {s}\n", .{ field.name, element.tag }); | ||||
|                 var iterator = element.findChildrenByTag(field.name); | ||||
|                 if (options.match_predicate) |predicate| { | ||||
|                     iterator.predicate = predicate; | ||||
|                     iterator.predicate_options = .{ .allocator = options.allocator.? }; | ||||
|                 } | ||||
|                 if (try iterator.next()) |child| { | ||||
|                     // I don't know that we would use comptime here. I'm also | ||||
|                     // not sure the nuance of setting this... | ||||
|                     // if (field.is_comptime) { | ||||
|                     //     if (!try parsesTo(field.field_type, field.default_value.?, tokens, options)) { | ||||
|                     //         return error.UnexpectedValue; | ||||
|                     //     } | ||||
|                     // } else { | ||||
|                     @field(r, field.name) = try parseInternal(field.field_type, child, options); | ||||
|                     fields_seen[i] = true; | ||||
|                     fields_set = fields_set + 1; | ||||
|                     // } | ||||
| 
 | ||||
|                 } else { | ||||
|                     return error.NoValueForField; | ||||
|                 } | ||||
|             } | ||||
|             if (fields_set != struct_info.fields.len) | ||||
|                 return error.FieldElementMismatch; // see fields_seen for details | ||||
|             return r; | ||||
|         }, | ||||
|         .Array => //|array_info| { | ||||
|         return error.ArrayNotImplemented, | ||||
|         // switch (token) { | ||||
|         //     .ArrayBegin => { | ||||
|         //         var r: T = undefined; | ||||
|         //         var i: usize = 0; | ||||
|         //         errdefer { | ||||
|         //             while (true) : (i -= 1) { | ||||
|         //                 parseFree(arrayInfo.child, r[i], options); | ||||
|         //                 if (i == 0) break; | ||||
|         //             } | ||||
|         //         } | ||||
|         //         while (i < r.len) : (i += 1) { | ||||
|         //             r[i] = try parse(arrayInfo.child, tokens, options); | ||||
|         //         } | ||||
|         //         const tok = (try tokens.next()) orelse return error.UnexpectedEndOfJson; | ||||
|         //         switch (tok) { | ||||
|         //             .ArrayEnd => {}, | ||||
|         //             else => return error.UnexpectedToken, | ||||
|         //         } | ||||
|         //         return r; | ||||
|         //     }, | ||||
|         //     .String => |stringToken| { | ||||
|         //         if (arrayInfo.child != u8) return error.UnexpectedToken; | ||||
|         //         var r: T = undefined; | ||||
|         //         const source_slice = stringToken.slice(tokens.slice, tokens.i - 1); | ||||
|         //         switch (stringToken.escapes) { | ||||
|         //             .None => mem.copy(u8, &r, source_slice), | ||||
|         //             .Some => try unescapeValidString(&r, source_slice), | ||||
|         //         } | ||||
|         //         return r; | ||||
|         //     }, | ||||
|         //     else => return error.UnexpectedToken, | ||||
|         // } | ||||
|         // }, | ||||
|         .Pointer => |ptr_info| { | ||||
|             const allocator = options.allocator orelse return error.AllocatorRequired; | ||||
|             switch (ptr_info.size) { | ||||
|                 .One => { | ||||
|                     const r: T = try allocator.create(ptrInfo.child); | ||||
|                     errdefer allocator.free(r); | ||||
|                     r.* = try parseInternal(ptrInfo.child, element, options); | ||||
|                     return r; | ||||
|                 }, | ||||
|                 .Slice => { | ||||
|                     // TODO: Detect and deal with arrays. This will require two | ||||
|                     //       passes through the element children - one to | ||||
|                     //       determine if it is an array, one to parse the elements | ||||
|                     // <Items> | ||||
|                     //   <Item>foo</Item> | ||||
|                     //   <Item>bar</Item> | ||||
|                     // <Items> | ||||
|                     if (ptr_info.child != u8) return error.UnexpectedToken; | ||||
|                     return try std.mem.dupe(allocator, u8, element.children.items[0].CharData); | ||||
|                 }, | ||||
|                 .Many => { | ||||
|                     return error.ManyPointerSizeNotImplemented; | ||||
|                 }, | ||||
|                 .C => { | ||||
|                     return error.CPointerSizeNotImplemented; | ||||
|                 }, | ||||
|             } | ||||
|         }, | ||||
|         else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"), | ||||
|         // } | ||||
|         // }, | ||||
|         // else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"), | ||||
|     } | ||||
|     unreachable; | ||||
| } | ||||
| pub fn fuzzyEqual(a: []const u8, b: []const u8, options: xml.PredicateOptions) !bool { | ||||
|     const allocator = options.allocator orelse return error.AllocatorRequired; | ||||
|     // std.debug.print("raw: a = '{s}', b = '{s}'\n", .{ a, b }); | ||||
|     const lower_a = try std.ascii.allocLowerString(allocator, a); | ||||
|     defer allocator.free(lower_a); | ||||
|     const lower_b = try std.ascii.allocLowerString(allocator, b); | ||||
|     defer allocator.free(lower_b); | ||||
|     // std.debug.print("lower: a = '{s}', b = '{s}'\n", .{ lower_a, lower_b }); | ||||
|     const normal_a = normalize(lower_a); | ||||
|     const normal_b = normalize(lower_b); | ||||
| 
 | ||||
|     // std.debug.print("normal: a = '{s}', b = '{s}'\n", .{ normal_a, normal_b }); | ||||
|     return std.mem.eql(u8, normal_a, normal_b); | ||||
| } | ||||
| 
 | ||||
| fn normalize(val: []u8) []u8 { | ||||
|     var underscores: u64 = 0; | ||||
|     for (val) |ch, i| { | ||||
|         if (ch == '_') { | ||||
|             underscores = underscores + 1; | ||||
|         } else { | ||||
|             val[i - underscores] = ch; | ||||
|         } | ||||
|     } | ||||
|     return val[0 .. val.len - underscores]; | ||||
| } | ||||
| 
 | ||||
| const testing = std.testing; | ||||
| test "can parse a simple type" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     // defer allocator.free(snake_case); | ||||
|     const data = | ||||
|         \\<?xml version="1.0" encoding="UTF-8"?> | ||||
|         \\<Example xmlns="http://example.example.com/doc/2016-11-15/"> | ||||
|         \\    <fooBar>bar</fooBar> | ||||
|         \\</Example> | ||||
|     ; | ||||
|     const Example = struct { | ||||
|         foo_bar: []const u8, | ||||
|     }; | ||||
|     // std.debug.print("{s}", .{data}); | ||||
|     const parsed_data = try parse(Example, data, .{ .allocator = allocator, .match_predicate = fuzzyEqual }); | ||||
|     defer parsed_data.deinit(); | ||||
|     try testing.expectEqualStrings("bar", parsed_data.parsed_value.foo_bar); | ||||
| } | ||||
| 
 | ||||
| test "can parse a boolean type" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     // defer allocator.free(snake_case); | ||||
|     const data = | ||||
|         \\<?xml version="1.0" encoding="UTF-8"?> | ||||
|         \\<Example xmlns="http://example.example.com/doc/2016-11-15/"> | ||||
|         \\    <fooBar>true</fooBar> | ||||
|         \\</Example> | ||||
|     ; | ||||
|     const Example = struct { | ||||
|         foo_bar: bool, | ||||
|     }; | ||||
|     // std.debug.print("{s}", .{data}); | ||||
|     const parsed_data = try parse(Example, data, .{ .allocator = allocator, .match_predicate = fuzzyEqual }); | ||||
|     defer parsed_data.deinit(); | ||||
|     try testing.expectEqual(true, parsed_data.parsed_value.foo_bar); | ||||
| } | ||||
| test "can parse an integer type" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     // defer allocator.free(snake_case); | ||||
|     const data = | ||||
|         \\<?xml version="1.0" encoding="UTF-8"?> | ||||
|         \\<Example xmlns="http://example.example.com/doc/2016-11-15/"> | ||||
|         \\    <fooBar>42</fooBar> | ||||
|         \\</Example> | ||||
|     ; | ||||
|     const Example = struct { | ||||
|         foo_bar: u8, | ||||
|     }; | ||||
|     // std.debug.print("{s}", .{data}); | ||||
|     const parsed_data = try parse(Example, data, .{ .allocator = allocator, .match_predicate = fuzzyEqual }); | ||||
|     defer parsed_data.deinit(); | ||||
|     try testing.expectEqual(@as(u8, 42), parsed_data.parsed_value.foo_bar); | ||||
| } | ||||
| test "can parse a boolean type" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     // defer allocator.free(snake_case); | ||||
|     const data = | ||||
|         \\<?xml version="1.0" encoding="UTF-8"?> | ||||
|         \\<Example xmlns="http://example.example.com/doc/2016-11-15/"> | ||||
|         \\    <fooBar>true</fooBar> | ||||
|         \\</Example> | ||||
|     ; | ||||
|     const Example = struct { | ||||
|         foo_bar: bool, | ||||
|     }; | ||||
|     // std.debug.print("{s}", .{data}); | ||||
|     const parsed_data = try parse(Example, data, .{ .allocator = allocator, .match_predicate = fuzzyEqual }); | ||||
|     defer parsed_data.deinit(); | ||||
|     try testing.expectEqual(true, parsed_data.parsed_value.foo_bar); | ||||
| } | ||||
| test "can parse an optional boolean type" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     // defer allocator.free(snake_case); | ||||
|     const data = | ||||
|         \\<?xml version="1.0" encoding="UTF-8"?> | ||||
|         \\<Example xmlns="http://example.example.com/doc/2016-11-15/"> | ||||
|         \\    <fooBar>true</fooBar> | ||||
|         \\</Example> | ||||
|     ; | ||||
|     const Example = struct { | ||||
|         foo_bar: ?bool = null, | ||||
|     }; | ||||
|     // std.debug.print("{s}", .{data}); | ||||
|     const parsed_data = try parse(Example, data, .{ .allocator = allocator, .match_predicate = fuzzyEqual }); | ||||
|     defer parsed_data.deinit(); | ||||
|     try testing.expectEqual(@as(?bool, true), parsed_data.parsed_value.foo_bar); | ||||
| } | ||||
| test "can parse a nested type" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     // defer allocator.free(snake_case); | ||||
|     const data = | ||||
|         \\<?xml version="1.0" encoding="UTF-8"?> | ||||
|         \\<Example xmlns="http://example.example.com/doc/2016-11-15/"> | ||||
|         \\    <foo> | ||||
|         \\        <bar>baz</bar> | ||||
|         \\    </foo> | ||||
|         \\</Example> | ||||
|     ; | ||||
|     const Example = struct { | ||||
|         foo: struct { | ||||
|             bar: []const u8, | ||||
|         }, | ||||
|     }; | ||||
|     // std.debug.print("{s}", .{data}); | ||||
|     const parsed_data = try parse(Example, data, .{ .allocator = allocator, .match_predicate = fuzzyEqual }); | ||||
|     defer parsed_data.deinit(); | ||||
|     try testing.expectEqualStrings("baz", parsed_data.parsed_value.foo.bar); | ||||
| } | ||||
		Loading…
	
	Add table
		
		Reference in a new issue