diff options
| -rw-r--r-- | .editorconfig | 12 | ||||
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | config.toml | 19 | ||||
| -rw-r--r-- | content/b/_index.md | 6 | ||||
| -rw-r--r-- | content/b/c99-vla-tricks.md | 308 | ||||
| -rw-r--r-- | content/b/first.md | 15 | ||||
| -rw-r--r-- | static/asset/link.png | bin | 0 -> 972 bytes | |||
| -rw-r--r-- | static/asset/linux.png | bin | 0 -> 1224 bytes | |||
| -rw-r--r-- | static/asset/lynx.png | bin | 0 -> 2193 bytes | |||
| -rw-r--r-- | static/asset/mailto.png | bin | 0 -> 735 bytes | |||
| -rw-r--r-- | static/asset/nvim.png | bin | 0 -> 12359 bytes | |||
| -rw-r--r-- | static/mips.html | 158 | ||||
| -rw-r--r-- | static/mips.js | 627 | ||||
| -rw-r--r-- | static/style.css | 106 | ||||
| -rw-r--r-- | templates/base.html | 19 | ||||
| -rw-r--r-- | templates/blog-page.html | 14 | ||||
| -rw-r--r-- | templates/blog.html | 15 | ||||
| -rw-r--r-- | templates/index.html | 34 |
18 files changed, 1335 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..12d9669 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# https://EditorConfig.org + +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.md] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48f08f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +public/ +site.tar.gz diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..4b1b1bb --- /dev/null +++ b/config.toml @@ -0,0 +1,19 @@ +# The URL the site will be built for +base_url = "https://lemon.rip" + +# Whether to automatically compile all Sass files in the sass directory +compile_sass = false + +# Whether to build a search index to be used later on by a JavaScript library +build_search_index = false + +# When set to "true", the generated HTML files are minified. +minify_html = false + +[markdown] +# Whether to do syntax highlighting +# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola +highlight_code = false + +[extra] +# Put all your custom variables here diff --git a/content/b/_index.md b/content/b/_index.md new file mode 100644 index 0000000..c504e1b --- /dev/null +++ b/content/b/_index.md @@ -0,0 +1,6 @@ ++++ +title = "Blog" +sort_by = "date" +template = "blog.html" +page_template = "blog-page.html" ++++ diff --git a/content/b/c99-vla-tricks.md b/content/b/c99-vla-tricks.md new file mode 100644 index 0000000..b2b07eb --- /dev/null +++ b/content/b/c99-vla-tricks.md @@ -0,0 +1,308 @@ +--- +title: "C99 doesn't need function bodies, or 'VLAs are Turing complete'" +date: 2022-02-19 +--- + +Preface +======= + +The 1999 revision to the C programming language brought several interesting +changes and additions to the standard. Among those are variable length arrays, +or VLAs for short, which allow array types to have lengths that are determined +at runtime. These can show up in different contexts, but for the purposes of +this post we will be looking at VLAs as parameters to functions. For example: + + void f(int n, float v[n]); + +Since array parameters decay to their corresponding pointer type, that +declaration is functionally equivalent, and compatible ([6.7.5.3p7]) to the +following: + + void f(int n, float *v); + +The length of `v` in the first declaration is given by the value of a previous +parameter of the function, which can only be known at runtime. However, in a +function prototype (i.e. not a function definition) like the above, the length +of a VLA parameter is never computed and essentially ignored ([6.7.5.2p5]). +But in a function definition, the length is evaluated and must be greater than +zero. In practice this is rather useless because the sizeof operator doesn't +work as one might expect it to with array parameters ([6.5.3.4-note88]): + + void f(int n, float param[n]) { + float local[n]; + int a = sizeof local, + b = sizeof param; + + printf("n = %d\n", n); + printf("sizeof local = %d\n", a); // this will be n * sizeof(float) + printf("sizeof param = %d\n", b); // but this will be sizeof(float *) + } + +In other words, using sizeof with a VLA will evaluate the runtime-known length +and calculate the array size based on that, except when that VLA is a function +parameter, then, for whatever reason, it is the size of the corresponding +pointer type (in this example, `sizeof local === n * sizeof(float)` but `sizeof +param == sizeof(float *)`). The length of a VLA parameter may be used when e.g. +computing indices when accessing multi-dimensional variable length arrays. + +Alas, the standard mandates that the variable array length be computed when +the function is called. Of course, the expression in between the square +brackets is not limited to simple expressions like `n`, so one can write +something like: + + void f(int a, int b, int m[(a + b) / 2]) {} + +or + + void f(int x, int b[abs(x)]) {} + +or even + + void f(int v[getchar()]) {} + + +[6.7.5.3p7]: http://port70.net/~nsz/c/c99/n1256.html#6.7.5.3p7 +[6.7.5.2p5]: http://port70.net/~nsz/c/c99/n1256.html#6.7.5.2p5 +[6.5.3.4-note88]: http://port70.net/~nsz/c/c99/n1256.html#note88 + + +Disembodiment +============= + +The following program should give you an idea of the kind of constructs +that these rules allow for ([try it on compiler explorer][a]): + + int main(int argc, char *argv[printf("Hello")]) + { + printf(" world!\n"); + } + +The length expression of the VLA is evaluated before any of the statements in +`main`. I couldn't find anywhere in the standard saying whether this evaluation +order is well-defined but it is what clang and gcc do, and luckily, it does not +matter for the sake of this article, as we will see shortly. + +Let us refer to the subset of C99 where function bodies must be empty as +*disembodied C*. You might naturally ask yourself what things can be +accomplished in this subset (though you can probably guess the answer from the +title of this page). In other words, what can you do in C if you are limited to +just evaluating expressions and no statements? + +- Using the comma operator, we can sequence arbitrarily many expressions for + their side effects, so + + void f() { + printf("Press enter to confirm: "); + getchar(); + printf("thanks.\n"); + } + + becomes + + void f(char _[( + printf("Press enter to confirm: "), + getchar(), + printf("thanks.\n"), + 1 + )]) {} + + In a comma expression, the operands are evaluated left-to-right and the value + of the last operand is the resulting value of the whole expression. The `1` + at the end ensures that the evaluated array length is >0 (to avoid UB). + +- Functions in disembodied C are going to need a dummy parameter where the VLA + length expression evaluation can take place. For consistency, we will denote + it as `char _[...]` and give it the value `""` (the empty string) when calling + said functions (note that the value we give it doesn't actually matter, though + its size should be at least as big as the computed VLA size to avoid UB). + +- If-else statements can be replaced with ternary conditional expressions, such that + + void f(int n) { + if (n < 0) + printf("negative!"); + else if (n > 0) + printf("positive!"); + else + printf("zero!"); + } + + becomes + + void f(int n, char _[( + (n < 0) ? + printf("negative!") + : (n > 0) ? + printf("positive!") + : + printf("zero!") + , 1 + )]) {} + +- Remember that the VLA length expression can access previous function arguments, + so parameter passing is essentially unchanged. + +- We cannot return values, but we can use out parameters by taking advantage of + the fact that assignments are expressions in C, so instead of + + int imax(int a, int b) { + return a > b ? a : b; + } + + we can write + + void imax(int *out, int a, int b, char _[ + (*out = a > b ? a : b), + 1 + ]) {} + +- We cannot define local variables inside of expressions, but we can just add + extra function parameters to use as temporaries, rewriting + + void fswapf(float *a, float *b) { + float tmp = *a; + *a = *b; + *b = tmp; + } + + as + + static void fswapf_aux(float *a, float *b, float tmp, char _[( + tmp = *a, + *a = *b, + *b = tmp, + 1 + )]) {} + + void fswapf(float *a, float *b, char _[( + fswapf_aux(a, b, 0, ""), 1 + )]) {} + + Alternatively, if re-entrancy and thread-safety are disregarded, we could + just use global (static) variables. + +- What about loops? Clearly we cannot use `while` or `for` inside expressions. + Thankfully, they are unnecessary thanks to recursion. For example: + + float sum(float *v, size_t n) { + float sum = 0.0f; + for (size_t i = 0; i < n; ++i) + sum += v[i]; + return sum; + } + + can be expressed as + + /* the forward declaration is necessary */ + static void sum_aux(float *out, float *v, size_t n, char *); + static void sum_aux(float *out, float *v, size_t n, char _[( + (n > 0) ? ( + *out += *v, + sum_aux(out, v + 1, n - 1, ""), + 1 + ) : 1 + )]) {} + + void sum(float *out, float *v, size_t n, char _[( + *out = 0.0f, + sum_aux(out, v, n, ""), + 1 + )]) {} + + In fact, any imperative-style loop can be turned into an equivalent recursive + loop (as any functional programmer will be happy to demonstrate), though since + C lacks closures or any form of anonymous functions, it can get quite unwieldy + (I hope you like auxiliary functions). + + The astute reader might point out that these two versions of `sum` are not + equivalent because the recursive definition may cause a stack overflow for + large enough values of `n`. This is unfortunately true, and the major hurdle + for the practicality of disembodied C, but does not preclude Turing + completeness (an ideal [Turing machine] has infinite memory at its + disposal). Luckily, modern compilers are smart, and if we write our functions + carefully to be [tail recursive], they will often be able to perform tail + call elimination, removing the risk of a stack overflow. For the above + example, both gcc and clang are able to optimize the tail call ([see on + compiler explorer][b]). + +- Although not relevant to standard C99, in case you had the idea of using [gcc + statement expressions] to bypass these limitations, the compiler will stop + you right on your tracks since it doesn't allow those outside of function + bodies. + + +[Turing machine]: https://en.wikipedia.org/wiki/Turing_machine +[tail recursive]: https://en.wikipedia.org/wiki/Tail_call +[gcc statement expressions]: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html +[a]: https://godbolt.org/z/f3chYMvh5 +[b]: https://godbolt.org/z/b1aG78os5 + + +Limitations +=========== + +After all of that, it seems that basically anything can be done in disembodied +C, so, is there something that *can't* be expressed in this C99 subset? + +With the rules that we've laid down so far, yes. In particular: + +- It is not possible in the general case to use APIs that require callbacks. + For example, look at the standard `qsort` function: + + void qsort(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *)); + + The `compar` parameter is a pointer to a function that should return the + result of comparing two values in the given array. Since its parameters are + void pointers, we can't have the VLA argument we need to perform computations + (arrays of `void` are not valid C), and we also cannot provide an `int` + return value. Thus there is no way (that I know of) to use this function in + standards-compliant disembodied C. + + This is not a dealbreaker since we could just reimplement `qsort` ;). + +- We cannot access the program arguments. The entry point of a program in + disembodied C looks like this: + + int main(int argc, char *argv[ /* code here ... */ ]) {} + + In the VLA length expression for `argv`, `argc` is in scope, but `argv` is + not. + + Fortunately there is a way to work around this: we can use the common + extension where `main` can take a third argument which is a pointer to the + environment. According to the standard ([J.5.1]), this is implemented in + hosted environments. In practice, POSIX environments and Microsoft both + support it. Then, our entry point looks like this instead: + + int main(int argc, char **argv, char *envp[/* ... */]) {} + + Now `argv` is in scope within the `envp` array length expression. + + Side note: even though we can't `return`, `main` is the exception to the rule + that reaching the closing `}` of a function returning non-void is verboten, + and is equivalent to returning zero ([5.1.2.2.3]). If we need to terminate + with an exit code other than zero, we can use `exit()`. + + +[J.5.1]: http://port70.net/~nsz/c/c99/n1256.html#J.5.1 +[5.1.2.2.3]: http://port70.net/~nsz/c/c99/n1256.html#5.1.2.2.3 + + +Final Words +=========== + +It seems that with enough effort anything can be done with these peculiar +restrictions. As an example, I wrote implementations of the [MD5] and [SHA256] +hash functions as well as [Conway's Game of Life using SDL for graphics][life]. +I was also working on a more interesting and "useful" program but I kind of +lost motivation halfway through. I'll update this article if I ever come back +to finish that. + +In case it wasn't clear enough, there is zero practical reason to ever do any +of this. I hope you'll agree it's a fun party trick though :). + + +[MD5]: https://git.sr.ht/~lsof/x/tree/main/item/vla/md5sum.c +[SHA256]: https://git.sr.ht/~lsof/x/tree/main/item/vla/sha256sum.c +[life]: https://git.sr.ht/~lsof/x/tree/main/item/vla/life.c diff --git a/content/b/first.md b/content/b/first.md new file mode 100644 index 0000000..291b364 --- /dev/null +++ b/content/b/first.md @@ -0,0 +1,15 @@ ++++ +title = "test post" +date = 2022-02-02 ++++ + +# test +lorem ipsum dolor sit amet + + u16 rng(u16 s){ + s^=s<<8,s=(s<<8 + |s>>8)^((s&0xff + )<<1),s=0x7e00^ + (s>>1)^(0x9e74& + -(~s&1));return + 46497^s?s:s^s;} diff --git a/static/asset/link.png b/static/asset/link.png Binary files differnew file mode 100644 index 0000000..6f2867f --- /dev/null +++ b/static/asset/link.png diff --git a/static/asset/linux.png b/static/asset/linux.png Binary files differnew file mode 100644 index 0000000..e9397bd --- /dev/null +++ b/static/asset/linux.png diff --git a/static/asset/lynx.png b/static/asset/lynx.png Binary files differnew file mode 100644 index 0000000..2b53dfa --- /dev/null +++ b/static/asset/lynx.png diff --git a/static/asset/mailto.png b/static/asset/mailto.png Binary files differnew file mode 100644 index 0000000..20d9c9e --- /dev/null +++ b/static/asset/mailto.png diff --git a/static/asset/nvim.png b/static/asset/nvim.png Binary files differnew file mode 100644 index 0000000..1559e3c --- /dev/null +++ b/static/asset/nvim.png diff --git a/static/mips.html b/static/mips.html new file mode 100644 index 0000000..219034c --- /dev/null +++ b/static/mips.html @@ -0,0 +1,158 @@ +<!DOCTYPE HTML> +<html> + +<head> + <meta http-equiv="Content-type" content="text/html; charset=utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>MIPS (dis)assembler</title> + <link rel="stylesheet" href="/style.css"/> + + <style> +body { + max-width: 90%; +} + +textarea { + resize: none; + width: 45%; + font-size: 110%; + font-family: monospace; + font-variant-ligatures: none; + color: var(--pre-fg); + background: var(--pre-bg); + border: 1px solid var(--border1); + border-radius: 5px; +} + +.option { + display: inline-block; + color: var(--code-fg); + background: var(--code-bg); + border: 1px solid var(--border1); + border-radius: 5px; + margin-right: 10px; + margin-top: 5px; + margin-bottom: 5px; + padding-left: 5px; + padding-right: 5px; + padding-bottom: 5px; +} + +label { + font-family: monospace; + font-size: 14px; + position: relative; + margin-bottom: 10px; +} +input[type="text"] { + margin: 0; + font-family: monospace; + font-variant-ligatures: none; + background: var(--code-bg); + border: 1px solid var(--border2); + color: var(--fg); + font-size: 16px; + appearance: none; + box-shadow: none; + border-radius: 3px; +} +input[type="checkbox"] { + margin: 0; + position: relative; + top: 2px; +} +select { + font-family: monospace; + background: var(--code-bg); + border: 1px solid var(--border2); + border-radius: 3px; + color: var(--fg); + font-size: 15px; + margin-left: 2px; +} + +button { +} + +hr { + position: absolute; + width: 78%; + left: 12.2%; +} + + </style> + +</head> + +<body> + <div style="width: 85%; margin: 0 auto; padding-left: 10%;"> + <noscript>JavaScript is required</noscript> + <h2>MIPS Assembler</h2> + <p>Input MIPS assembly code below. Labels, .org and simple .word directives are also recognized. Comments start with ';' and extend to the end of the line.</p> + <textarea wrap='off' rows="15" id="assembler-input" placeholder="MIPS assembly input" spellcheck="false"></textarea> + <textarea wrap='off' rows="15" readonly id="assembler-output" placeholder="Assembler output"></textarea> + <br> + <div class="option"> + <input type='checkbox' id="assembler-hideaddr"> + <label for='assembler-hideaddr'>Hide Addresses</label> + </div> + <div class="option"> + <input type='checkbox' id="assembler-hidemnemonic"> + <label for="assembler-hidemnemonic">Hide Mnemonics</label> + </div> + <div class="option"> + <input type='checkbox' id="assembler-hidenote"> + <label for="assembler-hidenote">Hide Annotations</label> + </div> + <div class="option"> + <select id="assembler-endian"> + <option value="big" selected>Big</option> + <option value="little">Little</option> + </select> + <label for="assembler-endian">Endianness</label> + </div> + <button class="option" onclick="assembleAndPresent();">ASSEMBLE</button> + <br><br><hr><br> + + <h2>MIPS Disassembler</h2> + <p>Input MIPS machine code in hexadecimal below.</p> + <div class="option"> + <label>.org (Start Address)</label> + <input type='text' id="disassembler-org" size="8" placeholder="00000000" spellcheck="false"> + </div> + <br> + <textarea wrap='off' rows="15" id="disassembler-input" placeholder="MIPS machine code in hex" spellcheck="false"></textarea> + <textarea wrap='off' rows="15" readonly id="disassembler-output" placeholder="Disassembler output"></textarea> + <br> + <div class="option"> + <input type='checkbox' id="disassembler-hideaddr"> + <label for='disassembler-hideaddr'>Hide Addresses</label> + </div> + <div class="option"> + <input type='checkbox' id="disassembler-hidehex"> + <label for="disassembler-hidehex">Hide Hex</label> + </div> + <div class="option"> + <input type='checkbox' id="disassembler-hidenote"> + <label for="disassembler-hidenote">Hide Annotations</label> + </div> + <div class="option"> + <select id="disassembler-endian"> + <option value="big" selected>Big</option> + <option value="little">Little</option> + </select> + <label for="disassembler-endian">Endianness</label> + </div> + + <button class="option" onclick="disassembleAndPresent();">DISASSEMBLE</button> + + <br><br><hr><br> + <a href="mips.js">source</a> <br> + <a href="/">go back</a> + + </div> + + <script type="text/javascript" src='./mips.js'></script> +</body> + +</html> diff --git a/static/mips.js b/static/mips.js new file mode 100644 index 0000000..c839482 --- /dev/null +++ b/static/mips.js @@ -0,0 +1,627 @@ +'use strict'; + +/* + * Copyright (c) 2022, lemon + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +const $ = x => document.getElementById(x); +const nullish = x => x === null || x === undefined || Number.isNaN(x); + +const registerNames = [ + 'zero', 'at', 'v0', 'v1', 'a0', 'a1', 'a2', 'a3', + 't0', 't1', 't2', 't3', 't4', 't5', 't6', 't7', + 's0', 's1', 's2', 's3', 's4', 's5', 's6', 's7', + 't8', 't9', 'k0', 'k1', 'gp', 'sp', 's8', 'ra' +]; +const regIdx2Name = i => registerNames[i]; +function regName2Idx(name) { + const asIdx = name.substring(0,1) == '$' ? parseInt(name.substring(1), 10) : null; + if (!nullish(asIdx) && asIdx >= 0 && asIdx < 32) return asIdx; + const asName = registerNames.indexOf(name.replace(/^\$/, "").replace(/^fp$/, "s8")); + if (asName >= 0) return asName; +}; + +function leftpad(s, count, pad=' ') { + return s.length >= count ? s : s + pad.repeat(count - s.length); +} +function rightpad(s, count, pad=' ') { + return s.length >= count ? s : pad.repeat(count - s.length) + s; +} + +// int -> 4-byte hex str (8 characters) +function int2hex(n) { + return rightpad(((n >> 16) & 0xFFFF).toString(16), 4, '0') + rightpad((n & 0xFFFF).toString(16), 4, '0'); +} + +function bswap32(n) { + return ((n >> 24) & 0xFF) << 0 + | ((n >> 16) & 0xFF) << 8 + | ((n >> 8) & 0xFF) << 16 + | ((n >> 0) & 0xFF) << 24; +} + + /////////////////////////////////// +//// //// +//// INSTRUCTION DEFINITIONS //// +//// //// + /////////////////////////////////// + +// some of the most common syntaxes are given a name here to facilitate reading comprehension +const syntax = { 'regArith' : ['rd', 'rs', 'rt'], + 'shiftImm' : ['rd', 'rt', 'sa'], + 'shiftReg' : ['rd', 'rt', 'rs'], + 'arithImm' : ['rt', 'rs', 'imm16'], + 'arithImmU': ['rt', 'rs', 'imm16.u'], + 'memory' : ['rt', ['imm16', 'rs']] + }; +const instructionTable = { + 'add' : { 'op': 0x00, 'funct': 32, 'syntax': syntax.regArith }, + 'addu' : { 'op': 0x00, 'funct': 33, 'syntax': syntax.regArith }, + 'sub' : { 'op': 0x00, 'funct': 34, 'syntax': syntax.regArith }, + 'subu' : { 'op': 0x00, 'funct': 35, 'syntax': syntax.regArith }, + 'and' : { 'op': 0x00, 'funct': 36, 'syntax': syntax.regArith }, + 'or' : { 'op': 0x00, 'funct': 37, 'syntax': syntax.regArith }, + 'xor' : { 'op': 0x00, 'funct': 38, 'syntax': syntax.regArith }, + 'nor' : { 'op': 0x00, 'funct': 39, 'syntax': syntax.regArith }, + 'sll' : { 'op': 0x00, 'funct': 0, 'syntax': syntax.shiftImm }, + 'srl' : { 'op': 0x00, 'funct': 2, 'syntax': syntax.shiftImm }, + 'sra' : { 'op': 0x00, 'funct': 3, 'syntax': syntax.shiftImm }, + 'sllv' : { 'op': 0x00, 'funct': 4, 'syntax': syntax.shiftReg }, + 'srlv' : { 'op': 0x00, 'funct': 6, 'syntax': syntax.shiftReg }, + 'srav' : { 'op': 0x00, 'funct': 7, 'syntax': syntax.shiftReg }, + 'mfhi' : { 'op': 0x00, 'funct': 16, 'syntax': ['rd'] }, + 'mflo' : { 'op': 0x00, 'funct': 18, 'syntax': ['rd'] }, + 'mthi' : { 'op': 0x00, 'funct': 17, 'syntax': ['rs'] }, + 'mtlo' : { 'op': 0x00, 'funct': 19, 'syntax': ['rs'] }, + 'mult' : { 'op': 0x00, 'funct': 24, 'syntax': ['rs', 'rt'] }, + 'multu' : { 'op': 0x00, 'funct': 25, 'syntax': ['rs', 'rt'] }, + 'div' : { 'op': 0x00, 'funct': 26, 'syntax': ['rs', 'rt'] }, + 'divu' : { 'op': 0x00, 'funct': 27, 'syntax': ['rs', 'rt'] }, + 'slt' : { 'op': 0x00, 'funct': 42, 'syntax': syntax.regArith }, + 'sltu' : { 'op': 0x00, 'funct': 43, 'syntax': syntax.regArith }, + 'jr' : { 'op': 0x00, 'funct': 8, 'syntax': ['rs'] }, + 'jalr' : { 'op': 0x00, 'funct': 9, 'syntax': ['rs'] }, + 'j' : { 'op': 0x02, 'syntax': ['imm26.j'] }, + 'jal' : { 'op': 0x03, 'syntax': ['imm26.j'] }, + 'beq' : { 'op': 0x04, 'syntax': ['rs', 'rt', 'imm16.rel'] }, + 'bne' : { 'op': 0x05, 'syntax': ['rs', 'rt', 'imm16.rel'] }, + 'blez' : { 'op': 0x06, 'syntax': ['rs', 'imm16.rel'] }, + 'bgtz' : { 'op': 0x07, 'syntax': ['rs', 'imm16.rel'] }, + 'beql' : { 'op': 0x14, 'syntax': ['rs', 'rt', 'imm16.rel'] }, + 'bnel' : { 'op': 0x15, 'syntax': ['rs', 'rt', 'imm16.rel'] }, + 'blezl' : { 'op': 0x16, 'syntax': ['rs', 'imm16.rel'] }, + 'bgtzl' : { 'op': 0x17, 'syntax': ['rs', 'imm16.rel'] }, + 'addi' : { 'op': 0x08, 'syntax': syntax.arithImm }, + 'addiu' : { 'op': 0x09, 'syntax': syntax.arithImm }, + 'slti' : { 'op': 0x0A, 'syntax': syntax.arithImm }, + 'sltiu' : { 'op': 0x0B, 'syntax': syntax.arithImm }, + 'andi' : { 'op': 0x0C, 'syntax': syntax.arithImmU }, + 'ori' : { 'op': 0x0D, 'syntax': syntax.arithImmU }, + 'xori' : { 'op': 0x0E, 'syntax': syntax.arithImmU }, + 'lui' : { 'op': 0x0F, 'syntax': ['rt', 'imm16.u'] }, + 'lb' : { 'op': 0x20, 'syntax': syntax.memory }, + 'lbu' : { 'op': 0x24, 'syntax': syntax.memory }, + 'lh' : { 'op': 0x21, 'syntax': syntax.memory }, + 'lhu' : { 'op': 0x25, 'syntax': syntax.memory }, + 'lw' : { 'op': 0x23, 'syntax': syntax.memory }, + 'lwl' : { 'op': 0x22, 'syntax': syntax.memory }, + 'lwr' : { 'op': 0x26, 'syntax': syntax.memory }, + 'sb' : { 'op': 0x28, 'syntax': syntax.memory }, + 'sh' : { 'op': 0x29, 'syntax': syntax.memory }, + 'sw' : { 'op': 0x2B, 'syntax': syntax.memory }, + 'swl' : { 'op': 0x2A, 'syntax': syntax.memory }, + 'swr' : { 'op': 0x2E, 'syntax': syntax.memory }, + 'cache' : { 'op': 0x2F, 'syntax': ['cacheop', ['imm16', 'rs']] }, + 'mfc0' : { 'op': 0x10, 'rs': 0, 'syntax': ['rt', 'fs'] }, + 'mtc0' : { 'op': 0x10, 'rs': 4, 'syntax': ['rt', 'fs'] }, + 'mfc1' : { 'op': 0x11, 'rs': 0, 'syntax': ['rt', 'fs'] }, + 'mtc1' : { 'op': 0x11, 'rs': 4, 'syntax': ['rt', 'fs'] }, + 'cfc1' : { 'op': 0x11, 'rs': 2, 'syntax': ['rt', 'fs'] }, + 'ctc1' : { 'op': 0x11, 'rs': 6, 'syntax': ['rt', 'fs'] }, + 'bc1f' : { 'op': 0x11, 'rs': 8, 'ndtf': 0, 'syntax': ['cc', 'imm16.rel'] }, + 'bc1t' : { 'op': 0x11, 'rs': 8, 'ndtf': 1, 'syntax': ['cc', 'imm16.rel'] }, + 'bc1fl' : { 'op': 0x11, 'rs': 8, 'ndtf': 2, 'syntax': ['cc', 'imm16.rel'] }, + 'bc1ft' : { 'op': 0x11, 'rs': 8, 'ndtf': 3, 'syntax': ['cc', 'imm16.rel'] }, + 'syscall': { 'op': 0x00, 'funct': 12, 'optsyntax': ['syscode'] }, + 'break' : { 'op': 0x00, 'funct': 13, 'optsyntax': ['brkcode'] }, + 'nop' : { 'op': 0x00, 'rs': 0, 'rt': 0, 'rd': 0, 'sa': 0, 'funct': 0 } +}; Object.freeze(instructionTable); + +function _decodeEncode(f) { + // f: (field,offset,size) -> { ... } + f('op', 26, 6) + f('rs', 21, 5) + f('rt', 16, 5) + f('rd', 11, 5) + f('sa', 6, 5) + f('funct', 0, 6) + f('imm16', 0, 16) + f('imm26', 0, 26) + f('fs', 11, 5) + f('ndtf', 16, 2) + f('cc', 18, 3) + f('brkcode', 16, 10) + f('syscode', 6, 20) + f('cacheop', 16, 5) +}; + +// bit masks containing the bits that each instruction makes use of, +// to detect if an instruction is setting unused bits to a non-zero value +let instrUsedBits = {}; +for (const i in instructionTable) { + instrUsedBits[i] = 0; + + // gather used fields + let usedFields = []; + for (const k in instructionTable[i]) + usedFields.push(k) + const visit = x => Array.isArray(x) ? x.forEach(visit) : usedFields.push(x.replace(/\..*$/,"")); + if (instructionTable[i].syntax) visit(instructionTable[i].syntax); + if (instructionTable[i].optsyntax) visit(instructionTable[i].optsyntax); + + // build bitmask from fields + _decodeEncode((name, offst, siz) => { + if (usedFields.includes(name)) + instrUsedBits[i] |= ((1 << siz) - 1) << offst; + }); +}; + + + ///////////////////////////////////////// +//// //// +//// INSTRUCTION ENCODING/DECODING //// +//// //// + ///////////////////////////////////////// + +function encode(ins) { + let word = 0; + let setbits = 0; + _decodeEncode((field, offset, siz) => { + const mask = (1 << siz) - 1; + if (!nullish(ins[field])) { + if ((setbits & (mask << offset)) != 0) + throw "! Internal error: overwriting instruction fields during encoding! " + field + " instr " + ins; + word = (word & ~(mask << offset)) | ((ins[field] & mask) << offset); + setbits |= mask << offset; + } + }); + //console.log(int2hex(setbits), ins); + //if (setbits != (0xFFFFFFFF|0)) throw "! Not all bits set during encoding!"; // hmm.... + return word; +}; + +function decode(word) { + let ins = {}; + _decodeEncode((field, offset, siz) => { + ins[field] = (word >> offset) & ((1 << siz) - 1); + }); + return ins; +}; + + /////////////////////////////////////////////////////// +//// //// +//// INDIVIDUAL INSTRUCTION ASSEMBLY/DISASSEMBLY //// +//// //// + /////////////////////////////////////////////////////// + +function assemble(labels, line, address) { + const insName = line.split(' ')[0]; + if (insName == '.org' || (insName.substring(insName.length - 1) == ':' && !nullish(labels[insName.substring(0, insName.length - 1)]))) return 'continue'; + const insFmt = instructionTable[insName]; + const args = line.indexOf(' ') < 0 ? null : line.substring(line.indexOf(' ')).replace(/\s*/g, "").split(','); + if (insName == '.word') { + if (!args || args.length != 1) throw "! Illegal syntax for .word: " + line; + const arg = args[0]; + let word; + if (arg.match(/^[0-9]+$/)) + word = parseInt(arg, 10); + else if (arg.match(/^0x[0-9a-f]+$/)) + word = parseInt(arg.substring(2), 16); + if (nullish(word)) throw "! Illegal syntax for .word: " + line; + if (word > 0xFFFFFFFF) throw "! .word: " + arg + " too large"; + return word; + } + + if (!insFmt) throw "! Unknown instruction " + insName; + for (const i in args) { + // turn "x(y)" into [x, y] + const matches = args[i].match(/^([^)]+)\(([^)]+)\)$/); + if (matches && matches.length == 3) args[i] = [matches[1], matches[2]]; + } + + let ins = {}; + for (const k in insFmt) ins[k] = insFmt[k]; + const syntax = insFmt.syntax || insFmt.optsyntax; + if (!(!args && insFmt.optsyntax) && (args || syntax)) { + if (!syntax || !args) + throw "! Invalid syntax for " + insName; + + function match(fmt, arg, dest) { + if (Array.isArray(fmt)) { + if (arg.length != fmt.length) throw "! Invalid syntax for " + insName; + for (let i = 0; i < arg.length; i++) { + const field = fmt[i]; + match(field, arg[i], dest); + } + } else { + //assert(typeof(fmt) == 'string'); + if (['rt','rs','rd'].includes(fmt)) { // base registers + const regIdx = regName2Idx(arg); + //console.log(fmt); + if (nullish(regIdx)) throw "! Invalid register name " + arg; + dest[fmt] = regIdx; + } else if (fmt == 'sa') { // shift amount + let shamt; + if (arg.match(/^[0-9]+$/)) + shamt = parseInt(arg, 10); + else if (arg.match(/^0x[0-9a-f]+$/)) + shamt = parseInt(arg.substring(2), 16); + if (nullish(shamt)) throw "! Invalid shift amount " + arg; + if (shamt < 0 || shamt > 31) throw "! Shift amount out of range"; + dest.sa = shamt; + } else if (['fs'].includes(fmt)) { // floating point registers + try { + const regIdx = parseInt(arg.match(/^\$?f([0-9]+)$/)[1], 10); + if (regIdx < 0 || regIdx > 31) throw ""; + dest[fmt] = regIdx; + } catch { + throw "! Invalid floating-point register name " + arg; + } + } else if (fmt == 'imm16' || fmt == 'imm16.u') { // 16-bit immediates + let negate = false; + if (Array.isArray(arg) && arg.length == 2 && ['%hi','%lo'].includes(arg[0])) { + if (!nullish(labels[arg[1]])) + dest.imm16 = (labels[arg[1]] >> (arg[0] == '%hi' ? 16 : 0)) & 0xFFFF; + else + throw "! No such label: " + arg[1]; + } else if (typeof arg === 'string') { + let str = arg; + if (str.substring(0,1) == '-') { + negate = true; + str = str.substring(1); + } + let n; + let hex = false; + if (str.match(/^[0-9]+$/)) n = parseInt(str, 10); + else if (str.match(/^0x[0-9a-fA-F]+$/)) { + n = parseInt(str.substring(2), 16); + hex = true; + } + if (nullish(n)) throw "! Invalid syntax for immediate value " + arg; + if (negate) n = -n; + + let min, max; + if (hex) { min = -0x7FFF - 1, max = 0xFFFF; } + else if (fmt == 'imm16') { min = -0x7FFF - 1, max = 0x7FFF; } + else /*fmt == 'imm16.u'*/{ min = 0, max = 0xFFFF; } + if (n > max || n < min) throw "! Immediate value out of range " + arg; + dest.imm16 = n & 0xFFFF; + } else throw "! Bad arg: " + arg; + } else if (fmt == 'imm16.rel') { // 16 (18)-bit address/relative branch offset + const baseAddr = address + 4; + let absolute = true; + let negate = false; + let str = arg; + if (str.substring(0,1) == '+') { + absolute = false; + str = str.substring(1); + } else if (str.substring(0,1) == '-') { + absolute = false; + negate = true; + str = str.substring(1); + } + let target; + if (!absolute && str.match(/^[0-9]+$/)) + target = parseInt(str, 10); + else if (str.match(absolute ? /^(0x)?[0-9a-f]+$/ : /^0x[0-9a-f]+$/)) + target = parseInt(str.replace(/^0x/, ""), 16); + else if (absolute) { + target = labels[str]; + if (nullish(target)) throw "! No such label: " + str; + } + + if (nullish(target)) throw "! Invalid syntax for branch target " + arg; + if (negate) target = -target; + if (!absolute) target = baseAddr + target * 4; + if (target & 3) throw "! Unaligned branch target " + line; + const disp = (target - baseAddr) >> 2; + if (disp < -0x8000 || disp > 0x7FFF) throw "! Jump target " + int2hex(target) + " out of range"; + dest.imm16 = disp & 0xFFFF; + } else if (fmt == 'imm26.j') { // j and jal + const baseAddr = address + 4; + let target; + if (arg.match(/^(0x)?[0-9a-f]+$/)) + target = parseInt(arg.replace(/^0x/, ""), 16); + else { + target = labels[arg]; + if (nullish(target)) throw "! No such label: " + arg; + } + + if (nullish(target)) throw "! Invalid syntax for branch target " + arg; + if (target & 3) throw "! Unaligned branch target"; + if ((baseAddr & 0xF0000000) != (target & 0xF0000000)) throw "! Branch target " + int2hex(target) + " out of range"; + dest.imm26 = (target >> 2) & 0x03FFFFFF; + } else if (fmt == 'brkcode' || fmt == 'syscode') { // break/syscall code + let str = arg; + let n; + if (str.match(/^[0-9]+$/)) n = parseInt(str, 10); + else if (str.match(/^0x[0-9a-fA-F]+$/)) + n = parseInt(str.substring(2), 16); + else + throw "! Invalid syntax for immediate value " + arg; + + const max = (fmt == 'brkcode') ? 0x3FF : 0xFFFFF; + if (n < 0 || n > max) throw "! Immediate value out of range " + arg; + dest[fmt] = n; + } else if (fmt == 'cacheop') { + let str = arg; + let n; + if (str.match(/^[0-9]+$/)) n = parseInt(str, 10); + else if (str.match(/^0x[0-9a-fA-F]+$/)) + n = parseInt(str.substring(2), 16); + if (nullish(n)) throw "! Invalid syntax for cache op " + arg; + + if (n < 0 || n > 31) throw "! Cache op out of range " + arg; + dest.cacheop = n; + } else throw "! Unimplemented syntax " + fmt; + } + } + + match(syntax, args, ins); + } + + return encode(ins); +}; + +function disassemble(word, address, showNote) { + if (word === 0) return 'nop'; + const ins = decode(word); + let name; + for (const insName in instructionTable) { + const insFmt = instructionTable[insName]; + let matches = true; + for (const k in insFmt) { + if (!k.match(/syntax/) && insFmt[k] != ins[k]) { matches = false; break; } + } + if (matches) { name = insName; break; } + } + + if (nullish(name)) return '??'; + + let note = ''; + function display(fmt, ins, depth=0) { + if (!fmt) return ""; + if (Array.isArray(fmt)) { + if (depth == 0 || fmt.length != 2) { + // arg1,arg2,... + let s = ''; + for (const i in fmt) { + s += display(fmt[i], ins, depth + 1); + if (i != fmt.length - 1) s += ','; + } + return s; + } else { + // arg1(arg2) + return display(fmt[0], ins, depth + 1) + '('+display(fmt[1], ins, depth + 1)+')'; + } + } else { + if (['rs','rt','rd'].includes(fmt)) // basic register + return regIdx2Name(ins[fmt]); + else if (fmt == 'sa') // shift amount + return ins.sa+''; + else if (['fs'].includes(fmt)) // float regiser + return '$f'+ins[fmt]; + else if (fmt == 'imm16') { // signed 16-bit immediate + const n = (ins.imm16 << 16) >> 16; // sign extension + if (n > 9 || n < -9) + note = leftpad(n+'', 6) + ' = '+n.toString(16).replace(/^-/, "-0x").replace(/^([^-])/, "0x$1") + return ''+n; + } else if (fmt == 'imm16.u') { // unsigned 16-bit immediate + const n = ins.imm16; + const paddedHex = '0x'+rightpad(n.toString(16), 4, '0'); + if (n > 9 || n < -9) + note = paddedHex + ' = ' + n; + return paddedHex; + } else if (fmt == 'imm16.rel') { // relative branch offset + const offst = (ins.imm16 << 16) >> 16; + const dispStr = offst >= 0 ? '+0x'+offst.toString(16) + : '-0x'+(-offst).toString(16); + if (nullish(address)) { + return dispStr; + } else { + const target = address + 4 + (offst << 2); + note = dispStr; + return int2hex(target); + } + } else if (fmt == 'imm26.j') { // j and jal + if (nullish(address)) + return int2hex(ins.imm26 << 2); + return ((address >> 28) & 0xF).toString(16)+int2hex(ins.imm26 << 2).substring(1); + } else if (fmt == 'brkcode' || fmt == 'syscode') { // break/syscall code + return ins[fmt] ? '0x'+ins[fmt].toString(16) : ''; + } else if (fmt == 'cacheop') { + return '0x'+ins.cacheop.toString(16); + } else throw "! Unimplemented syntax " + fmt; + + } + }; + const syntax = instructionTable[name].syntax || instructionTable[name].optsyntax; + const base = leftpad(name, 8) + display(syntax, ins); + if ((word & ~instrUsedBits[name]) != 0) note = "* non-zero unused bits"; + if (note && showNote) return leftpad(base, 27) + ' ; ' + note; + return base; +}; + + //////////////////////////////////////////////// +//// //// +//// "WHOLE-PROGRAM" ASSEMBLY/DISASSEMBLY //// +//// //// + //////////////////////////////////////////////// + +function assembleListing(input) { + let lines = input.split('\n'); + for (const i in lines) { + lines[i] = lines[i] + .toLowerCase() // normalize case + .replace(/^\s+/, "") // trim leading whitespace + .replace(/\s*(;.*)?$/,"") // trim trailing whitespace and comments + .replace(/\s+/, " "); // simplify whitespace + } + lines = lines.filter(s => s.length != 0); // remove empty lines + + // map lines -> addresses, also find labels + let addresses = []; + let lastAddress = -4; + let labels = {}; + for (const line of lines) { + if (line.match(/^\.org.*/)) { + try { + const matches = [...line.matchAll(/^\.org\s+(0x)?([a-zA-Z0-9]+)$/g)]; + const addr = parseInt(matches[0][2], 16); + addresses.push(addr); + lastAddress = addr - 4; + } catch { + throw "! Invalid .org syntax (should be ex: .org 800ABCD0)" + } + if (lastAddress+4 > 0xFFFFFFFF) throw "! .org: address out of range" + if (lastAddress & 3) throw "! Unaligned address is disallowed" + } else if (line.match(/^[_a-zA-Z$][_a-zA-Z0-9$]*:$/)) { // label + addresses.push(lastAddress+4); + const label = line.replace(/:$/, ""); + if (labels[label] !== undefined) throw "! Redefining label " + label; + labels[label] = lastAddress+4; + } else { + lastAddress += 4; + addresses.push(lastAddress); + } + } + + let output = []; + for (const i in lines) { + const res = assemble(labels, lines[i], addresses[i]); + if (res === 'continue') continue; + output.push([addresses[i], res]); + } + + return output; +}; + +function disassembleListing(input, orgAddr, littleEndian, showNote) { + // remove punctuation and whitespace + input = input.replace(/([-.,]|\^|\s|[\n])/g, ""); + if (input.length % 8 != 0) throw "! Hex words expected (incorrect bit length in input)"; + + let addr = orgAddr; + let output = []; + for (let i = 0; i < input.length; i += 8) { + const hex = input.substring(i, i + 8); + const word = hex.match(/^[0-9A-Fa-f]+$/) ? parseInt(hex, 16) : null; + if (nullish(word)) throw "! Invalid hex word: " + hex; + output.push([addr, word, disassemble(littleEndian ? bswap32(word) : word, addr, showNote)]); + addr += 4; + } + + return output; +}; + + //////////////////////////////////////// +//// //// +//// 'DOM' INTERFACE INPUT/OUTPUT //// +//// //// + //////////////////////////////////////// + +function assembleAndPresent() { + let out = $('assembler-output'); + try { + const insns = assembleListing($('assembler-input').value); + const hideAddr = $('assembler-hideaddr').checked; + const hideMnemonic = $('assembler-hidemnemonic').checked; + const hideNote = $('assembler-hidenote').checked; + const bigEndian = $('assembler-endian').value === 'big'; + + out.value = ''; + insns.sort((x, y) => y[0] < x[0]); + let lastAddr; + for (const insn of insns) { + if (lastAddr !== undefined) { + if (lastAddr >= insn[0]) throw "! Overwriting instructions at address " + int2hex(insn[0]); + if (lastAddr < insn[0] - 4) out.value += "........\n"; + } + + const [addr, word] = insn; + if (!hideAddr) out.value += int2hex(addr) + ': '; + out.value += int2hex(bigEndian ? word : bswap32(word)); + if (!hideMnemonic) out.value += ' ' + disassemble(word, addr, !hideNote); + out.value += '\n'; + + lastAddr = addr; + }; + } catch (e) { + console.error(e); + out.value = "! Error!!\n" + e; + } +} + +function disassembleAndPresent() { + let out = $('disassembler-output'); + try { + const hideAddr = $('disassembler-hideaddr').checked; + const hideHex = $('disassembler-hidehex').checked; + const hideNote = $('disassembler-hidenote').checked; + const bigEndian = $('disassembler-endian').value === 'big'; + const org = parseInt($('disassembler-org').value, 16) || 0; + if (org > 0xFFFFFFFF || org < 0) + throw "! Invalid starting address (out of range) " + rightpad(org.toString(16), 8, "0"); + else if ((org & 3) != 0) + throw "! Invalid starting address (unaligned) " + rightpad(org.toString(16), 8, "0"); + + const insns = disassembleListing($('disassembler-input').value, org, !bigEndian, !hideNote); + + out.value = ''; + for (const insn of insns) { + const [addr, word, mnemonic] = insn; + + if (!hideAddr) out.value += int2hex(addr) + ': '; + if (!hideHex) out.value += int2hex(word) + ' '; + out.value += mnemonic; + out.value += '\n'; + } + } catch (e) { + console.error(e); + out.value = "! Error!!\n" + e; + } +} + +window.onload = () => { + let disasmOrgPrevVal = $('disassembler-org').value || 0; + $('disassembler-org').oninput = (e) => { + // revert edit if not a valid hex address + const newval = e.target.value; + if (newval.length && !newval.match(/^[0-9A-Fa-f]+$/)) + e.target.value = disasmOrgPrevVal; + else + disasmOrgPrevVal = e.target.value; + }; +}; diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..72a7b1c --- /dev/null +++ b/static/style.css @@ -0,0 +1,106 @@ +:root { /* default theme (light) */ + --fg: #333; + --bg: #f4f0f0; + --subtitle: #777; + --pre-fg: #111; + --pre-bg: #eaeaea; + --border1: #ccc; + --code-bg: #e8e8e8; + --border2: #999; +} + +@media (prefers-color-scheme: dark) { + :root { + --fg: #fff; + --bg: #262222; + --subtitle: #888; + --pre-fg: #eaeaea; + --pre-bg: #262327; + --border1: #444; + --code-bg: #222; + --border2: #555; + } +} + +body { + margin: auto; + max-width: 820px; + line-height: 1.6; + font-family: Serif; + padding: 0 10px; + font-size: 18px; + color: var(--fg); + background: var(--bg); +} + +p { + text-align: justify; +} + +h1.title { + line-height: 1.2; + font-size: 40px; +} + +h1 { + line-height: 1.2; + font-size: 26px; +} + +h2 { + line-height: 1.2; + font-size: 21px; +} + +p.subtitle { + font-size: 16px; + color: var(--subtitle); +} + +pre { + padding: 8px 8px 8px 8px; + border-radius: 3px; + border: 1px solid var(--border1); + font-size: 14px; + overflow-x: auto; + background: var(--pre-bg); + color: var(--pre-fg); +} + +pre > code { + background: var(--pre-bg); + font-size: 14px; + border: none; + padding: 0px; +} + +code { + background: var(--code-bg); + border: 1px solid var(--border1); + font-size: 14px; + border-radius: 3px; + padding: 2px 2px; +} + +a { + color: #d53c54;/*#e77850;*/ + text-decoration: none; +} + +a:hover { + text-decoration: underline; + text-decoration-style: dotted; +} + +td,th { + vertical-align:top; + padding: 5px 5px 5px; + text-align:center; + border: solid 1px var(--border2); +} + +table { + border: solid 1px var(--border1); + background: var(--code-bg); + font-size: 15px; +} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..149831a --- /dev/null +++ b/templates/base.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>{% block title %} {% endblock %}</title> + <link rel="stylesheet" href="/style.css"/> +</head> + +<body> + <section class="section"> + <div class="container"> + {% block content %} {% endblock %} + </div> + </section> +</body> + +</html> diff --git a/templates/blog-page.html b/templates/blog-page.html new file mode 100644 index 0000000..b8263e0 --- /dev/null +++ b/templates/blog-page.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block title %}{{ page.title }}{% endblock title %} + +{% block content %} +<h1 class="title"> + {{ page.title }} +</h1> +<p class="subtitle">{{ page.date | date(format="%-d %b %Y") }}</p> + +<hr> +{{ page.content | safe }} +<hr> <a href="../">Go back</a> +{% endblock content %} diff --git a/templates/blog.html b/templates/blog.html new file mode 100644 index 0000000..afee41d --- /dev/null +++ b/templates/blog.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block title %}lemon's site{% endblock title %} + +{% block content %} +<h1 class="title"> + {{ section.title }} +</h1> +<ul> + {% for page in section.pages %} + <li><a href="{{ page.permalink | safe }}">{{ page.title }}</a> ({{ page.date }})</li> + {% endfor %} +</ul> +<hr> <a href="../">Go back</a> +{% endblock content %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..d6c2755 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} + +{% block title %}lemon's site{% endblock title %} +{% block content %} + +<h1 class="title">lemon's site</h1> +Hi, I'm lemon (she/her). I like compilers, tool-assisted speedruns and low level +programming among other things. I do stuff sometimes. Occasionally I might +share some of that stuff here. + +<h3>links</h3> + +<ul> +<li><a href="/mips.html">web MIPS assembler/disassembler (incomplete)</a> +<li><a href="/b">blog</a> (last updated on {{ get_section(path="b/_index.md") | get(key="pages") | map(attribute="date") | first }}) +<li><a href="https://git.sr.ht/~lsof/">git</a> +<li><a href="https://www.youtube.com/channel/UCoZ8JbiXlq_Zh0DGvaGRTDQ">youtube</a> +<li><img src="/asset/link.png" width="18" height="23"> +</ul> + +<h3>contact</h3> + +<ul> +<!--li><strong>lemon#8558</strong> on discord </li--!> +<li><img src="/asset/mailto.png" width="128" height="21"> +</ul> + +<hr> + +<img src="/asset/nvim.png"> +<img src="/asset/lynx.png"> +<img src="/asset/linux.png"> + +{% endblock content %} |