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
83
src/aws.zig
83
src/aws.zig
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
|
|
||||||
const awshttp = @import("awshttp.zig");
|
const awshttp = @import("awshttp.zig");
|
||||||
const json = @import("json.zig");
|
const json = @import("json.zig");
|
||||||
|
const xml_shaper = @import("xml_shaper.zig");
|
||||||
const url = @import("url.zig");
|
const url = @import("url.zig");
|
||||||
const case = @import("case.zig");
|
const case = @import("case.zig");
|
||||||
const servicemodel = @import("servicemodel.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);
|
return try case.snakeToPascal(encoding_options.allocator.?, field_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use for debugging json responses of specific requests
|
fn readFileToString(allocator: *std.mem.Allocator, file_name: []const u8, max_bytes: usize) ![]const u8 {
|
||||||
// test "dummy request" {
|
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 allocator = std.testing.allocator;
|
||||||
// const svs = Services(.{.sts}){};
|
// const svs = Services(.{.ec2}){};
|
||||||
// const request = svs.sts.get_session_token.Request{
|
// const request = svs.ec2.describe_instances.Request{};
|
||||||
// .duration_seconds = 900,
|
// const response = getTestData(allocator);
|
||||||
// };
|
|
||||||
// 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 SResponse = ServerResponse(request);
|
// const SResponse = ServerResponse(request);
|
||||||
// const r = try json.parse(SResponse, &stream, parser_options);
|
// // const parser = xml_shaper.Parser(SResponse){};
|
||||||
// json.parseFree(SResponse, r, parser_options);
|
// 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…
Reference in New Issue
Block a user