Merge pull request #2 from francisbouvier/generate

Generate
This commit is contained in:
Francis Bouvier
2023-05-23 17:06:52 +02:00
committed by GitHub
6 changed files with 641 additions and 501 deletions

View File

@@ -1,3 +1,5 @@
const generate = @import("generate.zig");
const Console = @import("jsruntime").Console;
// DOM
@@ -12,7 +14,7 @@ pub const HTMLDocument = @import("html/document.zig").HTMLDocument;
const E = @import("html/elements.zig");
// Interfaces
pub const Interfaces = .{
const interfaces = .{
Console,
// DOM
@@ -23,69 +25,8 @@ pub const Interfaces = .{
// HTML
HTMLDocument,
E.HTMLElement,
E.HTMLMediaElement,
// TODO: generate HTMLElements comptime
E.HTMLUnknownElement,
E.HTMLAnchorElement,
E.HTMLAreaElement,
E.HTMLAudioElement,
E.HTMLBRElement,
E.HTMLBaseElement,
E.HTMLBodyElement,
E.HTMLButtonElement,
E.HTMLCanvasElement,
E.HTMLDListElement,
E.HTMLDialogElement,
E.HTMLDataElement,
E.HTMLDivElement,
E.HTMLEmbedElement,
E.HTMLFieldSetElement,
E.HTMLFormElement,
E.HTMLFrameSetElement,
E.HTMLHRElement,
E.HTMLHeadElement,
E.HTMLHeadingElement,
E.HTMLHtmlElement,
E.HTMLIFrameElement,
E.HTMLImageElement,
E.HTMLInputElement,
E.HTMLLIElement,
E.HTMLLabelElement,
E.HTMLLegendElement,
E.HTMLLinkElement,
E.HTMLMapElement,
E.HTMLMetaElement,
E.HTMLMeterElement,
E.HTMLModElement,
E.HTMLOListElement,
E.HTMLObjectElement,
E.HTMLOptGroupElement,
E.HTMLOptionElement,
E.HTMLOutputElement,
E.HTMLParagraphElement,
E.HTMLPictureElement,
E.HTMLPreElement,
E.HTMLProgressElement,
E.HTMLQuoteElement,
E.HTMLScriptElement,
E.HTMLSelectElement,
E.HTMLSourceElement,
E.HTMLSpanElement,
E.HTMLStyleElement,
E.HTMLTableElement,
E.HTMLTableCaptionElement,
E.HTMLTableCellElement,
E.HTMLTableColElement,
E.HTMLTableRowElement,
E.HTMLTableSectionElement,
E.HTMLTemplateElement,
E.HTMLTextAreaElement,
E.HTMLTimeElement,
E.HTMLTitleElement,
E.HTMLTrackElement,
E.HTMLUListElement,
E.HTMLVideoElement,
E.HTMLElementsTypes,
};
pub const Interfaces = generate.TupleInst(generate.TupleT(interfaces), interfaces);

334
src/generate.zig Normal file
View File

@@ -0,0 +1,334 @@
const std = @import("std");
const builtin = @import("builtin");
fn fmtName(comptime T: type) []const u8 {
var it = std.mem.splitBackwards(u8, @typeName(T), ".");
return it.first();
}
// Generate a flatten tagged Union from various structs and union of structs
// TODO: make this function more generic
pub const Union = struct {
_enum: type,
_union: type,
pub fn compile(comptime tuple: anytype) Union {
return private_compile(tuple) catch |err| @compileError(@errorName(err));
}
fn private_compile(comptime tuple: anytype) !Union {
@setEvalBranchQuota(10000);
// check types provided
const tuple_T = @TypeOf(tuple);
const tuple_info = @typeInfo(tuple_T);
if (tuple_info != .Struct or !tuple_info.Struct.is_tuple) {
return error.GenerateArgNotTuple;
}
const tuple_members = tuple_info.Struct.fields;
// first iteration to get the total number of members
var members_nb = 0;
for (tuple_members) |member| {
const member_T = @field(tuple, member.name);
const member_info = @typeInfo(member_T);
if (member_info == .Union) {
const member_union = member_info.Union;
members_nb += member_union.fields.len;
} else if (member_info == .Struct) {
members_nb += 1;
} else {
return error.GenerateMemberNotUnionOrStruct;
}
}
// define the tag type regarding the members nb
var tag_type: type = undefined;
if (members_nb < 3) {
tag_type = u1;
} else if (members_nb < 4) {
tag_type = u2;
} else if (members_nb < 8) {
tag_type = u3;
} else if (members_nb < 16) {
tag_type = u4;
} else if (members_nb < 32) {
tag_type = u4;
} else if (members_nb < 64) {
tag_type = u6;
} else if (members_nb < 128) {
tag_type = u7;
} else if (members_nb < 256) {
tag_type = u8;
} else if (members_nb < 65536) {
tag_type = u16;
} else {
return error.GenerateTooMuchMembers;
}
// second iteration to generate tags
var enum_fields: [members_nb]std.builtin.Type.EnumField = undefined;
var done = 0;
for (tuple_members) |member| {
const member_T = @field(tuple, member.name);
const member_info = @typeInfo(member_T);
if (member_info == .Union) {
const member_union = member_info.Union;
for (member_union.fields) |field| {
enum_fields[done] = .{
.name = fmtName(field.field_type),
.value = done,
};
done += 1;
}
} else if (member_info == .Struct) {
enum_fields[done] = .{
.name = fmtName(member_T),
.value = done,
};
done += 1;
}
}
const decls: [0]std.builtin.Type.Declaration = undefined;
const enum_info = std.builtin.Type.Enum{
.layout = .Auto,
.tag_type = tag_type,
.fields = &enum_fields,
.decls = &decls,
.is_exhaustive = true,
};
const enum_T = @Type(std.builtin.Type{ .Enum = enum_info });
// third iteration to generate union type
var union_fields: [members_nb]std.builtin.Type.UnionField = undefined;
done = 0;
for (tuple_members) |member, i| {
const member_T = @field(tuple, member.name);
const member_info = @typeInfo(member_T);
if (member_info == .Union) {
const member_union = member_info.Union;
for (member_union.fields) |field| {
union_fields[done] = .{
.name = fmtName(field.field_type),
.field_type = field.field_type,
.alignment = field.alignment,
};
done += 1;
}
} else if (member_info == .Struct) {
const alignment = tuple_info.Struct.fields[i].alignment;
union_fields[done] = .{
.name = fmtName(member_T),
.field_type = member_T,
.alignment = alignment,
};
done += 1;
}
}
const union_info = std.builtin.Type.Union{
.layout = .Auto,
.tag_type = enum_T,
.fields = &union_fields,
.decls = &decls,
};
const union_T = @Type(std.builtin.Type{ .Union = union_info });
return .{
._enum = enum_T,
._union = union_T,
};
}
};
fn itoa(comptime i: u8) ![]u8 {
var len: usize = undefined;
if (i < 10) {
len = 1;
} else if (i < 100) {
len = 2;
} else {
return error.GenerateTooMuchMembers;
}
var buf: [len]u8 = undefined;
return try std.fmt.bufPrint(buf[0..], "{d}", .{i});
}
// Generate a flatten tuple type from various structs and tuple of structs.
// TODO: make this function more generic
pub fn TupleT(comptime tuple: anytype) type {
// check types provided
const tuple_T = @TypeOf(tuple);
const tuple_info = @typeInfo(tuple_T);
if (tuple_info != .Struct or !tuple_info.Struct.is_tuple) {
@compileError("GenerateArgNotTuple");
}
const tuple_members = tuple_info.Struct.fields;
// first iteration to get the total number of members
var members_nb = 0;
for (tuple_members) |member| {
const member_T = @field(tuple, member.name);
if (@TypeOf(member_T) == type) {
members_nb += 1;
} else {
const member_info = @typeInfo(@TypeOf(member_T));
if (member_info != .Struct and !member_info.Struct.is_tuple) {
@compileError("GenerateMemberNotTypeOrTuple");
}
for (member_info.Struct.fields) |field| {
if (@TypeOf(@field(member_T, field.name)) != type) {
@compileError("GenerateMemberTupleChildNotType");
}
}
members_nb += member_info.Struct.fields.len;
}
}
// second iteration to generate the tuple type
var fields: [members_nb]std.builtin.Type.StructField = undefined;
var done = 0;
while (done < members_nb) {
fields[done] = .{
.name = try itoa(done),
.field_type = type,
.default_value = null,
.is_comptime = false,
.alignment = @alignOf(type),
};
done += 1;
}
const decls: [0]std.builtin.Type.Declaration = undefined;
const info = std.builtin.Type.Struct{
.layout = .Auto,
.fields = &fields,
.decls = &decls,
.is_tuple = true,
};
return @Type(std.builtin.Type{ .Struct = info });
}
// Instantiate a flatten tuple from various structs and tuple of structs
// You need to call first TupleT to generate the according type
// TODO: make this function more generic
pub fn TupleInst(comptime T: type, comptime tuple: anytype) T {
// check types provided
const tuple_T = @TypeOf(tuple);
const tuple_info = @typeInfo(tuple_T);
const tuple_members = tuple_info.Struct.fields;
// instantiate the tuple
var t: T = undefined;
var done = 0;
for (tuple_members) |member| {
const member_T = @field(tuple, member.name);
var member_info: std.builtin.Type = undefined;
if (@TypeOf(member_T) == type) {
member_info = @typeInfo(member_T);
} else {
member_info = @typeInfo(@TypeOf(member_T));
}
var member_detail = member_info.Struct;
if (member_detail.is_tuple) {
for (member_detail.fields) |field| {
const name = try itoa(done);
@field(t, name) = @field(member_T, field.name);
done += 1;
}
} else {
const name = try itoa(done);
@field(t, name) = @field(tuple, member.name);
done += 1;
}
}
return t;
}
// Tests
// -----
const Error = error{
GenerateArgNotTuple,
GenerateMemberNotUnionOrStruct,
GenerateMemberNotTupleOrStruct,
GenerateMemberTupleNotStruct,
GenerateTooMuchMembers,
};
const Astruct = struct {
value: u8 = 0,
};
const Bstruct = struct {
value: u8 = 0,
};
const Cstruct = struct {
value: u8 = 0,
};
const Dstruct = struct {
value: u8 = 0,
};
pub fn tests() !void {
// Union from structs
const FromStructs = try Union.private_compile(.{ Astruct, Bstruct, Cstruct });
const from_structs_enum = @typeInfo(FromStructs._enum);
try std.testing.expect(from_structs_enum == .Enum);
try std.testing.expect(from_structs_enum.Enum.fields.len == 3);
try std.testing.expect(from_structs_enum.Enum.tag_type == u2);
try std.testing.expect(from_structs_enum.Enum.fields[0].value == 0);
try std.testing.expectEqualStrings(from_structs_enum.Enum.fields[0].name, "Astruct");
const from_structs_union = @typeInfo(FromStructs._union);
try std.testing.expect(from_structs_union == .Union);
try std.testing.expect(from_structs_union.Union.tag_type == FromStructs._enum);
try std.testing.expect(from_structs_union.Union.fields.len == 3);
try std.testing.expect(from_structs_union.Union.fields[0].field_type == Astruct);
try std.testing.expectEqualStrings(from_structs_union.Union.fields[0].name, "Astruct");
// Union from union and structs
const FromMix = try Union.private_compile(.{ FromStructs._union, Dstruct });
const from_mix_enum = @typeInfo(FromMix._enum);
try std.testing.expect(from_mix_enum == .Enum);
try std.testing.expect(from_mix_enum.Enum.fields.len == 4);
try std.testing.expect(from_mix_enum.Enum.tag_type == u3);
try std.testing.expect(from_mix_enum.Enum.fields[0].value == 0);
try std.testing.expectEqualStrings(from_mix_enum.Enum.fields[3].name, "Dstruct");
const from_mix_union = @typeInfo(FromMix._union);
try std.testing.expect(from_mix_union == .Union);
try std.testing.expect(from_mix_union.Union.tag_type == FromMix._enum);
try std.testing.expect(from_mix_union.Union.fields.len == 4);
try std.testing.expect(from_mix_union.Union.fields[3].field_type == Dstruct);
try std.testing.expectEqualStrings(from_mix_union.Union.fields[3].name, "Dstruct");
std.debug.print("Generate Union: OK\n", .{});
// Tuple from structs
const tuple_structs = .{ Astruct, Bstruct };
const tFromStructs = TupleInst(TupleT(tuple_structs), tuple_structs);
const t_from_structs = @typeInfo(@TypeOf(tFromStructs));
try std.testing.expect(t_from_structs == .Struct);
try std.testing.expect(t_from_structs.Struct.is_tuple);
try std.testing.expect(t_from_structs.Struct.fields.len == 2);
try std.testing.expect(@field(tFromStructs, "0") == Astruct);
try std.testing.expect(@field(tFromStructs, "1") == Bstruct);
// Tuple from tuple and structs
const tuple_mix = .{ tFromStructs, Cstruct };
const tFromMix = TupleInst(TupleT(tuple_mix), tuple_mix);
const t_from_mix = @typeInfo(@TypeOf(tFromMix));
try std.testing.expect(t_from_mix == .Struct);
try std.testing.expect(t_from_mix.Struct.is_tuple);
try std.testing.expect(t_from_mix.Struct.fields.len == 3);
try std.testing.expect(@field(tFromMix, "0") == Astruct);
try std.testing.expect(@field(tFromMix, "1") == Bstruct);
try std.testing.expect(@field(tFromMix, "2") == Cstruct);
std.debug.print("Generate Tuple: OK\n", .{});
}

View File

@@ -50,187 +50,13 @@ pub const HTMLDocument = struct {
pub fn _createElement(self: HTMLDocument, tag_name: []const u8) E.HTMLElements {
const base = parser.documentCreateElement(self.proto.base.?, tag_name);
// TODO: order by probability instead of alphabetically
// TODO: this does not seems very efficient, do we have a better way?
if (std.mem.eql(u8, tag_name, "a")) {
return .{ .anchor = E.HTMLAnchorElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "area")) {
return .{ .area = E.HTMLAreaElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "audio")) {
return .{ .audio = E.HTMLAudioElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "br")) {
return .{ .br = E.HTMLBRElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "base")) {
return .{ .base = E.HTMLBaseElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "body")) {
return .{ .body = E.HTMLBodyElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "button")) {
return .{ .button = E.HTMLButtonElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "canvas")) {
return .{ .canvas = E.HTMLCanvasElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "dl")) {
return .{ .dlist = E.HTMLDListElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "dialog")) {
return .{ .dialog = E.HTMLDialogElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "data")) {
return .{ .data = E.HTMLDataElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "div")) {
return .{ .div = E.HTMLDivElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "embed")) {
return .{ .embed = E.HTMLEmbedElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "fieldset")) {
return .{ .fieldset = E.HTMLFieldSetElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "form")) {
return .{ .form = E.HTMLFormElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "frameset")) {
return .{ .frameset = E.HTMLFrameSetElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "hr")) {
return .{ .hr = E.HTMLHRElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "head")) {
return .{ .head = E.HTMLHeadElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "h1")) {
return .{ .heading = E.HTMLHeadingElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "h2")) {
return .{ .heading = E.HTMLHeadingElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "h3")) {
return .{ .heading = E.HTMLHeadingElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "h4")) {
return .{ .heading = E.HTMLHeadingElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "h5")) {
return .{ .heading = E.HTMLHeadingElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "h6")) {
return .{ .heading = E.HTMLHeadingElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "html")) {
return .{ .html = E.HTMLHtmlElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "iframe")) {
return .{ .iframe = E.HTMLIFrameElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "img")) {
return .{ .img = E.HTMLImageElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "input")) {
return .{ .input = E.HTMLInputElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "li")) {
return .{ .li = E.HTMLLIElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "label")) {
return .{ .label = E.HTMLLabelElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "legend")) {
return .{ .legend = E.HTMLLegendElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "link")) {
return .{ .link = E.HTMLLinkElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "map")) {
return .{ .map = E.HTMLMapElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "meta")) {
return .{ .meta = E.HTMLMetaElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "meter")) {
return .{ .meter = E.HTMLMeterElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "ins")) {
return .{ .mod = E.HTMLModElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "del")) {
return .{ .mod = E.HTMLModElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "ol")) {
return .{ .olist = E.HTMLOListElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "object")) {
return .{ .object = E.HTMLObjectElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "optgroup")) {
return .{ .optgroup = E.HTMLOptGroupElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "option")) {
return .{ .option = E.HTMLOptionElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "output")) {
return .{ .output = E.HTMLOutputElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "p")) {
return .{ .paragraph = E.HTMLParagraphElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "picture")) {
return .{ .picture = E.HTMLPictureElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "pre")) {
return .{ .pre = E.HTMLPreElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "progress")) {
return .{ .progress = E.HTMLProgressElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "blockquote")) {
return .{ .quote = E.HTMLQuoteElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "q")) {
return .{ .quote = E.HTMLQuoteElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "script")) {
return .{ .script = E.HTMLScriptElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "select")) {
return .{ .select = E.HTMLSelectElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "source")) {
return .{ .source = E.HTMLSourceElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "span")) {
return .{ .span = E.HTMLSpanElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "style")) {
return .{ .style = E.HTMLStyleElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "table")) {
return .{ .table = E.HTMLTableElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "caption")) {
return .{ .tablecaption = E.HTMLTableCaptionElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "th")) {
return .{ .tablecell = E.HTMLTableCellElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "td")) {
return .{ .tablecell = E.HTMLTableCellElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "col")) {
return .{ .tablecol = E.HTMLTableColElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "tr")) {
return .{ .tablerow = E.HTMLTableRowElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "thead")) {
return .{ .tablesection = E.HTMLTableSectionElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "tbody")) {
return .{ .tablesection = E.HTMLTableSectionElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "tfoot")) {
return .{ .tablesection = E.HTMLTableSectionElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "template")) {
return .{ .template = E.HTMLTemplateElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "textarea")) {
return .{ .textarea = E.HTMLTextAreaElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "time")) {
return .{ .time = E.HTMLTimeElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "title")) {
return .{ .title = E.HTMLTitleElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "track")) {
return .{ .track = E.HTMLTrackElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "ul")) {
return .{ .ulist = E.HTMLUListElement.init(base) };
} else if (std.mem.eql(u8, tag_name, "video")) {
return .{ .video = E.HTMLVideoElement.init(base) };
}
return .{ .unknown = E.HTMLUnknownElement.init(base) };
return E.ElementToHTMLElementInterface(base);
}
};
// Tests
// -----
fn upper(comptime name: []const u8, comptime indexes: anytype) []u8 {
// indexes is [_]comptime_int
comptime {
var upper_name: [name.len]u8 = undefined;
for (name) |char, i| {
var toUpper = false;
for (indexes) |index| {
if (index == i) {
toUpper = true;
break;
}
}
if (toUpper) {
upper_name[i] = std.ascii.toUpper(char);
} else {
upper_name[i] = char;
}
}
return &upper_name;
}
}
// fn allUpper(comptime name: []const u8) []u8 {
// comptime {
// var upper_name: [name.len]u8 = undefined;
// for (name) |char, i| {
// upper_name[i] = std.ascii.toUpper(char);
// }
// return &upper_name;
// }
// }
pub fn testExecFn(
alloc: std.mem.Allocator,
js_env: *jsruntime.Env,
@@ -251,149 +77,29 @@ pub fn testExecFn(
};
try checkCases(js_env, &getElementById);
comptime var htmlElements = [_][]const u8{
"a", // Anchor
"area",
"audio",
"br", // BR
"base",
"body",
"button",
"canvas",
"dl", // DList
"dialog",
"data",
"div",
"embed",
"fieldset", // FieldSet
"form",
"frameset", // FrameSet
"hr", // HR
"head",
"h1", // Heading
"h2", // Heading
"h3", // Heading
"h4", // Heading
"h5", // Heading
"h6", // Heading
"html",
"iframe", // IFrame
"img", // Image
"input",
"li", // LI
"label",
"legend",
"link",
"map",
"meta",
"meter",
"ins", // Mod
"del", // Mod
"ol", // OList
"object",
"optgroup", // OptGroup
"option",
"output",
"p", // Paragraph
"picture",
"pre",
"progress",
"blockquote", // Quote
"q", // Quote
"script",
"select",
"source",
"span",
"style",
"table",
"caption", // TableCaption
"th", // TableCell
"td", // TableCell
"col", // TableCol
"tr", // TableRow
"thead", // TableSection
"tbody", // TableSection
"tfoot", // TableSection
"template",
"textarea", // TextArea
"time",
"title",
"track",
"ul", // UList
"video",
};
var createElement: [htmlElements.len * 3]Case = undefined;
inline for (htmlElements) |elem, i| {
var upperName: []const u8 = undefined;
if (std.mem.eql(u8, elem, "a")) {
upperName = "Anchor";
} else if (std.mem.eql(u8, elem, "dl")) {
upperName = "DList";
} else if (std.mem.eql(u8, elem, "fieldset")) {
upperName = "FieldSet";
} else if (std.mem.eql(u8, elem, "frameset")) {
upperName = "FrameSet";
} else if (std.mem.eql(u8, elem, "h1") or
std.mem.eql(u8, elem, "h2") or
std.mem.eql(u8, elem, "h3") or
std.mem.eql(u8, elem, "h4") or
std.mem.eql(u8, elem, "h5") or
std.mem.eql(u8, elem, "h6"))
{
upperName = "Heading";
} else if (std.mem.eql(u8, elem, "iframe")) {
upperName = "IFrame";
} else if (std.mem.eql(u8, elem, "img")) {
upperName = "Image";
} else if (std.mem.eql(u8, elem, "del") or std.mem.eql(u8, elem, "ins")) {
upperName = "Mod";
} else if (std.mem.eql(u8, elem, "ol")) {
upperName = "OList";
} else if (std.mem.eql(u8, elem, "optgroup")) {
upperName = "OptGroup";
} else if (std.mem.eql(u8, elem, "p")) {
upperName = "Paragraph";
} else if (std.mem.eql(u8, elem, "blockquote") or std.mem.eql(u8, elem, "q")) {
upperName = "Quote";
} else if (std.mem.eql(u8, elem, "caption")) {
upperName = "TableCaption";
} else if (std.mem.eql(u8, elem, "th") or std.mem.eql(u8, elem, "td")) {
upperName = "TableCell";
} else if (std.mem.eql(u8, elem, "col")) {
upperName = "TableCol";
} else if (std.mem.eql(u8, elem, "tr")) {
upperName = "TableRow";
} else if (std.mem.eql(u8, elem, "thead") or
std.mem.eql(u8, elem, "tbody") or
std.mem.eql(u8, elem, "tfoot"))
{
upperName = "TableSection";
} else if (std.mem.eql(u8, elem, "textarea")) {
upperName = "TextArea";
} else if (std.mem.eql(u8, elem, "ul")) {
upperName = "UList";
} else {
if (elem.len == 2) {
upperName = upper(elem, [_]comptime_int{ 0, 1 });
} else {
upperName = upper(elem, [_]comptime_int{0});
}
const tags = comptime parser.Tag.all();
const elements = comptime parser.Tag.allElements();
var createElements: [(tags.len - 1) * 3]Case = undefined;
inline for (tags) |tag, i| {
if (tag == .undef) {
continue;
}
createElement[i * 3] = Case{
.src = try std.fmt.allocPrint(alloc, "var {s}Elem = document.createElement('{s}')", .{ elem, elem }),
const tag_name = @tagName(tag);
const element_name = elements[i];
createElements[i * 3] = Case{
.src = try std.fmt.allocPrint(alloc, "var {s}Elem = document.createElement('{s}')", .{ tag_name, tag_name }),
.ex = "undefined",
};
createElement[(i * 3) + 1] = Case{
.src = try std.fmt.allocPrint(alloc, "{s}Elem.constructor.name", .{elem}),
.ex = try std.fmt.allocPrint(alloc, "HTML{s}Element", .{upperName}),
createElements[(i * 3) + 1] = Case{
.src = try std.fmt.allocPrint(alloc, "{s}Elem.constructor.name", .{tag_name}),
.ex = try std.fmt.allocPrint(alloc, "HTML{s}Element", .{element_name}),
};
createElement[(i * 3) + 2] = Case{
.src = try std.fmt.allocPrint(alloc, "{s}Elem.localName", .{elem}),
.ex = elem,
createElements[(i * 3) + 2] = Case{
.src = try std.fmt.allocPrint(alloc, "{s}Elem.localName", .{tag_name}),
.ex = tag_name,
};
}
try checkCases(js_env, &createElement);
try checkCases(js_env, &createElements);
var unknown = [_]Case{
.{ .src = "let unknown = document.createElement('unknown')", .ex = "undefined" },

View File

@@ -1,4 +1,5 @@
const parser = @import("../parser.zig");
const generate = @import("../generate.zig");
const Element = @import("../dom/element.zig").Element;
@@ -15,132 +16,71 @@ pub const HTMLElement = struct {
}
};
const HTMLElementsTags = enum {
unknown,
anchor,
area,
audio,
br,
base,
body,
button,
canvas,
dlist,
dialog,
data,
div,
embed,
fieldset,
form,
frameset,
hr,
head,
heading,
html,
iframe,
img,
input,
li,
label,
legend,
link,
map,
meta,
meter,
mod,
olist,
object,
optgroup,
option,
output,
paragraph,
picture,
pre,
progress,
quote,
script,
select,
source,
span,
style,
table,
tablecaption,
tablecell,
tablecol,
tablerow,
tablesection,
template,
textarea,
time,
title,
track,
ulist,
video,
};
// TODO: generate comptime?
pub const HTMLElements = union(HTMLElementsTags) {
unknown: HTMLUnknownElement,
anchor: HTMLAnchorElement,
area: HTMLAreaElement,
audio: HTMLAudioElement,
br: HTMLBRElement,
base: HTMLBaseElement,
body: HTMLBodyElement,
button: HTMLButtonElement,
canvas: HTMLCanvasElement,
dlist: HTMLDListElement,
dialog: HTMLDialogElement,
data: HTMLDataElement,
div: HTMLDivElement,
embed: HTMLEmbedElement,
fieldset: HTMLFieldSetElement,
form: HTMLFormElement,
frameset: HTMLFrameSetElement,
hr: HTMLHRElement,
head: HTMLHeadElement,
heading: HTMLHeadingElement,
html: HTMLHtmlElement,
iframe: HTMLIFrameElement,
img: HTMLImageElement,
input: HTMLInputElement,
li: HTMLLIElement,
label: HTMLLabelElement,
legend: HTMLLegendElement,
link: HTMLLinkElement,
map: HTMLMapElement,
meta: HTMLMetaElement,
meter: HTMLMeterElement,
mod: HTMLModElement,
olist: HTMLOListElement,
object: HTMLObjectElement,
optgroup: HTMLOptGroupElement,
option: HTMLOptionElement,
output: HTMLOutputElement,
paragraph: HTMLParagraphElement,
picture: HTMLPictureElement,
pre: HTMLPreElement,
progress: HTMLProgressElement,
quote: HTMLQuoteElement,
script: HTMLScriptElement,
select: HTMLSelectElement,
source: HTMLSourceElement,
span: HTMLSpanElement,
style: HTMLStyleElement,
table: HTMLTableElement,
tablecaption: HTMLTableCaptionElement,
tablecell: HTMLTableCellElement,
tablecol: HTMLTableColElement,
tablerow: HTMLTableRowElement,
tablesection: HTMLTableSectionElement,
template: HTMLTemplateElement,
textarea: HTMLTextAreaElement,
time: HTMLTimeElement,
title: HTMLTitleElement,
track: HTMLTrackElement,
ulist: HTMLUListElement,
video: HTMLVideoElement,
pub const HTMLElementsTypes = .{
HTMLUnknownElement,
HTMLAnchorElement,
HTMLAreaElement,
HTMLAudioElement,
HTMLBRElement,
HTMLBaseElement,
HTMLBodyElement,
HTMLButtonElement,
HTMLCanvasElement,
HTMLDListElement,
HTMLDialogElement,
HTMLDataElement,
HTMLDivElement,
HTMLEmbedElement,
HTMLFieldSetElement,
HTMLFormElement,
HTMLFrameSetElement,
HTMLHRElement,
HTMLHeadElement,
HTMLHeadingElement,
HTMLHtmlElement,
HTMLIFrameElement,
HTMLImageElement,
HTMLInputElement,
HTMLLIElement,
HTMLLabelElement,
HTMLLegendElement,
HTMLLinkElement,
HTMLMapElement,
HTMLMetaElement,
HTMLMeterElement,
HTMLModElement,
HTMLOListElement,
HTMLObjectElement,
HTMLOptGroupElement,
HTMLOptionElement,
HTMLOutputElement,
HTMLParagraphElement,
HTMLPictureElement,
HTMLPreElement,
HTMLProgressElement,
HTMLQuoteElement,
HTMLScriptElement,
HTMLSelectElement,
HTMLSourceElement,
HTMLSpanElement,
HTMLStyleElement,
HTMLTableElement,
HTMLTableCaptionElement,
HTMLTableCellElement,
HTMLTableColElement,
HTMLTableRowElement,
HTMLTableSectionElement,
HTMLTemplateElement,
HTMLTextAreaElement,
HTMLTimeElement,
HTMLTitleElement,
HTMLTrackElement,
HTMLUListElement,
HTMLVideoElement,
};
const HTMLElementsGenerated = generate.Union.compile(HTMLElementsTypes);
pub const HTMLElements = HTMLElementsGenerated._union;
pub const HTMLElementsTags = HTMLElementsGenerated._enum;
// Deprecated HTMLElements in Chrome (2023/03/15)
// HTMLContentelement
@@ -761,3 +701,69 @@ pub const HTMLVideoElement = struct {
return .{ .proto = HTMLMediaElement.init(elem_base) };
}
};
pub fn ElementToHTMLElementInterface(base: *parser.Element) HTMLElements {
const tag = parser.nodeTag(parser.elementNode(base));
return switch (tag) {
.a => .{ .HTMLAnchorElement = HTMLAnchorElement.init(base) },
.area => .{ .HTMLAreaElement = HTMLAreaElement.init(base) },
.audio => .{ .HTMLAudioElement = HTMLAudioElement.init(base) },
.br => .{ .HTMLBRElement = HTMLBRElement.init(base) },
.base => .{ .HTMLBaseElement = HTMLBaseElement.init(base) },
.body => .{ .HTMLBodyElement = HTMLBodyElement.init(base) },
.button => .{ .HTMLButtonElement = HTMLButtonElement.init(base) },
.canvas => .{ .HTMLCanvasElement = HTMLCanvasElement.init(base) },
.dl => .{ .HTMLDListElement = HTMLDListElement.init(base) },
.dialog => .{ .HTMLDialogElement = HTMLDialogElement.init(base) },
.data => .{ .HTMLDataElement = HTMLDataElement.init(base) },
.div => .{ .HTMLDivElement = HTMLDivElement.init(base) },
.embed => .{ .HTMLEmbedElement = HTMLEmbedElement.init(base) },
.fieldset => .{ .HTMLFieldSetElement = HTMLFieldSetElement.init(base) },
.form => .{ .HTMLFormElement = HTMLFormElement.init(base) },
.frameset => .{ .HTMLFrameSetElement = HTMLFrameSetElement.init(base) },
.hr => .{ .HTMLHRElement = HTMLHRElement.init(base) },
.head => .{ .HTMLHeadElement = HTMLHeadElement.init(base) },
.h1, .h2, .h3, .h4, .h5, .h6 => .{ .HTMLHeadingElement = HTMLHeadingElement.init(base) },
.html => .{ .HTMLHtmlElement = HTMLHtmlElement.init(base) },
.iframe => .{ .HTMLIFrameElement = HTMLIFrameElement.init(base) },
.img => .{ .HTMLImageElement = HTMLImageElement.init(base) },
.input => .{ .HTMLInputElement = HTMLInputElement.init(base) },
.li => .{ .HTMLLIElement = HTMLLIElement.init(base) },
.label => .{ .HTMLLabelElement = HTMLLabelElement.init(base) },
.legend => .{ .HTMLLegendElement = HTMLLegendElement.init(base) },
.link => .{ .HTMLLinkElement = HTMLLinkElement.init(base) },
.map => .{ .HTMLMapElement = HTMLMapElement.init(base) },
.meta => .{ .HTMLMetaElement = HTMLMetaElement.init(base) },
.meter => .{ .HTMLMeterElement = HTMLMeterElement.init(base) },
.ins, .del => .{ .HTMLModElement = HTMLModElement.init(base) },
.ol => .{ .HTMLOListElement = HTMLOListElement.init(base) },
.object => .{ .HTMLObjectElement = HTMLObjectElement.init(base) },
.optgroup => .{ .HTMLOptGroupElement = HTMLOptGroupElement.init(base) },
.option => .{ .HTMLOptionElement = HTMLOptionElement.init(base) },
.output => .{ .HTMLOutputElement = HTMLOutputElement.init(base) },
.p => .{ .HTMLParagraphElement = HTMLParagraphElement.init(base) },
.picture => .{ .HTMLPictureElement = HTMLPictureElement.init(base) },
.pre => .{ .HTMLPreElement = HTMLPreElement.init(base) },
.progress => .{ .HTMLProgressElement = HTMLProgressElement.init(base) },
.blockquote, .q => .{ .HTMLQuoteElement = HTMLQuoteElement.init(base) },
.script => .{ .HTMLScriptElement = HTMLScriptElement.init(base) },
.select => .{ .HTMLSelectElement = HTMLSelectElement.init(base) },
.source => .{ .HTMLSourceElement = HTMLSourceElement.init(base) },
.span => .{ .HTMLSpanElement = HTMLSpanElement.init(base) },
.style => .{ .HTMLStyleElement = HTMLStyleElement.init(base) },
.table => .{ .HTMLTableElement = HTMLTableElement.init(base) },
.caption => .{ .HTMLTableCaptionElement = HTMLTableCaptionElement.init(base) },
.th, .td => .{ .HTMLTableCellElement = HTMLTableCellElement.init(base) },
.col => .{ .HTMLTableColElement = HTMLTableColElement.init(base) },
.tr => .{ .HTMLTableRowElement = HTMLTableRowElement.init(base) },
.thead, .tbody, .tfoot => .{ .HTMLTableSectionElement = HTMLTableSectionElement.init(base) },
.template => .{ .HTMLTemplateElement = HTMLTemplateElement.init(base) },
.textarea => .{ .HTMLTextAreaElement = HTMLTextAreaElement.init(base) },
.time => .{ .HTMLTimeElement = HTMLTimeElement.init(base) },
.title => .{ .HTMLTitleElement = HTMLTitleElement.init(base) },
.track => .{ .HTMLTrackElement = HTMLTrackElement.init(base) },
.ul => .{ .HTMLUListElement = HTMLUListElement.init(base) },
.video => .{ .HTMLVideoElement = HTMLVideoElement.init(base) },
.undef => .{ .HTMLUnknownElement = HTMLUnknownElement.init(base) },
};
}

View File

@@ -7,6 +7,149 @@ const c = @cImport({
// Public API
// ----------
// Tag
pub const Tag = enum(u8) {
a = c.LXB_TAG_A,
area = c.LXB_TAG_AREA,
audio = c.LXB_TAG_AUDIO,
br = c.LXB_TAG_BR,
base = c.LXB_TAG_BASE,
body = c.LXB_TAG_BODY,
button = c.LXB_TAG_BUTTON,
canvas = c.LXB_TAG_CANVAS,
dl = c.LXB_TAG_DL,
dialog = c.LXB_TAG_DIALOG,
data = c.LXB_TAG_DATA,
div = c.LXB_TAG_DIV,
embed = c.LXB_TAG_EMBED,
fieldset = c.LXB_TAG_FIELDSET,
form = c.LXB_TAG_FORM,
frameset = c.LXB_TAG_FRAMESET,
hr = c.LXB_TAG_HR,
head = c.LXB_TAG_HEAD,
h1 = c.LXB_TAG_H1,
h2 = c.LXB_TAG_H2,
h3 = c.LXB_TAG_H3,
h4 = c.LXB_TAG_H4,
h5 = c.LXB_TAG_H5,
h6 = c.LXB_TAG_H6,
html = c.LXB_TAG_HTML,
iframe = c.LXB_TAG_IFRAME,
img = c.LXB_TAG_IMG,
input = c.LXB_TAG_INPUT,
li = c.LXB_TAG_LI,
label = c.LXB_TAG_LABEL,
legend = c.LXB_TAG_LEGEND,
link = c.LXB_TAG_LINK,
map = c.LXB_TAG_MAP,
meta = c.LXB_TAG_META,
meter = c.LXB_TAG_METER,
ins = c.LXB_TAG_INS,
del = c.LXB_TAG_DEL,
ol = c.LXB_TAG_OL,
object = c.LXB_TAG_OBJECT,
optgroup = c.LXB_TAG_OPTGROUP,
option = c.LXB_TAG_OPTION,
output = c.LXB_TAG_OUTPUT,
p = c.LXB_TAG_P,
picture = c.LXB_TAG_PICTURE,
pre = c.LXB_TAG_PRE,
progress = c.LXB_TAG_PROGRESS,
blockquote = c.LXB_TAG_BLOCKQUOTE,
q = c.LXB_TAG_Q,
script = c.LXB_TAG_SCRIPT,
select = c.LXB_TAG_SELECT,
source = c.LXB_TAG_SOURCE,
span = c.LXB_TAG_SPAN,
style = c.LXB_TAG_STYLE,
table = c.LXB_TAG_TABLE,
caption = c.LXB_TAG_CAPTION,
th = c.LXB_TAG_TH,
td = c.LXB_TAG_TD,
col = c.LXB_TAG_COL,
tr = c.LXB_TAG_TR,
thead = c.LXB_TAG_THEAD,
tbody = c.LXB_TAG_TBODY,
tfoot = c.LXB_TAG_TFOOT,
template = c.LXB_TAG_TEMPLATE,
textarea = c.LXB_TAG_TEXTAREA,
time = c.LXB_TAG_TIME,
title = c.LXB_TAG_TITLE,
track = c.LXB_TAG_TRACK,
ul = c.LXB_TAG_UL,
video = c.LXB_TAG_VIDEO,
undef = c.LXB_TAG__UNDEF,
pub fn all() []Tag {
comptime {
const info = @typeInfo(Tag).Enum;
comptime var l: [info.fields.len]Tag = undefined;
inline for (info.fields) |field, i| {
l[i] = @intToEnum(Tag, field.value);
}
return &l;
}
}
pub fn allElements() [][]const u8 {
comptime {
const tags = all();
var names: [tags.len][]const u8 = undefined;
inline for (tags) |tag, i| {
names[i] = tag.elementName();
}
return &names;
}
}
fn upperName(comptime name: []const u8) []const u8 {
comptime {
var upper_name: [name.len]u8 = undefined;
for (name) |char, i| {
var to_upper = false;
if (i == 0) {
to_upper = true;
} else if (i == 1 and name.len == 2) {
to_upper = true;
}
if (to_upper) {
upper_name[i] = std.ascii.toUpper(char);
} else {
upper_name[i] = char;
}
}
return &upper_name;
}
}
fn elementName(comptime tag: Tag) []const u8 {
return switch (tag) {
.area, .audio, .base, .body, .button, .br, .canvas, .dialog, .data, .div, .embed, .form, .head, .html, .hr, .input, .label, .li, .legend, .link, .map, .meta, .meter, .object, .option, .output, .picture, .pre, .progress, .script, .select, .source, .span, .style, .table, .template, .time, .title, .track, .video => upperName(@tagName(tag)),
.a => "Anchor",
.dl => "DList",
.fieldset => "FieldSet",
.frameset => "FrameSet",
.h1, .h2, .h3, .h4, .h5, .h6 => "Heading",
.iframe => "IFrame",
.img => "Image",
.ins, .del => "Mod",
.ol => "OList",
.optgroup => "OptGroup",
.p => "Paragraph",
.blockquote, .q => "Quote",
.caption => "TableCaption",
.th, .td => "TableCell",
.col => "TableCol",
.tr => "TableRow",
.thead, .tbody, .tfoot => "TableSection",
.textarea => "TextArea",
.ul => "UList",
.undef => "Unknown",
};
}
};
// EventTarget
pub const EventTarget = c.lxb_dom_event_target_t;
@@ -36,6 +179,10 @@ pub inline fn nodeEventTarget(node: *Node) *EventTarget {
return c.lxb_dom_interface_event_target(node);
}
pub inline fn nodeTag(node: *Node) Tag {
return @intToEnum(Tag, c.lxb_dom_node_tag_id(node));
}
pub const nodeWalker = (fn (node: ?*Node, _: ?*anyopaque) callconv(.C) Action);
pub inline fn nodeName(node: *Node) [*c]const u8 {

View File

@@ -1,6 +1,7 @@
const std = @import("std");
const jsruntime = @import("jsruntime");
const generate = @import("generate.zig");
const DOM = @import("dom.zig");
const testExecFn = @import("html/document.zig").testExecFn;
@@ -27,6 +28,11 @@ fn testsExecFn(
}
test {
std.debug.print("\n", .{});
// generate tests
try generate.tests();
// generate APIs
const apis = jsruntime.compile(DOM.Interfaces);