| author | |
| committer | |
| log | 620dc8ce2ed5424fc6f880a47faea0f3dd767699 |
| tree | 55191528ab4819973f0bad6a3f4c898a187803f8 |
| parent | e082ea3615ca7bbfe5e88adb3c723a205b924fbd |
8 files changed, 353 insertions(+), 0 deletions(-)
.gitignore created+3| ... | @@ -0,0 +1,3 @@ | ||
| 1 | zig-* | ||
| 2 | .zigmod | ||
| 3 | deps.zig | ||
build.zig created+23| ... | @@ -0,0 +1,23 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const deps = @import("./deps.zig"); | ||
| 3 | |||
| 4 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Token = @import("./tokenize.zig").Token; | ||
| 3 | |||
| 4 | // | ||
| 5 | // | ||
| 6 | |||
| 7 | pub const Value = union(enum) { | ||
| 8 | element: Element, | ||
| 9 | attr: Attr, | ||
| 10 | string: []const u8, | ||
| 11 | }; | ||
| 12 | |||
| 13 | pub const Element = struct { | ||
| 14 | name: []const u8, | ||
| 15 | attrs: []const Attr, | ||
| 16 | children: []const Value, | ||
| 17 | }; | ||
| 18 | |||
| 19 | pub const Attr = struct { | ||
| 20 | key: []const u8, | ||
| 21 | value: []const u8, | ||
| 22 | }; | ||
| 23 | |||
| 24 | // | ||
| 25 | // | ||
| 26 | |||
| 27 | pub fn do(comptime tokens: []const Token) Element { | ||
| 28 | var parser = Parser{ .tokens = tokens, .index = 0 }; | ||
| 29 | return parser.doElement(); | ||
| 30 | } | ||
| 31 | |||
| 32 | // | ||
| 33 | // | ||
| 34 | |||
| 35 | const 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 | |||
| 25 | const std = @import("std"); | ||
| 26 | const range = @import("range").range; | ||
| 27 | |||
| 28 | const tokenize = @import("./tokenize.zig"); | ||
| 29 | const astgen = @import("./astgen.zig"); | ||
| 30 | |||
| 31 | pub fn parse(comptime input: []const u8) astgen.Value { | ||
| 32 | return astgen.Value{ .element = astgen.do(tokenize.do(input, &.{ '[', '=', ']', '(', ')', '{', '}' })) }; | ||
| 33 | } | ||
| 34 | |||
| 35 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const pek = @import("pek"); | ||
| 3 | |||
| 4 | const 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 | |||
| 19 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const string = []const u8; | ||
| 3 | |||
| 4 | // | ||
| 5 | // | ||
| 6 | |||
| 7 | pub 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 | |||
| 18 | pub 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 @@ | ||
| 1 | id: gllspcgd4fa2jblyp17dspjm6cww2s6eaelmr7dme5gyv3fn | ||
| 2 | name: pek | ||
| 3 | main: src/lib.zig | ||
| 4 | license: AGPL-3.0 | ||
| 5 | description: An HTML preprocessor with a builtin template engine. | ||
| 6 | dependencies: | ||
| 7 | - src: git https://github.com/nektro/zig-range | ||
zigmod.lock created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | 2 | ||
| 2 | git https://github.com/nektro/zig-range commit-890ca308fe09b3d5c866d5cfb3b3d7a95dbf939f | ||