authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2021-08-05 00:47:09 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2021-08-05 00:47:09 -07:00
log620dc8ce2ed5424fc6f880a47faea0f3dd767699
tree55191528ab4819973f0bad6a3f4c898a187803f8
parente082ea3615ca7bbfe5e88adb3c723a205b924fbd

initial code commit


8 files changed, 353 insertions(+), 0 deletions(-)

.gitignore created+3
......@@ -0,0 +1,3 @@
1zig-*
2.zigmod
3deps.zig
build.zig created+23
......@@ -0,0 +1,23 @@
1const std = @import("std");
2const deps = @import("./deps.zig");
3
4pub fn build(b: *std.build.Builder) void {
5 const target = b.standardTargetOptions(.{});
6
7 const mode = b.standardReleaseOptions();
8
9 const exe = b.addExecutable("zig-pek", "src/main.zig");
10 exe.setTarget(target);
11 exe.setBuildMode(mode);
12 deps.addAllTo(exe);
13 exe.install();
14
15 const run_cmd = exe.run();
16 run_cmd.step.dependOn(b.getInstallStep());
17 if (b.args) |args| {
18 run_cmd.addArgs(args);
19 }
20
21 const run_step = b.step("run", "Run the app");
22 run_step.dependOn(&run_cmd.step);
23}
src/astgen.zig created+113
......@@ -0,0 +1,113 @@
1const std = @import("std");
2const Token = @import("./tokenize.zig").Token;
3
4//
5//
6
7pub const Value = union(enum) {
8 element: Element,
9 attr: Attr,
10 string: []const u8,
11};
12
13pub const Element = struct {
14 name: []const u8,
15 attrs: []const Attr,
16 children: []const Value,
17};
18
19pub const Attr = struct {
20 key: []const u8,
21 value: []const u8,
22};
23
24//
25//
26
27pub fn do(comptime tokens: []const Token) Element {
28 var parser = Parser{ .tokens = tokens, .index = 0 };
29 return parser.doElement();
30}
31
32//
33//
34
35const Parser = struct {
36 tokens: []const Token,
37 index: usize,
38
39 pub fn doElement(comptime self: *Parser) Element {
40 return Element{
41 .name = self.eat(.word),
42 .attrs = self.doAttrs(),
43 .children = self.doChildren(),
44 };
45 }
46
47 pub fn doAttrs(comptime self: *Parser) []const Attr {
48 var ret: []const Attr = &[_]Attr{};
49
50 if (self.tryEatSymbol("[")) {
51 inline while (true) {
52 if (self.tryEatSymbol("]")) break;
53 ret = ret ++ &[_]Attr{self.doAttr()};
54 }
55 }
56
57 return ret;
58 }
59
60 fn tryEatSymbol(comptime self: *Parser, comptime needle: []const u8) bool {
61 switch (self.tokens[self.index]) {
62 .symbol => |sym| {
63 if (std.mem.eql(u8, sym, needle)) {
64 self.index += 1;
65 return true;
66 }
67 return false;
68 },
69 else => {
70 return false;
71 },
72 }
73 }
74
75 pub fn doAttr(comptime self: *Parser) Attr {
76 const k = self.eat(.word);
77 self.eatSymbol("=");
78 const v = self.eat(.string);
79 return Attr{
80 .key = k,
81 .value = v,
82 };
83 }
84
85 pub fn eatSymbol(comptime self: *Parser, comptime needle: []const u8) void {
86 std.debug.assert(std.mem.eql(u8, self.eat(.symbol), needle));
87 }
88
89 pub fn doChildren(comptime self: *Parser) []const Value {
90 var ret: []const Value = &[_]Value{};
91
92 if (self.tryEatSymbol("(")) {
93 inline while (true) {
94 if (self.tryEatSymbol(")")) break;
95 ret = ret ++ &[_]Value{self.doValue()};
96 }
97 }
98
99 return ret;
100 }
101
102 pub fn doValue(comptime self: *Parser) Value {
103 if (self.tokens[self.index] == .string) {
104 return Value{ .string = self.eat(.string) };
105 }
106 return Value{ .element = self.doElement() };
107 }
108
109 fn eat(comptime self: *Parser, comptime typ: std.meta.Tag(Token)) []const u8 {
110 defer self.index += 1;
111 return @field(self.tokens[self.index], @tagName(typ));
112 }
113};
src/lib.zig created+69
......@@ -0,0 +1,69 @@
1//! Pek HTML Preprocessor Language
2//!
3//! Shortening of Pekingese
4//! https://en.wikipedia.org/wiki/Pekingese
5//!
6//! Loosely inspired by Pug
7//! https://pugjs.org/api/getting-started.html
8
9// // document.corgi
10// doctype html
11// html[lang="en"](
12// head(
13// title("Corgi Example")
14// meta[charset="UTF-8"]
15// meta[name="viewport",content="width=device-width,initial-scale=1"]
16// )
17// body(
18// h1("Corgi Example")
19// hr
20// p("This is an example HTML document written in "a[href="https://github.com/corgi-lang/corgi"]("Corgi")".")
21// p("Follow Nektro on Twitter @Nektro")
22// )
23// )
24
25const std = @import("std");
26const range = @import("range").range;
27
28const tokenize = @import("./tokenize.zig");
29const astgen = @import("./astgen.zig");
30
31pub fn parse(comptime input: []const u8) astgen.Value {
32 return astgen.Value{ .element = astgen.do(tokenize.do(input, &.{ '[', '=', ']', '(', ')', '{', '}' })) };
33}
34
35pub fn compile(writer: anytype, comptime value: astgen.Value, data: anytype, indent: usize, flag1: bool) anyerror!void {
36 switch (value) {
37 .element => |v| {
38 const hastext = for (v.children) |x| {
39 if (x == .string) break true;
40 } else false;
41
42 if (flag1) for (range(indent)) |_| try writer.writeAll(" ");
43 try writer.writeAll("<");
44 try writer.writeAll(v.name);
45
46 for (v.attrs) |it| {
47 try writer.print(" {s}=\"{}\"", .{ it.key, std.zig.fmtEscapes(it.value[1 .. it.value.len - 1]) });
48 }
49
50 if (v.children.len == 0) {
51 try writer.writeAll(" />\n");
52 } else {
53 try writer.writeAll(">");
54
55 if (!hastext) try writer.writeAll("\n");
56 inline for (v.children) |it| {
57 try compile(writer, it, data, indent + 1, !hastext);
58 }
59 if (!hastext) for (range(indent)) |_| try writer.writeAll(" ");
60 try writer.print("</{s}>", .{v.name});
61 if (flag1) try writer.writeAll("\n");
62 }
63 },
64 .string => |v| {
65 try writer.writeAll(v[1 .. v.len - 1]);
66 },
67 else => unreachable,
68 }
69}
src/main.zig created+26
......@@ -0,0 +1,26 @@
1const std = @import("std");
2const pek = @import("pek");
3
4const example_document =
5 \\html[lang="en"](
6 \\ head(
7 \\ title("Pek Example")
8 \\ meta[charset="UTF-8"]
9 \\ meta[name="viewport" content="width=device-width,initial-scale=1"]
10 \\ )
11 \\ body(
12 \\ h1("Pek Example")
13 \\ hr
14 \\ p("This is an example HTML document written in "a[href="https://github.com/nektro/zig-pek"]("Pek")".")
15 \\ )
16 \\)
17;
18
19pub fn main() !void {
20 std.log.info("All your codebase are belong to us.", .{});
21 std.debug.print("\n", .{});
22
23 const doc = comptime pek.parse(example_document);
24 try pek.compile(std.io.getStdErr().writer(), doc, .{}, 0, false);
25 std.debug.print("\n", .{});
26}
src/tokenize.zig created+110
......@@ -0,0 +1,110 @@
1const std = @import("std");
2const string = []const u8;
3
4//
5//
6
7pub const Token = union(enum) {
8 word: string,
9 symbol: string,
10 string: string,
11
12 pub const skippedChars = &[_]u8{ ' ', '\n', '\t', '\r' };
13};
14
15//
16//
17
18pub fn do(comptime input: string, comptime symbols: []const u8) []const Token {
19 var ret: []const Token = &[_]Token{};
20
21 var line = 1;
22 var pos = 1;
23
24 var start = 0;
25 var end = 0;
26 var mode = 0;
27
28 @setEvalBranchQuota(10001000);
29
30 inline for (input) |c, i| {
31 const s = &[_]u8{c};
32
33 var shouldFlush: bool = undefined;
34
35 blk: {
36 if (mode == 0) {
37 if (c == '#') {
38 mode = 1;
39 shouldFlush = false;
40 break :blk;
41 }
42 if (c == '"') {
43 mode = 2;
44 shouldFlush = false;
45 break :blk;
46 }
47 }
48 if (mode == 1) {
49 if (c == '\n') {
50 // skip comments
51 // f(v.handle(TTCom, in[s:i]))
52 start = i;
53 end = i;
54 mode = 0;
55 }
56 shouldFlush = c == '\n';
57 break :blk;
58 }
59 if (mode == 2) {
60 if (c == input[start]) {
61 ret = ret ++ &[_]Token{.{ .string = input[start .. i + 1] }};
62 start = i + 1;
63 end = i;
64 mode = 0;
65 }
66 shouldFlush = false;
67 break :blk;
68 }
69 if (std.mem.indexOf(u8, Token.skippedChars, s)) |_| {
70 shouldFlush = true;
71 break :blk;
72 }
73 if (std.mem.indexOf(u8, symbols, s)) |_| {
74 shouldFlush = true;
75 break :blk;
76 }
77 shouldFlush = false;
78 break :blk;
79 }
80
81 if (!shouldFlush) {
82 end += 1;
83 }
84 if (shouldFlush) {
85 if (mode == 0) {
86 if (end - start > 0) {
87 ret = ret ++ &[_]Token{.{ .word = input[start..end] }};
88 start = i;
89 end = i;
90 }
91 if (std.mem.indexOf(u8, Token.skippedChars, s)) |_| {
92 start += 1;
93 end += 1;
94 }
95 if (std.mem.indexOf(u8, symbols, s)) |_| {
96 ret = ret ++ &[_]Token{.{ .symbol = s }};
97 start += 1;
98 end += 1;
99 }
100 }
101 }
102
103 pos += 1;
104 if (c != '\n') continue;
105 line += 1;
106 pos = 1;
107 }
108
109 return ret;
110}
zig.mod created+7
......@@ -0,0 +1,7 @@
1id: gllspcgd4fa2jblyp17dspjm6cww2s6eaelmr7dme5gyv3fn
2name: pek
3main: src/lib.zig
4license: AGPL-3.0
5description: An HTML preprocessor with a builtin template engine.
6dependencies:
7 - src: git https://github.com/nektro/zig-range
zigmod.lock created+2
......@@ -0,0 +1,2 @@
12
2git https://github.com/nektro/zig-range commit-890ca308fe09b3d5c866d5cfb3b3d7a95dbf939f