Rewrote and optimized

This commit is contained in:
Logan 2024-04-28 17:06:53 -05:00
parent dd7995ac31
commit 39092ab198
56 changed files with 2293 additions and 1235 deletions

395
Cargo.lock generated
View file

@ -3,55 +3,45 @@
version = 3
[[package]]
name = "block-buffer"
version = "0.10.4"
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"generic-array",
"cfg-if",
"getrandom",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
[[package]]
name = "cc"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "equivalent"
version = "1.0.1"
@ -59,13 +49,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "generic-array"
version = "0.14.7"
name = "getrandom"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
dependencies = [
"typenum",
"version_check",
"cfg-if",
"libc",
"wasi",
]
[[package]]
@ -78,41 +69,38 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
name = "html"
version = "0.1.0"
dependencies = [
"html_parser",
"toml",
"walkdir",
]
[[package]]
name = "html_parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f56db07b6612644f6f7719f8ef944f75fff9d6378fdf3d316fd32194184abd"
dependencies = [
"doc-comment",
"pest",
"pest_derive",
"serde",
"serde_derive",
"serde_json",
"thiserror",
"inkjet",
"v_htmlescape",
]
[[package]]
name = "indexmap"
version = "2.2.5"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.10"
name = "inkjet"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
checksum = "b44a14d77fc5879e5137be9beee1401ca24038be727ca1b4b68bc81705f98b40"
dependencies = [
"ahash",
"anyhow",
"cc",
"once_cell",
"serde",
"thiserror",
"toml 0.7.8",
"toml 0.8.12",
"tree-sitter",
"tree-sitter-highlight",
"v_htmlescape",
]
[[package]]
name = "libc"
@ -122,9 +110,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "memchr"
version = "2.7.1"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "once_cell"
@ -132,115 +120,73 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "pest"
version = "2.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546"
dependencies = [
"memchr",
"thiserror",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]]
name = "proc-macro2"
version = "1.0.78"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.17"
name = "regex"
version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [
"winapi-util",
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "serde"
version = "1.0.197"
name = "regex-automata"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "serde"
version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.5"
@ -250,22 +196,11 @@ dependencies = [
"serde",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "syn"
version = "2.0.51"
version = "2.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
dependencies = [
"proc-macro2",
"quote",
@ -274,18 +209,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.57"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.57"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
@ -294,14 +229,26 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.10"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
"toml_edit 0.19.15",
]
[[package]]
name = "toml"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.22.12",
]
[[package]]
@ -315,28 +262,50 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.6"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
"winnow 0.5.40",
]
[[package]]
name = "typenum"
version = "1.17.0"
name = "toml_edit"
version = "0.22.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow 0.6.6",
]
[[package]]
name = "ucd-trie"
version = "0.1.6"
name = "tree-sitter"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d"
dependencies = [
"cc",
"regex",
]
[[package]]
name = "tree-sitter-highlight"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "042342584c5a7a0b833d9fc4e2bdab3f9868ddc6c4b339a1e01451c6720868bc"
dependencies = [
"regex",
"thiserror",
"tree-sitter",
]
[[package]]
name = "unicode-ident"
@ -344,6 +313,12 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "v_htmlescape"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c"
[[package]]
name = "version_check"
version = "0.9.4"
@ -351,51 +326,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.5.0"
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winnow"
version = "0.6.5"
version = "0.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
dependencies = [
"memchr",
]
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -6,6 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
html_parser = "0.7"
toml = "0.8"
walkdir = "2.5"
inkjet = "0.10"
v_htmlescape = "0.15"
[profiles.dev.package.inkjet]
opt-level = 3

View file

@ -1,7 +1,6 @@
<A>
<a href=@href>
<bold>
<tp:children />
</bold>
<a>
<@children />
<@children />
</a>
</A>

View file

@ -1,5 +0,0 @@
[compile]
output="dist"
templates="tp"
source="src"
minify=false

35
hyper-build/home.html Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,88 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><meta content="width=device-width, initial-scale=1" name="viewport"/><link type="image/x-icon" rel="icon" href="resources/favicon.svg"/><!-- --><!-- --></head><body><div class="pure-g"><div class="home-menu pure-menu pure-menu-horizontal"><ul class="pure-menu-list"><li class="pure-menu-item"><a class="pure-menu-link" href="/home"> Home </a></li><li class="pure-menu-item"><a href="/projects" class="pure-menu-link"> Projects </a></li><li class="pure-menu-item"><a href="/blog" class="pure-menu-link"> Blog </a></li><li class="pure-menu-item"><a class="pure-menu-link" href="/about"> About </a></li></ul></div><div class="pure-u-1-1"><div class="header"><h1> Rust HTML Templating Engine </h1><h2> Front End - Parser Design </h2></div><div class="content"><h2 class="distinct"> Motivation </h2>
HTML is a powerful tool for creating static web pages, but
is not easily modular or scalable. Front end frameworks like
React provide reusable components. I want to use components
without the overhead of a front-end framework. I want the final
output to be statically generated rather than generated on the fly
by the server or the client, minified and with inlined CSS and
JavaScript.
<h2 class="distinct"> Approach </h2>
After looking at some of the HTML parsing libraries, I was
unsatisfied with the approaches most developers took. The DOM
is represented as a tree structure, where each node keeps a
pointer to its children.
<div class="code-block">
<pre><code>
<span class="comment">&#x2f;&#x2f; From the html_parser crate</span>
<span class="keyword">pub</span> <span class="keyword storage type">enum</span> <span class="type">Node</span> <span class="punctuation bracket">{</span>
<span class="type enum variant">Text</span><span class="punctuation bracket">(</span><span class="type">String</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
<span class="type enum variant">Element</span><span class="punctuation bracket">(</span><span class="type">Element</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
<span class="type enum variant">Comment</span><span class="punctuation bracket">(</span><span class="type">String</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
<span class="punctuation bracket">}</span>
<span class="keyword">pub</span> <span class="keyword storage type">struct</span> <span class="type">Element</span> <span class="punctuation bracket">{</span>
<span class="keyword">pub</span> <span class="variable other member">name</span>: <span class="type">String</span><span class="punctuation delimiter">,</span>
<span class="keyword">pub</span> <span class="variable other member">attributes</span>: <span class="type">HashMap</span><span class="punctuation bracket">&lt;</span><span class="type">String</span><span class="punctuation delimiter">,</span> <span class="type">Option</span><span class="punctuation bracket">&lt;</span><span class="type">String</span><span class="punctuation bracket">&gt;</span><span class="punctuation bracket">&gt;</span><span class="punctuation delimiter">,</span>
<span class="keyword">pub</span> <span class="variable other member">children</span>: <span class="type">Vec</span><span class="punctuation bracket">&lt;</span><span class="type">Node</span><span class="punctuation bracket">&gt;</span><span class="punctuation delimiter">,</span>
<span class="comment">&#x2f;&#x2f; other fields omitted</span>
<span class="punctuation bracket">}</span>
</code></pre>
</div>
This is bad for cache locality and effectively forces the use
of recursion in order to traverse the DOM. Recursion is undesirable
for performance and robustness reasons. My approach is to create a
flat array of elements.
<div class="code-block">
<pre><code>
<span class="comment">&#x2f;&#x2f; My implementation (fields omitted)</span>
<span class="keyword">pub</span> <span class="keyword storage type">enum</span> <span class="type">HtmlElement</span> <span class="punctuation bracket">{</span>
<span class="type enum variant">DocType</span><span class="punctuation delimiter">,</span>
<span class="type enum variant">Comment</span><span class="punctuation bracket">(</span><span class="comment">&#x2f;* *&#x2f;</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
<span class="type enum variant">OpenTag</span> <span class="punctuation bracket">{</span> <span class="comment">&#x2f;* *&#x2f;</span> <span class="punctuation bracket">}</span><span class="punctuation delimiter">,</span>
<span class="type enum variant">CloseTag</span> <span class="punctuation bracket">{</span> <span class="comment">&#x2f;* *&#x2f;</span> <span class="punctuation bracket">}</span><span class="punctuation delimiter">,</span>
<span class="type enum variant">Text</span><span class="punctuation bracket">(</span> <span class="comment">&#x2f;* *&#x2f;</span> <span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
<span class="type enum variant">Script</span> <span class="punctuation bracket">{</span> <span class="comment">&#x2f;* *&#x2f;</span> <span class="punctuation bracket">}</span><span class="punctuation delimiter">,</span>
<span class="type enum variant">Style</span> <span class="punctuation bracket">{</span> <span class="comment">&#x2f;* *&#x2f;</span> <span class="punctuation bracket">}</span><span class="punctuation delimiter">,</span>
<span class="type enum variant">Directive</span> <span class="punctuation bracket">{</span> <span class="comment">&#x2f;* *&#x2f;</span> <span class="punctuation bracket">}</span><span class="punctuation delimiter">,</span>
<span class="punctuation bracket">}</span>
</code></pre>
</div>
This approach lends itself to linear iterative algorithms. An
element's children can be represented as a slice of the DOM array,
from the opening tag to its close tag.
<h2 class="distinct"> Templates </h2>
I define a "template" as a user defined element which expands into
a larger piece of HTML. Templates can pass on children and attributes
to the final output. Some special templates can perform other functions,
like inlining a CSS file or syntax highlighting a code block.
<div class="code-block">
<pre><code>
<span class="comment">&lt;!-- Here is an example of a template definition --&gt;</span>
<span class="punctuation bracket">&lt;</span><span class="tag">Echo</span><span class="punctuation bracket">&gt;</span>
<span class="comment">&lt;!--
This means to inherit the &#x27;class&#x27; attribute from the invocation
--&gt;</span>
<span class="punctuation bracket">&lt;</span><span class="tag">h1</span> <span class="attribute">class</span><span class="punctuation delimiter">=</span><span class="string">@class</span><span class="punctuation bracket">&gt;</span>
<span class="comment">&lt;!--
This means place the children of the invocation here
--&gt;</span>
<span class="punctuation bracket">&lt;</span>@children<span class="punctuation bracket">&#x2f;&gt;</span>
<span class="punctuation bracket">&lt;&#x2f;</span><span class="tag">h1</span><span class="punctuation bracket">&gt;</span>
<span class="punctuation bracket">&lt;</span><span class="tag">h2</span><span class="punctuation bracket">&gt;</span>
<span class="comment">&lt;!-- This means to inherit the &#x27;href&#x27; attribute from the invocation --&gt;</span>
<span class="punctuation bracket">&lt;</span><span class="tag">a</span> <span class="attribute">href</span><span class="punctuation delimiter">=</span><span class="string">&quot;</span><span class="string">@href</span><span class="string">&quot;</span><span class="punctuation bracket">&gt;</span>
<span class="comment">&lt;!-- Children can be duplicated any number of times --&gt;</span>
<span class="punctuation bracket">&lt;</span>@children<span class="punctuation bracket">&#x2f;&gt;</span>
<span class="punctuation bracket">&lt;&#x2f;</span><span class="tag">a</span><span class="punctuation bracket">&gt;</span>
<span class="punctuation bracket">&lt;&#x2f;</span><span class="tag">h2</span><span class="punctuation bracket">&gt;</span>
<span class="comment">&lt;!--
Attributes can also be duplciated, and boolean attributes
work exactly as expected
--&gt;</span>
<span class="punctuation bracket">&lt;</span><span class="tag">h3</span> <span class="attribute">class</span><span class="punctuation delimiter">=</span><span class="string">@class</span> <span class="attribute">hidden</span><span class="punctuation delimiter">=</span><span class="string">@hidden</span><span class="punctuation bracket">&gt;</span>
<span class="punctuation bracket">&lt;</span>@children<span class="punctuation bracket">&#x2f;&gt;</span>
<span class="punctuation bracket">&lt;&#x2f;</span><span class="tag">h3</span><span class="punctuation bracket">&gt;</span>
<span class="punctuation bracket">&lt;&#x2f;</span><span class="tag">Echo</span><span class="punctuation bracket">&gt;</span>
<span class="comment">&lt;!-- End of template definition --&gt;</span>
</code></pre>
</div></div></div></div></body></html>

View file

@ -0,0 +1,56 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><meta content="width=device-width, initial-scale=1" name="viewport"/><link type="image/x-icon" href="resources/favicon.svg" rel="icon"/><!-- --><!-- --></head><body><div class="pure-g"><div class="home-menu pure-menu pure-menu-horizontal"><ul class="pure-menu-list"><li class="pure-menu-item"><a href="/home" class="pure-menu-link"> Home </a></li><li class="pure-menu-item"><a href="/projects" class="pure-menu-link"> Projects </a></li><li class="pure-menu-item"><a href="/blog" class="pure-menu-link"> Blog </a></li><li class="pure-menu-item"><a href="/about" class="pure-menu-link"> About </a></li></ul></div><div class="pure-u-1-1"><div class="header"><h1> Rust HTTP Server </h1><h2> Keywords: Back End - TCP - SSL </h2></div><div class="content"><h2 class="distinct"> Motivation </h2>
As a systems programmer, I want to understand the technologies
I use at a very low level. Modern web stacks abstract away the
process of handling TCP connections, serving SSL certificates,
and parsing HTTP requests. The best way to understand a technology
is to implement it from the ground up, I studied the HTTP 1.1
specification and wrote a server to host my website.
<h2 class="distinct"> Architecture </h2>
At the most basic level, the server will translate the request URL
into a local file path, and respond with the file matching that path
if it exists. Request URLs are sanitized to prevent accessing files
outside of the server's directory. Two tables are used for rewriting
and routing URLs. This makes handling URLs predictable and robust.
Because I route all traffic through Cloudflare, I decided to use its
caching feature rather than implement a cache locally.
<div class="code-block">
<pre><code>
<span class="comment">&#x2f;&#x2f; Snippet from the request handling code</span>
<span class="keyword">pub</span> <span class="keyword">async</span> <span class="keyword function">fn</span> <span class="function">construct_response</span><span class="punctuation bracket">(</span><span class="keyword storage modifier">&amp;</span><span class="variable builtin">self</span><span class="punctuation delimiter">,</span> <span class="variable parameter">request_url</span>: <span class="keyword storage modifier">&amp;</span><span class="type builtin">str</span><span class="punctuation bracket">)</span> <span class="operator">-&gt;</span> <span class="type">Response</span> <span class="punctuation bracket">{</span>
<span class="keyword storage">let</span> <span class="variable">sanitized_url</span> <span class="operator">=</span> <span class="function">rewrite_url</span><span class="punctuation bracket">(</span><span class="variable parameter">request_url</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">;</span>
<span class="keyword control conditional">if</span> <span class="variable">sanitized_url</span> <span class="operator">!=</span> <span class="variable parameter">request_url</span> <span class="punctuation bracket">{</span>
<span class="keyword control return">return</span> <span class="type">Self</span><span class="punctuation delimiter">::</span><span class="function">redirect</span><span class="punctuation bracket">(</span><span class="operator">&amp;</span><span class="variable">sanitized_url</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">;</span>
<span class="punctuation bracket">}</span>
<span class="keyword storage">let</span> <span class="variable">routed_url</span> <span class="operator">=</span> <span class="variable builtin">self</span><span class="punctuation delimiter">.</span><span class="function">perform_routing</span><span class="punctuation bracket">(</span><span class="operator">&amp;</span><span class="variable">sanitized_url</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">.</span><span class="keyword control return">await</span><span class="punctuation delimiter">;</span>
<span class="keyword control conditional">if</span> <span class="keyword storage">let</span> <span class="constructor">Some</span><span class="punctuation bracket">(</span><span class="variable">response</span><span class="punctuation bracket">)</span> <span class="operator">=</span>
<span class="variable builtin">self</span><span class="punctuation delimiter">.</span><span class="function">perform_redirecting</span><span class="punctuation bracket">(</span><span class="operator">&amp;</span><span class="variable">routed_url</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">.</span><span class="keyword control return">await</span> <span class="punctuation bracket">{</span>
<span class="keyword control return">return</span> <span class="variable">response</span><span class="punctuation delimiter">;</span>
<span class="punctuation bracket">}</span>
<span class="keyword control conditional">match</span> <span class="variable builtin">self</span><span class="punctuation delimiter">.</span><span class="function">get_resource</span><span class="punctuation bracket">(</span><span class="operator">&amp;</span><span class="variable">routed_url</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">.</span><span class="keyword control return">await</span> <span class="punctuation bracket">{</span>
<span class="constructor">Some</span><span class="punctuation bracket">(</span><span class="variable">bytes</span><span class="punctuation bracket">)</span> <span class="operator">=&gt;</span> <span class="type">Response</span><span class="punctuation delimiter">::</span><span class="function">from_data</span><span class="punctuation bracket">(</span><span class="variable">bytes</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
<span class="constructor">None</span> <span class="operator">=&gt;</span> <span class="type">Self</span><span class="punctuation delimiter">::</span><span class="function">not_found</span><span class="punctuation bracket">(</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
<span class="punctuation bracket">}</span>
<span class="punctuation bracket">}</span>
</code></pre>
</div><h2 class="distinct"> Dependencies </h2>
My goal with this project was to provide a functional server using as few
libraries as possible. The final project directly depends on four crates:
<div class="code-block">
<pre><code>
&#x2f;&#x2f; Snippet from Cargo<span class="punctuation delimiter">.</span>toml
<span class="punctuation bracket">[</span><span class="type">dependencies</span><span class="punctuation bracket">]</span>
<span class="variable other member">log</span> <span class="operator">=</span> <span class="string">&quot;0.4&quot;</span>
<span class="variable other member">log4rs</span> <span class="operator">=</span> <span class="string">&quot;1.3&quot;</span>
<span class="variable other member">tiny_http</span> <span class="operator">=</span> <span class="string">&quot;0.12&quot;</span>
<span class="variable other member">tokio</span> <span class="operator">=</span> <span class="punctuation bracket">{</span><span class="variable other member">version</span> <span class="operator">=</span> <span class="string">&quot;1&quot;</span><span class="punctuation delimiter">,</span> <span class="variable other member">features</span> <span class="operator">=</span> <span class="punctuation bracket">[</span><span class="string">&quot;full&quot;</span><span class="punctuation bracket">]</span><span class="punctuation bracket">}</span>
</code></pre>
</div>
The first two crates, log and log4rs, provide logging functions which aren't critical to the
server's functionality. The tiny_http crate provides a simple wrapper for the standard
library TCP stream, and performs SSL encryption. It is used as the backbone
for many Rust web frameworks. While I could implement these features myself, I
decided against it for security and browser compatibility reasons. Finally there is
tokio, an async runtime. This is necessary because Rust does not provide a runtime
by default, and building one is out of scope.
</div></div></div></body></html>

Binary file not shown.

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-award" viewBox="0 0 16 16">
<path d="M9.669.864 8 0 6.331.864l-1.858.282-.842 1.68-1.337 1.32L2.6 6l-.306 1.854 1.337 1.32.842 1.68 1.858.282L8 12l1.669-.864 1.858-.282.842-1.68 1.337-1.32L13.4 6l.306-1.854-1.337-1.32-.842-1.68zm1.196 1.193.684 1.365 1.086 1.072L12.387 6l.248 1.506-1.086 1.072-.684 1.365-1.51.229L8 10.874l-1.355-.702-1.51-.229-.684-1.365-1.086-1.072L3.614 6l-.25-1.506 1.087-1.072.684-1.365 1.51-.229L8 1.126l1.356.702z"/>
<path d="M4 11.794V16l4-1 4 1v-4.206l-2.018.306L8 13.126 6.018 12.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 620 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#FFF" d="m115.679 69.288-15.591-8.94-2.689-1.163-63.781.436v32.381h82.061z"/><path fill="#F38020" d="M87.295 89.022c.763-2.617.472-5.015-.8-6.796-1.163-1.635-3.125-2.58-5.488-2.689l-44.737-.581c-.291 0-.545-.145-.691-.363s-.182-.509-.109-.8c.145-.436.581-.763 1.054-.8l45.137-.581c5.342-.254 11.157-4.579 13.192-9.885l2.58-6.723c.109-.291.145-.581.073-.872-2.906-13.158-14.644-22.97-28.672-22.97-12.938 0-23.913 8.359-27.838 19.952a13.35 13.35 0 0 0-9.267-2.58c-6.215.618-11.193 5.597-11.811 11.811-.145 1.599-.036 3.162.327 4.615C10.104 70.051 2 78.337 2 88.549c0 .909.073 1.817.182 2.726a.895.895 0 0 0 .872.763h82.57c.472 0 .909-.327 1.054-.8l.617-2.216z"/><path fill="#FAAE40" d="M101.542 60.275c-.4 0-.836 0-1.236.036-.291 0-.545.218-.654.509l-1.744 6.069c-.763 2.617-.472 5.015.8 6.796 1.163 1.635 3.125 2.58 5.488 2.689l9.522.581c.291 0 .545.145.691.363.145.218.182.545.109.8-.145.436-.581.763-1.054.8l-9.924.582c-5.379.254-11.157 4.579-13.192 9.885l-.727 1.853c-.145.363.109.727.509.727h34.089c.4 0 .763-.254.872-.654.581-2.108.909-4.325.909-6.614 0-13.447-10.975-24.422-24.458-24.422"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-ethernet" viewBox="0 0 16 16">
<path d="M14 13.5v-7a.5.5 0 0 0-.5-.5H12V4.5a.5.5 0 0 0-.5-.5h-1v-.5A.5.5 0 0 0 10 3H6a.5.5 0 0 0-.5.5V4h-1a.5.5 0 0 0-.5.5V6H2.5a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5M3.75 11h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m2 0h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m1.75.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25zM9.75 11h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m1.75.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25z"/>
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM1 2a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1z"/>
</svg>

After

Width:  |  Height:  |  Size: 993 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill="#181616"><path fill-rule="evenodd" clip-rule="evenodd" d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"/><path d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path d="M114.906 84.145s-.172-1.04-.27-1.028l-18.823 1.817a3.062 3.062 0 00-2.77 2.84l-.516 7.413-14.566 1.04-.988-6.72a3.089 3.089 0 00-3.04-2.62H54.067a3.089 3.089 0 00-3.039 2.62l-.988 6.72-14.566-1.04-.516-7.414a3.058 3.058 0 00-2.77-2.84l-18.835-1.816c-.094-.012-.168 1.028-.266 1.028l-.024 4.074 15.954 2.574.52 7.477a3.084 3.084 0 002.843 2.847l20.059 1.434c.078.004.152.008.226.008a3.087 3.087 0 003.031-2.621l1.02-6.915h14.57l1.02 6.915a3.088 3.088 0 003.254 2.613l20.062-1.434a3.084 3.084 0 002.844-2.847l.52-7.477 15.945-2.586zm0 0" fill="#fff"/><path d="M13.086 53.422v30.723c.059 0 .113.003.168.007L32.09 85.97a2.027 2.027 0 011.828 1.875l.582 8.316 16.426 1.172 1.133-7.672a2.03 2.03 0 012.007-1.734h19.868a2.03 2.03 0 012.007 1.734l1.133 7.672 16.43-1.172.578-8.316a2.027 2.027 0 011.828-1.875l18.828-1.817c.055-.004.11-.007.168-.007V81.69h.008V53.42c2.652-3.335 5.16-7.019 7.086-10.116-2.941-5.008-6.543-9.48-10.395-13.625a101.543 101.543 0 00-10.316 6.004c-1.64-1.633-3.484-2.965-5.3-4.36-1.782-1.43-3.79-2.48-5.696-3.703.566-4.223.848-8.379.96-12.719-4.913-2.476-10.155-4.113-15.456-5.293-2.117 3.559-4.055 7.41-5.738 11.176-2-.332-4.008-.457-6.02-.48V20.3c-.016 0-.027.004-.039.004s-.023-.004-.04-.004v.004c-2.01.023-4.019.148-6.019.48-1.683-3.765-3.62-7.617-5.738-11.176-5.3 1.18-10.543 2.817-15.457 5.293.113 4.34.395 8.496.961 12.72-1.906 1.222-3.914 2.273-5.695 3.702-1.813 1.395-3.66 2.727-5.301 4.36a101.543 101.543 0 00-10.316-6.004C12.543 33.824 8.94 38.297 6 43.305c2.313 3.629 4.793 7.273 7.086 10.117zm0 0" fill="#478cbf"/><path d="M98.008 89.84l-.582 8.36a2.024 2.024 0 01-1.88 1.878l-20.062 1.434c-.046.004-.097.004-.144.004-.996 0-1.86-.73-2.004-1.73l-1.152-7.806H55.816l-1.152 7.805a2.026 2.026 0 01-2.148 1.727l-20.063-1.434a2.024 2.024 0 01-1.879-1.879l-.582-8.36-16.937-1.632c.008 1.82.03 3.816.03 4.211 0 17.887 22.692 26.484 50.88 26.582h.07c28.188-.098 50.871-8.695 50.871-26.582 0-.402.024-2.39.031-4.211zm0 0" fill="#478cbf"/><path d="M48.652 65.895c0 6.27-5.082 11.351-11.351 11.351-6.266 0-11.348-5.082-11.348-11.351 0-6.266 5.082-11.344 11.348-11.344 6.27 0 11.351 5.078 11.351 11.344" fill="#fff"/><path d="M45.922 66.566a7.531 7.531 0 01-7.535 7.532 7.534 7.534 0 01-7.535-7.532 7.534 7.534 0 017.535-7.53 7.531 7.531 0 017.535 7.53" fill="#414042"/><path d="M64 78.277c-2.02 0-3.652-1.488-3.652-3.32v-10.45c0-1.831 1.632-3.32 3.652-3.32 2.016 0 3.656 1.489 3.656 3.32v10.45c0 1.832-1.64 3.32-3.656 3.32m15.348-12.382c0 6.27 5.082 11.351 11.351 11.351 6.266 0 11.348-5.082 11.348-11.351 0-6.266-5.082-11.344-11.348-11.344-6.27 0-11.351 5.078-11.351 11.344" fill="#fff"/><path d="M82.078 66.566a7.53 7.53 0 007.531 7.532 7.531 7.531 0 100-15.063 7.53 7.53 0 00-7.53 7.531" fill="#414042"/></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#E44D26" d="M19.037 113.876L9.032 1.661h109.936l-10.016 112.198-45.019 12.48z"/><path fill="#F16529" d="M64 116.8l36.378-10.086 8.559-95.878H64z"/><path fill="#EBEBEB" d="M64 52.455H45.788L44.53 38.361H64V24.599H29.489l.33 3.692 3.382 37.927H64zm0 35.743l-.061.017-15.327-4.14-.979-10.975H33.816l1.928 21.609 28.193 7.826.063-.017z"/><path fill="#fff" d="M63.952 52.455v13.763h16.947l-1.597 17.849-15.35 4.143v14.319l28.215-7.82.207-2.325 3.234-36.233.335-3.696h-3.708zm0-27.856v13.762h33.244l.276-3.092.628-6.978.329-3.692z"/></svg>

After

Width:  |  Height:  |  Size: 607 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#0074BD" d="M52.581 67.817s-3.284 1.911 2.341 2.557c6.814.778 10.297.666 17.805-.753 0 0 1.979 1.237 4.735 2.309-16.836 7.213-38.104-.418-24.881-4.113zm-2.059-9.415s-3.684 2.729 1.945 3.311c7.28.751 13.027.813 22.979-1.103 0 0 1.373 1.396 3.536 2.157-20.352 5.954-43.021.469-28.46-4.365z"/><path fill="#EA2D2E" d="M67.865 42.431c4.151 4.778-1.088 9.074-1.088 9.074s10.533-5.437 5.696-12.248c-4.519-6.349-7.982-9.502 10.771-20.378.001 0-29.438 7.35-15.379 23.552z"/><path fill="#0074BD" d="M90.132 74.781s2.432 2.005-2.678 3.555c-9.716 2.943-40.444 3.831-48.979.117-3.066-1.335 2.687-3.187 4.496-3.576 1.887-.409 2.965-.334 2.965-.334-3.412-2.403-22.055 4.719-9.469 6.762 34.324 5.563 62.567-2.506 53.665-6.524zm-35.97-26.134s-15.629 3.713-5.534 5.063c4.264.57 12.758.439 20.676-.225 6.469-.543 12.961-1.704 12.961-1.704s-2.279.978-3.93 2.104c-15.874 4.175-46.533 2.23-37.706-2.038 7.463-3.611 13.533-3.2 13.533-3.2zM82.2 64.317c16.135-8.382 8.674-16.438 3.467-15.353-1.273.266-1.845.496-1.845.496s.475-.744 1.378-1.063c10.302-3.62 18.223 10.681-3.322 16.345 0 0 .247-.224.322-.425z"/><path fill="#EA2D2E" d="M72.474 1.313s8.935 8.939-8.476 22.682c-13.962 11.027-3.184 17.313-.006 24.498-8.15-7.354-14.128-13.828-10.118-19.852 5.889-8.842 22.204-13.131 18.6-27.328z"/><path fill="#0074BD" d="M55.749 87.039c15.484.99 39.269-.551 39.832-7.878 0 0-1.082 2.777-12.799 4.981-13.218 2.488-29.523 2.199-39.191.603 0 0 1.98 1.64 12.158 2.294z"/><path fill="#EA2D2E" d="M94.866 100.181h-.472v-.264h1.27v.264h-.47v1.317h-.329l.001-1.317zm2.535.066h-.006l-.468 1.251h-.216l-.465-1.251h-.005v1.251h-.312v-1.581h.457l.431 1.119.432-1.119h.454v1.581h-.302v-1.251zm-44.19 14.79c-1.46 1.266-3.004 1.978-4.391 1.978-1.974 0-3.045-1.186-3.045-3.085 0-2.055 1.146-3.56 5.738-3.56h1.697v4.667h.001zm4.031 4.548v-14.077c0-3.599-2.053-5.973-6.997-5.973-2.886 0-5.416.714-7.473 1.622l.592 2.493c1.62-.595 3.715-1.147 5.771-1.147 2.85 0 4.075 1.147 4.075 3.521v1.779h-1.424c-6.921 0-10.044 2.685-10.044 6.723 0 3.479 2.058 5.456 5.933 5.456 2.49 0 4.351-1.028 6.088-2.533l.316 2.137h3.163v-.001zm13.452 0h-5.027l-6.051-19.689h4.391l3.756 12.099.835 3.635c1.896-5.258 3.24-10.596 3.912-15.733h4.271c-1.143 6.481-3.203 13.598-6.087 19.688zm19.288-4.548c-1.465 1.266-3.01 1.978-4.392 1.978-1.976 0-3.046-1.186-3.046-3.085 0-2.055 1.149-3.56 5.736-3.56h1.701v4.667h.001zm4.033 4.548v-14.077c0-3.599-2.059-5.973-6.999-5.973-2.889 0-5.418.714-7.475 1.622l.593 2.493c1.62-.595 3.718-1.147 5.774-1.147 2.846 0 4.074 1.147 4.074 3.521v1.779h-1.424c-6.923 0-10.045 2.685-10.045 6.723 0 3.479 2.056 5.456 5.93 5.456 2.491 0 4.349-1.028 6.091-2.533l.318 2.137h3.163v-.001zm-56.693 3.346c-1.147 1.679-3.005 3.008-5.037 3.757l-1.989-2.345c1.547-.794 2.872-2.075 3.489-3.269.532-1.063.753-2.43.753-5.701V92.891h4.284v22.173c0 4.375-.348 6.144-1.5 7.867z"/></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#FFD845" d="M49.33 62h29.159C86.606 62 93 55.132 93 46.981V19.183c0-7.912-6.632-13.856-14.555-15.176-5.014-.835-10.195-1.215-15.187-1.191-4.99.023-9.612.448-13.805 1.191C37.098 6.188 35 10.758 35 19.183V30h29v4H23.776c-8.484 0-15.914 5.108-18.237 14.811-2.681 11.12-2.8 17.919 0 29.53C7.614 86.983 12.569 93 21.054 93H31V79.952C31 70.315 39.428 62 49.33 62zm-1.838-39.11c-3.026 0-5.478-2.479-5.478-5.545 0-3.079 2.451-5.581 5.478-5.581 3.015 0 5.479 2.502 5.479 5.581-.001 3.066-2.465 5.545-5.479 5.545zm74.789 25.921C120.183 40.363 116.178 34 107.682 34H97v12.981C97 57.031 88.206 65 78.489 65H49.33C41.342 65 35 72.326 35 80.326v27.8c0 7.91 6.745 12.564 14.462 14.834 9.242 2.717 17.994 3.208 29.051 0C85.862 120.831 93 116.549 93 108.126V97H64v-4h43.682c8.484 0 11.647-5.776 14.599-14.66 3.047-9.145 2.916-17.799 0-29.529zm-41.955 55.606c3.027 0 5.479 2.479 5.479 5.547 0 3.076-2.451 5.579-5.479 5.579-3.015 0-5.478-2.502-5.478-5.579 0-3.068 2.463-5.547 5.478-5.547z"/></svg>

After

Width:  |  Height:  |  Size: 1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 MiB

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 107.62 107.62"><defs><style>
.cls-1 {
fill: #654ff0;
}
</style></defs><title>web-assembly-icon</title><g id="Layer_2" data-name="Layer 2"><g id="Notch_-_Purple" data-name="Notch - Purple"><g id="icon"><path class="cls-1" d="M66.12,0c0,.19,0,.38,0,.58a12.34,12.34,0,1,1-24.68,0c0-.2,0-.39,0-.58H0V107.62H107.62V0ZM51.38,96.1,46.14,70.17H46L40.39,96.1H33.18L25,58h7.13L37,83.93h.09L42.94,58h6.67L54.9,84.25H55L60.55,58h7L58.46,96.1Zm39.26,0-2.43-8.48H75.4L73.53,96.1H66.36L75.59,58H86.83L98,96.1Z"/><polygon class="cls-1" points="79.87 67.39 76.76 81.37 86.44 81.37 82.87 67.39 79.87 67.39"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 685 B

View file

@ -0,0 +1,231 @@
@font-face {
font-family: Cascadia;
src: url("resources/CascadiaMonoPL.woff2");
}
body {
color: #444;
font-size: 1.2em;
}
.pure-img-responsive { max-width: 100%; height: auto;
}
.cascadia {
font-family: Cascadia;
}
.home-menu {
padding: 0.5em;
text-align: center;
box-shadow: 0 1px 1px rgba(0,0,0, 0.10);
}
.home-menu {
background: #333;
}
.home-menu a {
color: #ccc;
}
.home-menu li a:hover,
.home-menu li a:focus {
animation: menu-hover-anim 0.25s ease-out;
animation-fill-mode: forwards;
background: none;
border: none;
}
.link {
color: #00f;
text-decoration: none;
}
.video {
text-align: center;
}
.video iframe {
width: 32em;
height: 18em;
frameborder: "0";
}
/*
Add transition to containers so they can push in and out.
*/
#layout,
#menu,
.menu-link {
-webkit-transition: all 0.2s ease-out;
-moz-transition: all 0.2s ease-out;
-ms-transition: all 0.2s ease-out;
-o-transition: all 0.2s ease-out;
transition: all 0.2s ease-out;
}
/*
This is the parent `<div>` that contains the menu and the content area.
*/
#layout {
position: relative;
left: 0;
padding-left: 0;
}
#layout.active #menu {
left: 150px;
width: 150px;
}
#layout.active .menu-link {
left: 150px;
}
/*
The content `<div>` is where all your content goes.
*/
.content {
margin: 0 auto;
padding: 0 2em;
max-width: 800px;
margin-bottom: 50px;
line-height: 1.6em;
}
.header {
margin: 0;
color: #444;
text-align: center;
padding: 2.5em 4em 0;
border-bottom: 1px solid #eee;
}
.header h1 {
margin: 0.2em 0;
font-size: 2.5em;
font-weight: bold;
}
.header h2 {
font-weight: 300;
color: #888;
padding: 0;
margin-top: 0;
}
.distinct {
background-color: #444;
color: #fff;
padding: 0.5em 0.5em 0.5em 0.5em;
}
@keyframes card-hover-anim {
to {background-color: #ccf;}
}
@keyframes card-text-hover-anim {
to {color: #222;}
}
.card {
background-color: #eee;
padding: 0.5em 0.5em 0.5em 0.5em;
margin: 1em 0em 1em 0em;
text-decoration: none;
}
.card h1 {
color: #444;
}
.card h2 {
color: #777;
}
.card h3 {
color: #999;
}
.card a {
text-decoration: none;
display: block;
}
.card:hover {
animation: card-hover-anim 0.25s ease-out;
animation-fill-mode: forwards;
}
/* .card:hover h3 { */
/* animation: card-text-hover-anim 0.25s ease-out; */
/* animation-fill-mode: forwards; */
/* } */
.card img {
vertical-align: bottom;
float: right;
padding: 0em 0.1em 0em 0.1em;
width: 60px;
}
.card svg {
vertical-align: bottom;
float: right;
padding: 0em 0.1em 0em 0.1em;
width: 60px;
}
.content-subhead {
margin: 50px 0 20px 0;
font-weight: 300;
color: #444;
}
.code-block {
background-color: #fdf6e3;
color: #002b36;
font-family: Cascadia;
padding: 0em 0.5em 0em 0.5em;
margin: 1em 0em 1em 0em;
page-break-inside: avoid;
display: block;
overflow: auto;
word-wrap: break-word;
max-width: 100%;
}
.code-block pre {
display: block;
margin: 0 0 0 0;
padding: 0 0 0 0;
}
.code-block pre code {
font-family: Cascadia;
display: block;
white-space: pre-wrap;
}
.code-block span {
background-color: #fdf6e3;
}
.keyword {
color: #6c71c4;
}
.constant {
color: #cb4b16;
}
.function {
color: #268bd2;
}
.operator {
color: #6c71c4;
}
.punctuation {
color: #586e75;
}
.string {
color: #859900;
}
.type {
color: #2aa198;
}
.property {
color: #586e75;
}

File diff suppressed because one or more lines are too long

101
hyper-src/home.html Normal file
View file

@ -0,0 +1,101 @@
<Container>
<div class="header">
<h1 id="demo" />
<h2> Completed Projects </h2>
</div>
<script>
var i = 0;
var txt = 'Welcome to my home page';
var speed = 30;
function typeWriter() {
if (i < txt.length) {
document.getElementById("demo").innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter, speed + Math.random() * 25.0);
}
}
typeWriter()
</script>
<div class="content">
<Card href="http-server.html">
<RustIcon />
<CloudflareIcon />
<h1> HTTP Server </h1>
<h3> Back End - TCP - SSL </h3>
<h2>
Currently serving you this website
</h2>
</Card>
<Card>
<RustIcon />
<HtmlIcon />
<h1> HTML Templating Engine </h1>
<h3> Front End - Parser Design </h3>
<h2>
Used to create this website
</h2>
</Card>
<Card href="html-templating.html">
<RustIcon />
<WasmIcon />
<h1> Forte Assembly Language </h1>
<h3> Programming Language - Hackathon </h3>
<h2>
Radically different machine code. A creative-coding endeavor
</h2>
</Card>
<Card>
<RustIcon />
<WasmIcon />
<h1> Fishbowl </h1>
<h3> Image Encoding - Hardware Rendering </h3>
<h2> Kinematic image processing with GPU acceleration </h2>
</Card>
<Card>
<RustIcon />
<WasmIcon />
<h1> Math Interpreter </h1>
<h3> Parser Design </h3>
<h2>
Interpret and evaluate plain-text math expressions
</h2>
</Card>
<Card>
<RustIcon />
<h1> nd-range </h1>
<h3> Vector Math - Standard Library </h3>
<h2>
An extension of Rust's 'Range' type
using the Cartesian Product Algorithm
</h2>
</Card>
<Card>
<RustIcon />
<h1> Fractal Explorer </h1>
<h3> Parallel Algorithms - Optimization - Hackathon </h3>
<h2>
A Mandelbrot Fractal viewer using CPU parallelism
and the derivative bail algorithm
</h2>
</Card>
<Card>
<PythonIcon />
<h1> Pokédex </h1>
<h3> TKinter - Web APIs - Native UI </h3>
<h2>
A TKinter app for viewing the original Pokédex, with
stats scraped from online sources
</h2>
</Card>
<Card>
<PythonIcon />
<h1> Stock Trading A.I. </h1>
<h3> Command Line App - Web APIs </h3>
<h2>
A simple heuristic trading algorithm
</h2>
</Card>
</div>
</Container>

View file

@ -0,0 +1,90 @@
<Container>
<div class="header">
<h1> Rust HTML Templating Engine </h1>
<h2> Front End - Parser Design </h2>
</div>
<div class="content">
<h2 class="distinct"> Motivation </h2>
HTML is a powerful tool for creating static web pages, but
is not easily modular or scalable. Front end frameworks like
React provide reusable components. I want to use components
without the overhead of a front-end framework. I want the final
output to be statically generated rather than generated on the fly
by the server or the client, minified and with inlined CSS and
JavaScript.
<h2 class="distinct"> Approach </h2>
After looking at some of the HTML parsing libraries, I was
unsatisfied with the approaches most developers took. The DOM
is represented as a tree structure, where each node keeps a
pointer to its children.
<@code lang="rust">
// From the html_parser crate
pub enum Node {
Text(String),
Element(Element),
Comment(String),
}
pub struct Element {
pub name: String,
pub attributes: HashMap<String, Option<String>>,
pub children: Vec<Node>,
// other fields omitted
}
</@code>
This is bad for cache locality and effectively forces the use
of recursion in order to traverse the DOM. Recursion is undesirable
for performance and robustness reasons. My approach is to create a
flat array of elements.
<@code lang="rust">
// My implementation (fields omitted)
pub enum HtmlElement {
DocType,
Comment(/* */),
OpenTag { /* */ },
CloseTag { /* */ },
Text( /* */ ),
Script { /* */ },
Style { /* */ },
Directive { /* */ },
}
</@code>
This approach lends itself to linear iterative algorithms. An
element's children can be represented as a slice of the DOM array,
from the opening tag to its close tag.
<h2 class="distinct"> Templates </h2>
I define a "template" as a user defined element which expands into
a larger piece of HTML. Templates can pass on children and attributes
to the final output. Some special templates can perform other functions,
like inlining a CSS file or syntax highlighting a code block.
<@code lang="html">
<!-- Here is an example of a template definition -->
<Echo>
<!--
This means to inherit the 'class' attribute from the invocation
-->
<h1 class=@class>
<!--
This means place the children of the invocation here
-->
<@children/>
</h1>
<h2>
<!-- This means to inherit the 'href' attribute from the invocation -->
<a href="@href">
<!-- Children can be duplicated any number of times -->
<@children/>
</a>
</h2>
<!--
Attributes can also be duplciated, and boolean attributes
work exactly as expected
-->
<h3 class=@class hidden=@hidden>
<@children/>
</h3>
</Echo>
<!-- End of template definition -->
</@code>
</div>
</Container>

View file

@ -0,0 +1,60 @@
<Container>
<div class="header">
<h1> Rust HTTP Server </h1>
<h2> Keywords: Back End - TCP - SSL </h2>
</div>
<div class="content">
<h2 class="distinct"> Motivation </h2>
As a systems programmer, I want to understand the technologies
I use at a very low level. Modern web stacks abstract away the
process of handling TCP connections, serving SSL certificates,
and parsing HTTP requests. The best way to understand a technology
is to implement it from the ground up, I studied the HTTP 1.1
specification and wrote a server to host my website.
<h2 class="distinct"> Architecture </h2>
At the most basic level, the server will translate the request URL
into a local file path, and respond with the file matching that path
if it exists. Request URLs are sanitized to prevent accessing files
outside of the server's directory. Two tables are used for rewriting
and routing URLs. This makes handling URLs predictable and robust.
Because I route all traffic through Cloudflare, I decided to use its
caching feature rather than implement a cache locally.
<@code lang="rust">
// Snippet from the request handling code
pub async fn construct_response(&self, request_url: &str) -> Response {
let sanitized_url = rewrite_url(request_url);
if sanitized_url != request_url {
return Self::redirect(&sanitized_url);
}
let routed_url = self.perform_routing(&sanitized_url).await;
if let Some(response) =
self.perform_redirecting(&routed_url).await {
return response;
}
match self.get_resource(&routed_url).await {
Some(bytes) => Response::from_data(bytes),
None => Self::not_found(),
}
}
</@code>
<h2 class="distinct"> Dependencies </h2>
My goal with this project was to provide a functional server using as few
libraries as possible. The final project directly depends on four crates:
<@code lang=toml>
// Snippet from Cargo.toml
[dependencies]
log = "0.4"
log4rs = "1.3"
tiny_http = "0.12"
tokio = {version = "1", features = ["full"]}
</@code>
The first two crates, log and log4rs, provide logging functions which aren't critical to the
server's functionality. The tiny_http crate provides a simple wrapper for the standard
library TCP stream, and performs SSL encryption. It is used as the backbone
for many Rust web frameworks. While I could implement these features myself, I
decided against it for security and browser compatibility reasons. Finally there is
tokio, an async runtime. This is necessary because Rust does not provide a runtime
by default, and building one is out of scope.
</div>
</Container>

Binary file not shown.

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-award" viewBox="0 0 16 16">
<path d="M9.669.864 8 0 6.331.864l-1.858.282-.842 1.68-1.337 1.32L2.6 6l-.306 1.854 1.337 1.32.842 1.68 1.858.282L8 12l1.669-.864 1.858-.282.842-1.68 1.337-1.32L13.4 6l.306-1.854-1.337-1.32-.842-1.68zm1.196 1.193.684 1.365 1.086 1.072L12.387 6l.248 1.506-1.086 1.072-.684 1.365-1.51.229L8 10.874l-1.355-.702-1.51-.229-.684-1.365-1.086-1.072L3.614 6l-.25-1.506 1.087-1.072.684-1.365 1.51-.229L8 1.126l1.356.702z"/>
<path d="M4 11.794V16l4-1 4 1v-4.206l-2.018.306L8 13.126 6.018 12.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 620 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#FFF" d="m115.679 69.288-15.591-8.94-2.689-1.163-63.781.436v32.381h82.061z"/><path fill="#F38020" d="M87.295 89.022c.763-2.617.472-5.015-.8-6.796-1.163-1.635-3.125-2.58-5.488-2.689l-44.737-.581c-.291 0-.545-.145-.691-.363s-.182-.509-.109-.8c.145-.436.581-.763 1.054-.8l45.137-.581c5.342-.254 11.157-4.579 13.192-9.885l2.58-6.723c.109-.291.145-.581.073-.872-2.906-13.158-14.644-22.97-28.672-22.97-12.938 0-23.913 8.359-27.838 19.952a13.35 13.35 0 0 0-9.267-2.58c-6.215.618-11.193 5.597-11.811 11.811-.145 1.599-.036 3.162.327 4.615C10.104 70.051 2 78.337 2 88.549c0 .909.073 1.817.182 2.726a.895.895 0 0 0 .872.763h82.57c.472 0 .909-.327 1.054-.8l.617-2.216z"/><path fill="#FAAE40" d="M101.542 60.275c-.4 0-.836 0-1.236.036-.291 0-.545.218-.654.509l-1.744 6.069c-.763 2.617-.472 5.015.8 6.796 1.163 1.635 3.125 2.58 5.488 2.689l9.522.581c.291 0 .545.145.691.363.145.218.182.545.109.8-.145.436-.581.763-1.054.8l-9.924.582c-5.379.254-11.157 4.579-13.192 9.885l-.727 1.853c-.145.363.109.727.509.727h34.089c.4 0 .763-.254.872-.654.581-2.108.909-4.325.909-6.614 0-13.447-10.975-24.422-24.458-24.422"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-ethernet" viewBox="0 0 16 16">
<path d="M14 13.5v-7a.5.5 0 0 0-.5-.5H12V4.5a.5.5 0 0 0-.5-.5h-1v-.5A.5.5 0 0 0 10 3H6a.5.5 0 0 0-.5.5V4h-1a.5.5 0 0 0-.5.5V6H2.5a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5M3.75 11h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m2 0h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m1.75.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25zM9.75 11h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m1.75.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25z"/>
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM1 2a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1z"/>
</svg>

After

Width:  |  Height:  |  Size: 993 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill="#181616"><path fill-rule="evenodd" clip-rule="evenodd" d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"/><path d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path d="M114.906 84.145s-.172-1.04-.27-1.028l-18.823 1.817a3.062 3.062 0 00-2.77 2.84l-.516 7.413-14.566 1.04-.988-6.72a3.089 3.089 0 00-3.04-2.62H54.067a3.089 3.089 0 00-3.039 2.62l-.988 6.72-14.566-1.04-.516-7.414a3.058 3.058 0 00-2.77-2.84l-18.835-1.816c-.094-.012-.168 1.028-.266 1.028l-.024 4.074 15.954 2.574.52 7.477a3.084 3.084 0 002.843 2.847l20.059 1.434c.078.004.152.008.226.008a3.087 3.087 0 003.031-2.621l1.02-6.915h14.57l1.02 6.915a3.088 3.088 0 003.254 2.613l20.062-1.434a3.084 3.084 0 002.844-2.847l.52-7.477 15.945-2.586zm0 0" fill="#fff"/><path d="M13.086 53.422v30.723c.059 0 .113.003.168.007L32.09 85.97a2.027 2.027 0 011.828 1.875l.582 8.316 16.426 1.172 1.133-7.672a2.03 2.03 0 012.007-1.734h19.868a2.03 2.03 0 012.007 1.734l1.133 7.672 16.43-1.172.578-8.316a2.027 2.027 0 011.828-1.875l18.828-1.817c.055-.004.11-.007.168-.007V81.69h.008V53.42c2.652-3.335 5.16-7.019 7.086-10.116-2.941-5.008-6.543-9.48-10.395-13.625a101.543 101.543 0 00-10.316 6.004c-1.64-1.633-3.484-2.965-5.3-4.36-1.782-1.43-3.79-2.48-5.696-3.703.566-4.223.848-8.379.96-12.719-4.913-2.476-10.155-4.113-15.456-5.293-2.117 3.559-4.055 7.41-5.738 11.176-2-.332-4.008-.457-6.02-.48V20.3c-.016 0-.027.004-.039.004s-.023-.004-.04-.004v.004c-2.01.023-4.019.148-6.019.48-1.683-3.765-3.62-7.617-5.738-11.176-5.3 1.18-10.543 2.817-15.457 5.293.113 4.34.395 8.496.961 12.72-1.906 1.222-3.914 2.273-5.695 3.702-1.813 1.395-3.66 2.727-5.301 4.36a101.543 101.543 0 00-10.316-6.004C12.543 33.824 8.94 38.297 6 43.305c2.313 3.629 4.793 7.273 7.086 10.117zm0 0" fill="#478cbf"/><path d="M98.008 89.84l-.582 8.36a2.024 2.024 0 01-1.88 1.878l-20.062 1.434c-.046.004-.097.004-.144.004-.996 0-1.86-.73-2.004-1.73l-1.152-7.806H55.816l-1.152 7.805a2.026 2.026 0 01-2.148 1.727l-20.063-1.434a2.024 2.024 0 01-1.879-1.879l-.582-8.36-16.937-1.632c.008 1.82.03 3.816.03 4.211 0 17.887 22.692 26.484 50.88 26.582h.07c28.188-.098 50.871-8.695 50.871-26.582 0-.402.024-2.39.031-4.211zm0 0" fill="#478cbf"/><path d="M48.652 65.895c0 6.27-5.082 11.351-11.351 11.351-6.266 0-11.348-5.082-11.348-11.351 0-6.266 5.082-11.344 11.348-11.344 6.27 0 11.351 5.078 11.351 11.344" fill="#fff"/><path d="M45.922 66.566a7.531 7.531 0 01-7.535 7.532 7.534 7.534 0 01-7.535-7.532 7.534 7.534 0 017.535-7.53 7.531 7.531 0 017.535 7.53" fill="#414042"/><path d="M64 78.277c-2.02 0-3.652-1.488-3.652-3.32v-10.45c0-1.831 1.632-3.32 3.652-3.32 2.016 0 3.656 1.489 3.656 3.32v10.45c0 1.832-1.64 3.32-3.656 3.32m15.348-12.382c0 6.27 5.082 11.351 11.351 11.351 6.266 0 11.348-5.082 11.348-11.351 0-6.266-5.082-11.344-11.348-11.344-6.27 0-11.351 5.078-11.351 11.344" fill="#fff"/><path d="M82.078 66.566a7.53 7.53 0 007.531 7.532 7.531 7.531 0 100-15.063 7.53 7.53 0 00-7.53 7.531" fill="#414042"/></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#E44D26" d="M19.037 113.876L9.032 1.661h109.936l-10.016 112.198-45.019 12.48z"/><path fill="#F16529" d="M64 116.8l36.378-10.086 8.559-95.878H64z"/><path fill="#EBEBEB" d="M64 52.455H45.788L44.53 38.361H64V24.599H29.489l.33 3.692 3.382 37.927H64zm0 35.743l-.061.017-15.327-4.14-.979-10.975H33.816l1.928 21.609 28.193 7.826.063-.017z"/><path fill="#fff" d="M63.952 52.455v13.763h16.947l-1.597 17.849-15.35 4.143v14.319l28.215-7.82.207-2.325 3.234-36.233.335-3.696h-3.708zm0-27.856v13.762h33.244l.276-3.092.628-6.978.329-3.692z"/></svg>

After

Width:  |  Height:  |  Size: 607 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#0074BD" d="M52.581 67.817s-3.284 1.911 2.341 2.557c6.814.778 10.297.666 17.805-.753 0 0 1.979 1.237 4.735 2.309-16.836 7.213-38.104-.418-24.881-4.113zm-2.059-9.415s-3.684 2.729 1.945 3.311c7.28.751 13.027.813 22.979-1.103 0 0 1.373 1.396 3.536 2.157-20.352 5.954-43.021.469-28.46-4.365z"/><path fill="#EA2D2E" d="M67.865 42.431c4.151 4.778-1.088 9.074-1.088 9.074s10.533-5.437 5.696-12.248c-4.519-6.349-7.982-9.502 10.771-20.378.001 0-29.438 7.35-15.379 23.552z"/><path fill="#0074BD" d="M90.132 74.781s2.432 2.005-2.678 3.555c-9.716 2.943-40.444 3.831-48.979.117-3.066-1.335 2.687-3.187 4.496-3.576 1.887-.409 2.965-.334 2.965-.334-3.412-2.403-22.055 4.719-9.469 6.762 34.324 5.563 62.567-2.506 53.665-6.524zm-35.97-26.134s-15.629 3.713-5.534 5.063c4.264.57 12.758.439 20.676-.225 6.469-.543 12.961-1.704 12.961-1.704s-2.279.978-3.93 2.104c-15.874 4.175-46.533 2.23-37.706-2.038 7.463-3.611 13.533-3.2 13.533-3.2zM82.2 64.317c16.135-8.382 8.674-16.438 3.467-15.353-1.273.266-1.845.496-1.845.496s.475-.744 1.378-1.063c10.302-3.62 18.223 10.681-3.322 16.345 0 0 .247-.224.322-.425z"/><path fill="#EA2D2E" d="M72.474 1.313s8.935 8.939-8.476 22.682c-13.962 11.027-3.184 17.313-.006 24.498-8.15-7.354-14.128-13.828-10.118-19.852 5.889-8.842 22.204-13.131 18.6-27.328z"/><path fill="#0074BD" d="M55.749 87.039c15.484.99 39.269-.551 39.832-7.878 0 0-1.082 2.777-12.799 4.981-13.218 2.488-29.523 2.199-39.191.603 0 0 1.98 1.64 12.158 2.294z"/><path fill="#EA2D2E" d="M94.866 100.181h-.472v-.264h1.27v.264h-.47v1.317h-.329l.001-1.317zm2.535.066h-.006l-.468 1.251h-.216l-.465-1.251h-.005v1.251h-.312v-1.581h.457l.431 1.119.432-1.119h.454v1.581h-.302v-1.251zm-44.19 14.79c-1.46 1.266-3.004 1.978-4.391 1.978-1.974 0-3.045-1.186-3.045-3.085 0-2.055 1.146-3.56 5.738-3.56h1.697v4.667h.001zm4.031 4.548v-14.077c0-3.599-2.053-5.973-6.997-5.973-2.886 0-5.416.714-7.473 1.622l.592 2.493c1.62-.595 3.715-1.147 5.771-1.147 2.85 0 4.075 1.147 4.075 3.521v1.779h-1.424c-6.921 0-10.044 2.685-10.044 6.723 0 3.479 2.058 5.456 5.933 5.456 2.49 0 4.351-1.028 6.088-2.533l.316 2.137h3.163v-.001zm13.452 0h-5.027l-6.051-19.689h4.391l3.756 12.099.835 3.635c1.896-5.258 3.24-10.596 3.912-15.733h4.271c-1.143 6.481-3.203 13.598-6.087 19.688zm19.288-4.548c-1.465 1.266-3.01 1.978-4.392 1.978-1.976 0-3.046-1.186-3.046-3.085 0-2.055 1.149-3.56 5.736-3.56h1.701v4.667h.001zm4.033 4.548v-14.077c0-3.599-2.059-5.973-6.999-5.973-2.889 0-5.418.714-7.475 1.622l.593 2.493c1.62-.595 3.718-1.147 5.774-1.147 2.846 0 4.074 1.147 4.074 3.521v1.779h-1.424c-6.923 0-10.045 2.685-10.045 6.723 0 3.479 2.056 5.456 5.93 5.456 2.491 0 4.349-1.028 6.091-2.533l.318 2.137h3.163v-.001zm-56.693 3.346c-1.147 1.679-3.005 3.008-5.037 3.757l-1.989-2.345c1.547-.794 2.872-2.075 3.489-3.269.532-1.063.753-2.43.753-5.701V92.891h4.284v22.173c0 4.375-.348 6.144-1.5 7.867z"/></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#FFD845" d="M49.33 62h29.159C86.606 62 93 55.132 93 46.981V19.183c0-7.912-6.632-13.856-14.555-15.176-5.014-.835-10.195-1.215-15.187-1.191-4.99.023-9.612.448-13.805 1.191C37.098 6.188 35 10.758 35 19.183V30h29v4H23.776c-8.484 0-15.914 5.108-18.237 14.811-2.681 11.12-2.8 17.919 0 29.53C7.614 86.983 12.569 93 21.054 93H31V79.952C31 70.315 39.428 62 49.33 62zm-1.838-39.11c-3.026 0-5.478-2.479-5.478-5.545 0-3.079 2.451-5.581 5.478-5.581 3.015 0 5.479 2.502 5.479 5.581-.001 3.066-2.465 5.545-5.479 5.545zm74.789 25.921C120.183 40.363 116.178 34 107.682 34H97v12.981C97 57.031 88.206 65 78.489 65H49.33C41.342 65 35 72.326 35 80.326v27.8c0 7.91 6.745 12.564 14.462 14.834 9.242 2.717 17.994 3.208 29.051 0C85.862 120.831 93 116.549 93 108.126V97H64v-4h43.682c8.484 0 11.647-5.776 14.599-14.66 3.047-9.145 2.916-17.799 0-29.529zm-41.955 55.606c3.027 0 5.479 2.479 5.479 5.547 0 3.076-2.451 5.579-5.479 5.579-3.015 0-5.478-2.502-5.478-5.579 0-3.068 2.463-5.547 5.478-5.547z"/></svg>

After

Width:  |  Height:  |  Size: 1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 MiB

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 107.62 107.62"><defs><style>
.cls-1 {
fill: #654ff0;
}
</style></defs><title>web-assembly-icon</title><g id="Layer_2" data-name="Layer 2"><g id="Notch_-_Purple" data-name="Notch - Purple"><g id="icon"><path class="cls-1" d="M66.12,0c0,.19,0,.38,0,.58a12.34,12.34,0,1,1-24.68,0c0-.2,0-.39,0-.58H0V107.62H107.62V0ZM51.38,96.1,46.14,70.17H46L40.39,96.1H33.18L25,58h7.13L37,83.93h.09L42.94,58h6.67L54.9,84.25H55L60.55,58h7L58.46,96.1Zm39.26,0-2.43-8.48H75.4L73.53,96.1H66.36L75.59,58H86.83L98,96.1Z"/><polygon class="cls-1" points="79.87 67.39 76.76 81.37 86.44 81.37 82.87 67.39 79.87 67.39"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 685 B

231
hyper-src/styles/extend.css Normal file
View file

@ -0,0 +1,231 @@
@font-face {
font-family: Cascadia;
src: url("resources/CascadiaMonoPL.woff2");
}
body {
color: #444;
font-size: 1.2em;
}
.pure-img-responsive { max-width: 100%; height: auto;
}
.cascadia {
font-family: Cascadia;
}
.home-menu {
padding: 0.5em;
text-align: center;
box-shadow: 0 1px 1px rgba(0,0,0, 0.10);
}
.home-menu {
background: #333;
}
.home-menu a {
color: #ccc;
}
.home-menu li a:hover,
.home-menu li a:focus {
animation: menu-hover-anim 0.25s ease-out;
animation-fill-mode: forwards;
background: none;
border: none;
}
.link {
color: #00f;
text-decoration: none;
}
.video {
text-align: center;
}
.video iframe {
width: 32em;
height: 18em;
frameborder: "0";
}
/*
Add transition to containers so they can push in and out.
*/
#layout,
#menu,
.menu-link {
-webkit-transition: all 0.2s ease-out;
-moz-transition: all 0.2s ease-out;
-ms-transition: all 0.2s ease-out;
-o-transition: all 0.2s ease-out;
transition: all 0.2s ease-out;
}
/*
This is the parent `<div>` that contains the menu and the content area.
*/
#layout {
position: relative;
left: 0;
padding-left: 0;
}
#layout.active #menu {
left: 150px;
width: 150px;
}
#layout.active .menu-link {
left: 150px;
}
/*
The content `<div>` is where all your content goes.
*/
.content {
margin: 0 auto;
padding: 0 2em;
max-width: 800px;
margin-bottom: 50px;
line-height: 1.6em;
}
.header {
margin: 0;
color: #444;
text-align: center;
padding: 2.5em 4em 0;
border-bottom: 1px solid #eee;
}
.header h1 {
margin: 0.2em 0;
font-size: 2.5em;
font-weight: bold;
}
.header h2 {
font-weight: 300;
color: #888;
padding: 0;
margin-top: 0;
}
.distinct {
background-color: #444;
color: #fff;
padding: 0.5em 0.5em 0.5em 0.5em;
}
@keyframes card-hover-anim {
to {background-color: #ccf;}
}
@keyframes card-text-hover-anim {
to {color: #222;}
}
.card {
background-color: #eee;
padding: 0.5em 0.5em 0.5em 0.5em;
margin: 1em 0em 1em 0em;
text-decoration: none;
}
.card h1 {
color: #444;
}
.card h2 {
color: #777;
}
.card h3 {
color: #999;
}
.card a {
text-decoration: none;
display: block;
}
.card:hover {
animation: card-hover-anim 0.25s ease-out;
animation-fill-mode: forwards;
}
/* .card:hover h3 { */
/* animation: card-text-hover-anim 0.25s ease-out; */
/* animation-fill-mode: forwards; */
/* } */
.card img {
vertical-align: bottom;
float: right;
padding: 0em 0.1em 0em 0.1em;
width: 60px;
}
.card svg {
vertical-align: bottom;
float: right;
padding: 0em 0.1em 0em 0.1em;
width: 60px;
}
.content-subhead {
margin: 50px 0 20px 0;
font-weight: 300;
color: #444;
}
.code-block {
background-color: #fdf6e3;
color: #002b36;
font-family: Cascadia;
padding: 0em 0.5em 0em 0.5em;
margin: 1em 0em 1em 0em;
page-break-inside: avoid;
display: block;
overflow: auto;
word-wrap: break-word;
max-width: 100%;
}
.code-block pre {
display: block;
margin: 0 0 0 0;
padding: 0 0 0 0;
}
.code-block pre code {
font-family: Cascadia;
display: block;
white-space: pre-wrap;
}
.code-block span {
background-color: #fdf6e3;
}
.keyword {
color: #6c71c4;
}
.constant {
color: #cb4b16;
}
.function {
color: #268bd2;
}
.operator {
color: #6c71c4;
}
.punctuation {
color: #586e75;
}
.string {
color: #859900;
}
.type {
color: #2aa198;
}
.property {
color: #586e75;
}

11
hyper-src/styles/main.css Normal file

File diff suppressed because one or more lines are too long

View file

View file

@ -1,5 +0,0 @@
[compile]
output="dist"
templates="tp"
source="src"
minify=false

View file

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<link "asdf">
<lg:include rel='css' href="./style.css" />
</head>
<body>
<A href="fdfs"> asdf </A>
</body>
</html>

354
src/compile.rs Normal file
View file

@ -0,0 +1,354 @@
use crate::trace::*;
use std::{collections::HashMap, io::Write, path::Path, rc::Rc};
use crate::parse::*;
type Lexeme = HtmlElement;
pub const RECURSION_LIMIT: usize = 256;
#[derive(Debug, Clone)]
pub struct Element {
attributes: Attributes,
child_span: Vec<Lexeme>,
}
pub type Templates = HashMap<String, Element>;
fn parse_element<'a>(tail: &'a [Lexeme]) -> Option<(Element, &'a [Lexeme])> {
// Parse first element
let (_, attributes) = match tail.first() {
Some(lm) => match lm {
Lexeme::OpenTag {
attributes,
is_empty: true,
..
} => {
return Some((
Element {
attributes: attributes.clone(),
child_span: vec![],
},
&tail[1..],
))
},
Lexeme::OpenTag {
name,
attributes,
is_empty: false,
} => (name, attributes),
_ => return None,
},
None => return None,
};
let mut depth = 0;
let mut end = 1;
for lm in tail {
match lm {
Lexeme::OpenTag { is_empty, .. } => {
if !is_empty {
depth += 1;
}
},
Lexeme::CloseTag { .. } => {
depth -= 1;
if depth == 0 {
break;
}
},
_ => {
if depth == 0 {
return None;
}
},
}
end += 1;
}
Some((
Element {
attributes: attributes.clone(),
child_span: tail[1..end - 1].to_vec(),
},
&tail[end..],
))
}
pub fn parse_templates(source: impl AsRef<str>) -> Result<Templates> {
let mut tail: &[Lexeme] = &parse_html(source.as_ref())?;
let mut new_templates: Templates = Default::default();
while let Some(lm) = tail.first() {
match lm {
Lexeme::OpenTag {
name,
is_empty: false,
..
} => {
let (head, new_tail) = parse_element(tail)
.ctx(format!("at template definition {}", name))?;
new_templates.insert(name.clone(), head);
tail = new_tail;
},
// Ignore comments
Lexeme::Comment(_) => {
tail = &tail[1..];
},
_ => {
eprintln!("[WARNING] Unexpected root node: {}", lm.serialize());
tail = &tail[1..];
},
}
}
Ok(new_templates)
}
pub fn parse_templates_file(path: impl AsRef<Path>) -> Result<Templates> {
let file = read_file(path.as_ref()).ctx(format!("opening templates file"))?;
parse_templates(file).ctx(format!("in file {}", path.as_ref().display()))
}
fn expand_template(
base: &Element,
template: &Element,
output: &mut Vec<Lexeme>,
) {
for element in &template.child_span {
match element {
Lexeme::OpenTag {
name,
is_empty,
attributes,
} => {
let mut new_attributes = HashMap::new();
for (key, value) in attributes {
if let Some(at_key) = value.strip_prefix('@') {
if let Some(at_value) = base.attributes.get(at_key) {
new_attributes.insert(at_key.to_string(), at_value.clone());
}
} else {
new_attributes.insert(key.into(), value.into());
}
}
// for (key, value) in attributes {
// // If value is non-null
// if let Some(id) = value {
// // If value is '@' prefixed
// if let Some(variable_name) = id.strip_prefix('@') {
// // If key exists in base
// if let Some(new_value) =
// base.attributes.get(variable_name) {
// new_attributes.insert(key.clone(),
// new_value.clone()); }
// // Otherwise discard variable
// }
// // Insert non-null non-variable value
// else {
// new_attributes.insert(key.clone(), value.clone());
// }
// } else {
// // Insert null non-variable value
// new_attributes.insert(key.clone(), value.clone());
// }
// }
output.push(Lexeme::OpenTag {
name: name.clone(),
attributes: new_attributes,
is_empty: *is_empty,
});
},
Lexeme::Directive { name, .. } => {
if name == "children" {
output.append(&mut base.child_span.to_vec());
} else {
output.push(element.clone());
}
},
_ => output.push(element.clone()),
}
}
}
fn compilation_pass(
mut source: &[Lexeme],
templates: &Templates,
) -> Result<(Vec<Lexeme>, usize)> {
let mut output: Vec<Lexeme> = vec![];
let mut num_expanded = 0;
while let Some(lm) = source.first() {
match lm {
Lexeme::OpenTag { name, .. } => {
if let Some(tmp) = templates.get(name) {
num_expanded += 1;
let (base, new_tail) =
parse_element(source).ctx(format!("at template usage {}", name))?;
expand_template(&base, tmp, &mut output);
source = new_tail;
continue;
} else {
output.push(lm.clone());
}
},
_ => output.push(lm.clone()),
}
source = &source[1..];
}
Ok((output, num_expanded))
}
pub fn compile_source(
source: impl AsRef<str>,
templates: &Templates,
) -> Result<Vec<Lexeme>> {
let mut source: Vec<Lexeme> = parse_html(source.as_ref())?;
for i in 1..=RECURSION_LIMIT {
let (new_source, num_expanded) = compilation_pass(&source, templates)?;
source = new_source;
if num_expanded == 0 {
break;
}
if source.len() >= LEXEME_MEMORY_LIMIT {
return Err(compile_error("reached memory limit expanding templates"));
}
if i == RECURSION_LIMIT {
return Err(compile_error("reached recursion limit expanding templates"));
}
}
Ok(source.to_vec())
}
pub fn compile_source_file(
path: impl AsRef<Path>,
templates: &Templates,
) -> Result<Vec<Lexeme>> {
let file = read_file(&path)?;
compile_source(file, templates)
.ctx(format!("while compiling file {}", path.as_ref().display()))
}
pub fn serialize(output: &Vec<Lexeme>) -> String {
output
.iter()
.map(|lm| lm.serialize())
.collect::<Vec<_>>()
.join("")
}
pub fn serialize_mini(output: &Vec<Lexeme>) -> String {
output
.iter()
.map(|lm| lm.serialize())
.collect::<Vec<_>>()
.join("")
}
pub struct Compiler {
templates: HashMap<String, Element>,
}
impl Compiler {
pub fn new() -> Self {
Self {
templates: Default::default(),
}
}
pub fn with_template_file(
&mut self,
path: impl AsRef<Path>,
) -> Result<&mut Self> {
let templates = parse_templates_file(path)?;
self.templates.extend(templates);
Ok(self)
}
pub fn with_src(
&mut self,
from: impl AsRef<Path>,
to: impl AsRef<Path>,
) -> Result<&mut Self> {
let source = compile_source_file(from, &self.templates)?;
let serial = serialize(&source);
let mut new_file = std::fs::File::create(to)?;
new_file.write_all(serial.as_bytes())?;
Ok(self)
}
pub fn with_template_folder(
&mut self,
from: impl AsRef<Path>,
) -> Result<&mut Self> {
for file in std::fs::read_dir(&from)? {
let file =
file.ctx(format!("reading directory {}", from.as_ref().display()))?;
let path = file.path();
let last = path.components().last().ctx("empty path encountered")?;
let ft = match file.file_type() {
Ok(ft) => ft,
Err(_) => continue,
};
if ft.is_dir() {
self.with_template_folder(&from.as_ref().join(&last))?;
continue;
}
let ext = match path.extension() {
Some(ext) => ext,
None => continue,
};
if ext != "html" {
continue;
}
self.with_template_file(&path)?;
}
Ok(self)
}
pub fn with_src_folder(
&mut self,
from: impl AsRef<Path>,
to: impl AsRef<Path>,
) -> Result<&mut Self> {
let _ = std::fs::remove_dir_all(&to);
let _ = std::fs::create_dir_all(&to);
for file in std::fs::read_dir(&from)? {
let file =
file.ctx(format!("reading directory {}", from.as_ref().display()))?;
let path = file.path();
let last = path.components().last().ctx("empty path encountered")?;
let destination = to.as_ref().join(&last);
let ft = match file.file_type() {
Ok(ft) => ft,
Err(_) => continue,
};
if ft.is_dir() {
self.with_src_folder(
&from.as_ref().join(&last),
&to.as_ref().join(&last),
)?;
continue;
}
let ext = match path.extension() {
Some(ext) => ext,
None => continue,
};
// Copy file but do not compile
if ext != "html" {
let file = std::fs::read(&path)
.ctx(format!("opening file to read: {}", path.display()))?;
let mut new_file = std::fs::File::create(&destination)
.ctx(format!("opening file to write: {}", destination.display()))?;
new_file
.write_all(&file)
.ctx(format!("writing to file: {}", destination.display()))?;
continue;
}
self.with_src(&path, destination)?;
}
Ok(self)
}
}

View file

@ -1,278 +0,0 @@
use html_parser::{Dom, DomVariant, Element, ElementVariant, Node};
use std::{collections::HashMap, path::Path};
use crate::{
macros::*,
trace::{WithContext, *},
};
/// Replaces @variables in template with appropriate
/// values, or removes if not provided
fn init_vars(
node: Node,
attributes: &HashMap<String, Option<String>>,
children: &Vec<Node>,
) -> Node {
let mut element = match node {
Node::Text(_) | Node::Comment(_) => return node,
Node::Element(e) => e,
};
let mut new_classes = Vec::with_capacity(element.classes.len());
for cls in &mut element.classes {
if !cls.starts_with('@') {
new_classes.push(cls.clone());
continue;
}
let name = &cls[1..];
// Attributes in class must not be null
if let Some(Some(k)) = attributes.get(name) {
new_classes.push(k.clone());
}
// Otherwise discard variable from classes
}
element.classes = new_classes;
// Attributes in ID must not be null
if let Some(id) = &element.id {
if let Some(name) = id.strip_prefix('@') {
element.id = attributes.get(name).unwrap_or(&None).clone();
} else {
element.id = None;
}
}
let mut new_attributes = HashMap::new();
for (key, value) in &element.attributes {
if let Some(id) = value {
if let Some(variable_name) = id.strip_prefix('@') {
if let Some(value) = attributes.get(variable_name) {
// Insert null and non-null variables if key exists
new_attributes.insert(key.clone(), value.clone());
}
// Otherwise discard variable
}
// Insert non-null non-variable value
else {
new_attributes.insert(key.clone(), value.clone());
}
} else {
// Insert null non-variable value
new_attributes.insert(key.clone(), value.clone());
}
}
element.attributes = new_attributes;
let mut new_children = Vec::with_capacity(element.children.len());
for child in &element.children {
let name = child.element().map(|c| c.name.as_str()).unwrap_or("");
if "tp:children" == name {
for child in children {
new_children.push(child.clone());
}
} else {
let child = init_vars(child.clone(), attributes, children);
new_children.push(child);
}
}
element.children = new_children;
Node::Element(element)
}
/// Swaps out a template invocation for its definition
fn expand_templates(
invocation: Element,
templates: &Element,
) -> Result<Vec<Node>> {
for child in &invocation.children {
if child
.element()
.map(|c| c.name.as_str().strip_prefix("tp:").is_some())
.unwrap_or(false)
{
return Err(Error::new(
ErrorKind::Compilation,
"Illegal use of tp namespace",
));
}
for subchild in child {
if subchild
.element()
.map(|c| c.name.as_str().strip_prefix("tp:").is_some())
.unwrap_or(false)
{
return Err(Error::new(
ErrorKind::Compilation,
"Illegal use of tp namespace",
));
}
}
}
// Collect params
let mut attributes = invocation.attributes;
let classes = invocation.classes.join(" ");
if classes.len() != 0 {
attributes.insert("class".into(), Some(classes));
}
if let Some(id) = invocation.id {
attributes.insert("id".into(), Some(id));
}
// Swap params
let expanded = init_vars(
Node::Element(templates.clone()),
&attributes,
&invocation.children,
);
Ok(expanded.element().unwrap().children.clone())
}
/// Serializes HTML node
/// * `node` - Node to serialize
/// * `templates` - Map of templates by their names
fn node_to_string(
node: &Node,
templates: &HashMap<String, Element>,
macros: &HashMap<String, Macro>,
) -> Result<String> {
const OPEN: bool = false;
const CLOSE: bool = true;
let mut stack: Vec<(Node, bool)> = vec![(node.clone(), OPEN)];
let mut buf = String::new();
while let Some((current, closing)) = stack.pop() {
if closing == OPEN {
stack.push((current.clone(), CLOSE));
match current {
Node::Text(t) => {
buf.push_str(&t);
},
Node::Element(e) => {
// Expand if macro
if let Some(mc) = macros.get(&e.name) {
stack.pop().unwrap();
let expanded =
mc(&e.attributes).ctx(format!("Expanding macro: {}", e.name))?;
buf.push_str(&expanded);
continue;
}
// Expand if template
if let Some(tp) = templates.get(&e.name) {
let _ = stack.pop();
let elements = expand_templates(e.clone(), tp)
.ctx(format!("Expanding template: {}", e.name))?;
for element in elements.into_iter().rev() {
stack.push((element, OPEN));
}
continue;
}
// <
buf.push('<');
// Tag name
buf.push_str(&e.name);
// Classes
if !e.classes.is_empty() {
buf.push_str(&format!(" class='{}'", e.classes.join(" ")));
}
// ID
if let Some(id) = &e.id {
buf.push_str(&format!(" id='{}'", id));
}
// Attributes
for (k, v) in &e.attributes {
match v {
Some(v) => buf.push_str(&format!(" {}='{}'", k, v)),
None => buf.push_str(&format!(" {}", k)),
}
}
match e.variant {
ElementVariant::Normal => {
buf.push_str(">\n");
for child in e.children.iter().rev() {
stack.push((child.clone(), OPEN));
}
},
ElementVariant::Void => {
buf.push_str("/>\n");
let _ = stack.pop();
},
}
},
Node::Comment(_) => {},
}
} else {
match current {
Node::Text(_) => buf.push('\n'),
Node::Comment(_) => {},
Node::Element(e) => {
buf.push_str(&format!("</{}>\n", e.name));
},
}
}
}
Ok(buf)
}
pub struct Compiler {
templates: HashMap<String, Element>,
macros: HashMap<String, Macro>,
}
impl Compiler {
pub fn new() -> Self {
Self {
templates: HashMap::new(),
macros: default_macros(),
}
}
pub fn parse_templates_file(&mut self, path: impl AsRef<Path>) -> Result<()> {
let file = read_file(&path)?;
self.parse_templates(&file).ctx(format!(
"Parsing templates file: {}",
path.as_ref().to_string_lossy()
))?;
Ok(())
}
pub fn parse_templates(&mut self, html: &str) -> Result<()> {
let tps: HashMap<String, Element> = Dom::parse(html)?
.children
.into_iter()
.map(|i| i.element().cloned())
.flatten()
.map(|i| (i.name.clone(), i))
.collect();
if tps.is_empty() {
return Err(Error::new(ErrorKind::Parsing, "No blueprints found"));
}
self.templates.extend(tps);
Ok(())
}
pub fn compile_source_file(&self, path: impl AsRef<Path>) -> Result<String> {
let file = read_file(&path)?;
self.compile_source(&file).ctx(format!(
"Compiling source file: {}",
path.as_ref().to_string_lossy()
))
}
pub fn compile_source(&self, html: &str) -> Result<String> {
let dom = Dom::parse(html)?;
if !dom.errors.is_empty() {}
if dom.tree_type == DomVariant::Empty {
return Err(Error::new(ErrorKind::Parsing, "Empty DOM"));
}
if dom.tree_type != DomVariant::Document {
return Err(Error::new(
ErrorKind::Parsing,
"DOM must exactly have 1 root node",
));
}
let tree = &dom.children[0];
node_to_string(&tree, &self.templates, &self.macros)
}
}

106
src/directives.rs Normal file
View file

@ -0,0 +1,106 @@
use crate::parse::Attributes;
pub fn expand_directive(
name: &str,
attributes: &Attributes,
contents: &str,
) -> String {
match name {
"style" => style_dir(attributes).unwrap_or("<!-- -->".to_string()),
"script" => script_dir(attributes).unwrap_or("<!-- -->".to_string()),
"code" => code(attributes, contents).unwrap_or("<!-- -->".to_string()),
_ => "<!-- -->".to_string(),
}
}
fn style_dir(attributes: &Attributes) -> Option<String> {
let path = attributes.get("href")?;
let file = std::fs::read_to_string(path).ok()?;
Some(format!("<style>\n{}\n</style>", file.trim()))
}
fn script_dir(attributes: &Attributes) -> Option<String> {
let path = attributes.get("href")?;
let file = std::fs::read_to_string(path).ok()?;
Some(format!("<script>\n{}\n</script>", file.trim()))
}
fn code(attributes: &Attributes, contents: &str) -> Option<String> {
use inkjet::*;
let minimum_indent = contents
.trim()
.lines()
.map(|line| line.len() - line.trim_start().len())
.min()
.unwrap_or(0);
let normalized_string: String = contents
.lines()
.map(|line| line.replacen(" ", "", minimum_indent))
.map(|line| line + "\n")
.collect();
let language = parse_language(attributes.get("lang")?)?;
let mut hl = Highlighter::new();
let buffer = hl
.highlight_to_string(language, &MyFormatter(), normalized_string)
.ok()?;
let buffer = format!(
"<div class=\"code-block\">\n<pre><code>{}</code></pre>\n</div>",
buffer
);
Some(buffer)
}
struct MyFormatter();
use inkjet::{
constants::HIGHLIGHT_CLASS_NAMES, formatter::*,
tree_sitter_highlight::HighlightEvent,
};
impl Formatter for MyFormatter {
fn write<W>(
&self,
source: &str,
writer: &mut W,
event: inkjet::tree_sitter_highlight::HighlightEvent,
) -> inkjet::Result<()>
where
W: std::fmt::Write,
{
match event {
HighlightEvent::Source { start, end } => {
let span = source
.get(start..end)
.expect("Source bounds should be in bounds!");
let span = v_htmlescape::escape(span).to_string();
writer.write_str(&span)?;
},
HighlightEvent::HighlightStart(idx) => {
let name = HIGHLIGHT_CLASS_NAMES[idx.0];
write!(writer, "<span class=\"{}\">", name)?;
},
HighlightEvent::HighlightEnd => {
writer.write_str("</span>")?;
},
}
Ok(())
}
}
fn parse_language(lang: &str) -> Option<inkjet::Language> {
use inkjet::*;
Some(match lang {
"rust" => Language::Rust,
"python" => Language::Python,
"javascript" => Language::Javascript,
"html" => Language::Html,
"css" => Language::Css,
"toml" => Language::Toml,
_ => return None,
})
}

View file

@ -1,32 +0,0 @@
use crate::trace::*;
use std::collections::HashMap;
pub fn default_macros() -> HashMap<String, Macro> {
let mut hm: HashMap<String, Macro> = HashMap::new();
hm.insert("lg:include".into(), INCLUDE_MACRO);
hm
}
pub type Macro = fn(&HashMap<String, Option<String>>) -> Result<String>;
const INCLUDE_MACRO: Macro = |input| {
let href = input
.get("href")
.ok_or(compile_error("'href' attribute missing"))?
.as_ref()
.ok_or(compile_error("'href' attribute is empty"))?;
let rel = input
.get("rel")
.ok_or(compile_error("'rel' attribute missing"))?
.as_ref()
.ok_or(compile_error("'rel' attribute is empty"))?;
let r = match rel.as_str() {
"css" => format!("<style>\n{}\n</style>\n", read_file(href)?),
"js" => format!("<script>\n{}\n</script>\n", read_file(href)?),
"inline" => read_file(href)?,
_ => return Err(compile_error(format!("Invalid 'rel' value: {}", rel))),
};
Ok(r)
};

View file

@ -1,81 +1,11 @@
mod compiler;
mod macros;
mod parser;
mod compile;
mod directives;
mod parse;
mod trace;
use compiler::*;
use trace::*;
fn run_compiler() -> Result<()> {
// Read config file
let default_config = include_str!("../default.toml")
.parse::<toml::Table>()
.unwrap();
let user_config = std::fs::read_to_string("lg.toml")
.ctx("Reading config file")?
.parse::<toml::Table>()
.ctx("Parsing config file")?;
let mut config = default_config.clone();
config.extend(user_config);
let mut c = Compiler::new();
let compile_table = config["compile"]
.as_table()
.ctx("Reading compile options")?;
// Parse all templates
let templates_path = compile_table["templates"]
.as_str()
.ctx("Reading templates path")?;
let dirs = walkdir::WalkDir::new(templates_path)
.into_iter()
.flatten()
.filter_map(|v| {
if v.file_name().to_str().unwrap_or("").ends_with(".html")
&& v.file_type().is_file()
{
Some(v)
} else {
None
}
});
for file in dirs {
c.parse_templates_file(file.path())?;
}
// Compile all source files
let source_path = compile_table["source"]
.as_str()
.ctx("Reading templates path")?;
let dirs = walkdir::WalkDir::new(source_path)
.into_iter()
.flatten()
.filter_map(|v| {
if v.file_name().to_str().unwrap_or("").ends_with(".html")
&& v.file_type().is_file()
{
Some(v)
} else {
None
}
});
for file in dirs {}
// println!("{}", s);
fn main() -> Result<(), trace::Error> {
let mut c = compile::Compiler::new();
c.with_template_folder("templates/")?
.with_src_folder("hyper-src/", "hyper-build/")?;
Ok(())
}
fn main() {
let test = include_str!("../simple.html").to_string();
// let test = " <as> </as> ".to_string();
let r = parser::parse_html(&test).unwrap();
for l in r {
println!("{l:?}");
}
// match run_compiler() {
// Ok(_) => {},
// Err(e) => {
// eprintln!("{}", e);
// std::process::exit(1);
// },
// }
}

497
src/parse.rs Normal file
View file

@ -0,0 +1,497 @@
use std::collections::HashMap;
use crate::directives::expand_directive;
pub type Attributes = HashMap<String, String>;
pub type Offset = usize;
pub type Parse<'a, T> = (T, &'a str, Offset);
pub type MaybeParse<'a, T> = Option<Parse<'a, T>>;
#[inline]
fn parse_until(i: &str, condition: impl Fn(char) -> bool) -> Parse<&str> {
match i.chars().position(condition) {
Some(pos) => (&i[..pos], &i[pos..], pos),
None => (&i, "", i.len()),
}
}
fn parse_until_str<'a>(
tail: &'a str,
to_match: &'static str,
) -> MaybeParse<'a, &'a str> {
for i in 0..tail.len() {
let substr = &tail[0..i];
if substr.ends_with(to_match) {
let end = i - to_match.len();
return Some((&tail[0..end], &tail[end..], end));
}
}
None
}
#[inline]
fn parse_str<'a>(i: &'a str, matches: &str) -> MaybeParse<'a, &'a str> {
let length = matches.len();
match i.get(0..matches.len()) {
Some(s) => {
if s.eq_ignore_ascii_case(matches) {
Some((s, &i[length..], length))
} else {
None
}
},
None => None,
}
}
fn parse_char(i: &str, matches: char) -> MaybeParse<char> {
if let Some(c) = i.chars().next() {
if c == matches {
return Some((c, &i[1..], 1));
}
}
None
}
fn parse_delimited(i: &str, delim: char) -> MaybeParse<&str> {
let (_start, i, o1) = parse_char(i, delim)?;
let (contents, i, o2) = parse_until(i, |c| c == delim);
let (_end, i, o3) = parse_char(i, delim)?;
Some((contents, i, o1 + o2 + o3))
}
fn index_to_rc(input: &str, index: usize) -> (usize, usize) {
let (mut row, mut col) = (1, 1);
for c in input[0..index].chars() {
if c == '\n' {
row += 1;
col = 1;
} else {
col += 1;
}
}
(row, col)
}
pub const LEXEME_MEMORY_LIMIT: usize = 65535;
// Tags that are implicitly self closing, ending in /> is
// optional
const VOID_ELEMENTS: [&str; 16] = [
"area", "base", "br", "col", "command", "embed", "hr", "img", "input",
"keygen", "link", "meta", "param", "source", "track", "wbr",
];
#[derive(Clone, Debug)]
pub enum ErrorKind {
/// Encountered illegal sequence
Illegal,
/// Closing tag does not match previous opening tag
UnbalancedTags,
/// Tried to parse `N > 65535` elements
MemoryLimit,
}
impl From<Error> for crate::trace::Error {
fn from(value: Error) -> Self {
Self {
kind: crate::trace::ErrorKind::Parsing,
reason: match value.kind {
ErrorKind::Illegal => "Illegal character encountered",
ErrorKind::UnbalancedTags => "Unbalanced open and close tags",
ErrorKind::MemoryLimit => "Ran out of memory",
}
.to_string(),
backtrace: vec![format!("at line {} column {}", value.row, value.column)],
}
}
}
#[derive(Clone, Debug)]
pub struct Error {
pub kind: ErrorKind,
pub char_index: usize,
pub row: usize,
pub column: usize,
}
#[derive(Clone, Debug)]
pub enum HtmlElement {
/// The required `<!DOCTYPE HTML>` preamble
DocType,
/// Text inside of a `<!-- comment -->`
Comment(String),
/// Any opening tag, including <empty/> tags
OpenTag {
/// The name of the tag
name: String,
/// Attribute names and their values if present
attributes: Attributes,
/// Whether the tag closes itself. Tags that are
/// implicitly empty are:
/// `area, base, br, col, command, embed, hr, img, input
/// keygen, link, meta, param, source, track, wbr`
is_empty: bool,
},
/// Any closing tag that is not empty. Closing implicitly
/// empty tags is an error
CloseTag { name: String },
/// Any inner text that is not entirely whitespace
Text(String),
/// A `<script>` tag and its contents
Script {
/// Attribute names and their values if present
attributes: Attributes,
/// The raw text between the open and close `<script>`
/// tags
contents: String,
},
/// A `<style>` tag and its contents
Style {
/// Attribute names and their values if present
attributes: Attributes,
/// The raw text between the open and close `<style>`
/// tags
contents: String,
},
Directive {
name: String,
attributes: Attributes,
contents: String,
},
}
fn serialize_attributes(attr: &Attributes) -> String {
attr
.iter()
.map(|(k, v)| {
if v.is_empty() {
format!(" {k}")
} else {
format!(" {k}=\"{v}\"")
}
})
.collect::<Vec<_>>()
.join("")
}
impl HtmlElement {
pub fn serialize(&self) -> String {
match self {
Self::DocType => "<!DOCTYPE html>".into(),
Self::Comment(_) => format!("<!---->"),
Self::OpenTag {
name,
attributes,
is_empty: false,
} => format!("<{}{}>", name, serialize_attributes(&attributes)),
Self::OpenTag {
name,
attributes,
is_empty: true,
} => format!("<{}{}/>", name, serialize_attributes(&attributes)),
Self::CloseTag { name } => format!("</{}>", name),
Self::Style {
attributes,
contents,
} => {
format!(
"<style{}>\n{}\n</style>",
serialize_attributes(attributes),
contents
)
},
Self::Script {
attributes,
contents,
} => {
format!(
"<script{}>\n{}\n</script>",
serialize_attributes(attributes),
contents
)
},
Self::Text(t) => t.clone(),
Self::Directive {
name,
attributes,
contents,
} => expand_directive(&name, &attributes, &contents),
}
}
}
/// Used as the `condition` argument for `parse_until` to
/// parse names of things
const NAME_REGEX: fn(char) -> bool =
|c| !(c.is_ascii_alphanumeric() || [':', '_', '-', '@'].contains(&c));
/// Used as the `condition` argument for `parse_until` to
/// arbitrary whitespace
const WS_REGEX: fn(char) -> bool = |c| !c.is_whitespace();
fn parse_doctype(i: &str) -> MaybeParse<HtmlElement> {
let (_, i, o1) = parse_str(i, "<!doctype")?;
let (_, i, o2) = parse_until(i, WS_REGEX);
// Minimum of 1 whitespace
if o2 == 0 {
return None;
}
let (_, i, o3) = parse_str(i, "html")?;
let (_, i, o4) = parse_until(i, WS_REGEX);
let (_, i, o5) = parse_str(i, ">")?;
Some((HtmlElement::DocType, i, o1 + o2 + o3 + o4 + o5))
}
fn parse_comment<'a>(tail: &'a str) -> MaybeParse<HtmlElement> {
let (_, tail, o1) = parse_str(tail, "<!--")?;
let (comment, tail, o2) = parse_until_str(tail, "-->")?;
let (_, tail, o3) = parse_str(tail, "-->")?;
Some((HtmlElement::Comment(comment.into()), tail, o1 + o2 + o3))
}
fn parse_raw_text(i: &str) -> MaybeParse<(String, Attributes, String)> {
let (open, mut i, o1) = parse_open_tag(i)?;
let (open_name, attributes, is_empty) = match open {
HtmlElement::OpenTag {
name,
attributes,
is_empty,
} => (name, attributes, is_empty),
_ => unreachable!(),
};
let (close_name, contents, i, o2) = if is_empty {
(open_name.clone(), "".to_string(), i, 0)
} else {
let mut contents = String::new();
let mut o2 = 0;
while !i.is_empty() {
let (text, new_i, new_off) = parse_text(i);
contents.push_str(&text);
o2 += new_off;
i = new_i;
if i.starts_with("</") {
if let Some((HtmlElement::CloseTag { name }, _, _)) = parse_close_tag(i)
{
if name == open_name {
break;
}
}
}
if text.is_empty() {
i = &i[1..];
o2 += 1;
contents.push('<');
}
}
let (close, i, o3) = parse_close_tag(i)?;
let close_name = match close {
HtmlElement::CloseTag { name } => name,
_ => unreachable!(),
};
(close_name, contents, i, o2 + o3)
};
if open_name != close_name {
return None;
}
Some(((open_name, attributes, contents), i, o1 + o2))
}
fn parse_style(i: &str) -> MaybeParse<HtmlElement> {
let ((name, attributes, contents), i, o) = parse_raw_text(i)?;
if name != "style" {
None
} else {
Some((
HtmlElement::Style {
attributes,
contents,
},
i,
o,
))
}
}
fn parse_script(i: &str) -> MaybeParse<HtmlElement> {
let ((name, attributes, contents), i, o) = parse_raw_text(i)?;
if name != "script" {
None
} else {
Some((
HtmlElement::Script {
attributes,
contents,
},
i,
o,
))
}
}
fn parse_directive(i: &str) -> MaybeParse<HtmlElement> {
let ((name, attributes, contents), i, o) = parse_raw_text(i)?;
if let Some(name) = name.strip_prefix('@') {
Some((
HtmlElement::Directive {
name: name.to_string(),
attributes,
contents,
},
i,
o,
))
} else {
None
}
}
fn parse_open_tag(i: &str) -> MaybeParse<HtmlElement> {
let (_, i, o1) = parse_str(i, "<")?;
let (name, i, o2) = parse_until(i, NAME_REGEX);
let mut attributes = HashMap::new();
let mut i = i;
let mut o3 = 0;
while let Some(((key, value), new_i, new_o)) = parse_attribute(i) {
attributes.insert(key, value);
i = new_i;
o3 += new_o;
}
let (_, i, o4) = parse_until(i, WS_REGEX);
// Find all attributes
let (is_empty, i, o5) = match parse_str(i, "/") {
Some((_, i, o5)) => (true, i, o5),
None => (false, i, 0),
};
let is_empty = is_empty || VOID_ELEMENTS.contains(&name);
let (_, i, o6) = parse_char(i, '>')?;
Some((
HtmlElement::OpenTag {
name: name.to_string(),
attributes,
is_empty,
},
i,
o1 + o2 + o3 + o4 + o5 + o6,
))
}
fn parse_attribute(i: &str) -> MaybeParse<(String, String)> {
let (_, i, o1) = parse_until(i, WS_REGEX);
if o1 == 0 {
return None;
}
let (key, i, o2) = parse_until(i, NAME_REGEX);
if o2 == 0 {
return None;
}
let get_value = || -> Option<(&str, &str, Offset)> {
let (_, i, o1) = parse_until(i, WS_REGEX);
let (_, i, o2) = parse_str(i, "=")?;
let (_, i, o3) = parse_until(i, WS_REGEX);
let (value, i, o4) = parse_delimited(i, '\"')
.or_else(|| parse_delimited(i, '\''))
.unwrap_or_else(|| parse_until(i, NAME_REGEX));
Some((value, i, o1 + o2 + o3 + o4))
};
let (value, i, o3) = get_value().unwrap_or(("", i, 0));
Some(((key.to_string(), value.to_string()), i, o1 + o2 + o3))
}
fn parse_close_tag(i: &str) -> MaybeParse<HtmlElement> {
let (_, i, o1) = parse_str(i, "</")?;
let (name, i, o2) = parse_until(i, NAME_REGEX);
if o2 == 0 {
return None;
}
let (_, i, o3) = parse_until(i, WS_REGEX);
let (_, i, o4) = parse_str(i, ">")?;
Some((
HtmlElement::CloseTag {
name: name.to_string(),
},
i,
o1 + o2 + o3 + o4,
))
}
fn parse_text(i: &str) -> Parse<String> {
let (text, i, o1) = parse_until(i, |c| c == '<');
(text.to_string(), i, o1)
}
pub fn parse_html(
input: &str,
) -> Result<Vec<HtmlElement>, crate::trace::Error> {
let mut output = vec![];
let mut validation_stack = vec![];
let mut i = input;
let mut offset = 0;
let throw_err = |kind, offset| {
let (row, column) = index_to_rc(input, offset);
Error {
kind,
char_index: offset,
row,
column,
}
.into()
};
while i.len() > 0 {
let (lm, new_i, new_off) = if i.starts_with("<!--") {
parse_comment(i)
} else if i.starts_with("<!") {
parse_doctype(i)
} else if i.starts_with("<style") {
parse_style(i)
} else if i.starts_with("<script") {
parse_script(i)
} else if i.starts_with("</") {
parse_close_tag(i)
} else if i.starts_with("<@") {
parse_directive(i)
} else if i.starts_with("<") {
parse_open_tag(i)
} else {
let (text, new_i, new_off) = parse_text(i);
// skip if text is all whitespace
if text.trim().is_empty() {
i = new_i;
offset += new_off;
continue;
}
Some((HtmlElement::Text(text), new_i, new_off))
}
.ok_or_else(|| throw_err(ErrorKind::Illegal, offset))?;
if output.len() >= LEXEME_MEMORY_LIMIT {
return Err(throw_err(ErrorKind::MemoryLimit, offset));
}
match &lm {
HtmlElement::OpenTag {
name,
is_empty: false,
..
} => validation_stack.push(name.clone()),
HtmlElement::CloseTag { name } => {
let top = validation_stack.pop();
if top.map_or(true, |top| &top != name) {
return Err(throw_err(ErrorKind::UnbalancedTags, offset));
}
},
_ => {},
};
output.push(lm);
i = new_i;
offset += new_off;
}
if validation_stack.is_empty() {
Ok(output)
} else {
Err(throw_err(ErrorKind::UnbalancedTags, offset))
}
}

View file

@ -1,474 +0,0 @@
use std::collections::HashMap;
#[derive(Clone, Debug)]
pub enum ParseError {
InvalidTag,
MismatchedClosing { expected: String, found: String },
UnmatchedOpen(String),
UnmatchedClose(String),
VoidClosingTag(String),
Unknown,
}
use crate::trace::{self, WithContext};
impl From<ParseError> for trace::Error {
fn from(value: ParseError) -> Self {
let msg = match value {
ParseError::InvalidTag => "Failed to parse a tag".into(),
ParseError::MismatchedClosing { expected, found } => {
format!(
"Found closing tag '{}' where '{}' was expected",
found, expected
)
},
ParseError::UnmatchedOpen(s) => {
format!("The tag '{}' is opened, but never closed", s)
},
ParseError::UnmatchedClose(s) => {
format!("The tag '{}' is closed, but never opened", s)
},
ParseError::VoidClosingTag(s) => {
format!("The tag '{}' should not have a closing tag", s)
},
ParseError::Unknown => {
return trace::Error::new(
trace::ErrorKind::Unknown,
"Unknown error while parsing",
)
},
};
trace::Error::new(trace::ErrorKind::Parsing, msg)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Lexeme<'a> {
OpenTag {
name: &'a str,
attributes: HashMap<&'a str, Option<&'a str>>,
is_void: bool,
},
CloseTag {
name: &'a str,
},
Text(&'a str),
Doctype,
Comment,
}
fn normalize_whitespace(mut tail: &str) -> String {
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
let mut _index = 0;
let mut buffer = String::with_capacity(tail.len());
while !tail.is_empty() {
match parse_whitespace_min(tail, 1, &mut _index) {
Some((_, new_tail)) => {
buffer.push(' ');
tail = new_tail;
},
None => {},
}
let (chars, new_tail) =
parse_while(tail, |c| !c.is_whitespace(), &mut _index);
buffer.push_str(chars);
tail = new_tail
}
buffer
}
/// Try parsing single specific character ignoring case
fn parse_char<'a>(
tail: &'a str,
c: char,
index: &mut usize,
) -> Option<(&'a str, &'a str)> {
if !tail.is_empty() && tail[0..1].eq_ignore_ascii_case(&c.to_string()) {
*index += 1;
Some((&tail[0..1], &tail[1..]))
} else {
None
}
}
fn parse_str<'a>(
tail: &'a str,
to_match: &'a str,
index: &mut usize,
) -> Option<(&'a str, &'a str)> {
if tail.len() < to_match.len() {
return None;
}
if tail[0..to_match.len()].eq_ignore_ascii_case(to_match) {
*index += to_match.len();
Some((&tail[0..to_match.len()], &tail[to_match.len()..]))
} else {
None
}
}
fn parse_until_str<'a>(
tail: &'a str,
to_match: &'a str,
index: &mut usize,
) -> Option<(&'a str, &'a str)> {
for i in 0..tail.len() {
let substr = &tail[0..i];
if substr.ends_with(to_match) {
*index += i;
return Some((&tail[0..i], &tail[i..]));
}
}
None
}
/// Parse until condition is not true for next character
fn parse_while<'a>(
tail: &'a str,
condition: impl Fn(char) -> bool,
index: &mut usize,
) -> (&'a str, &'a str) {
let mut end;
let mut it = tail.char_indices();
'outer: loop {
match it.next() {
Some((i, c)) => {
end = i;
if !condition(c) {
break 'outer;
}
},
None => {
// Reached end of input
return (&tail, "");
},
};
}
*index += end;
(&tail[0..end], &tail[end..])
}
fn parse_whitespace<'a>(i: &'a str, index: &mut usize) -> (&'a str, &'a str) {
parse_while(i, |c| c.is_whitespace(), index)
}
fn parse_whitespace_min<'a>(
tail: &'a str,
min: usize,
index: &mut usize,
) -> Option<(&'a str, &'a str)> {
let mut new_index = 0;
let (ws, tail) = parse_whitespace(tail, &mut new_index);
if ws.len() < min {
None
} else {
*index += new_index;
Some((ws, tail))
}
}
/// Try parsing all characters between two delimiter
/// characters
fn parse_delimited<'a>(
i: &'a str,
delimiter: char,
index: &mut usize,
) -> Option<(&'a str, &'a str)> {
let mut new_index = 0;
let (_, tail) = parse_char(i, delimiter, &mut new_index)?;
let (value, tail) = parse_while(tail, |c| c != delimiter, &mut new_index);
let (_, tail) = parse_char(tail, delimiter, &mut new_index)?;
*index += new_index;
Some((value, tail))
}
fn parse_tag_name<'a>(
i: &'a str,
index: &mut usize,
) -> Option<(&'a str, &'a str)> {
let mut new_index = 0;
let (value, tail) = parse_while(
i,
|c| c.is_ascii_alphanumeric() || [':', '_', '-'].contains(&c),
&mut new_index,
);
if value.is_empty() {
None
} else {
*index += new_index;
Some((value, tail))
}
}
fn parse_attribute_key<'a>(
i: &'a str,
index: &mut usize,
) -> Option<(&'a str, &'a str)> {
let mut new_index = 0;
let (value, tail) = parse_while(
i,
|c| {
!(['"', '\'', '>', '/', '='].contains(&c)
|| c.is_control()
|| c.is_whitespace())
},
&mut new_index,
);
if value.is_empty() {
None
} else {
*index += new_index;
Some((value, tail))
}
}
fn parse_attribute_val<'a>(
i: &'a str,
index: &mut usize,
) -> Option<(&'a str, &'a str)> {
const SINGLE_QUOTE: char = '\'';
const DOUBLE_QUOTE: char = '"';
let mut new_index = 0;
let (value, tail) =
parse_delimited(i, SINGLE_QUOTE, &mut new_index) // Single quote delimit
.or_else(|| parse_delimited(i, DOUBLE_QUOTE, &mut new_index)) // Double quote delimit
.or_else(|| { // Unquoted
Some(parse_while(i, |c| {
!(c.is_whitespace()
|| [SINGLE_QUOTE, DOUBLE_QUOTE, '=', '<', '>', '`'].contains(&c))
}, &mut new_index))
})?;
*index += new_index;
Some((value, tail))
}
/// Returns Option<((key, value), tail)>
fn parse_key_val<'a>(
tail: &'a str,
index: &mut usize,
) -> Option<((&'a str, Option<&'a str>), &'a str)> {
let mut new_index = 0;
// Require whitespace
let (_, tail) = parse_whitespace_min(tail, 1, &mut new_index)?;
// Fail when no key found
let (key, tail) = parse_attribute_key(tail, &mut new_index)?;
if let Some((_, tail)) = parse_char(
parse_whitespace(tail, &mut new_index).1,
'=',
&mut new_index,
) {
let (_, tail) = parse_whitespace(tail, &mut new_index);
// Fail when = is not followed by value
let (val, tail) = parse_attribute_val(tail, &mut new_index)?;
let val = if val.is_empty() { None } else { Some(val) };
*index += new_index;
Some(((key, val), tail))
} else {
*index += new_index;
Some(((key, None), tail))
}
}
// Tags that are implicitly self closing, ending in /> is optional
const VOID_ELEMENTS: [&str; 16] = [
"area", "base", "br", "col", "command", "embed", "hr", "img", "input",
"keygen", "link", "meta", "param", "source", "track", "wbr",
];
fn parse_open_tag<'a>(
tail: &'a str,
index: &mut usize,
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
let mut new_index = 0;
// <
let (_, tail) =
parse_char(tail, '<', &mut new_index).ok_or(ParseError::Unknown)?;
// tag name
let (name, mut tail) =
parse_tag_name(tail, &mut new_index).ok_or(ParseError::InvalidTag)?;
// attributes
let mut attributes: HashMap<&str, Option<&str>> = HashMap::new();
while let Some((kv, new_tail)) = parse_key_val(tail, &mut new_index) {
attributes.insert(kv.0, kv.1);
tail = new_tail;
}
let (_, tail) = parse_whitespace(tail, &mut new_index);
let (is_void, tail) =
parse_char(tail, '/', &mut new_index).unwrap_or(("", tail));
let is_void = !is_void.is_empty() || VOID_ELEMENTS.contains(&name);
let (_, tail) = match parse_char(tail, '>', &mut new_index) {
Some(v) => v,
None => {
return Err(ParseError::InvalidTag);
},
};
*index += new_index;
Ok((
Lexeme::OpenTag {
name,
attributes,
is_void,
},
tail,
))
}
fn parse_close_tag<'a>(
tail: &'a str,
index: &mut usize,
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
let mut new_index = 0;
let (_, tail) =
parse_char(tail, '<', &mut new_index).ok_or(ParseError::Unknown)?;
let (_, tail) =
parse_char(tail, '/', &mut new_index).ok_or(ParseError::Unknown)?;
let (name, tail) =
parse_tag_name(tail, &mut new_index).ok_or(ParseError::InvalidTag)?;
let (_, tail) = parse_whitespace(tail, &mut new_index);
let (_, tail) =
parse_char(tail, '>', &mut new_index).ok_or(ParseError::InvalidTag)?;
*index += new_index;
Ok((Lexeme::CloseTag { name }, tail))
}
fn parse_doctype<'a>(
tail: &'a str,
index: &mut usize,
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
let mut new_index = 0;
let mut closure = || -> Option<(&str, &str)> {
let (_, tail) = parse_str(tail, "<!doctype", &mut new_index)?;
let (_, tail) = parse_whitespace_min(tail, 1, &mut new_index)?;
let (_, tail) = parse_str(tail, "html", &mut new_index)?;
let (_, tail) = parse_whitespace(tail, &mut new_index);
parse_char(tail, '>', &mut new_index)
};
let (_, tail) = closure().ok_or(ParseError::Unknown)?;
*index += new_index;
Ok((Lexeme::Doctype, tail))
}
fn parse_comment<'a>(
tail: &'a str,
index: &mut usize,
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
let mut new_index = 0;
let (_, tail) =
parse_str(tail, "<!--", &mut new_index).ok_or(ParseError::Unknown)?;
let (_, tail) =
parse_until_str(tail, "-->", &mut new_index).ok_or(ParseError::Unknown)?;
*index += new_index;
Ok((Lexeme::Comment, tail))
}
fn parse_text<'a>(
tail: &'a str,
index: &mut usize,
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
let mut new_index = 0;
let (txt, tail) = parse_while(tail, |c| c != '<', &mut new_index);
if txt.is_empty() {
Err(ParseError::Unknown)
} else {
*index += new_index;
Ok((Lexeme::Text(txt), tail))
}
}
fn or_keep_error<'a>(
r: Result<(Lexeme<'a>, &'a str), ParseError>,
op: impl FnOnce() -> Result<(Lexeme<'a>, &'a str), ParseError>,
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
match r {
Ok(val) => Ok(val),
Err(e) => op().map_err(|new_e| match e {
ParseError::Unknown => new_e,
_ => e,
}),
}
}
fn index_to_rc(input: &str, index: usize) -> (usize, usize) {
let (mut row, mut col) = (1, 1);
for c in input[0..index].chars() {
if c == '\n' {
row += 1;
col = 1;
} else {
col += 1;
}
}
(row, col)
}
pub fn parse_html(input: &str) -> trace::Result<Vec<Lexeme>> {
let mut tail = input;
let mut lexeme_stack = vec![];
let mut validation_stack = vec![];
let mut index = 0;
let err = |error: ParseError, index: usize| -> trace::Result<Vec<Lexeme>> {
let (row, col) = index_to_rc(input, index);
let e: trace::Error = error.into();
Err(e).ctx(format!("Starting at line {} character {}", row, col))
};
while !tail.is_empty() {
let (_, new_tail) = parse_whitespace(tail, &mut index);
if new_tail.is_empty() {
break;
}
let result = or_keep_error(parse_open_tag(new_tail, &mut index), || {
parse_close_tag(new_tail, &mut index)
});
let result = or_keep_error(result, || parse_text(new_tail, &mut index));
let result = or_keep_error(result, || parse_comment(new_tail, &mut index));
let (lm, new_tail) =
match or_keep_error(result, || parse_doctype(new_tail, &mut index)) {
Ok(v) => v,
Err(e) => {
return err(e, index);
},
};
// Validate that open and close tags match
match lm {
Lexeme::OpenTag { name, is_void, .. } => {
if !is_void {
validation_stack.push(name);
}
},
Lexeme::CloseTag { name } => {
if VOID_ELEMENTS.contains(&name) {
return err(ParseError::VoidClosingTag(name.into()).into(), index);
}
if let Some(top) = validation_stack.pop() {
if name != top {
return err(
ParseError::MismatchedClosing {
expected: top.into(),
found: name.into(),
},
index,
);
}
} else {
return err(ParseError::UnmatchedClose(name.into()), index);
}
},
Lexeme::Comment => {
tail = new_tail;
continue;
},
_ => {},
};
lexeme_stack.push(lm);
tail = new_tail;
}
if let Some(top) = validation_stack.pop() {
let e: trace::Error = ParseError::UnmatchedOpen(top.into()).into();
return Err(e).ctx("At end of file");
}
Ok(lexeme_stack)
}

View file

@ -41,6 +41,7 @@ pub enum ErrorKind {
IO,
Parsing,
Compilation,
Memory,
Unknown,
}
@ -54,6 +55,7 @@ impl Display for ErrorKind {
ErrorKind::Parsing => "PARSING",
ErrorKind::Compilation => "COMPILATION",
ErrorKind::Unknown => "UNKNOWN",
ErrorKind::Memory => "MEMORY",
}
)
}
@ -85,39 +87,6 @@ impl From<std::io::Error> for Error {
}
}
impl From<html_parser::Error> for Error {
fn from(value: html_parser::Error) -> Self {
match value {
html_parser::Error::Parsing(e) => Self {
kind: ErrorKind::Parsing,
reason: e,
backtrace: vec![],
},
html_parser::Error::Cli(e) => Self {
kind: ErrorKind::Unknown,
reason: e,
backtrace: vec![],
},
html_parser::Error::IO(e) => Self {
kind: ErrorKind::IO,
reason: format!("{}", e),
backtrace: vec![],
},
html_parser::Error::Serde(e) => Self {
kind: ErrorKind::Unknown,
reason: format!("{}", e),
backtrace: vec![],
},
}
}
}
impl From<toml::de::Error> for Error {
fn from(value: toml::de::Error) -> Self {
Error::new(ErrorKind::Parsing, value.message())
}
}
pub trait WithContext<T, S: Into<String>> {
fn ctx(self, s: S) -> Result<T>;
}

View file

@ -1,8 +0,0 @@
body {
background-color: linen;
}
h1 {
color: maroon;
margin-left: 40px;
}

183
templates/blueprints.html Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,90 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="test_files/main-ad0a5132b4027392.css">
<link rel="preload" href="https://logan-gatlin.com/web-2abb0afbb41a1a01_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
<link rel="modulepreload" href="https://logan-gatlin.com/web-2abb0afbb41a1a01.js">
<link
href="data:text/css,%3Ais(%5Bid*%3D'google_ads_iframe'%5D%2C%5Bid*%3D'taboola-'%5D%2C.taboolaHeight%2C.taboola-placeholder%2C%23credential_picker_container%2C%23credentials-picker-container%2C%23credential_picker_iframe%2C%5Bid*%3D'google-one-tap-iframe'%5D%2C%23google-one-tap-popup-container%2C.google-one-tap-modal-div%2C%23amp_floatingAdDiv%2C%23ez-content-blocker-container)%20%7Bdisplay%3Anone!important%3Bmin-height%3A0!important%3Bheight%3A0!important%3B%7D"
rel="stylesheet" type="text/css">
<link
href="data:text/css,%3Ais(%5Bid*%3D'google_ads_iframe'%5D%2C%5Bid*%3D'taboola-'%5D%2C.taboolaHeight%2C.taboola-placeholder%2C%23credential_picker_container%2C%23credentials-picker-container%2C%23credential_picker_iframe%2C%5Bid*%3D'google-one-tap-iframe'%5D%2C%23google-one-tap-popup-container%2C.google-one-tap-modal-div%2C%23amp_floatingAdDiv%2C%23ez-content-blocker-container)%20%7Bdisplay%3Anone!important%3Bmin-height%3A0!important%3Bheight%3A0!important%3B%7D"
rel="stylesheet" type="text/css">
</head>
<body>
<script type="module">import init from '/web-2abb0afbb41a1a01.js'; init('/web-2abb0afbb41a1a01_bg.wasm');</script>
<main class="container">
<article>
<header>
<nav aria-label="breadcrumb">
<ul>
<li><a href="https://logan-gatlin.com/">Home</a></li><!----><!---->
<li><a class="contrast">Base Conversion</a></li><!----><!---->
</ul>
</nav>
</header><!---->
<div class="grid">
<div>
<div><input type="text" placeholder="Decimal number" value="0"></div>
<table role="grid">
<tr>
<td role="button" type="button" class="secondary">HEX</td><!---->
<td>00<!----></td><!---->
</tr>
<tr>
<td role="button" type="button" class="secondary contrast">DEC</td><!---->
<td>0<!----></td><!---->
</tr>
<tr>
<td role="button" type="button" class="secondary">OCT</td><!---->
<td>000<!----></td><!---->
</tr>
<tr>
<td role="button" type="button" class="secondary">BIN</td><!---->
<td>0000 0000<!----></td><!---->
</tr>
</table>
<table>
<tr>
<td role="button" type="button" class="secondary contrast">BYTE</td><!---->
<td role="button" type="button" class="secondary">WORD</td><!---->
<td role="button" type="button" class="secondary">DWORD</td><!---->
<td role="button" type="button" class="secondary">QWORD</td><!---->
</tr>
</table><!----><!---->
</div>
<div>
<table class="binary">
<tr>
<td type="button" role="button" class="secondary">0<!----></td>
<td type="button" role="button" class="secondary">0<!----></td>
<td type="button" role="button" class="secondary">0<!----></td>
<td type="button" role="button" class="secondary">0<!----></td>
<td type="button" role="button" class="secondary">0<!----></td>
<td type="button" role="button" class="secondary">0<!----></td>
<td type="button" role="button" class="secondary">0<!----></td>
<td type="button" role="button" class="secondary">0<!----></td><!---->
</tr><!----><!----><!---->
</table><!---->
</div>
</div>
<hr>
<div><button type="button">Remember</button><!----><button type="button" class="secondary">Clear
history</button><!---->
<table><!----><!----></table><!---->
</div>
<footer><a href="https://github.com/Xterminate1818/based" target="_blank">View source code on GitHub</a><!---->
</footer>
</article><!----><!----><!---->
</main><!----><!----><!---->
</body>
</html>