summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig12
-rw-r--r--.gitignore2
-rw-r--r--config.toml19
-rw-r--r--content/b/_index.md6
-rw-r--r--content/b/c99-vla-tricks.md308
-rw-r--r--content/b/first.md15
-rw-r--r--static/asset/link.pngbin0 -> 972 bytes
-rw-r--r--static/asset/linux.pngbin0 -> 1224 bytes
-rw-r--r--static/asset/lynx.pngbin0 -> 2193 bytes
-rw-r--r--static/asset/mailto.pngbin0 -> 735 bytes
-rw-r--r--static/asset/nvim.pngbin0 -> 12359 bytes
-rw-r--r--static/mips.html158
-rw-r--r--static/mips.js627
-rw-r--r--static/style.css106
-rw-r--r--templates/base.html19
-rw-r--r--templates/blog-page.html14
-rw-r--r--templates/blog.html15
-rw-r--r--templates/index.html34
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
new file mode 100644
index 0000000..6f2867f
--- /dev/null
+++ b/static/asset/link.png
Binary files differ
diff --git a/static/asset/linux.png b/static/asset/linux.png
new file mode 100644
index 0000000..e9397bd
--- /dev/null
+++ b/static/asset/linux.png
Binary files differ
diff --git a/static/asset/lynx.png b/static/asset/lynx.png
new file mode 100644
index 0000000..2b53dfa
--- /dev/null
+++ b/static/asset/lynx.png
Binary files differ
diff --git a/static/asset/mailto.png b/static/asset/mailto.png
new file mode 100644
index 0000000..20d9c9e
--- /dev/null
+++ b/static/asset/mailto.png
Binary files differ
diff --git a/static/asset/nvim.png b/static/asset/nvim.png
new file mode 100644
index 0000000..1559e3c
--- /dev/null
+++ b/static/asset/nvim.png
Binary files differ
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 %}