diff --git a/README.md b/README.md index 0aa742511ebc6dd1a609cf6988331aa4890286e0..3003546dc131b79610300a945be6e115faf78893 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # Ix -Ix (***I***nferable E***x***pression) is a high-level, general purpose -programming language. It features a mix of Rust, Zig, and JavaScript's great -points. +Ix (`I`nferable E`x`pression) is a high-level, general purpose programming +language. It features a mix of Rust, Zig, and JavaScript's great points. For a guide on how to use Ix, see the manual included in the binary distribution. Additionally, the latest commit of the manual is hosted online at diff --git a/build.zig b/build.zig index 58f4952d58492c27162cbf6fdaa99c4d90a2c0c2..fd1ddde0fd0851b9a7bfc20743532ba72102d5d0 100644 --- a/build.zig +++ b/build.zig @@ -465,6 +465,7 @@ pub fn build(b: *Build) !void { } manual_run.addFileArg(b.path("doc/language-manual.md")); manual_run.addFileArg(b.path("src/manual/template.html")); + manual_run.addFileArg(b.path("src/manual/style.css")); const manual_out = manual_run.addOutputFileArg("language-reference.html"); const install_manual = b.addInstallFile(manual_out, "doc/README.html"); diff --git a/doc/language-manual.md b/doc/language-manual.md index b6dde2e8da040ddafad1af0001c509e367235fbf..ffca26d424414617bbe800242efbf1d5dc49b9b3 100644 --- a/doc/language-manual.md +++ b/doc/language-manual.md @@ -1,6 +1,6 @@ # Ix Programming Language -Ix (***I***nferable E***x***pression) is a high-level programming language for +Ix (**I**nferable E**x**pression) is a high-level programming language for developing web applications and games. Key goals of this project are: diff --git a/dprint.jsonc b/dprint.jsonc index a4f51e8c2bcde9c62c1998c87bbb5388fd97c48c..81aa39ca399978b005897ab94c311cb2ec05ab32 100644 --- a/dprint.jsonc +++ b/dprint.jsonc @@ -2,6 +2,7 @@ "plugins": [ // sort-lines: start "https://plugins.dprint.dev/exec-0.5.0.json@8d9972eee71fa1590e04873540421f3eda7674d0f1aae3d7c788615e7b7413d0", + "https://plugins.dprint.dev/g-plane/malva-v0.10.1.wasm", "https://plugins.dprint.dev/g-plane/markup_fmt-v0.12.0.wasm", "https://plugins.dprint.dev/json-0.19.3.wasm", "https://plugins.dprint.dev/markdown-0.17.2.wasm", diff --git a/src/manual/NaiveHtmlMinifier.zig b/src/manual/NaiveHtmlMinifier.zig index 61ed6588c769079ffee39acdbab95927534dbda3..b7b9214529081766a184534db0703aeee18c0c76 100644 --- a/src/manual/NaiveHtmlMinifier.zig +++ b/src/manual/NaiveHtmlMinifier.zig @@ -46,7 +46,16 @@ fn processHtml(m: *NaiveHtmlMinifier) !void { ' ', '\t', '\n', '\r' => unreachable, // did not skip whitespace '<' => switch (m.input[1]) { - '!' => try m.moveTokenWith(">"), + '!' => { + if (m.input[2] == '-' and m.input[3] == '-') { + // + m.input = m.input[(mem.indexOfPos(u8, m.input, 4, "-->") orelse @panic("no comment end")) + 3 ..]; + return; + } else { + // + try m.moveTokenWith(">"); + } + }, '/' => try m.moveTokenWith(">"), else => { const first_tok = getToken(m, ws); diff --git a/src/manual/generate_manual.zig b/src/manual/generate_manual.zig index 6f4ff77e5d66bbcf9dc87cc29a5bc1b2992f51c3..fe5b542200397c48a47dda1f35e145b9f67b423c 100644 --- a/src/manual/generate_manual.zig +++ b/src/manual/generate_manual.zig @@ -16,10 +16,8 @@ comptime { assert(@import("builtin").mode == .Debug); } -const cli_name = "wlang"; -const build_cli = cli_name ++ " build"; -const test_cli = cli_name ++ " test"; -const run_cli = cli_name ++ " run"; +const minify_html = true; +const minify_css = false; pub fn main() !void { comptime std.debug.assert(@import("builtin").mode == .Debug); @@ -37,6 +35,7 @@ pub fn main() !void { const runtime_path = args.next().?; const markdown_src = try std.fs.cwd().readFileAlloc(gpa, args.next().?, std.math.maxInt(usize)); const template_src = try std.fs.cwd().readFileAlloc(gpa, args.next().?, std.math.maxInt(usize)); + const style_src_raw = try std.fs.cwd().readFileAlloc(gpa, args.next().?, std.math.maxInt(usize)); const out_path = args.next().?; assert(args.next() == null); @@ -44,8 +43,6 @@ pub fn main() !void { var contents = ArrayList(u8).init(gpa); var header: HeaderWriter = .{}; - const minify = false; - try processMarkdown( gpa, progress, @@ -58,16 +55,23 @@ pub fn main() !void { try header.end(gpa); - const minified_template = if (minify) + const minified_template = if (minify_html) try NaiveHtmlMinifier.minify(template_src, gpa) else template_src; + const style_src = try std.fmt.allocPrint(gpa, "", .{style_src_raw}); + const minified_style = if (minify_css) + try NaiveHtmlMinifier.minify(style_src, gpa) + else + style_src; + const applied_template = try replaceInTemplate(gpa, minified_template, .{ .contents = contents.items, .navigation = header.data.items, .nav_h1 = "Ix Language", .repository = "https://github.com/paperdave/wl", + .style = minified_style, }); const out_file = try std.fs.createFileAbsolute(out_path, .{}); @@ -103,6 +107,7 @@ const InlineType = enum { bold, code, link, + comment, }; const MarkdownTokenizer = struct { @@ -396,7 +401,7 @@ fn processMarkdown( } var example: CodeExample = .{ - .filename = "example.wl", + .filename = "example.ix", .source = .{ .raw = code.items }, }; @@ -444,8 +449,8 @@ fn processMarkdown( example.terminal = try mem.concat(gpa, u8, &.{ "$ ", switch (tag) { - .code_run => run_cli, - .code_build => build_cli, + .code_run => "ix run", + .code_build => "ix build", else => unreachable, }, " ", @@ -592,9 +597,9 @@ fn processMarkdown( try para.appendSlice(t.advanceLine()); var next_line = t.advanceLine(); - while (t.str.len > 0 and next_line.len > 0) { + while (t.str.len > 0 and next_line.len > 0 and next_line[0] == '>') { try para.append(' '); - try para.appendSlice(next_line); + try para.appendSlice(next_line[1..]); next_line = t.advanceLine(); } @@ -609,7 +614,7 @@ fn processMarkdown( pub fn nextInlineToken(src: []const u8) ?struct { InlineType, usize } { var pos: usize = 0; while (true) : (pos += 1) { - pos = mem.indexOfAnyPos(u8, src, pos, "*`[") orelse return null; + pos = mem.indexOfAnyPos(u8, src, pos, "*`[<") orelse return null; switch (src[pos]) { '*' => switch (src[pos + 1]) { @@ -618,6 +623,10 @@ pub fn nextInlineToken(src: []const u8) ?struct { InlineType, usize } { }, '`' => return .{ .code, pos }, '[' => return .{ .link, pos }, + '<' => if (src[pos + 1] == '!' and src[pos + 2] == '-' and src[pos + 3] == '-') + return .{ .comment, pos } + else + continue, else => unreachable, } } @@ -666,6 +675,10 @@ fn processInlineMarkdown(gpa: Allocator, src: []const u8, writer: anytype) !void try writer.writeAll(""); remain = remain[href_end + 1 ..]; }, + .comment => { + const end = mem.indexOfPos(u8, remain, 4, "-->") orelse @panic("missing comment end"); + remain = remain[1 .. end + "-->".len]; + }, } } try writer.writeAll(remain); @@ -676,7 +689,7 @@ fn processInlineMarkdown(gpa: Allocator, src: []const u8, writer: anytype) !void } // TODO: Change this to use Tree-sitter, which would also make it very simple -// to highlight JavaScript code in addition to the language. +// to highlight JavaScript code in addition to Ix source code fn writeTokenizedCode(gpa: Allocator, code: []const u8, writer: anytype) !void { const code_z = try gpa.dupeZ(u8, code); defer gpa.free(code_z); diff --git a/src/manual/style.css b/src/manual/style.css new file mode 100644 index 0000000000000000000000000000000000000000..db484ff010ffab8dc922b623be556f90af98881e --- /dev/null +++ b/src/manual/style.css @@ -0,0 +1,551 @@ +:root { + /* Catppuccin Latte (light) & Mocha (dark) */ + --mantle: light-dark(#e6e9ef, #181825); + --crust: light-dark(#dce0e8, #11111b); + --base: light-dark(#eff1f5, #1e1e2e); + --surface-0: light-dark(#ccd0da, #313244); + --surface-1: light-dark(#bcc0cc, #45475a); + --surface-2: light-dark(#acb0be, #585b70); + --overlay-0: light-dark(#9ca0b0, #6c7086); + --overlay-1: light-dark(#8c8fa1, #7f849c); + --overlay-2: light-dark(#7c7f93, #9399b2); + --subtext-0: light-dark(#6c6f85, #a6adc8); + --subtext-1: light-dark(#5c5f77, #bac2de); + --text: light-dark(#4c4f69, #cdd6f4); + --accent: light-dark(#179299, #94e2d5); + --link: light-dark(#179299, #94e2d5); + + --font-text: + system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + --font-code: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + background-color: var(--base); + color: var(--text); + font-family: var(--font-text); + color-scheme: light dark; + display: flex; +} + +main { + width: 100%; + max-width: 80ch; + padding: 1rem; + padding-top: 2rem; + margin: auto; + line-height: 1.5; +} + +h1, h2, h3, h4, h5, h6, p, ul, pre, blockquote, table { + margin-bottom: 1rem; +} + +h2, h3, h4, h5, h6 { + padding-top: 0.8rem; + margin-top: -0.8rem; +} + +ul { + padding-left: 2rem; +} + +li { + margin-bottom: 0.25rem; +} + +blockquote { + background-color: var(--surface-0); + border-left: 4px solid var(--overlay-2); + padding-left: 16px; + padding-top: 8px; + padding-bottom: 8px; +} + +a { + color: inherit; +} + +table, th, td { + border: 1px solid #9ca0b0; + border-collapse: collapse; +} + +th, td { + padding: 0.5rem; +} + +.ansi-1 { + font-weight: bold; +} + +.ansi-31 { + color: light-dark(#d20f39, #f38ba8); +} + +.ansi-32 { + color: light-dark(#40a02b, #a6e3a1); +} + +.ansi-33 { + color: light-dark(#de8d1c, #f9e2af); +} + +.ansi-34 { + color: light-dark(#1e66f5, #89b4fa); +} + +.ansi-35 { + color: light-dark(#ea76cb, #f5c2e7); +} + +.ansi-36 { + color: light-dark(#179299, #94e2d5); +} + +.ansi-2 { + color: light-dark(#bcc0cc, #8087a2); +} + +.kw { + color: light-dark(#8839ef, #cba6f7); + font-weight: bold; +} + +.tok-string { + color: light-dark(#40a02b, #a6e3a1); +} + +.tok-builtin_id { + color: light-dark(#d20f39, #f38ba8); + font-weight: bold; +} + +.tok-number_int, .tok-number_float { + color: light-dark(#fe640b, #fab387); +} + +.tok-semi, +.tok-end_paren, +.tok-start_paren, +.tok-equal, +.tok-colon, +.tok-start_brace, +.tok-end_brace, +.tok-dot { + color: light-dark(#7c7f93, #9399b2); +} + +aside { + position: sticky; + top: 0; + left: 0; + height: 100dvh; + height: 100vh; +} + +#sidebar-content { + display: flex; + flex-direction: column; + background-color: var(--mantle); + height: 100%; + padding: 1rem; + border-radius: 0 1rem 1rem 0; + overflow-y: auto; + width: 40ch; +} + +#toc, #toc ul, #links { + list-style: none; +} + +nav header { + font-weight: bold; + font-size: 1.5rem; +} + +#toc { + padding: 0; +} + +#toc ul { + margin: 0rem; + padding-left: 1rem; + margin-bottom: 0; +} + +#toc li { + margin: 0; +} + +#toc ul li { + border-left: 1px solid var(--surface-2); + padding-left: 0.5rem; +} + +#toc ul li:hover { + border-left: 1px solid var(--overlay-2); +} + +#toc .number { + color: var(--accent); + font-weight: bold; +} + +aside a { + position: relative; + /* anchor point for 'aside a::before' */ + display: block; + text-decoration: none; + color: var(--text); + padding: 0.45rem 0.75rem; + border-radius: 0.25rem; +} + +/* this invisible hitbox lets you click in the left gutter */ +aside a::before { + content: " "; + position: absolute; + display: block; + right: 100%; + top: 0; + bottom: 0; + width: 100vw; +} + +aside a:hover { + background-color: var(--surface-0); +} + +#links { + list-style: none; + padding: 0; + margin-top: auto; +} + +#links li { + margin: 0; +} + +#hidden-menu-checkbox, [name="tabs"] { + display: block; + width: 1px; + height: 1px; + position: fixed; + top: -3px; + left: -3px; +} + +#menu-btn, #clickable-background { + display: none; +} + +main a { + color: var(--link); +} + +main a:active { + background-color: var(--accent); + color: var(--base); + border-radius: 0.25rem; + text-decoration: none; +} + +.external::after { + background-color: var(--link); + content: ""; + display: inline-flex; + height: 10px; + margin-left: 4px; + mask-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTYgMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTcuNSAxLjVWMEgxNnY4LjVoLTEuNVYyLjZMNyAxMC4xIDYgOWw3LjUtNy42aC02Wk0xMyAxNmMuNiAwIDEtLjQgMS0xdi01aC0xLjV2NC41aC0xMXYtMTFINlYySDFhMSAxIDAgMCAwLTEgMXYxMmMwIC42LjQgMSAxIDFoMTJaIiBmaWxsPSJyZWQiLz48L3N2Zz4="); + mask-size: cover; + width: 10px; +} +.external:active::after { + background-color: var(--base); +} + +.heading { + color: inherit; + text-decoration: none; +} + +.heading:hover, .heading:focus-visible { + text-decoration: underline; + position: relative; +} + +.heading:hover::before, .heading:focus-visible::before { + content: " #"; + color: var(--subtext-0); + position: absolute; + right: calc(100% + 0.5rem); + top: -0.125em; +} + +/* Tablet/small screen layout */ +@media screen and (max-width: 1000px) { + html { + scroll-padding-top: 5rem; + } + + main { + padding-top: 5rem; + } + + #hidden-menu-checkbox:focus-visible + aside #menu-btn { + outline: 2px solid var(--text); + } + + aside { + position: fixed; + left: -100%; + transition: left 500ms, bottom 300ms; + z-index: 9; + padding-top: 3rem; + } + + aside a { + padding: 0.5rem 0.75rem; + } + + #top-part { + position: fixed; + inset: 0; + height: fit-content; + background-color: var(--mantle); + padding: 0.5rem; + box-shadow: 0 4px 4px rgba(0, 0, 0, 0.3); + z-index: 9; + } + + #menu-btn { + display: grid; + padding: 0.5rem; + width: fit-content; + cursor: pointer; + border-radius: 0.25rem; + user-select: none; + } + + #close { + display: none; + } + + #chevron { + display: none; + } + + #menu-btn:hover { + background-color: var(--surface-0); + } + + #hidden-menu-checkbox:checked ~ aside { + left: 0; + transition: left 300ms, bottom 300ms; + } + + #hidden-menu-checkbox:checked ~ aside #clickable-background { + display: block; + position: fixed; + width: 100%; + height: 100%; + inset: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: -1; + } + + #hidden-menu-checkbox:checked ~ aside #menu-btn .open { + display: none; + } + + #hidden-menu-checkbox:checked ~ aside #menu-btn .close { + display: block; + } +} + +/* Mobile layout */ +@media screen and (max-width: 600px) { + main { + padding: 3rem 1rem; + } + + aside { + position: fixed; + top: initial; + left: 0; + bottom: calc(-100% + 6rem); + width: 100%; + display: flex; + flex-direction: column; + } + + aside a { + padding: 0.8rem 0.75rem; + } + + aside a:hover { + background-color: var(--surface-1); + } + + #sidebar-content { + border-radius: 0px; + padding: 1rem; + width: 100%; + background-color: var(--surface-0); + } + + #top-part { + position: initial; + padding: 0; + border-radius: 1.5rem 1.5rem 0 0; + box-shadow: 0 -4px 5px rgba(0, 0, 0, 0.15); + border-bottom: var(--overlay-2) 1px solid; + background-color: var(--surface-0); + } + + #open, #close { + display: none !important; + } + + #chevron { + display: block; + transition: transform 300ms; + } + + #menu-btn { + display: flex; + justify-content: center; + width: 100%; + padding: 1.5rem 0; + border-radius: 1.5rem 1.5rem 0 0; + } + + #hidden-menu-checkbox:checked ~ aside { + bottom: 0; + } + + #hidden-menu-checkbox:checked ~ aside #chevron { + transform: rotateX(180deg); + } + + #hidden-menu-checkbox:checked ~ aside #top-part { + box-shadow: none; + } +} + +pre > code { + border: none; + padding: 0; +} + +pre, code { + border: 0; + background-color: transparent; + font-family: var(--font-code); + font-size: 0.9rem; +} + +.file { + border: 1px solid var(--surface-1); + background-color: var(--crust); + border-radius: 0.5rem; + overflow: hidden; + margin-bottom: 0.5rem; +} + +.file figcaption { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + border-bottom: 1px solid var(--surface-1); + /* background-color: var(--crust); */ + background-color: var(--accent); + color: var(--base); +} + +.file pre { + margin: 0; + padding: 0.75rem 0.5rem; + overflow-y: scroll; + color: var(--text); + position: relative; +} + +.file pre.terminal { + background-color: #24273a; + color: #cdd6f4; + border-radius: 0 0 0.5rem 0.5rem; +} + +.file [role="tablist"] { + display: flex; + border-top: 1px solid var(--surface-1); + border-bottom: 1px solid var(--surface-1); + background-color: var(--surface-0); +} + +.file [role="tab"] { + padding: 0.25rem 0.75rem; + cursor: pointer; + /* flex: 1; */ + border-top: 2px solid transparent; + text-align: center; + font-size: 0.9rem; +} + +.file .bottom { + display: none; +} + +[name="tabs"][value="output"]:checked ~ main .file .tabs .output { + display: block; +} + +[name="tabs"][value="output"]:checked + ~ main + .file + .tabs + [role="tab"][for="output"] { + font-weight: bold; + background-color: var(--base); + border-top: 2px solid var(--accent); +} + +[name="tabs"][value="js"]:checked ~ main .file .tabs .js { + display: block; +} + +[name="tabs"][value="js"]:checked ~ main .file .tabs [role="tab"][for="js"] { + font-weight: bold; + border-top: 2px solid var(--accent); + background-color: var(--base); +} + +.file .copy-button { + position: absolute; + top: 0.25rem; + right: 0.25rem; + padding: 0.25rem; + border: 1px solid var(--surface-1); + border-radius: 0.25rem; + background-color: var(--base); + cursor: pointer; + display: grid; +} + +.file .copy-button:hover { + background-color: var(--crust); +} + +.file .copy-button .hidden { + display: none; +} diff --git a/src/manual/template.html b/src/manual/template.html index 39cc4cd5d1a8825a55c0808cc9d02b4c5334c972..31f077c3371d04e33113372fa16db616cc650e5a 100644 --- a/src/manual/template.html +++ b/src/manual/template.html @@ -3,581 +3,10 @@
+