Compare commits

..

112 commits
rewind ... main

Author SHA1 Message Date
al
4d5e1e8b10 Merge pull request 'initial switch to latexmlmath, per #5' (#6) from improve_math into main
Reviewed-on: #6
2025-07-01 19:28:23 -04:00
Alexander
376c0f5f93 initial switch to latexmlmath, per #5 2025-07-01 19:25:02 -04:00
Alexander
38d8d44b00 save 2025-07-01 17:21:59 -04:00
Alexander
e5d436e1cd a little pointless work 2025-07-01 00:34:28 -04:00
al
120aaee0e3 Update acl.cool/site/draft/umbral1.dj 2025-06-30 19:07:43 -04:00
Alexander
82e214c582 added init umbral1 2025-06-30 16:00:51 -04:00
al
b6ef44fe9f Upload files to "acl.cool/site"
Gottlob Graal CS, lvl 10
2025-06-29 09:36:19 -04:00
Alexander
60eedbb25e pull the diamonds from the fonts you actually serve, dumbass 2025-06-28 16:16:01 -04:00
al
3078e0a3eb Update build.sh 2025-06-28 13:55:58 -04:00
al
33968a1189 Update acl.cool/site/css-sample.dj 2025-06-28 13:52:07 -04:00
Alexander
bd425894a6 thematic break styling 2025-06-28 11:38:01 -04:00
Alexander
b17a6254a2 added Alfie detail (halfling) 2025-06-28 10:03:18 -04:00
Alexander
a53cdab897 weight adjustments for subheadings 2025-06-27 21:37:30 -04:00
Alexander
f94d3c32c2 roman literatatt headings 2025-06-27 12:34:05 -04:00
al
18a7029e1c Update acl.cool/site/writings/gottlob.dj 2025-06-26 11:54:56 -04:00
Alexander
1390e8f832 tweak 2025-06-25 13:55:11 -04:00
Alexander
6675de299c wording 2025-06-25 10:50:59 -04:00
Alexander
5e90b1ca52 detail about warren intro 2025-06-25 10:42:01 -04:00
Alexander
fba9f88956 cache supported lexers for pygments 2025-06-24 14:29:38 -04:00
Alexander
af97adacbe "fix" issue #3 by manually including the cyrillic small e, u0435 2025-06-24 11:25:35 -04:00
al
1b0e96f464 Update acl.cool/site/writings/umbral0.5.dj 2025-06-23 17:15:24 -04:00
al
26a55316f4 Update acl.cool/site/writings/umbral0.5.dj 2025-06-23 17:10:14 -04:00
al
7f44b8ee0f Update acl.cool/site/writings/umbral0.5.dj 2025-06-23 17:06:23 -04:00
al
734b3104c1 Update acl.cool/site/writings/umbral0.5.dj 2025-06-23 16:58:29 -04:00
al
631c05d317 Update acl.cool/site/writings/umbral0.5.dj 2025-06-23 16:50:23 -04:00
al
b67e23a137 Update acl.cool/site/writings/umbral0.5.dj 2025-06-23 16:49:27 -04:00
Alexander
5f19e1759c minify output html,css,svg 2025-06-23 16:10:13 -04:00
Alexander
6eeb91a93d stop subsetting alegreya- it's breaking layout 2025-06-23 15:45:20 -04:00
Alexander
06f1591d33 revision 2025-06-23 13:10:40 -04:00
Alexander
41eb7e94ea initial umbral 1/2 2025-06-23 11:56:16 -04:00
Alexander
7537a968b7 stash 2025-06-22 11:14:02 -04:00
Alexander
f3f4fe296f toml.frag->frag.toml 2025-06-22 08:48:19 -04:00
Alexander
e87fcf1fa7 . 2025-06-21 21:18:52 -04:00
al
f6dd858575 Update acl.cool/site/writings/gottlob.dj 2025-06-21 18:09:57 -04:00
Alexander
2558a23c47 typo fix 2025-06-21 16:43:32 -04:00
Alexander
4b8ca0fdfd gottlob 0 2025-06-21 16:16:57 -04:00
al
aac253508d Fix typo in comment 2025-06-21 13:08:52 -04:00
Alexander
f2b91323a4 blockquote spacing and math font sizing 2025-06-21 11:51:45 -04:00
Alexander
23fa0e33d8 set math font 2025-06-21 10:42:17 -04:00
Alexander
48eb7840b3 initial math, and other things 2025-06-21 10:15:17 -04:00
Alexander
594b9ae2d2 fixed syntax highlighting, finally, I hope 2025-06-16 22:19:58 -04:00
Alexander
808ebfdfa2 enabled lining nums and now clear old serve dirs 2025-06-16 21:39:42 -04:00
Alexander
53154ec9b7 neat trick 2025-06-16 19:51:07 -04:00
Alexander
64acf09214 correctly handle unknown syntaxes when highlighting 2025-06-16 19:27:42 -04:00
Alexander
18b0b14e25 fixed bad link in culture order 2025-06-16 17:14:32 -04:00
Alexander
38d366c895 no longer leaving behind garbage in the form of serve_xyz 2025-06-16 16:20:15 -04:00
al
015dfd1769 Merge pull request 'Merge nix into main: no need for ugly deps fetching anymore.' (#1) from nix into main
Reviewed-on: #1
2025-06-16 15:13:23 -04:00
Alexander
a07978207a removed imports to fix blocking css loads 2025-06-16 15:11:43 -04:00
25d1088227 made deploy atomic with symlink switch 2025-06-16 14:59:05 -04:00
e951246a70 salsa pdf 2025-06-16 14:24:22 -04:00
Alexander
dca96bc17d well there's more nixification that could be done, but this fixes my biggest gripe atm (deps fetching) 2025-06-16 13:54:41 -04:00
Alexander
f9ca6208bd oops 2025-06-16 12:02:18 -04:00
Alexander
e1258d43c5 oops 2025-06-16 12:00:26 -04:00
Alexander
3620d94e7a switched to harfbuzz subsetting and now subset alegreya* 2025-06-16 11:56:44 -04:00
al
9092be7e61 Merge pull request 'soup is really the main branch anyway' (#1) from soup into main
Reviewed-on: https://git.house-of-lucas.net/al/pages/pulls/1
2025-06-15 13:53:55 -04:00
al
54246eafc3 Update README.md 2025-06-15 13:52:55 -04:00
Alexander
bf5cfaf067 swap fonts to fake load speed 2025-06-14 20:36:25 -04:00
Alexander
aaf72ee38b stop subsetting alegreya until I can figure out why it's breaking layout features and write a fix 2025-06-14 20:23:40 -04:00
Alexander
ddef487d73 unbreak features 2025-06-14 18:53:14 -04:00
Alexander
89b650af7e subset alegreya now 2025-06-14 18:47:11 -04:00
db4e7eb22b status 2025-06-14 17:50:16 -04:00
53896dc5a8 delete old files 2025-06-14 16:53:01 -04:00
Alexander
e5fda203c3 subset julia mono 2025-06-14 16:51:41 -04:00
Alexander
4388064d92 blockquote indent all with multi paragraphs 2025-06-14 16:12:48 -04:00
8c5b42a1b1 local highlighter 2025-06-14 14:31:35 -04:00
Alexander
b9df659e5d typo 2025-06-14 11:45:59 -04:00
Alexander
177c2732b6 typo 2025-06-14 11:44:15 -04:00
Alexander
93a32bd9bc stop nuking serve/ 2025-06-14 11:40:14 -04:00
Alexander
5e75b7e5a3 deps 2025-06-14 11:24:30 -04:00
Alexander
7e898e720b highlighting 2025-06-14 11:16:36 -04:00
Alexander
4c775286ef switched to woff2 for juliamono 2025-06-14 10:05:09 -04:00
Alexander
7e670c7406 asdf 2025-06-14 09:41:45 -04:00
Alexander
655dc262f0 moved appfun back to draft. needs polish 2025-06-14 09:41:45 -04:00
Alexander
76601aa6e7 assfrrwg 2025-06-14 09:41:45 -04:00
Alexander
dce20ad6fc djrtyj 2025-06-14 09:25:57 -04:00
Alexander
0959896f82 djrtyj 2025-06-14 09:21:51 -04:00
Alexander
dc379f5671 djrtyj 2025-06-14 09:17:30 -04:00
Alexander
9ff0f33180 deltas 2025-06-14 09:15:37 -04:00
Alexander
1e88f77d79 deltas for soupault config 2025-06-13 19:43:00 -04:00
Alexander
afd9958eea blue 2025-06-13 19:34:18 -04:00
Alexander
af2cdd53db merge 2025-06-13 12:54:22 -04:00
Alexander
a056164bd7 merge 2025-06-13 08:49:07 -04:00
Alexander
02c92e795f more 2025-06-13 08:46:48 -04:00
Alexander
f3f75f91f7 tweak 2025-06-12 14:34:32 -04:00
Alexander
fa2e6c4e08 eeffbkyg 2025-06-12 13:23:02 -04:00
Alexander
3a1da22c36 minor styling + some writing 2025-06-12 12:04:01 -04:00
Alexander
ef028bb4de more minor style fixes 2025-06-11 20:18:00 -04:00
Alexander
dea942d02e spacing 2025-06-11 16:45:01 -04:00
Alexander
6e88c1a696 removed forced sans on quotes 2025-06-11 10:53:17 -04:00
Alexander
97032c1593 more style and style sample page wip 2025-06-11 10:40:57 -04:00
Alexander
02c80ce9dd styling and a draft 2025-06-10 23:21:42 -04:00
Alexander
c33fdf4cbb tweak 2025-06-10 14:10:05 -04:00
Alexander
ab3e4085a2 tweak 2025-06-10 13:56:11 -04:00
Alexander
8964bcc241 tweak 2025-06-10 13:55:52 -04:00
Alexander
e0da85fec3 tweak 2025-06-10 13:54:28 -04:00
Alexander
7b26216f44 culture touchups 2025-06-10 13:37:33 -04:00
Alexander
8dadbe2beb uncoloring 2025-06-10 13:32:23 -04:00
Alexander
8c56e22753 egh 2025-06-10 13:22:57 -04:00
Alexander
a3e82f34b5 what 2025-06-10 13:15:24 -04:00
Alexander
864544624c split into multiple soupault sites 2025-06-10 13:07:54 -04:00
Alexander
988b14da4d more consistent line height 2025-06-09 19:06:05 -04:00
Alexander
5404ce85b4 more consistent line height 2025-06-09 18:56:29 -04:00
Alexander
c37455d587 made subheadings medium and italic 2025-06-09 17:35:40 -04:00
Alexander
55758d6871 made subheadings light and italic 2025-06-09 17:34:25 -04:00
Alexander
3c2bb95cec fix complaind about h1 in section (dj) 2025-06-09 17:08:07 -04:00
Alexander
cca6419ffc lighten subheadings 2025-06-09 17:00:40 -04:00
Alexander
fee8d579af typo fix in title 2025-06-09 16:55:15 -04:00
Alexander
ceb477e51f added placeholder favicon; blockquote style 2025-06-09 16:48:04 -04:00
Alexander
7ccb524efa minor style stuff 2025-06-09 15:39:31 -04:00
Alexander
c175437b55 fixed resume 2025-06-09 11:51:47 -04:00
Alexander
2e5936de94 some font changes 2025-06-09 11:47:34 -04:00
Alexander
66af302e2d new 2025-06-09 00:20:26 -04:00
179 changed files with 2765 additions and 105 deletions

11
.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
**/serve/
**/soupault.toml
css/code.css
pgvv/
all_chars.txt
**/fonts/**/*-Subset.*
woff2/
**/serve_*
/lexers.out
fonts/*trial
**/*.log

40
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,40 @@
{
"cSpell.words": [
"Alfie",
"Almuth",
"Carmal",
"Chondalwood",
"Chondath",
"demi",
"Eldath",
"extraplanar",
"extraplanars",
"Faerûn",
"Feywild",
"Figrett",
"Figridge",
"Gottlob",
"Grinbriar",
"guardly",
"harengon",
"Iggwilv",
"Karmel",
"Laeral",
"Moradin",
"Mystra",
"nelist",
"planeswalker",
"pranking",
"Pteey",
"Rumbar",
"scrying",
"Silverhand",
"Toril",
"Venron",
"Waterdeep",
"Waterhavian",
"Waterhavians",
"Whitlock",
"Xanathar"
]
}

View file

@ -1,3 +1 @@
# plexpage
All of my websites, self-served from a single host.
Scripts for building my static web pages.

1
acl.cool/lexers.out Symbolic link
View file

@ -0,0 +1 @@
../lexers.out

1
acl.cool/math_wrapper.sh Symbolic link
View file

@ -0,0 +1 @@
../math_wrapper.sh

Binary file not shown.

View file

@ -1,6 +0,0 @@
<ol>
<li><a href="CoronationChicken.pdf">Coronation Chicken</a></li>
<li><a href="EasySalsa.pdf">Easy Medium Salsa</a></li>
<li><a href="AmarettiCookies.pdf">Amaretti Cookies</a></li>
</ol>

View file

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>acl.cool</h1>
<p>Welcome to acl.cool.</p> <a href="recipes/">recipes</a>
</body>
</html>

Binary file not shown.

View file

@ -0,0 +1,549 @@
<h1 id="programming-language-general-purpose-1">Overview of the <br>GP1 Programming Language</h1>
<p>GP1 is a statically typed, multi-paradigm programming language with
an emphasis on brevity and explicitness. It provides both value and
reference types, as well as higher-order functions and first-class
support for many common programming patterns.</p><p>This document serves as a quick, informal reference for developers of GP1 (or anyone who's curious).</p>
<h2 id="variables-and-constants">Variables and Constants</h2>
<p>A given "variable" is defined with either the <code>var</code> or
<code>con</code> keyword, for mutable and immutable assignment
respectively, alonside the assignment operator, <code>&lt;-</code>. An
uninitialized variable MUST have an explicit type, and cannot be
accessed until it is assigned. A variable that is initialized in its
declaration may have an explicit type, but the type may be inferred
here, when possible, if one is omitted. Normal type-coercion rules apply
in assignments, as described in the <em>Coercion and Casting</em>
section.</p>
<p>Non-ascii unicode characters are allowed in variable names as long as
the character doesn't cause a parsing issue. For example, whitespace
tokens are not allowed in variable names.</p>
<p>Some examples of assigning variables:</p>
<pre class="language-gp1"><code>var x: i32; // x is an uninitialized 32-bit signed integer
var y &lt;- x; // this won&#39;t work, because x has no value
x &lt;- 7;
var y &lt;- x; // this time it works, because x is now 7
con a: f64 &lt;- 99.8; // a is immutable
a &lt;- 44.12; // this doesn&#39;t work, because con variables cannot be reassigned</code></pre>
<p>The following lines are equivalent,</p>
<pre class="language-gp1"><code>con a &lt;- f64(7.2);
con a: f64 &lt;- 7.2;
con a &lt;- 7.2; // 7.2 is implicitly of type f64
con a &lt;- 7.2D; // With an explicit type suffix</code></pre>
<p>as are these.</p>
<pre class="language-gp1"><code>var c: f32 &lt;- 9;
var c &lt;- f32(9);
var c: f32 &lt;- f32(9);
var c &lt;- 9F;</code></pre>
<p>Variable assignments are expressions in GP1, which can enable some
very interesting code patterns. For example, it allows multiple
assignments on one line with the following syntax.
<code>con a &lt;- var b &lt;- "death and taxes"</code> assigns the
string <code>"death and taxes"</code> to both <code>a</code> and
<code>b</code>, leaving you with one constant and one variable
containing separate instances of identical data. This is equivalent to
writing <code>con a &lt;- "death and taxes"</code> and
<code>var b &lt;- "death and taxes"</code> each on their own line.
Assignment as an expression also eliminates much of the need to define
variables immediately before the control structure in which they're
used, which improves readability.</p>
<h2 id="intrinsic-types">Intrinsic Types</h2>
<h3 id="numeric-types">Numeric Types</h3>
<p><code>u8</code> <code>u16</code> <code>u32</code> <code>u64</code>
<code>u128</code> <code>u256</code> <code>usize</code>
<code>byte</code></p>
<p><code>i8</code> <code>i16</code> <code>i32</code> <code>i64</code>
<code>i128</code> <code>i256</code> <code>isize</code></p>
<p><code>f16</code> <code>f32</code> <code>f64</code> <code>f128</code>
<code>f256</code></p>
<p>GP1 has signed integer, unsigned integer, and floating point numeric
types. Numeric types take the form of a single-letter indicator followed
by the type's size in bits. The indicators are <strong>i</strong>
(signed integer), <strong>u</strong> (unsigned integer), and
<strong>f</strong> (floating point). <code>usize</code> and
<code>isize</code> are pointer-width types. For example, on a 64-bit
system, <code>usize</code> is a 64-bit unsigned integer. However, it
must be cast to <code>u64</code> when assigning to a <code>u64</code>
variable. The type <code>byte</code> is an alias for <code>u8</code>.
Numeric operators are as one expects from C, with the addition of
<code>**</code> as a power operator.</p>
<p>Numeric literals have an implicit type, or the type can be specified
by a case-insensitive suffix. For example:</p>
<pre class="language-gp1"><code>var i1 &lt;- 1234; // implicitly i32
var f1 &lt;- 1234.5; // implicitly f64
var i3 &lt;- 1234L; // i64
var u3 &lt;- 1234ui; // u32
var f2 &lt;- 1234.6F; // f32</code></pre>
<p>The complete set of suffixes is given.</p>
<table>
<thead>
<tr class="header">
<th>suffix</th>
<th>corresponding type</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>s</td>
<td>i16</td>
</tr>
<tr class="even">
<td>i</td>
<td>i32</td>
</tr>
<tr class="odd">
<td>l</td>
<td>i64</td>
</tr>
<tr class="even">
<td>p</td>
<td>isize</td>
</tr>
<tr class="odd">
<td>b</td>
<td>byte</td>
</tr>
<tr class="even">
<td>us</td>
<td>u16</td>
</tr>
<tr class="odd">
<td>ui</td>
<td>u32</td>
</tr>
<tr class="even">
<td>ul</td>
<td>u64</td>
</tr>
<tr class="odd">
<td>up</td>
<td>usize</td>
</tr>
<tr class="even">
<td>f</td>
<td>f32</td>
</tr>
<tr class="odd">
<td>d</td>
<td>f64</td>
</tr>
<tr class="even">
<td>q</td>
<td>f128</td>
</tr>
</tbody>
</table>
<h3 id="booleans">Booleans</h3>
<p><code>bool</code> is the standard boolean type with support for all
the usual operations. The boolean literals are <code>true</code> and
<code>false</code>. Bool operators are as one expects from C, with the
exception that NOT is <code>!!</code> instead of <code>!</code>.</p>
<h3 id="bitwise-operators">Bitwise Operators</h3>
<p>Bitwise operators can be applied only to integers and booleans. They
are single counterparts of the doubled boolean operators, e.g. boolean
negation is <code>!!</code>, so bitwise negation is <code>!</code>.</p>
<h3 id="strings-and-characters">Strings and Characters</h3>
<p><code>char</code> is a unicode character of variable size. Char
literals are single-quoted, e.g. <code>'c'</code>. Any single valid char
value can be used as a literal in this fasion.</p>
<p><code>string</code> is a unicode string. String literals are
double-quoted, e.g. <code>"Hello, World."</code>.</p>
<h3 id="arrays">Arrays</h3>
<p>GP1 supports typical array operations.</p>
<pre class="language-gp1"><code>var tuples : (int, int)[]; // declare array of tuples
var strings : string[]; // declare array of strings
var array &lt;- i32[n]; // declare and allocate array of n elements
// n is any number that can be coerced to usize
con nums &lt;- {1, 2, 3}; // immutable array of i32
</code></pre>
<p>Use the <code>length</code> property to access the number of elements
in an allocated array. Attempting to access <code>length</code> of an
unallocated array is an exception.</p>
<pre class="language-gp1"><code>
var colors &lt;- {&quot;Red&quot;, &quot;White&quot;, &quot;Blue&quot;}; // allocate array
var count &lt;- colors.length; // count is usize(3)
</code></pre>
<p>Arrays can be indexed with any integer type (signed or unsigned).
Negative values wrap from the end (-1 is the last element). An exception
occurs if the value is too big, i.e.no modulo operation is
performed.</p>
<pre class="language-gp1"><code>var w &lt;- {1, 2, 3, 4, 5, 6, 7};
w[0] // first element, 1
w[-1] // last element, 7
var x &lt;- isize(-5);
w[x] // 5th to last element, 3
</code></pre>
<h3 id="tuples">Tuples</h3>
<p>Tuples group multiple values into a single value with anonymous,
ordered fields. <code>()</code> is an empty tuple.
<code>("hello", i32(17))</code> is a tuple of type
<code>(string i32)</code>. Tuple fields are named like indices,
i.e.<code>(u128(4), "2").1</code> would be <code>"2"</code>.</p>
<p>The unit type, represented as a 0-tuple, is written
<code>()</code>.</p>
<h3 id="regex">Regex</h3>
<p><code>regex</code> is a regular expression. GP1 regex format is
identical to that of .NET 5 and very similar to that of gawk.</p>
<h2 id="named-functions">Named Functions</h2>
<p>Some examples of defining named functions:</p>
<pre class="language-gp1"><code>fn sum(a: f32, b: f32): f32 { a + b } // takes parameters and returns an f32
fn twice_println(s: string) { // takes parameters and implicitly returns ()
println(&quot;${s}\n${s}&quot;);
}
fn join_println(a: string, b: string): () { // takes parameters and explicitly returns ()
println(&quot;${a} ${b}&quot;);
}
fn seven(): u32 { 7 } // takes no parameters and returns the u32 value of 7</code></pre>
<p>There are a number of syntaxes allowed for calling a given function.
This is because the caller is allowed to assign to zero or more of that
function's parameters by name. Parameters assigned by name are freely
ordered, while those assigned normally bind to the first parameter
ordered from left to right in the function definition that is
unassigned. With regard to the <code>join_println</code> function
defined above, this means that all of the following are valid and behave
identically.</p>
<pre class="language-gp1"><code>join_println(a &lt;- &quot;Hello,&quot;, b &lt;- &quot;World.&quot;);
join_println(b &lt;- &quot;World.&quot;, a &lt;- &quot;Hello,&quot;);
join_println(b &lt;- &quot;World.&quot;, &quot;Hello,&quot;);
join_println(&quot;Hello,&quot;, &quot;World.&quot;);</code></pre>
<p>Function names may be overloaded. For example,
<code>join_println</code> could be additionally defined as</p>
<pre class="language-gp1"><code>fn join_println(a: string, b: string, sep: string) {
println(&quot;${a}${sep}${b}&quot;);
}</code></pre>
<p>and then both <code>join_println("Hello,", "World.", " ")</code> and
<code>join_println("Hello,", "World.")</code> would be valid calls.</p>
<p>Functions may be defined and called within other functions. You may
be familar with this pattern from functional languages like F#, wherein
a wrapper function is often used to guard an inner recursive function
(GP1 permits both single and mutual recursion in functions). For
example:</p>
<pre class="language-gp1"><code>fn factorial(n: u256): u256 {
fn aux(n: u256, accumulator: u256): u256 {
match n &gt; 1 {
true =&gt; aux(n - 1, accumulator * n),
_ =&gt; accumulator,
}
}
aux(n, 1)
}</code></pre>
<p>Arguments are passed by value by default. For information on the
syntax used in this example, refer to <em>Control Flow</em>.</p>
<h2 id="anonymous-functions">Anonymous Functions</h2>
<h3 id="closures">Closures</h3>
<p>Closures behave as one would expect in GP1, exactly like they do in
most other programming languages that feature them. Closures look like
this:</p>
<pre class="language-gp1"><code>var x: u32 &lt;- 8;
var foo &lt;- { y, z =&gt; x * y * z}; // foo is a closure; its type is fn&lt;u32 | u32&gt;
assert(foo(3, 11) == (8 * 3 * 11)); // true
x &lt;- 5;
assert(foo(3) == (8 * 3 * 11)); // true
con bar &lt;- { =&gt; x * x }; // bar is a closure of type `fn&lt;u32&gt;`
assert(bar() == 25); // true because closure references already-defined x</code></pre>
<p>They are surrounded by curly braces. Within the curly braces goes an
optional, comma-separated parameter list, followed by a required
<code>=&gt;</code> symbol, followed by an optional expression. If no
expression is included, the closure implicitly returns
<code>()</code>.</p>
<p>The reason the match-expression uses the same <code>=&gt;</code>
symbol is because the <code>when</code> section of a match arm is an
implicit closure. The reason <code>=&gt;</code> in particular was chosen
for closures is twofold. One, arrows are conventional for expressing
anonymous functions, and two, the space between the lines of an equals
sign is enclosed by them.</p>
<h3 id="lambdas">Lambdas</h3>
<p>Lambdas are nearly identical to closures, but they don't close over
their environment, and they use the <code>-&gt;</code> symbol in place
of <code>=&gt;</code>. A few examples of lambdas:</p>
<pre class="language-gp1"><code>con x: u32 &lt;- 4; // this line is totally irrelevant
con square &lt;- { x -&gt; x * x }; // this in not valid, because the type of the function is not known
con square &lt;- { x: u32 -&gt; x * x }; // this if fine, because the type is specified in the lambda
con square: fn&lt;u32 | u32&gt; &lt;- { x -&gt; x * x }; // also fine, because the type is specified in the declaration</code></pre>
<h2 id="function-types">Function Types</h2>
<p>Functions are first-class citizens in GP1, so you can assign them to
variables, pass them as arguments, &amp;c.However, using the function
definition syntax is suboptimal when using function types. Instead,
there is a separate syntax for function types. Given the function
<code>fn sum(a: f64, b: f64): f64 { a + b }</code> the function type is
expressed <code>fn&lt;f64 f64 | f64&gt;</code>, meaning a function that
accepts two f64 values and returns an f64. Therefore,</p>
<pre class="language-gp1"><code>fn sum(a: f64, b: f64): f64 { a + b } </code></pre>
<pre class="language-gp1"><code>con sum: fn&lt;f64 f64 | f64&gt; &lt;- { a, b -&gt; a + b };</code></pre>
<pre class="language-gp1"><code>con sum &lt;- { a: f64, b: f64 -&gt; a + b };</code></pre>
<p>are all equivalent ways of binding a function of type
<code>fn&lt;f64 f64 | f64&gt;</code> to the constant <code>sum</code>.
Here's an example of how to express a function type for a function
argument.</p>
<pre class="language-gp1"><code>fn apply_op(a: i32, b: i32, op: fn&lt;i32 i32 | i32&gt;): i32 {
op(a, b)
}</code></pre>
<h3 id="function-type-inference">Function Type Inference</h3>
<p>The above example provides an explicit type for the argument
<code>op</code>. You could safely rewrite this as</p>
<pre class="language-gp1"><code>fn apply_op(a: i32, b: i32, op: fn): i32 {
op(a, b)
}</code></pre>
<p>because the compiler can safely infer the function type of
<code>op</code>. Type inference only works to figure out the function
signature, so <code>fn apply_op(a:i32, b:i32, op):i32 { . . . }</code>
is not allowed.</p>
<h2 id="coercion-and-casting">Coercion and Casting</h2>
<p>Refer to <em>Variables and Constants</em> for information on the
syntax used in this section.</p>
<p>Numeric types are automatically coerced into other numeric types as
long as that coercion is not lossy. For example,</p>
<pre class="language-gp1"><code>var x: i32 &lt;- 10;
var y: i64 &lt;- x;</code></pre>
<p>is perfectly legal (the 32-bit value fits nicely in the 64-bit
variable). However, automatic coercion doesn't work if it would be
lossy, so</p>
<pre class="language-gp1"><code>var x: i64 &lt;- 10;
var y: i32 &lt;- x;</code></pre>
<p>doesn't work. This holds for numeric literals as well.
Unsurprisingly, <code>var x: i32 &lt;- 3.14</code> wouldn't compile. The
floating point value can't be automatically coerced to an integer type.
So what does work? Casting via the target type's pseudo-constructor
works.</p>
<pre class="language-gp1"><code>con x: f64 &lt;- 1234.5; // okay because the literal can represent any floating point type
con y: f64 &lt;- f16(1234.5); // also okay, because any f16 can be losslessly coerced to an f64
con z: i32 &lt;- i32(x); // also okay; uses the i32 pseudo-constructor to &#39;cast&#39; x to a 32-bit integer
assert(z == 1234)
con a: f64 &lt;- 4 * 10 ** 38; // this value is greater than the greatest f32
con b: f32 &lt;- f32(a); // the value of b is the maximum value of f32</code></pre>
<p>This approach is valid for all intrinsic types. For example,
<code>var flag: bool &lt;- bool(0)</code> sets <code>flag</code> to
<code>false</code> and <code>var txt: string &lt;- string(83.2)</code>
sets <code>txt</code> to the string value <code>"83.2"</code>. Such
behavior can be implemented by a programmer on their own types via a
system we'll discuss in the <em>Interfaces</em> section.</p>
<h2 id="program-structure">Program Structure</h2>
<p>Every GP1 program has an entry-point function. Within that function,
statements are executed from top to bottom and left to right. The
entry-point function can be declared with the <code>entry</code> keyword
in place of <code>fn</code> and returns an integer, which will be
provided to the host operating system as an exit code. Naturally, this
means that the handling of that code is platform-dependent once it
passes the program boundry, so it's important to keep in mind that a
system may implicitly downcast or otherwise modify it before it is made
available to the user. If no exit code is specified, or if the return
type of the function is not an integer, GP1 assumes an exit code of
<code>usize(0)</code> and returns that to the operating system.</p>
<p>The following program prints Hello, World. and exits with an error
code.</p>
<pre class="language-gp1"><code>entry main(): usize {
hello_world();
1
}
fn hello_world() {
println(&quot;Hello, World.&quot;);
}</code></pre>
<p>The entry function may have any name; it's the <code>entry</code>
keyword that makes it the entry point. The entry function may also be
implicit. If one is not defined explicitly, the entire file is treated
as being inside an entry function. Therefore,</p>
<pre class="language-gp1"><code>println(&quot;Hello, World.&quot;);</code></pre>
<p>is a valid and complete program identical to</p>
<pre class="language-gp1"><code>entry main(): usize {
println(&quot;Hello, World.&quot;);
}</code></pre>
<p>This behavior can lend GP1 a very flexible feeling akin to many
scripting languages.</p>
<p>In a program where there is an entry-point specified, only
expressions made within that function will be evaluated. This means that
the following program does NOT print anything to the console.</p>
<pre class="language-gp1"><code>entry main(): usize {
con x: usize &lt;- 7;
}
println(&quot;This text will not be printed.&quot;);</code></pre>
<p>In fact, this program is invalid. Whenever there is an explicit entry
point, no statements may be made in the global scope.</p>
<h2 id="control-flow">Control Flow</h2>
<h3 id="conditionals">Conditionals</h3>
<p>At this time, GP1 has only one non-looping conditional control
structure, in two variants: <code>match</code> and
<code>match all</code>. The syntax is as follows, where
<code>*expr*</code> are expressions and <code>pattern*</code> are
pattern matching options (refer to <em>Pattern Matching</em> for more
info).</p>
<pre class="language-gp1"><code>match expr {
pattern1 =&gt; arm_expr1,
pattern2 =&gt; arm_expr2,
_ =&gt; arm_expr3,
}</code></pre>
<p>The <code>match</code> expression executes the first arm that matches
the pattern passed in <code>expr</code>. The <code>match all</code>
expression executes all arms that match the pattern. Both flavors return
their last executed expression.</p>
<p>The <code>when</code> keyword may be used in a given match arm to
further restrict the conditions of execution, e.g.</p>
<pre class="language-gp1"><code>con fs &lt;- 43;
con is_even &lt;- match fs {
n when n % 2 == 0 =&gt; &quot; is &quot;
_ =&gt; &quot; is not &quot;
};
print(fs + is_even + &quot;even.&quot;)</code></pre>
<h3 id="loops">Loops</h3>
<p>Several looping structures are supported in GP1</p>
<ul>
<li><code>loop</code></li>
<li><code>for</code></li>
<li><code>while</code></li>
<li><code>do/while</code></li>
</ul>
<p>along with <code>continue</code> and <code>break</code> to help
control program flow. All of these are statements.</p>
<pre class="language-gp1"><code>loop { . . . } // an unconditional loop -- runs forever or until broken</code></pre>
<pre class="language-gp1"><code>for i in some_iterable { . . . } // loop over anything that is iterable</code></pre>
<pre class="language-gp1"><code>while some_bool { . . . } // classic conditional loop that executes until the predicate is false</code></pre>
<pre class="language-gp1"><code>do { . . .
} while some_bool // traditional do/while loop that ensures body executes at least once</code></pre>
<h2 id="pattern-matching">Pattern Matching</h2>
<p>Pattern matching behaves essentially as it does in SML, with support
for various sorts of destructuring. It works in normal assignment and in
<code>match</code> arms. It will eventually work in function parameter
assignment, but perhaps not at first.</p>
<p>For now, some examples.</p>
<pre class="language-gp1"><code>a &lt;- (&quot;hello&quot;, &quot;world&quot;); // a is a tuple of strings
(b, c) &lt;- a;
assert(b == &quot;hello&quot; &amp;&amp; c == &quot;world&quot;)
fn u32_list_to_string(l: List&lt;u32&gt;): string { // this is assuming that square brackets are used for linked lists
con elements &lt;- match l {
[] =&gt; &quot;&quot;,
[e] =&gt; string(e),
h::t =&gt; string(h) + &quot;, &quot; + u32_list_to_string(t), // the bit before the arrow in each arm is a pattern
} // h::t matches the head and tail of the list to h and t, respectively
&quot;[&quot; + elements + &quot;]&quot; // [s] matches any single-element list
} // [] matches any empty list</code></pre>
<h2 id="interfaces">Interfaces</h2>
<p>Interfaces are in Version 2 on the roadmap.</p>
<h2 id="user-defined-types">User-Defined Types</h2>
<h3 id="enums">Enums</h3>
<p>Enums are pretty powerful in GP1. They can be the typical enumerated
type you'd expect, like</p>
<pre class="language-gp1"><code>enum Coin { penny, nickle, dime, quarter } // &#39;vanilla&#39; enum
var a &lt;- Coin.nickle
assert a == Coin.nickle
</code></pre>
<p>Or an enum can have an implicit field named <code>value</code></p>
<pre class="language-gp1"><code>enum Coin: u16 { penny(1), nickle(5), dime(10), quarter(25) }
var a &lt;- Coin.nickle;
assert(a == Coin.nickle);
assert(a.value == 5);</code></pre>
<p>Or an enum can be complex with a user-defined set of fields, like</p>
<pre class="language-gp1"><code>enum CarModel(make: string, mass: f32, wheelbase: f32) { // enum with multiple fields
gt ( &quot;ford&quot;, 1581, 2.71018 ),
c8_corvette ( &quot;chevy&quot;, 1527, 2.72288 )
}</code></pre>
<p>A field can also have a function type. For example</p>
<pre class="language-gp1"><code>enum CarModel(make: string, mass: f32, wheelbase: f32, gasUsage: fn&lt;f32 | f32&gt;) {
gt ( &quot;ford&quot;, 1581, 2.71018, { miles_traveled -&gt; miles_traveled / 14 } ),
c8_corvette ( &quot;chevy&quot;, 1527, 2.72288, { miles_traveled -&gt; miles_traveled / 19 } )
}
var my_car &lt;- CarModel.c8_corvette;
var gas_used &lt;- my_car.gasUsage(200); // estimate how much gas I&#39;d use on a 200 mile trip</code></pre>
<p>Equivalence of enums is not influenced by case values, e.g.</p>
<pre class="language-gp1"><code>enum OneOrAnother: u16 { one(0), another(0) }
con a &lt;- OneOrAnother.one;
con b &lt;- OneOrAnother.another;
assert(a != b);
assert(a.value == b.value);</code></pre>
<p>It's important to remember that enums are 100% always totally in
every concieveable fashion immutable. To make this easier to enforce,
only value types are allowed for enum fields.</p>
<h3 id="records">Records</h3>
<p>Records are record types, defined with the <code>record</code>
keyword. Fields are defined in the <code>record</code> block and
behavior is defined in the optional <code>impl</code> block.</p>
<p>For example,</p>
<pre class="language-gp1"><code>record Something {
label: i32 // field label followed by some type
} impl { . . . } // associated functions. This is different than having functions in the fields section because impl functions are not assignable.</code></pre>
<p>If the record implements some interface, <code>SomeInterface</code>,
the <code>impl</code> would be replaced with
<code>impl SomeInterface</code>, and the functions of
<code>SomeInterface</code> would be defined alongside any other
functions of the <code>Something</code> record.</p>
<h3 id="unions">Unions</h3>
<p>Unions are the classic discriminated sum type.</p>
<pre class="language-gp1"><code>union BinaryTree {
Empty,
Leaf: i32,
Node: (BinaryTree BinaryTree),
}</code></pre>
<h3 id="type-aliases">Type Aliases</h3>
<p>Refer to <em>Generics</em> for info on the syntax used in this
section.</p>
<p>Type aliasing is provided with the <code>type</code> keyword,
e.g.</p>
<pre class="language-gp1"><code>type TokenStream Sequence&lt;Token&gt;
type Ast Tree&lt;AbstractNode&gt;
fn parse(ts: TokenStream): Ast { . . . }</code></pre>
<p>Notice how much cleaner the function definition looks with the
aliased types. This keyword is useful mainly for readability and domain
modeling.</p>
<h2 id="generics">Generics</h2>
<p>Generics are in Version 2 on the official GP1 roadmap. They roughly
use C++ template syntax or Rust generic syntax.</p>
<h2 id="references-and-reference-types">References and Reference
Types</h2>
<p>GP1 has three operators involved in handling references,
<code>#</code>, <code>&amp;</code>, and <code>@</code>. These are
immutable reference, mutable reference, and dereference, respectively.
Some examples of referencing/dereferencing values:</p>
<pre class="language-gp1"><code>var a &lt;- &quot;core dumped&quot;;
var b &lt;- &amp;a; // b is a mutable reference to a
assert(a == @b);
assert(a != b);
@b &lt;- &quot;missing ; at line 69, column 420&quot;;
assert(a == &quot;missing ; at line 69, column 420&quot;);
b &lt;- &amp;&quot;missing ; at line 420, column 69&quot;;
assert(a != &quot;missing ; at line 420, column 69&quot;);
var c &lt;- #b; // c is an immutable reference to b
assert(@c == b);
assert(@@c == a);
@c &lt;- &amp;&quot;kablooey&quot;; // this does not work. `c` is an immutable reference and cannot be used to assign its referent.</code></pre>
<p>Naturally, only <code>var</code> values can be mutated through
references.</p>
<p>The reference operators may be prepended to any type, T, to describe
the type of a reference to a value of type T, e.g.</p>
<pre class="language-gp1"><code>fn set_through(ref: &amp;string) { // this function takes a mutable reference to a string and returns `()`
@ref &lt;- &quot;goodbye&quot;;
}
var a &lt;- &quot;hello&quot;;
set_through(&amp;a);
assert(a == &quot;goodbye&quot;);</code></pre>

BIN
acl.cool/site/assets/favicon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

1
acl.cool/site/assets/fonts Symbolic link
View file

@ -0,0 +1 @@
../../../fonts

70
acl.cool/site/cats.ml Normal file
View file

@ -0,0 +1,70 @@
module type Functor = sig
type 'a t
val map : ('a -> 'b) -> 'a t -> 'b t
end
module type Applicative = sig
type 'a t
val pure : 'a -> 'a t
val apply : ('a -> 'b) t -> 'a t -> 'b t
end
module type Monad = sig
type 'a t
val return : 'a -> 'a t
val bind : ('a -> 'b t) -> 'a t -> 'b t
end
module ApplicativeOfMonad (M : Monad) : Applicative with type 'a t = 'a M.t = struct
type 'a t = 'a M.t
let pure = M.return
let apply f x = M.(bind (fun y -> bind (fun g -> return (g y)) f) x)
end
module FunctorOfApplicative (A : Applicative) : Functor with type 'a t = 'a A.t = struct
type 'a t = 'a A.t
let map f x = A.(apply (pure f) x)
end
module FunctorOfMonad (M : Monad) : Functor with type 'a t = 'a M.t = struct
include FunctorOfApplicative(ApplicativeOfMonad(M))
end
module MonadDerive (M : Monad) = struct
include M
include ApplicativeOfMonad(M)
include FunctorOfMonad(M)
let (>>=) x f = bind f x
let (<$>) x f = map x f
let (<*>) x f = apply x f
end
module ListMonad = struct
type 'a t = 'a list
let return x = [x]
let rec bind (f : 'a -> 'b list) : 'a list -> 'b list = function
| [] -> []
| x :: xs -> f x @ bind f xs
end
module Dlm = MonadDerive(ListMonad)
let pair x y = x, y
let cart_prod xs ys = Dlm.(pair <$> xs <*> ys)
let () = cart_prod [1;2;3;4] ["7"; "hello there"; "forthwith!"]
|> List.iter (fun (x, y) -> print_endline @@ "(" ^ string_of_int x ^ ", " ^ y ^ ")")
(* ============================================================================================= *)
module StateMonad (S : sig type t end) = struct
type 'a t = S.t -> S.t * 'a
let return x s = (s, x)
let bind f x s = let s', a = x s in f a s'
end
module IntStateMonad = StateMonad(struct type t = int end)

70
acl.cool/site/cats.ml.txt Normal file
View file

@ -0,0 +1,70 @@
module type Functor = sig
type 'a t
val map : ('a -> 'b) -> 'a t -> 'b t
end
module type Applicative = sig
type 'a t
val pure : 'a -> 'a t
val apply : ('a -> 'b) t -> 'a t -> 'b t
end
module type Monad = sig
type 'a t
val return : 'a -> 'a t
val bind : ('a -> 'b t) -> 'a t -> 'b t
end
module ApplicativeOfMonad (M : Monad) : Applicative with type 'a t = 'a M.t = struct
type 'a t = 'a M.t
let pure = M.return
let apply f x = M.(bind (fun y -> bind (fun g -> return (g y)) f) x)
end
module FunctorOfApplicative (A : Applicative) : Functor with type 'a t = 'a A.t = struct
type 'a t = 'a A.t
let map f x = A.(apply (pure f) x)
end
module FunctorOfMonad (M : Monad) : Functor with type 'a t = 'a M.t = struct
include FunctorOfApplicative(ApplicativeOfMonad(M))
end
module MonadDerive (M : Monad) = struct
include M
include ApplicativeOfMonad(M)
include FunctorOfMonad(M)
let (>>=) x f = bind f x
let (<$>) x f = map x f
let (<*>) x f = apply x f
end
module ListMonad = struct
type 'a t = 'a list
let return x = [x]
let rec bind (f : 'a -> 'b list) : 'a list -> 'b list = function
| [] -> []
| x :: xs -> f x @ bind f xs
end
module Dlm = MonadDerive(ListMonad)
let pair x y = x, y
let cart_prod xs ys = Dlm.(pair <$> xs <*> ys)
let () = cart_prod [1;2;3;4] ["7"; "hello there"; "forthwith!"]
|> List.iter (fun (x, y) -> print_endline @@ "(" ^ string_of_int x ^ ", " ^ y ^ ")")
(* ============================================================================================= *)
module StateMonad (S : sig type t end) = struct
type 'a t = S.t -> S.t * 'a
let return x s = (s, x)
let bind f x s = let s', a = x s in f a s'
end
module IntStateMonad = StateMonad(struct type t = int end)

1
acl.cool/site/css Symbolic link
View file

@ -0,0 +1 @@
../../css

View file

@ -0,0 +1,56 @@
# Evaluate This Style.
Abraham Lincoln delivered the Gettysburg Address in 1863 at the dedication of Soldiers' National Cemetery.
> Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.
>
> Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.
>
> But, in a larger sense, we can not dedicate --- we can not consecrate --- we can not hallow --- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us --- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion --- that we here highly resolve that these dead shall not have died in vain --- that this nation, under God, shall have a new birth of freedom --- and that government of the people, by the people, for the people, shall not perish from the earth.
## Djot Syntax
We can set block quotes like the above by prefixing each line (including blank ones) with `> `. This is _essentially_ a port from [commonmark](https://commonmark.org/), though as usual, [djot](https://djot.net/) makes minor changes for simplicity and consistency.
> .o'i mu xagji sofybakni cu zvati le purdi
*Caution.* Five hungry Soviet cows are in the garden.
```lean4
def map' {α β : Type} (f : α → β) : List α → List β
| [] => []
| x :: xs => f x :: map' f xs
theorem fuse {α β γ} : ∀ (xs : List α) (f : α → β) (g : β → γ),
(map' g ∘ map' f) xs = map' (g ∘ f) xs := by
intro xs f g
induction xs
. simp [map']
. rename_i _ _ IH
simp [map', ←IH, Function.comp]
```
A simple proof of fusion for `List.map` in Lean 4. This and similar theorems are why pure languages like Lean and Haskell can transform e.g. `map f xs |> map g xs` into `map (fun x -> g (f x)) xs`, i.e. `map g . map f` into `map (g . f)`, reducing the number of times a structure must be iterated and thus improving spatial locality.
The above block is set with ```` ``` ```` above and below the code. Interestingly, trying to type that little backtick sequence there is a little unintuitive in markdown/djot. If the code you're trying to set has $`n` consecutive backticks in it, you have to surround it with $`n+1` backticks and pad those delimiters inside with a space. 1234567890 is a pretty small number, really.
{.thematic}
***
There are ten books in Iain M. Banks' _Culture_ series, possibly the greatest cohesive science-fiction series of all time. Banks' prose and poetry leaves little to be desired.
1. Consider Phlebas
2. The Player of Games
3. The State of the Art
4. Use of Weapons
5. Excession
6. Inversions
7. Look to Windward
8. Matter
9. Surface Detail
10. The Hydrogen Sonata
Math is something I'm still working on; in more ways than one. Something like $$`\forall x \in \mathbb{N}, x \in \mathbb{N}` is easy enough, but other things are harder.
Not everyone knows what distfix grammars[^mixfix] are, but they're quite useful when it comes to implementing user-defined syntax.
[^mixfix]: Also, perhaps more commonly, called "mixfix" grammars.

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View file

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 12.1.0 (20240811.2233)
-->
<!-- Pages: 1 -->
<svg width="844pt" height="426pt"
viewBox="0.00 0.00 843.85 425.92" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 421.92)">
<polygon fill="white" stroke="none" points="-4,4 -4,-421.92 839.85,-421.92 839.85,4 -4,4"/>
<!-- cp -->
<g id="node1" class="node">
<title>cp</title>
<ellipse fill="#cceedd" stroke="#aaaaaa" cx="102.53" cy="-375" rx="96.17" ry="19.45"/>
<text text-anchor="start" x="42.53" y="-372.45" font-family="Open Sans" font-weight="bold" font-size="14.00">Consider Phlebas</text>
</g>
<!-- ltw -->
<g id="node7" class="node">
<title>ltw</title>
<ellipse fill="#cceedd" stroke="#aaaaaa" cx="102.53" cy="-209" rx="102.53" ry="19.45"/>
<text text-anchor="start" x="38.03" y="-206.45" font-family="Open Sans" font-weight="bold" font-size="14.00">Look to Windward</text>
</g>
<!-- cp&#45;&gt;ltw -->
<g id="edge1" class="edge">
<title>cp&#45;&gt;ltw</title>
<path fill="none" stroke="black" d="M102.53,-355.19C102.53,-327.09 102.53,-273.87 102.53,-240.26"/>
<polygon fill="black" stroke="black" points="106.03,-240.36 102.53,-230.36 99.03,-240.36 106.03,-240.36"/>
</g>
<!-- pog -->
<g id="node2" class="node">
<title>pog</title>
<ellipse fill="#cceedd" stroke="#aaaaaa" cx="392.53" cy="-375" rx="112.08" ry="19.45"/>
<text text-anchor="start" x="321.28" y="-372.45" font-family="Open Sans" font-weight="bold" font-size="14.00">The Player of Games</text>
</g>
<!-- uow -->
<g id="node3" class="node">
<title>uow</title>
<ellipse fill="#cceedd" stroke="#aaaaaa" cx="312.53" cy="-209" rx="89.8" ry="19.45"/>
<text text-anchor="start" x="257.03" y="-206.45" font-family="Open Sans" font-weight="bold" font-size="14.00">Use of Weapons</text>
</g>
<!-- sota -->
<g id="node4" class="node">
<title>sota</title>
<ellipse fill="#cceedd" stroke="#aaaaaa" cx="311.53" cy="-43" rx="107.3" ry="19.45"/>
<text text-anchor="start" x="243.66" y="-40.45" font-family="Open Sans" font-weight="bold" font-size="14.00">The State of the Art</text>
</g>
<!-- uow&#45;&gt;sota -->
<g id="edge2" class="edge">
<title>uow&#45;&gt;sota</title>
<path fill="none" stroke="black" d="M312.42,-189.19C312.24,-161.09 311.92,-107.87 311.71,-74.26"/>
<polygon fill="black" stroke="black" points="315.22,-74.34 311.65,-64.36 308.22,-74.38 315.22,-74.34"/>
</g>
<!-- ivs -->
<g id="node6" class="node">
<title>ivs</title>
<ellipse fill="#cceedd" stroke="#aaaaaa" cx="499.53" cy="-43" rx="62.23" ry="19.45"/>
<text text-anchor="start" x="463.53" y="-40.45" font-family="Open Sans" font-weight="bold" font-size="14.00">Inversions</text>
</g>
<!-- uow&#45;&gt;ivs -->
<g id="edge3" class="edge">
<title>uow&#45;&gt;ivs</title>
<path fill="none" stroke="black" d="M333.26,-189.82C366.71,-160.48 432.69,-102.62 470.71,-69.28"/>
<polygon fill="black" stroke="black" points="472.93,-71.98 478.14,-62.76 468.32,-66.72 472.93,-71.98"/>
</g>
<!-- sd -->
<g id="node9" class="node">
<title>sd</title>
<ellipse fill="#cceedd" stroke="#aaaaaa" cx="104.53" cy="-43" rx="80.26" ry="19.45"/>
<text text-anchor="start" x="55.78" y="-40.45" font-family="Open Sans" font-weight="bold" font-size="14.00">Surface Detail</text>
</g>
<!-- uow&#45;&gt;sd -->
<g id="edge6" class="edge">
<title>uow&#45;&gt;sd</title>
<path fill="none" stroke="black" d="M289.48,-189.82C252.21,-160.44 178.64,-102.44 136.39,-69.12"/>
<polygon fill="black" stroke="black" points="138.58,-66.39 128.56,-62.95 134.25,-71.89 138.58,-66.39"/>
</g>
<!-- ex -->
<g id="node5" class="node">
<title>ex</title>
<ellipse fill="#cceedd" stroke="#aaaaaa" cx="613.53" cy="-375" rx="58.51" ry="19.45"/>
<text text-anchor="start" x="580.16" y="-372.45" font-family="Open Sans" font-weight="bold" font-size="14.00">Excession</text>
</g>
<!-- mat -->
<g id="node8" class="node">
<title>mat</title>
<ellipse fill="#cceedd" stroke="#aaaaaa" cx="539.53" cy="-209" rx="45.25" ry="19.45"/>
<text text-anchor="start" x="515.53" y="-206.45" font-family="Open Sans" font-weight="bold" font-size="14.00">Matter</text>
</g>
<!-- ex&#45;&gt;mat -->
<g id="edge5" class="edge">
<title>ex&#45;&gt;mat</title>
<path fill="none" stroke="black" d="M605.19,-355.51C592.29,-326.93 567.44,-271.85 552.28,-238.27"/>
<polygon fill="black" stroke="black" points="555.66,-237.23 548.35,-229.56 549.28,-240.11 555.66,-237.23"/>
</g>
<!-- hs -->
<g id="node10" class="node">
<title>hs</title>
<ellipse fill="#cceedd" stroke="#aaaaaa" cx="719.53" cy="-209" rx="116.32" ry="19.45"/>
<text text-anchor="start" x="645.28" y="-206.45" font-family="Open Sans" font-weight="bold" font-size="14.00">The Hydrogen Sonata</text>
</g>
<!-- ex&#45;&gt;hs -->
<g id="edge4" class="edge">
<title>ex&#45;&gt;hs</title>
<path fill="none" stroke="black" d="M625.48,-355.51C644.01,-326.84 679.79,-271.49 701.48,-237.93"/>
<polygon fill="black" stroke="black" points="704.15,-240.25 706.64,-229.95 698.27,-236.45 704.15,-240.25"/>
</g>
<!-- ltw&#45;&gt;sd -->
<g id="edge7" class="edge">
<title>ltw&#45;&gt;sd</title>
<path fill="none" stroke="black" d="M102.76,-189.19C103.1,-161.09 103.75,-107.87 104.16,-74.26"/>
<polygon fill="black" stroke="black" points="107.66,-74.4 104.28,-64.36 100.66,-74.32 107.66,-74.4"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -0,0 +1,21 @@
digraph {
node [style=filled, fontname="Open Sans", fillcolor="#cceeddff", color="#aaaaaaff"];
ratio=0.5;
cp [label=<<B>Consider Phlebas</B>>];
pog [label=<<B>The Player of Games</B>>];
uow [label=<<B>Use of Weapons</B>>];
sota [label=<<B>The State of the Art</B>>];
ex [label=<<B>Excession</B>>];
ivs [label=<<B>Inversions</B>>];
ltw [label=<<B>Look to Windward</B>>];
mat [label=<<B>Matter</B>>];
sd [label=<<B>Surface Detail</B>>];
hs [label=<<B>The Hydrogen Sonata</B>>];
cp -> ltw; // It's about the Idiran War, dummy.
uow -> sota; // It's largely about Sma.
uow -> ivs; // This book gives the best idea about what SC is, and you should know that before reading Inversions.
ex -> hs; // Hydrogen Sonata is dual to Excession in many ways. Not a hard rule, but HS is better if you know Excession.
ex -> mat; // Sleeper Service mentioned as "The granddaddy, the exemplary hero figure, the very God...".
uow -> sd; // Zakalwe/chair killer is in this one, and you should know who that is.
ltw -> sd; // LTW is more impactful (with Chel heaven being specially real) without the knowledge of SD.
}

View file

@ -0,0 +1,17 @@
# Algorithm
During the implementation of my library "Aasam", based on the paper "Precedences in specifications and implementations of programming languages" by Annika Aasa, was the first time I fully read and understood an academic CS paper. It's a nice algorithm, and worth revisiting.
If you want to look at the implementation ahead-of-time, [Hackage](https://hackage.haskell.org/package/aasam) has got you covered. Frankly though, just keep reading; it was also the first time I wrote a Haskell program, and the code not terribly penetrable.
## Distfix Grammars
_Distfix grammars and notation are more commonly referred to as "mixfix", but the paper calls them "distfix" and that's what I'm sticking with._
The idea of distfix grammars is to provide a formalism for manipulating user-defined operators. The formalism is weaker than that of context-free grammars--- CFGs can describe far more languages then DFGs--- but it is because of this weakness that we can reason about DFGs with relative ease.
Let's look at a definition.
*STUFF HERE*
As we can see, ...

View file

@ -0,0 +1,110 @@
# Functors and Applicatives, Gratis[^falsehood]
It's usually possible to derive implementations of general structures from those of more specific ones; here, `Applicative` from `Monad` and `Functor` from `Applicative`. This is how to do it, and why not to.
```ocaml
module type Monad = sig
type 'a t
val return : 'a -> 'a t
val bind : ('a -> 'b t) -> 'a t -> 'b t
end
module type Applicative = sig
type 'a t
val pure : 'a -> 'a t
val apply : ('a -> 'b) t -> 'a t -> 'b t
end
module type Functor = sig
type 'a t
val map : ('a -> 'b) -> 'a t -> 'b t
end
```
Above, we have the usual OCaml signatures for functors[^other-functor], applicative functors, and monads respectively. The only thing of note is that I've written the functions in pipe-last[^pipe-last] style. It's more common to see pipe-first style, which agrees with the usual infix operators, but I don't see any of those around to get offended; do you?
```ocaml
module ApplicativeOfMonad (M : Monad) :
Applicative with type 'a t = 'a M.t = struct
type 'a t = 'a M.t
let pure = M.return
let apply f x = M.(bind (fun y -> bind (fun g -> return (g y)) f) x)
end
module FunctorOfApplicative (A : Applicative) :
Functor with type 'a t = 'a A.t = struct
type 'a t = 'a A.t
let map f x = A.(apply (pure f) x)
end
module FunctorOfMonad (M : Monad) :
Functor with type 'a t = 'a M.t = struct
include FunctorOfApplicative(ApplicativeOfMonad(M))
end
```
Each of these functors accepts an instance of a less general structure and uses only the elements that module provides to implement an instance of the more general structure. `FunctorOfMonad` is boring, being just the composition of the others, but those themselves are somewhat more interesting. Looking at the implementation of `map` in terms of `pure` and `apply`, we can see that we have the function `f` put into an empty context and then applied to `x`. Since we can't know more about the particular applicative functor given, this is the only way to obtain a new instance of its backing type `('a -> 'b) t`, so there aren't any real options here in the way of implementation[^unique-functor]. Still, the meaning in terms of monads and/or effects is worth contemplating; applicatives tell us what application looks like in a context while monads do the same for composition[^makes-sense].
The more involved implementation in `ApplicativeOfMonad` is where we get some options in terms of implementation and also, not unrelatedly, where our problems arise. Because it turns out that there are multiple ways to implement the derivation functor--- also multiple ways to implement a particular monad or applicative--- it becomes hard to predict whether a derived implementation is the expected one without resorting to _ad hoc_ testing, testing that rather defeats the point of "gratis"[^low-effort]. The `apply` function, shown above, is derived from `bind` and `return` by threading `x` and `f` into the context through bind (as `y` and `g`, respectively), thus "stripping off" the context in the body of the lambda, then applying `g` to `y` to obtain a fresh `'b`, and finally passing that `'b` to `M.return`, thus producing a `'b M.t`, which of course is also an `'a t`, since `type 'a t = 'a M.t`[^with-type].
To explore concretely how deriving instances can go wrong, consider the following `EitherMonad`.
```ocaml
type ('a, 'b) either = A of 'a | B of 'b
module EitherMonad (T : sig type t end) : Monad with type 'a t = ('a, T.t) either = struct
return x = [x]
let rec bind (f : 'a -> 'b list) : 'a list -> 'b list = function
| [] -> []
| x :: xs -> f x @ bind f xs
end
```
This has the following derived `Applicative`.
```ocaml
derived app for bar monad
```
And then, here's a lawful[^legis] implementation of `Applicative` for `bar` which is _not_ the one produced by `ApplicativeOfMonad (BarMonad)`.
```ocaml
lawful other bar
```
Just to be sure, let's check that this new instance satisfies the required properties, i.e. that
1. `apply (pure Fun.id) x` = `x`
1. `apply (apply (apply (pure Fun.compose) f) g) x` = `apply f (apply g x)`
1. `apply (pure f) (pure x)` = `pure (f x)`
1. `apply f (pure x)` = `apply (pure (fun g -> g x)) f`
for `BarApplicative'` just as for `ApplicativeOfMonad (BarMonad)`.
{% these are identity, composition, homomorphism, and interchange %}
Monads have their own laws of course, but while a lawful monad is guaranteed to entail a lawful applicative, as we can see, it isn't guaranteed to produce the _desired_ applicative when given as input to a particular derivation functor. The alternate definition above may or may not be a useful one, but the point is that the arbitrary nature of what applicative implementation is considered "correct"[^correct] means you don't `per se` get the applicative you want when deriving an instance; rather, you get the version that accords with the monad, which is _probably_ what you want, but perhaps not. The nice thing about OCaml here, as opposed to Haskell with its typeclasses, is that there's no barrier to having multiple implementation of the `Applicative` signature, so it's easy to implement a different instance without ambiguity. Furthermore, because `ApplicativeOfMonad` operates on a generic applicative and thus cannot make use of any peculiar structure in its argument, even a result that does behave as desired may be less performant in code than a decent handwritten version--- compare such implementations for *FooMonad*.
```ocaml
foo monad stuff
```
[^with-type]: In regards to the `with type = ...` syntax, there is a subtlety of the OCaml module system that if a module is defined with a particular `module type` (a.k.a. signature) attached--- for example, `module M : S = struct ...`--- all the types that are abstract in the signature `S` will _also_ be abstract in the module `M` itself. This means that the compiler can't see or be convinced that for some `F (M)` with `type t = M.t` in `F`, `M.t` and `(F (M)).t` are equal, because both types are abstract, i.e. the underlying type is not available. To fix this, we can explicitly expose the equality by using the `with type` construct. In the above example, `Functor with type 'a t = 'a M.t` exposes the equality of `'a t` and `'a M.t`, so that functions defined as expecting arguments of `'a t` can accept `'a M.t`, and _vice versa_, etc.
[^falsehood]: Unsurprisingly, that's a lie. Isn't it always? You have to buy a monad first.
[^1ml]: See [1ML](https://people.mpi-sws.org/~rossberg/1ml/1ml-jfp-draft.pdf) for an OCaml-like language without this stratification.
[^pipe-last]: This idea is that to harmonize with OCaml's automatic currying, parameters to which a function is more likely to be "partially applied" should be earlier in its parameter list. This cuts down on syntactic noise; particularly, pipes which apply to the _last_ argument (see?) don't require shuffling-about of the parameter order, e.g. `xs |> List.map f` rather than `xs |> (fun xs -> List.map xs f)`. Jane Street will tell you that [labels](https://ocaml.org/docs/labels) can [address](https://opensource.janestreet.com/base/) the issue best, but to my eyes, `:` and `~` were never meant to spend so much time that close to one another.
[^makes-sense]: It makes sense then, that having a monad nets you an applicative functor--- how can one talk about composition without having application first, which is needed to consider function-like objects at all?
[^other-functor]: A functor, in OCaml parlance, is distinct from anything called a "functor" elsewhere, being essentially a function from modules to modules. This can indeed become confusing. For practical reasons, modules and value-level programs are stratified from one another in OCaml, so a functor does not literally have a function type, but to think of them that way is still basically correct. See [1ML](https://people.mpi-sws.org/~rossberg/1ml/1ml-jfp-draft.pdf) if interested in an OCaml-like language without such a stratification.
[^low-effort]: On the other hand, the derivations here can usually be performed rather mechanically, with little insight, by following the types in much the same way one might mechanically infer a direct proof in sentential logic, making them fairly low-effort and so still possibly not a waste of time.
[^correct]: Assuming, of course, that you admit some definition of correct as roughly "making sense in context" or "being broadly useful".
[^legis]: There are [laws about these sorts of things](https://hackage.haskell.org/package/base-4.14.1.0/docs/Control-Applicative.html), you know.
[^unique-functor]: This is true is a [precise sense](https://mail.haskell.org/pipermail/libraries/2011-February/015964.html).

View file

@ -0,0 +1 @@
- Good Eats, Chocolate Lava Cake and Chocolate Mousse episode: great style; plenty of references

View file

@ -0,0 +1,111 @@
# A Monad in OCaml
It's an old tradition that any programmer who thinks they know something useful about monads eventually succumbs to the temptation to go off and write a blog post about their revelations . . .
_Anyway_ . . .
Lets take a look at a `Monad` definition in OCaml and walk through the clues that suggest a monad's implementation.
In OCaml, abstract structures such as monads are typically best represented using [modules](https://www.pathsensitive.com/2023/03/modules-matter-most-for-masses.html). A module is essentially a record, containing types and terms, along with a manifest or interface that allows a programmer to selectively expose information about that module to the outside world and, dually, to selectively depend on particular characteristics of other modules. Modules provide programmers the machinery of composition and reuse and are the primary mechanism by which OCaml code is structured, neatly capturing the notion of a program abstraction boundary.
```ocaml
module type Monad = sig
type 'a t
val return : 'a -> 'a t
val bind : ('a -> 'b t) -> 'a t -> 'b t
end
```
Above is a module _signature_. Signatures themselves can be thought of as relating to modules in much the same way that types relate to values (hence `module type` in the syntax): each one defines the set of all possible modules that comply with the structure it describes. In this case, we give the name "`Monad`" to the set of modules exposing _at least_ a type constructor `'a t`[^alpha], a function `return : 'a -> 'a t`, and a function `bind : ('a -> 'b t) -> 'a t -> 'b t`. Abstractly, these three items together are what constitute a monad.
It's helpful to think about what each item means in general before examining them in more concrete terms. `t` is a function from types to types, also known as a type constructor, or a "generic type"[^not-quite] in some languages. `list` and `option` both are examples of type constructors. The presence of `t` in our `Monad` signature--- specifically the fact that it's parametric, i.e. `'a t` rather than just `t`--- represents the idea that a monad is essentially a _context_ around underlying computations of an abstract type. For some particular `'a` and some particular module that fits the `Monad` signature above, `'a` is the type of the underlying computation; that is, `t` is the generic context itself, and `'a t` is an instance of that generic context which is specific to the inner type `'a`; `'a t` is the type of alphas in the `t` sort of context.
Hopefully, something in that bundle of phrasings made at least a little bit of sense--- what exactly is meant by "context" is the key to this whole endeavor, but I'm going to avoid addressing it directly until we're a little further along. For now, let's consider `return`.
If `t` is the generic context, then `return` is the function that makes it specific or "specializes" it to the type `'a` of some particular value `x : 'a`. This function takes an object[^object] of the base type `'a` and puts it into the context of `t`. The specialized context of the resulting `'a t` value will be in some sense basic, empty, default, null; it is the starting-point context that exists just to have `x` in it, so that computations involving `x` can take place in that sort of context later on.
```ocaml
module ListMonad = struct
type 'a t = 'a list
let return : 'a -> 'a t = fun x -> [x]
. . .
end
```
Since `t` here is `list`, `return` is the function that takes an argument and sticks it into a list, i.e. `fun x -> [x]`. As you might guess, `list` forms a monad when equipped with suitable definitions of `return` and `bind` (the latter of which is omitted for now). The meaning of `list` as a monad--- that is, the context that `list` and its natural accompanying definitions of `bind` and `return` represent--- is interesting, broadly useful, and sufficiently non-obvious as to demand some intuition, so I'll use it as a running example.
In its most natural interpretation, `list` represents--- or simulates[^physical]--- the property of [nondeterminism](https://en.wikipedia.org/wiki/Nondeterministic_Turing_machine), which is characteristic of a computational model in which all possible paths are taken _simultaneously_. A value of type `'a list` thus represents all possible results of a particular computation of type `'a`, with each result being a list element. Considered in this light, `[x]` is a value where only one path is taken, i.e. where no branches in execution are encountered. Examining the code above, notice how the implementation of `return` inherently gives rise to the "no branches" notion of the empty context, which is embedded in it by definition. That notion, that the null context means there are no branches, is specific to nondeterminism, and `return` is what encodes it into the formal structure of the `ListMonad` module.
Finally, we move on to `bind`. `bind` is the driving force of monads; it performs the heavy lifting that makes them a useful tool for structuring algorithms. An implementation of `bind` is what captures the meaning of a particular sort of context and contextual data by encoding it into a `Monad` instance. Thus, it is `bind` _abstractly_, as it appears in the definition of the `Monad` signature, that captures what is meant by "context" in general. A context should thusly be thought of as some computation that is driven by--- and gives additional structure to--- the underlying computation in `'a`. In other words, every time a program manipulates an `'a t`, some additional, implicit computation is carried out alongside, or possibly modifying, that direct interaction with the context or data therein. This implicit computation is embedded in the implementation of `bind`, and, thus, it is the `bind` function for a type constructor that fundamentally determines what is the context in question, what that context _means_ informally, and how it behaves.
```ocaml
module ListMonad = struct
type 'a t = 'a list
let return x = [x]
let rec bind (f : 'a -> 'b list) (xs : 'a list) : 'b list =
match xs with
| [] -> []
| x :: xs' -> f x @ bind f xs'
end
```
Above is the completed definition of `ListMonad`, including `bind`. A good way to think about what any implementation of bind is doing at a high level is that it
1. extracts the value of the underlying type `'a` from `xs`,
1. transforms it _via_ `f`, producing some `'b` value with its own associated context, and
1. uses that new context, along with the original context of `xs`, to determine the final context of the returned `'b t`.
The "value of the underlying type" may be literally a single value of type `'a`, but it needn't be. In `ListMonad.bind`, above, we are actually extracting a whole list's worth of alphas, applying `f` to them as we recurse over the list structure--- these constitute the "underlying value" of the `'a list` `xs`. To understand how this plays out concretely, let's walk through the definition `ListMonad.bind`.
If `xs` is empty, `bind` returns the empty list; this is the usual base case for recursion on lists. If `xs` is not empty, we have an inductive case; it is safe to
1. take the first element `x` and the remaining elements `xs'`,
1. apply `f` to `x` to obtain a new `'b list`,
1. append the result of `f x` to the recursive call `bind f xs'`.
We know that the `bind` function returns a `'b list`, so we're appending the `'b list` `f x` to the `'b list` `bind f xs'`, thus obtaining a new `'b list` that we return the caller.
Pay careful attention to the parallels here with the high-level steps outlined previously. It may seem at first that we don't use the original context, but we do! We recurse over the context, i.e. the list structure of the `'a list` `xs`; it determines the call graph of `bind` and is integral to the final result of the function.
```ocaml
let sqrt (x : float) =
if x < 0 then invalid_arg "negatives have no sqrt" else
if x = 0 then [0] else
let pos_root = Float.sqrt x in [pos_root; ~-.pos_root]
let various_floats = [1.0; 4.0; 9.0]
let together : float ListMonad.t = ListMonad.bind sqrt various_floats
```
Here's an example of the monad in action. `together` is `[1.0; -1.0] @ [2.0; -2.0] @ [3.0; -3.0]`, which of course is `[1.0; -1.0; 2.0; -2.0; 3.0; -3.0]`. In this case, we used the taking-all-branches nature of the list monad to compute all the square roots of the numbers in the provided list. If taking the square-root is considered to be an ambiguous operation[^i-know], then *yada yada*. This monad is often used to run operations that have some ambiguous result, to capture the idea that multiple possible return values are valid, e.g. "the square root of four" can be considered ambiguous, since both 2^2^ and (-2)^2^ are 4. Another example of this can be found in parsing with ambiguous grammars. Parser combinator libraries often make it easy to define ambiguous-on-the-face parsers and resolve the ambiguity through some convention, but perhaps
looks like multiple return with only one in the chain, but the sequencing is what gives us nondeterminism; multiple return doesn't chain the same. To do this without a monad we'd need to do
```ocaml
bad shit
```
The list monad allows us to write non-deterministic code in much the same style as we would write fundamentally simpler deterministic code, albeit with substantial boilerplate. We can reduce this boilerplate by making use of OCaml's `let*` [binding operators](https://ocaml.org/manual/5.3/bindingops.html#ss:letops-rationale)[^haskocaml].
```ocaml
insert code using binding operators
```
***
[^physical]: Of course, we say that `list` _simulates_ nondeterminism for the same reason that we say physical computers simulate Turing machines: both are constrained by the resource limitations of physical reality and thus less capable than the theoretical devices they seem to emulate.
[^alpha]: Pronounced "alpha tee".
[^object]: "Object" in the general sense; nothing to do with object-orientation or kin.
TODO: be explicit about how monads exist independently and we are _capturing_ them in the particular language of ocaml. `list` forms a monad whether we actually implement that monad, or not
[^action-std]: This gives rise to a standard term. See [Monads as Computations](https://wiki.haskell.org/Monads_as_computation).
[^not-quite]: These are not exactly the same, but the similarity is more important than the difference.
[^haskocaml]: Haskell and Lean lack binding operators and instead use [Typeclasses](https://www.haskell.org/tutorial/classes.html) and an infix operator `>>=` for this boilerplate reduction. OCaml, in turn, lacks typeclasses (or the more likely equivalent feature, [modular implicits](https://www.cl.cam.ac.uk/~jdy22/papers/modular-implicits.pdf)).
[^i-know]: Whether taking the square root of a number _is_ considered to be ambiguous tends to be regional. In some regions, only to the positive root is referred to, by convention.

View file

@ -0,0 +1,62 @@
# Umbral Gaze 1
Two years have passed since our intrepid adventurers fought and defeated a hydra near Venron in what villagers now call "the hydra incident". In other parts of Faerûn, those less in-the-know refer to the battle as "that thing at M. Pteey Lake".
As they stand together now, summoned by supreme authority to a high-curtained forecourt under the mild Waterhavian sun, party members speculate about their situation and watch as shadows cast by serried wall-top grotesques slide unremittingly across the flagstones. Jaggedly sculpted profiles grow long and pointed in the golden hour, a hundred umbral fingers that stretch over our heroes to scrabble at the stonework and prank the quadrangle in narrow slats of shade. At one end, a pair of doors blocks the entrance of the mansion to which the courtyard belongs; at the other, iron gates fill an archway that leads to the road.
In spite of the shared wealth of knowledge and experience between them, no adventurer can say what has ultimately brought about this happy reunion, only they attend a summons from Laeral Silverhand[^laeral], Open Lord of Waterdeep, whom it would be unwise to disappoint. Their conversation is still worthwhile--- all are glad to see one-another again and to share a few brief stories of where they've been and what they've been doing since that fated day two years ago.
Warren[^warren] looks much as his companions remember, powerful and rotund as the day they parted. He opened a business a while back, crafting and trading in high-end cutlery, and has taken up residence within a nearby mid-sized town, to which customers are drawn from all over Faerûn by the fine workmanship of his forks, knives, and spoons. He carries several examples of that handiwork with him now, secured by loops and pouches all about his person. As the harengon talks, he reveals a few interesting details of his past, including that he was raised underground among the dwarves!
Eyes turn to Clementine[^clementine], who is dressed in the crisp uniform of an officer of the city guard--- one heavily altered to accommodate her equine anatomy. Clementine' rank is clear, but no insignia advertizes an allegiance to any house or party: a rare independence for a person of standing in Waterdeep. The discharge of her guardly duties has allowed the her to amass considerable knowledge of the city's criminal and/or political institutions, but even that intimate familiarity fails to yield clues about the situation. Like Warren, Clementine's person is largely unchanged by the intervening years, save for a new and conspicuously superior longbow at her back.
Constitutionally peaceful cleric Almuth Cheerio[^almuth] sits pensively, forgoing his vestments today in favor of well-worn leather armor. The servant of Eldath is prepared for the possibility that their forthcoming task involves inescapable violence--- he has arrived with steel in his heart; whatever the matter, he will do the will of his goddess without hesitation. His gaze radiates self assurance and wisdom of a new profundity.
Gottlob Graal[^gottlob] leans casually against the door, a cloak of pale twill hanging evenly on his broad shoulders. The richness of its fabric and precision of its stitch give some hint at the success the satyr has found since his immigration to Waterdeep twenty months earlier. As he found his place in the city, happenstance led Gottlob to join the Unblinking Patrol, a tiny, quasi-religious order dedicated to protecting this Waterhavian slice of Coast from unnatural incursion. There, he learned the real business of the Watchers and advanced his abilities with rapidity that startled even him. Though proud of his achievements, the satyr feels conflicted: as he and the order grow in renown, will his pursuers, too, take notice?
{% "ambles" %}
Carmal Rumbar[^carmal] exchanges idle words as he looks about the courtyard. Behind him hangs a bulging traveler's pack, stuffed to its limit with unseen pounds of equipment, a large, polished button securing a flap over its opening. The actor shares little of his recent escapades, preferring to listen to those of the others: the fundaments of stories yet unwritten.
As the party counts heads, they notice that one of their number is missing: the wizard, Louisa Whitlock[^louisa]. Sensing an immense challenge ahead of them, the present members are hopeful that no ill-fortune has befallen her. Louisa will be around as soon as she can, no doubt; probably she was waylaid reassuring some hapless farmer that talking animals are _not_ on the rise and that he needn't worry about his pigs planning a revolt any time soon.
Before long, a guard emerges from the heavy double doors, holding one open with his gauntleted hand and stating politely that their presence is requested inside. Taking up the rear, he sets a brisk pace down a long and richly decorated hallway. On the walls hang paintings of otherworldly scenes--- some of which Gottlob recognizes as belonging to other planes--- and scores of magical artifacts beyond a mean treasure hunter's wildest dreams. As the party comes to yet another set of doors, silver-inlaid slabs of oak that reach up to the ceiling, a second guard swings them open and ushers our heroes through.
With trepidation, they glance around the war-room before them. Gottlob and Clementine, Waterhavians of the group, recognize Laeral Silverhand, child of Mystra and Lord of Waterdeep, as she reposes on a shallow dais at the end of a long, low table in the center of the chamber. Her white robes and silver hair conspire in a stately cascade to convey the momentary impression of a calcite-hewn portrait gilded with a thousand-thousand pearls. Dozens of officials and functionaries fill rows of seats toward the periphery or the room, each behind their own small desk, and only as Lord Silverhand gestures to the newly-arrived party do they stymie the frenzy of their conversations. Silverhand addresses its members, inviting them to approach her grand table. They bow deeply, and she proceeds to explain the situation that constitutes the ultimate reason for their presence.
{% This supposedly happened "within the last month". %}
> I have received troubling reports of an otherworldly incursion in the Sword Coast's northern peak--- eyewitnesses verify what I am about to tell you. The region has undergone a planar fissure, a tear in the fabric that separates one aspect of reality from the next. By great fortune, a powerful wizard was able to patch the hole, but not quickly enough to contain everything. My intelligence has determined that _nine_ beholders slipped into our realm before the fissure was closed and have since scatted themselves to the far reaches of Toril. Each is wreaking something between havoc and irritation as we speak.
The Open Lord goes on to divulge details of the incursion, prompting Almuth--- who was summoned for his expertise on beholders--- to elucidate the species for his companions. He describes beholders' conventional behaviors and motivations, explaining their cunning paranoia, supreme arrogance, and their origin in the Far Realms, being descended from a deity that beholder-kind calls the "Great Mother". Producing an image, he goes on to detail the monsters' abilities: they project a cone a magic suppression from a single central eye and rays of devastating magical power from the eye stalks that surround it. Though capable melee fighters, beholders usually prefer to float just beyond the range of attackers' primitive physical weapons, raining curses and death on their playthings from above.
As Almuth concludes his lesson and his answers to succeeding questions, Lord Silverhand makes the adventurers' task clear: they will vanquish the invaders, or they will die in their attempt. As skepticism permeates the group, Laeral reveals the attendance of two consultants, each of whom has agreed to provide whatever assistance they can. As if on queue, a lurid vortex fills the space beside her and a raven-haired woman[^tasha], dressed to match, materializes in a rush of air with a crack like a gods's tankard, fumbled from the table of heaven, striking earth a mile off.
She is introduced as "Tasha", but needs no introduction. It was she who sealed the planar fissure and tracked several of the nine beholders to their current locations. The demonologist and renowned planeswalker volunteers to serve as transportation for the party, shuttling them through dimensions to far flung corners of Toril unreachable by non-magical means. Unfortunately, this will the the extent of her help, as other, more pressing issues demand the bulk of her attention elsewhere.
Concurrently, Laeral's hands intricately over the table, tracing an inscrutable pattern across its top. A ten-pound sphere of hazy crystal deploys from the great slab's center and comes to rest on a dark, squat, satin-lined plinth. As Tasha finishes speaking, Laeral continues her spell, and smaller spheres, set into the walls of the room, float upward, issuing a limpid glow in solidarity with the overextended lamplight of the chamber's recesses. A voice like sand and broken glass emanates from the central ball as it too glows and the second consultant makes himself heard.
> Oh, Great Xanathar!
{.thematic}
***
Under the light of the scimitar moon that slips coolly through blinds and around shutters drawn only half-shut, our heroes slumber in rented beds, paid for with municipal coin. While some toss in excitement, thrilled by dreams of the dawn's adventures, others are serene in their anticipation. Amidst the clamor of clocks across Waterdeep that chime the midnight hour, the party is whisked by unanswerable magics through a chasm of scintillating vapors and deposited, standing under their own powers, upon a roundel of charoite in a muddle of lilac effervescence. Each is unsettled to see the others in their dreams so suddenly and in so strange a setting, but before any can ask the question on their lips, Tasha reappears in a heady turbulence of bubbles, preempting them all.
She explains her reasons; the renowned planes-traveler provides an alternative to the Open Lord's path of violence. If the party is willing to attempt to reason with the beholders they encounter on their quest, learning their motives, relating to them, and guiding them away from evil, she is willing in turn to compensate them most handsomely for their efforts. The irregular behaviors of the beholders loosed into the realms hint at irregular temperaments, and if there is a chance to align new such powerful creatures with the light, it must be seized with all tenacity and ardor.
[^warren]: Harengon Forge Cleric (10) of Moradin
[^clementine]: Centaur Fey Wanderer Ranger (10)
[^almuth]: Human Peace Cleric (10) of Eldath
[^gottlob]: Satyr Paladin (10) of the Watchers
[^carmal]: Human Bard (10) of the [College of Masks](https://www.dandwiki.com/wiki/College_of_Masks_%285e_Subclass%29)
[^louisa]: Human (llama) Wizard (10) of the Scribes' Order
[^laeral]: Immortal Chosen of the goddess Mystra, Laeral Silverhand is a wizard of untold beauty and power who has been the public face of Waterdeep's elites for decades; she will no doubt continue to rule for centuries more.
[^tasha]: You already know who [Iggwilv](https://forgottenrealms.fandom.com/wiki/Iggwilv) is.

View file

@ -0,0 +1 @@
../../../inclusions/footer.html

10
acl.cool/site/index.html Normal file
View file

@ -0,0 +1,10 @@
<h1>
<a href="/resume.pdf" style="text-decoration: none" class="est-color">acl</a><a
href="https://en.wikipedia.org/wiki/Special:Random" style="text-decoration: none" class="est-color">.</a><a
href="https://git.acl.cool/al/pages" style="text-decoration: none" class="est-color">cool</a>
</h1>
<div id="writings" class="null-color" style="line-height: var(--ui-spacing);"></span>
<p style="font-weight: bold;">Welcome! Below are links to things I've made or just enjoy.</p>
<!-- soupault-metadata no_footer = true -->

BIN
acl.cool/site/msvcr110.dll Normal file

Binary file not shown.

BIN
acl.cool/site/resume.pdf Executable file

Binary file not shown.

112
acl.cool/site/resume.typ.txt Executable file
View file

@ -0,0 +1,112 @@
#let fontsize = 10.2pt
#let typeface_text = "Fira Sans"
#let typeface_math = "STIX Two Math"
#set text(font: typeface_text, size: fontsize)
#show math.equation: set text(font: typeface_math, size: fontsize)
#let inlineFrac(a, b) = [$#super([#a]) #h(-1pt) slash #h(-1pt) #sub([#b])$]
#set smartquote(enabled: false)
#show heading: q => {
if q.depth != 1 {
v(fontsize / 5)
}
text(q, weight: "bold")
if q.depth == 2 {
v(-0.7em)
line(length: 100%, stroke: (thickness: 0.7pt))
v(0.5em)
}
}
#let head = {
align(center)[
= Alexander Lucas
#set text(font: "Fira Code")
#text([alexander.clay.lucas\@gmail.com], size: fontsize * 0.9)
#linebreak()
#text([(+1) 347-644-9265], size: fontsize * 0.8)
]
}
#set page(margin: (x: 0.9in, y: 0.35in))
#head
== Summary
I am a computer science enthusiast with an inclination for harnessing computer science
theory to tackle practical challenges as cleanly as possible. I believe strongly in the
importance of codebase health and quality, maintaining those values over time as programs and projects evolve.
== Skills
#let skills = [
*Languages*: Javascript/Typescript, Java, Python, Rust, Haskell, OCaml, F\#, Ruby, C\#, C, C++, Lean 4, HTML/CSS, LaTeX, Typst
#linebreak()
*Platforms*: Ten years using GNU/Linux including Debian and Redhat, QEMU, Google Cloud
#linebreak()
*Technologies*: Buildroot, WebGL, Numpy/Pytorch/Sklearn, Matplotlib, Git, Gitlab/Github, PostgreSQL, Node, Slurm
#linebreak()
*Soft Skills*: Technical Writing, Software Documentation, Presentation
]
#skills
#let interline() = {
box(width: 1fr, inset: fontsize / 4, line(length: 100%, stroke: (thickness: fontsize / 10, dash: "loosely-dotted")))
}
== Experience
#let experience = [
*Embedded Software Engineer, Jr.* #interline() #text(weight: "semibold")[Trusted Microelectronics, KBR, 01/2025-05/2025 (End of Funds)]
- Continuing to work with the same great team, tools and software as during my internship.
- Developing QEMU virtual hardware devices for building/testing platform-specific applications.
*Linux Driver Development Intern* #interline() #text(weight: "semibold")[Trusted Microelectronics, KBR, 05/2024-08/2024]
- Learned Linux kernel subsystems and developed device drivers for custom "system on a chip" hardware, including GPIO/pin controllers and an AES encryption accelerator module.
- Worked with team members to develop testing and assurance methodologies including coverage profiling and input fuzzing for Linux drivers while porting Linux to our boards.
- Automated common tasks, writing scripts to handle OS installations and code restructuring.
- Presented project status and details to large, cross-functional and interdisciplinary groups.
*Teaching Assistant* #interline() #text(weight: "semibold")[James Madison University, 08/2022-12/2023]
- Took questions and led review sessions in proofs, programming, tooling, debugging code.
- Maintained a calm and encouraging environment while helping students with difficult problem sets against a deadline.
]
#experience
== Education
#let degrees = [
*B.S. Computer Science* (3.8 GPA) #interline() #text(weight: "semibold")[James Madison University, 12/2023]
]
#degrees
#let courses = [
- Programming Languages, Compiler Construction
- Independent Study in Constructive Logic, Symbolic Logic
- Applied Algorithms, Data Structures
- Parallel and Distributed Systems, 3D Graphics
]
#courses
*Study Abroad, London, UK* #interline() #text(weight: "semibold")[JMU at Florida State Study Center, Summer 2023]
#let cw = [
- Rigidity Theory
- Independent Study in Computational Geometry
]
#cw
*Academic Awards*
#let awards = [
- "President's List" #interline() #text(weight: "semibold")[JMU, 2023]
- "Alonzo Church Award for Theory" #interline() #text(weight: "semibold")[JMU CS Department, 2024]
]
#awards
== Personal Projects
#let projects = [
*Aasam* (on #underline([#link("https://hackage.haskell.org/package/aasam")[Hackage]])) is a Haskell implementation of the CFG-generation algorithm $#math.cal([M])$ from Annika Aasa's paper "Precedences in specifications and implementations of programming languages".
#linebreak()
*Randall* (on #underline([#link("https://gitlab.com/mobotsar/randall")[Gitlab]])) is a Discord bot for executing dice-notation, making it easy to play TTRPGs remotely. It uses a recursive descent parser and tree-walk interpreter on the backend and the .NET Discord library up front.
]
#projects

View file

@ -0,0 +1,42 @@
# Amaretti (Chewy Almond Cookies)
These are genuinely excellent and surprisingly undemanding to make, particularly if you don't beat the egg whites by hand. From start to finish, the process should take less than an hour.
Keep in mind that the ideal crispy-outside chewy-inside texture forms over time while the cookies are cool. They _taste_ right immediately after coming out of the oven, but for the best texture, let them cool completely and rest in a sealed container for several hours before consuming.
Total caloric content of this recipe is 2840 kilocalories, or 109.6 kcal per cookie for twenty-five cookies.
## Ingredients
- 1/2 cup white granulated sugar
- 1/2 cup demerara sugar (Florida Crystals or similar)
- 280 grams blanched almond flour
- 1/2 teaspoon kosher salt
- 3 extra large eggs
- 1/2 teaspoon vanilla extract
- 1 ounce (weight) Lazzaroni Amaretto
- 1 cup powdered sugar
- 25 whole, roasted almonds
## Equipment
- A medium mixing bowl (for dry ingredients)
- A small mixing bowl (for beating egg whites)
- A whisk or mixer
- A standard set of measuring spoons
- A kitchen scale
- A medium cookie sheet
- A silicone sheet-pan liner
## Process
1. Preheat the oven to 325°F.
2. Mix the flour and sugars into a medium bowl.
3. Separate three egg whites into another bowl and discard the yolks.
4. Beat the egg whites until peaks are stiff.
5. Add the vanilla extract and amaretto to the bowl of dry ingredients.
6. Add the beaten egg whites to the dries and fold gently until a paste forms.
7. Form small balls of the dough and coat them completely in powdered sugar.
8. Gently press the dough balls onto a sheet-pan with a silicone mat, flattening them slightly.
9. Press an almond into the top of each cookie so that the dough will hold it when baked.
10. Place the pan (with cookies) in the oven for 25 minutes then remove it and let them cool.

View file

@ -0,0 +1,29 @@
# Unassailable Slow-Cooker Chili
This is a simple recipe of beans, tomato, and ground beef, refined across generations into the local maximum you see before you.
## Ingredients
- A bit more than 1 lb of ground beef
- 2 14.5 oz cans of diced tomatoes
- 2 1.25 oz packets of chili powder spice mix (such as McCormick, etc)
- 4 cans of beans (typically one each of pinto, black, great-northern, and light kidney)
- 1 large bottle of tomato juice
- 1 medium or large onion
- Black pepper to taste
## Equipment
- A knife to cut the onion
- A skillet to cook the beef
- A large slow-cooker
- A heat-resistant spoon
## Process
1. Cook the ground beef until most of the fat has rendered, then drain most of the grease away.
2. Chop onion and add to skillet with beef, cooking just until color develops.
3. Add the beef and onion to the pot with tomatoes, beans, and spice mix.
5. Pour in tomato juice until it reaches a consistency too thick for soup but still more suitable for consumption with a spoon than with any other dining implement.
4. Cook on low until you're happy with the texture of your onions, as these usually take the longest.
5. Black pepper to taste shortly before serving.

View file

@ -0,0 +1,45 @@
# Typical Coronation Chicken
This recipe is adapted from the original recipe used for Queen Elizabeth's “Coronation Luncheon” in 1953 and faithfully incorporates elements of several variations served around London in 2023. Most of the changes I've made are to ratios, but I've also included more fruits, omitted watercress, and used a mayonnaise/milk combination in place of whipping cream.
## Ingredients
- 3 tbsp almond slivers
- 1/2 shallot
- 1 tbsp dried apricots
- 1 tbsp lemon juice (roughly 1/4 of a smooth lemon)
- 1 tbsp extra-virgin olive oil
- 3 tsp your favorite yellow curry powder
- 1 tsp tomato paste
- 90 ml dry red wine
- 30 ml water
- 1/4 tsp dark brown sugar
- 225 ml Duke's mayonnaise \*
- 100 ml 2% milk \*
- 4 tsp thompson raisins \*
- 1 tsp dried black currants \*
- 650 g shredded cooked chicken breast *
Values marked with \* have been estimated after-the-fact. I made visual judgments during preparation to decide actual amounts and neglected to record them. The combined volume of mayonnaise and milk was measured at 325 ml. The chicken was measured at 500 g, but was too little for the amount of dressing made.
## Equipment
- A wide skillet or frypan
- A knife and cutting surfaces
- A standard set of measuring spoons
- Two medium mixing bowls
## Process
1. Toast the almonds in your pan before setting them aside.
2. Chop the half shallot and apricots very finely.
3. Squeeze 1 tbsp of lemon juice and set it aside.
4. Add olive oil to the pan and place it on medium heat.
5. Add the shallot and curry powder then cook about two minutes or until the shallot begins to soften.
6. Add tomato paste, wine, and water, then bring the pan to a mild boil.
7. Once it boils, add lemon juice and brown sugar then simmer until the mixture is slightly reduced.
8. Remove from heat and let cool substantially.
9. Transfer to a bowl and mix in mayonnaise, milk, almonds, and all the fruit to complete the dressing.
10. Place shredded chicken in another bowl and add dressing until the desired consistency is reached.
11. Sample the mixture before adding salt and pepper to taste.
12. Let rest in the refrigerator until completely cooled.

View file

@ -0,0 +1,17 @@
# Reading Order of The Culture
I've generated a reading order dependency graph for books in Iain M. Banks' unsurpassable Sci-Fi classic, the _Culture_ series. The idea is that if there's an arrow from book A to book B, then to get the most possible enjoyment from either A or B, A should be read before B.
![A dependency graph diagram of what Culture books must eb read before what others.](/culture.dot.png) Above is the graph, and [right here](/culture.dot.txt) is the vizgraph description file that lists my rationale for each dependency.
- _Consider Phlebas_ before _Look to Windward_--- both are about the Idiran War. The events of _Consider Phlebas_ happen first and are important both for familiarity and for putting LtW into some emotional context.
- _Use of Weapons_ before _The State of the Art_--- these share a main character in Diziet Sma. SotA was actually released before UoW but in my opinion is more satisfying if read after it.
- _Use of Weapons_ before _Inversions_--- UoW gives the best idea of any book about what Special Circumstances is, which must be understood to fully appreciate _Inversions_ in all its subtlety.
- _Excession_ before _The Hydrogen Sonata_--- _Hydrogen Sonata_ is dual to _Excession_ in many ways that can't be explained here without abject spoilage. This one is not a hard rule, but HS is better if you know _Excession_.
- _Excession_ before _Matter_--- GSV Sleeper Service is mentioned in _Matter_ as "The granddaddy, the exemplary hero figure, the very God...", referencing events in _Excession_.
- _Use of Weapons_ before _Surface Detail_--- you must know who Zakalwe is, the main character of UoW, to fully appreciate the ending of _Surface Detail_.
- _Look to Windward_ before _Surface Detail_--- These books deal with common themes and subjects. Some will disagree with me here, but LtW is more impactful _without_ certain knowledge revealed in _Surface Detail_.
Assuming one agrees with the graph, the set of ideal reading orders (that is, the set such that for all orders it contains, no order exists which is strictly better) is the set of [topological sorts](https://en.wikipedia.org/wiki/Topological_sorting) of the graph.
This gives the number of possible ideal orders as 63840. That's a lot of good ways to do it!

View file

@ -0,0 +1,11 @@
# Woes of Gottlob Graal: Umbral Gaze 0
Gottlob Graal is a level-{five,ten} Watchers' Oath paladin, a satyr, bent on avoiding a faery card sharp he pissed off years ago during a regular visit to the Feywild. He fled that plane and his native Chondalwood for Waterdeep, half a continent away, where he took up defenses against any and all extraplanar pursuers. At first he just wanted to avoid his creditor, but later, finding purpose and fraternity in the city as never before, the satyr began to feel at home. A jovial fellow, Gottlob is energetic in his late middle-age, but finds the reckless carousing of his youth a bit beyond him now.
> The last time I _really_ partied, you know, I wound up in debt to an Unseelie courtier! Sylas Grinbriar of the Bleeding Grove... hmm, but there's no sense _worrying_ about that sort of thing, my friend!
## A Watchers' Order
Upon leaving Chondath, Gottlob found himself heading West until he could do so no more. Thereupon, he was in Waterdeep. The satyr decided he would find a place to sleep, but instead found the bottoms of some tankards, alongside a couple of Watchers' paladins--- out for some merry-making themselves--- who became very interested in this strange, cloven-hoofed man and his experiences in the Feywild. With so little coin weighing in his pockets and his stomach grumbling for something richer than mead, the offer they made him at the end of the night--- of help staying hidden and a job that payed--- was too good to turn down.
In the few intervening years, Gottlob took the oath of their little order, the Unblinking Patrol, and showed natural talent, quickly becoming a formidable and knowledgeable member. He's since remained in Waterdeep, living well, but always keeping an eye over his shoulder in case Lord Grinbriar--- or one of his lackeys--- ever manages to track him down.

View file

@ -0,0 +1,45 @@
# Umbral Gaze ½
It's early morning; our intrepid adventurers are just beginning their day. They awaken in Venron, a small inland village equilatitudinal to the southern Sword Coast, where, for one reason or another, each wound up stopping for rest the night before.
The centaur Clementine[^clementine] helps a farmer store his crops, managing bins of grain with ease. On her back hangs a powerful bow, but she has no need of it now--- as she takes in the wind and the smell of earth and the stirring sounds of nature, she's at peace.
Warren[^warren], a harengon, clad in plate and festooned with weaponry, makes breakfast over an outdoor fire. A local innkeeper, unexpectedly short-staffed this morning, follows the succulent smell of Warren's griddle to its source and beseeches that he come cook for _him_ today, just through the breakfast rush, and for an outstanding wage too! Our harengon friend obliges, but accepts only half of the money offered, not one to take advantage of someone in a bind. As the innkeeper thinks back on things later, he too decides that he may have been too generous in his desperation.
In the scant lanes of Venron, a _llama_ breathes the cool light of dawn; an ornate pendant hangs from its neck. No villager suspects the creature of its true identity: she is a powerful wizard, Louisa Whitlock[^louisa], cursed by an unseen adversary to wander Faerûn on four legs.
Further on, Karmel[^karmel], a brightly-dressed human, snoozes under a tree, indulging in an early morning nap. His tightly-drawn ponytail lies splayed about him as it gathers subtle moisture from the dewy grass. A noise wakes him, and he plods off in search of breakfast.
Across the road from where Warren will soon man the stove, hunkered in Venron's cheapest and poorest inn, Almuth Cheerio[^almuth] sits down to "pancakes". A half-elf woman and a human man fill the seats beside him. In his usual way, the gray-haired, white-robed cleric of Eldath strikes up a conversation that somehow turns toward the subject of his deity. His captive companions are surprisingly receptive.
By the town's northern entrance, Gottlob Graal[^gottlob], a satyr, chats with a lone town guard. Though the guard can get in little more than the occasional nod or word of confusion edgewise, he is grateful for the aid staying awake near the end of his long shift. Still, as minutes threaten to become hours, he wearily wishes that this chatty goat and his obvious comb-over would find someone else to regale with pointless trivia and dubious stories of past revels.
As the morning continues and the breakfast hour draws to a close, villagers and adventurers alike begin to take notice of a giant frog, apparently arrived undetected early in the morning. The monstrously massy amphibian perches with apparent ease, though presumed precariousness, over the mouth of the town's main well, which is adjacent to the inn wherein Warren now labors. A second guard, a half-elf, approaches Gottlob and his interlocutor for the changing shift. Drawing up close, he takes the helmeted head of the latter between his hands and turns it forcibly toward the frog; neither Gottlob nor his companion have looked down the road for some time. "That's becoming a problem now, ay!", the new arrival injects, but the tired and frankly overwhelmed human just stares, uncaring or uncomprehending. Gottlob, suddenly aware of the potential danger, abandons his conversation and heads toward the well--- giant frogs aren't unheard of on the prime material, but they are common in the Feywild. He casts "detect evil and good", furtively, a little embarrassed, but finds nothing to fear from other planes this morning. "Perhaps the frog is enchanted", he thinks, but doesn't bother with "detect magic", satisfied to know that if there is any threat, at least it's native.
Almuth, having finished his breakfast across the street, comes outside and makes his way over to the curiously poised anuran. He tests the bounds of its docility, inching close and pushing it gently, trying to physically coax it off the well, but wisely backs away when it shows signs of aggression. A better idea occurs to him, and he heads back to his room to prepare for another attempt. Gottlob starts to follow, eager to talk to this interesting fellow, but the cleric doesn't even notice.
Most townsfolk have now taken heed of the commotion. The innkeeper offers Karmel, who chose the establishment for his breakfast, a profligate sum to remove the frog from its perch--- the villagers need water and the innkeeper needs one less shiny green gargoyle at the entrance scaring away his customers--- so the bard makes his way outside. The rest of the party shows up apace, finished with their respective morning activities and finally getting keyed in to the issue at hand.
Almuth has returned. He invokes the magic of his goddess to "calm" bumpy green "emotions", which he hopes will make the beast more receptive to his coaxing, but alas, with no success. Peering sidelong down into the well, he determines the reason for the frog's obstinacy: it's guarding a milky, speckled mass of eggs with its life. As he makes this known to spectators, the innkeeper repeats to Karmel--- and to any who will listen--- the earlier offer of payment for the relocation of the frog and its clutch. Almuth and Karmel hatch a plan: they'll retrieve the eggs from the well with the winch bucket then hightail it to the nearest large body of water, wherein they can deposit the eggs and the no-doubt fiercely pursuing frog. They find a wagon, but fail to find a horse; the centaur Clementine is the only unoccupied equine around, who, though she finds it somewhat demeaning, volunteers to pull the load.
As they put their plan to voice, a halfling threads his way through the crowd. He steps forward and introduces himself as "Alfie". The newcomer offers information about the area, where the frog may have come from, and what it might take to put it back. Our adventurers learn that the nearest suitable body of water is nearly three-hours' swift ride from Venron! Only something truly dire could have driven the amphibian this far from home, no mere fancy or wanderlust. The plan is revised--- the frog will be taken to this distant lake and whatever expelled it will be brought under control or vanquished! Almuth calms the frog magically, with success this time, and Clementine carefully maneuvers the eggs into the bucket and up, out of the well. Seeing their success, and having nothing better to do at the moment--- or perhaps just catching wind of money to be earned--- Gottlob interjects. He can provide another horse (and says as much, to Clementine's chagrin), which should speed the journey considerably. He casts "find steed", and hitches the ensuing beast to the growing party's rented wagon. Warren and Louisa decide that this is getting interesting, and each likewise decides to attend the mission. Before the party sets off, Almuth probes Alfie for further helpfulness and tries to convince the man to accompany them, but the only help Alfie will be convinced to give is a map of the area and directions to the lake.
The mission gets underway. As the party travels, its members talk amongst themselves. Almuth expresses interest in the Feywild, and queries Clementine and Gottlob about their origins to that effect. Clementine is reticent, but Gottlob shares a bit of his past. The satyr hails from Chondalwood, in the south-eastern country of Chondath, but he has visited the Feywild on numerous occasions and under a variety of circumstances. Alas, he can never return to that plane, nor even to his home, lest he be caught and made to pay a gambling debt owed to a powerful enemy.
As they near the lake, a swarm of birds can be heard above; Clementine uses magic to understand their caws. She listens as the ravens squabble over whether to attack the group. "Those eggs look so tasty, but those people are _so_ well-armed!" Gluttony beats self-preservation, and the swarm dives to attack. They snatch an egg or two, but are easily beaten back with magic and swords. The giant frog even gets in on the action, launching its huge, sticky harpoon of a tongue to swallow a score of ravens like flies. Though he's too late to object, Almuth wonders whether killing the birds was really necessary.
As the lake comes into view, terrain grows low and a thick miasma fills the party's sinuses. They abandon their charges for the moment, at a safe distance from whatever produced the matted skein of carrion they see slicked over the water before them and from which, no-doubt, emanates this odor clogs their every orifice. As they disembark, Clementine asks the frog to describe what drove it from its home. "Big thing, many heads!", it answers. Forging onward toward the shore, the party spots what appear to be five crocodiles, just their eyes and nostrils breaching the surface. Clementine queries them--- do they know what's going on here? Why do they seem so unbothered? They divulge no helpful information, but the centaur gets her answer as the five heads are jerked out of the water on five reptilian necks, joined at a single base. A hydra! No wonder the place reeks...
As the party is thrown into combat, Gottlob takes the first action, guiding his steed sideways as he casts "moonbeam" on the monster's position. In the heat of melee, Warren does substantial damage with "guiding bolt", and the other party members make good use of the advantage it grants them. Karmel wraps the hydra's many teeth in [sheathes of water](https://www.worldanvil.com/block/1548815) that dull its bite, reducing the danger to his companions. Louisa casts "fireball", a foresighted choice against their many-headed foe, but the hydra attacks Warren viciously in response, nearly downing him. He fights on bravely, and the party manages to avoid most damage, striking reliably at their enemy's vitality, until Clementine is similarly attacked after rushing in close to strike. As the Hydra bites her, Louisa seen an opening. She reaches out to the weave and summons a trio of magic missiles. Sure that they will find their targets, she sends them hurtling upward, piercing through the hydra's multitude of necks in a triple sextuple collateral. The monster jerks wildly, then crumples, half floating on the lake, half lying on the shore.
{.thematic}
***
Our heroic band of adventurers has slain a terror and lived to enjoy the rewards! In the following days, merchants and scavengers cart away the hydra--- a valuable prize--- and skim the lake of its refuse. The giant frog returns to its life as before, guarding its eggs in the lake's sloughs for just a few more days, when they hatch dozens of new, tiny giant frogs into the world. The party members collect their gold in town and go their separate ways, but they worked well together. Perhaps they will meet again, someday.
[^clementine]: A Centaur "fey wanderer" ranger (5)
[^warren]: A Harengon forge cleric (5)
[^louisa]: A Human (llama) wizard (5) of the scribes' order
[^karmel]: A Human bard (5) from the College of Masks
[^almuth]: A Human peace cleric (5) of Eldath
[^gottlob]: A Satyr paladin (5) of the Watchers

View file

@ -0,0 +1,28 @@
[index]
index = true
# sort_descending = true
# sort_type = "calendar"
# date_formats = ["%F"]
# strict_sort = true
[index.fields]
[index.fields.title]
selector = ["h1"]
# [index.fields.date]
# selector = ["time"]
# extract_attribute = "datetime"
# fallback_to_content = true
[index.fields.abstract]
selector = ["p"]
[index.views.writings-index]
index_selector = "#writings"
section = "writings"
index_item_template = """
<p><a href="{{url}}">{{title}}</a>
<br>{{abstract}}</p>
"""

1
acl.cool/syntax_wrapper.sh Symbolic link
View file

@ -0,0 +1 @@
../syntax_wrapper.sh

View file

@ -0,0 +1 @@
../../shared_templates/main.html

84
build.sh Executable file
View file

@ -0,0 +1,84 @@
#! /usr/bin/env nix-shell
#! nix-shell --pure -i bash
#! nix-shell --pure -p nodejs_24 bash harfbuzz soupault woff2 jotdown python3 recode perl538Packages.LaTeXML minify
if ! [[ -d pgvv/ ]]; then
python3 -m venv pgvv
source ./pgvv/bin/activate
python3 -m pip install --upgrade pip
pip install --upgrade pygments
deactivate
fi
function soup_config {
rm soupault.toml
cp ../soupault.toml soupault.toml
cat soupault.frag*.toml >>soupault.toml
}
source ./pgvv/bin/activate
if ! [[ -f lexers.out ]] || [[ "$(head -1 lexers.out)" != "$(pygmentize -V)" ]]; then
pygmentize -L lexer >lexers.out
echo "Created pygments lexer cache at lexers.out"
fi
find acl.cool/site/ ytheleus.org/site/ -type f \( -name '*.dj' -o -name '*.html' \) -exec cat {} + >all_chars.txt
cat common_chars.txt >>all_chars.txt
for font in fonts/LiterataTT/LiterataTT-Subhead{Regular,Italic,Semibold,SemiboldItalic,Bold,BoldItalic}.woff2 \
fonts/JuliaMono/*{-Light,-Regular,-SemiBold}{,Italic}.woff2; do
woff2_decompress "$font"
ttf_font="${font%.woff2}.ttf"
subset_ttf="${ttf_font%.ttf}-Subset.ttf"
hb-subset "$ttf_font" \
--output-file="$subset_ttf" \
--text-file=all_chars.txt \
--layout-features='*' \
--passthrough-tables
woff2_compress "$subset_ttf"
rm "$subset_ttf" "$ttf_font"
done
for font in \
fonts/Alegreya/static/Alegreya-{Regular,Italic,Bold,BoldItalic}.ttf \
fonts/Alegreya_Sans/AlegreyaSans-{Regular,Italic,Bold,BoldItalic}.ttf; do
subset_ttf="${font%.ttf}-Subset.ttf"
hb-subset "$font" \
--output-file="$subset_ttf" \
--text-file=all_chars.txt \
--layout-features='*' \
--passthrough-tables \
--unicodes+="0x0435" # this is the cyrillic e. For some reason, alegreya's ff calt breaks without it
woff2_compress "$subset_ttf"
rm "$subset_ttf"
done
rm css/code.css
pygmentize -f html -S algol_nu | grep -v 'line-height' >css/code.css
for site in acl.cool ytheleus.org; do
pushd "$site"
soup_config
rm -rf serve/
soupault
pushd serve/
find -type f -name '*.html' -o -name '*.css' -o -name '*.svg' | xargs -0 -d\\n -I{} minify -o {} {}
popd
NEXT_DIR="serve_$(date +%s)"
CUR_DIR=$(find . -maxdepth 1 -type d -regex './serve_[0-9]+')
echo "$PREV_DIR"
cp -a serve "$NEXT_DIR"
ln -sfn "$NEXT_DIR" serve_
for d in $CUR_DIR; do
rm -r $d
done
popd
done
deactivate

13
common_chars.txt Normal file
View file

@ -0,0 +1,13 @@
—–-
⁰¹²³⁴⁵⁶⁷⁸⁹ ⁽⁾
↩︎
“”
!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~
◆◇⬥⬦

252
css/fonts.css Normal file
View file

@ -0,0 +1,252 @@
@font-face {
font-family: 'Heading';
src: url('../assets/fonts/LiterataTT/LiterataTT-SubheadRegular-Subset.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Heading';
src: url('../assets/fonts/LiterataTT/LiterataTT-SubheadItalic-Subset.woff2') format('woff2');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'Heading';
src: url('../assets/fonts/LiterataTT/LiterataTT-SubheadBold-Subset.woff2') format('woff2');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Heading';
src: url('../assets/fonts/LiterataTT/LiterataTT-SubheadBoldItalic-Subset.woff2') format('woff2');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: 'Subheading';
src: url('../assets/fonts/LiterataTT/LiterataTT-SubheadRegular-Subset.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Subheading';
src: url('../assets/fonts/LiterataTT/LiterataTT-SubheadItalic-Subset.woff2') format('woff2');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'Subheading';
src: url('../assets/fonts/LiterataTT/LiterataTT-SubheadSemibold-Subset.woff2') format('woff2');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Subheading';
src: url('../assets/fonts/LiterataTT/LiterataTT-SubheadSemiboldItalic-Subset.woff2') format('woff2');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: 'BodySerif';
src: url('../assets/fonts/Alegreya/static/Alegreya-Regular-Subset.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-feature-settings: "lnum" 1, "kern" 1, "liga" 1;
}
@font-face {
font-family: 'BodySerif';
src: url('../assets/fonts/Alegreya/static/Alegreya-Italic-Subset.woff2') format('woff2');
font-weight: normal;
font-style: italic;
font-feature-settings: "lnum" 1, "kern" 1, "liga" 1;
}
@font-face {
font-family: 'BodySerif';
src: url('../assets/fonts/Alegreya/static/Alegreya-Bold-Subset.woff2') format('woff2');
font-weight: bold;
font-style: normal;
font-feature-settings: "lnum" 1, "kern" 1, "liga" 1;
}
@font-face {
font-family: 'BodySerif';
src: url('../assets/fonts/Alegreya/static/Alegreya-BoldItalic-Subset.woff2') format('woff2');
font-weight: bold;
font-style: italic;
font-feature-settings: "lnum" 1, "kern" 1, "liga" 1;
}
@font-face {
font-family: 'BodySans';
src: url('../assets/fonts/Alegreya_Sans/AlegreyaSans-Regular-Subset.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-feature-settings: "lnum" 1, "kern" 1, "liga" 1;
}
@font-face {
font-family: 'BodySans';
src: url('../assets/fonts/Alegreya_Sans/AlegreyaSans-Italic-Subset.woff2') format('woff2');
font-weight: normal;
font-style: italic;
font-feature-settings: "lnum" 1, "kern" 1, "liga" 1;
}
@font-face {
font-family: 'BodySans';
src: url('../assets/fonts/Alegreya_Sans/AlegreyaSans-Bold-Subset.woff2') format('woff2');
font-weight: bold;
font-style: normal;
font-feature-settings: "lnum" 1, "kern" 1, "liga" 1;
}
@font-face {
font-family: 'BodySans';
src: url('../assets/fonts/Alegreya_Sans/AlegreyaSans-BoldItalic-Subset.woff2') format('woff2');
font-weight: bold;
font-style: italic;
font-feature-settings: "lnum" 1, "kern" 1, "liga" 1;
}
@font-face {
font-family: 'Mono';
src: url('../assets/fonts/JuliaMono/JuliaMono-Light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'Mono';
src: url('../assets/fonts/JuliaMono/JuliaMono-LightItalic-Subset.woff2') format('woff2');
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: 'Mono';
src: url('../assets/fonts/JuliaMono/JuliaMono-Regular-Subset.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Mono';
src: url('../assets/fonts/JuliaMono/JuliaMono-RegularItalic-Subset.woff2') format('woff2');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'Mono';
src: url('../assets/fonts/JuliaMono/JuliaMono-SemiBold-Subset.woff2') format('woff2');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Mono';
src: url('../assets/fonts/JuliaMono/JuliaMono-SemiBoldItalic-Subset.woff2') format('woff2');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: "Quote";
src: url('../assets/fonts/Alegreya_Sans/AlegreyaSans-Italic.ttf') format('woff2');
}
@font-face {
font-family: 'Math';
src: url('../assets/fonts/STIXTwo/STIXTwoMath-Regular.woff2') format('woff2');
}
:root {
--base-font-size: 16pt;
}
html {
font-size: var(--base-font-size);
}
/* Setting the line height here apparently stops "normal" from varying
across the course of <body>. */
body {
font-size: var(--base-font-size);
line-height: var(--read-spacing);
font-display: swap;
}
.font-hidpi body {
font-family: "BodySerif", serif;
}
.font-lodpi body {
font-family: "BodySans", sans-serif;
}
h1 {
font-family: "Heading";
line-height: normal;
font-optical-sizing: auto;
}
h2,
h3,
h4,
h5,
h6 {
font-family: "Subheading";
line-height: var(--ui-spacing);
font-optical-sizing: auto;
font-style: normal;
}
:root {
--head-mult: 0.88; /* This is pairwise fixed, Literata + Alegreya. */
}
h1 {
margin-block: 0.67em;
font-size: calc(2.3 * var(--base-font-size) * var(--head-mult));
}
h2 {
font-size: calc(1.8 * var(--base-font-size) * var(--head-mult));
}
h3 {
font-size: calc(1.6 * var(--base-font-size) * var(--head-mult));
}
h4 {
font-size: calc(1.4 * var(--base-font-size) * var(--head-mult));
}
h5 {
font-size: calc(1.2 * var(--base-font-size) * var(--head-mult));
}
h6 {
font-size: calc(1.0 * var(--base-font-size * var(--head-mult)))
}
code, pre code {
font-family: "Mono";
font-size: calc(0.84 * var(--base-font-size));
}
/* for STIX 2 */
math {
font-family: "Math";
font-size: calc(0.96 * var(--base-font-size));
}

75
css/inverted_colors.css Normal file
View file

@ -0,0 +1,75 @@
:root {
--lightest-color: rgb(255, 250, 245);
--lighter-color: rgb(205, 200, 195);
--light-color: rgb(170, 165, 160);
--wide-gray-invert: rgb(10, 5, 5);
--narrow-gray-invert: rgb(15, 8, 8);
}
.invert .est-color {
color: var(--lightest-color) !important
}
.invert .er-color {
color: var(--lighter-color) !important
}
.invert .null-color {
color: var(--light-color) !important
}
.invert {
background-color: black;
color: var(--lighter-color);
}
.invert code:not(pre code) {
background-color: var(--wide-gray-invert);
color: white;
}
.invert pre {
color: white;
background-color: var(--wide-gray-invert);
border-color: var(--wide-gray-invert);
}
.invert body {
background-color: black;
color: var(--lighter-color);
}
.invert h1 {
color: var(--lightest-color);
}
.invert h2,
.invert h3,
.invert h4,
.invert h5,
.invert h6 {
color: var(--lighter-color);
}
.invert a:link,
.invert a:visited {
color: var(--lightest-color)
}
.invert table tr th,
.invert table tr td {
border-right: 2px solid var(--narrow-gray-invert);
border-bottom: 2px solid var(--narrow-gray-invert);
}
.invert table tr th:first-child,
.invert table tr td:first-child {
border-left: 2px solid var(--narrow-gray-invert);
}
/* top row a.k.a. table header */
.invert table tr th {
border-top: 2px solid var(--narrow-gray-invert);
}
.invert hr {
background-color: var(--narrow-gray-invert);
}

12
css/layout.css Normal file
View file

@ -0,0 +1,12 @@
.container {
max-width: 900px;
margin-left: auto;
margin-right: auto;
padding-left: 0.3rem;
padding-right: 0.3rem;
overflow: auto;
}
body {
margin: 0;
}

221
css/looks.css Normal file
View file

@ -0,0 +1,221 @@
:root {
--darkest-color: rgb(10, 5, 0);
--darker-color: rgb(60, 55, 50);
--dark-color: rgb(95, 85, 80);
--ui-spacing: 1.25;
--read-spacing: 1.5;
--wide-gray: rgb(245, 240, 240);
--narrow-gray: rgb(240, 233, 233);
}
.est-color {
color: var(--darkest-color) !important
}
.er-color {
color: var(--darker-color) !important
}
.null-color {
color: var(--dark-color) !important
}
code {
font-variant-ligatures: no-contextual;
}
code:not(pre code) {
background-color: var(--wide-gray);
padding-left: 3px;
padding-right: 3px;
padding-top: 1px;
padding-bottom: 1px;
color: black;
border-radius: 3px;
line-height: var(--ui-spacing);
/* This prevents inline code from wrapping, c.f. Typst's `box`. */
display: inline-block;
/* These are needed if we allow code to line break. */
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
}
pre {
color: black;
background-color: var(--wide-gray);
overflow-x: auto;
border-style: solid;
border-radius: 3px;
border-width: 2px;
border-color: var(--wide-gray);
padding-left: 0.35em;
padding-top: 0.1em;
padding-bottom: 0.2em;
line-height: normal;
}
body {
background-color: rgb(255, 255, 255);
color: var(--darker-color);
}
h1 {
color: var(--darkest-color);
margin-bottom: 0.05em;
margin-top: 1em;
}
h2,
h3,
h4,
h5,
h6 {
color: var(--darker-color);
margin-bottom: 0;
margin-top: 1em;
}
/* This must be above the hn+* rules */
blockquote > p {
margin-top: 0.7em;
margin-bottom: 0.7em;
}
h1+*,
h2+*,
h3+*,
h4+*,
h5+*,
h6+* {
margin-top: 0;
padding-top: calc(var(--base-font-size) * (2/3));
}
blockquote {
font-size: 1em;
line-height: var(--ui-spacing);
border-left: 4px solid var(--darkest-color);
padding-left: 0.45em;
margin: 0;
}
blockquote:has(p + p) {
text-indent: 1em;
}
a:link,
a:visited {
color: var(--darkest-color)
}
[role="doc-noteref"] {
text-decoration: none;
font-family: "BodySans";
}
[role="doc-noteref"] sup {
font-size: 0.7em;
font-style: normal;
}
[role="doc-noteref"] sup::before {
content: "(";
padding-left: 0.075em;
}
[role="doc-noteref"] sup::after {
content: ")";
}
[role="doc-backlink"] {
margin-left: 0.25em;
text-decoration: none;
}
img {
width: 100%;
height: auto;
}
table {
--padding-px-h: 0.35em;
--padding-px-v: 0;
border-collapse: separate;
border-spacing: 0;
overflow-x: auto;
}
table tr th,
table tr td {
border-right: 2px solid var(--narrow-gray);
border-bottom: 2px solid var(--narrow-gray);
padding-right: var(--padding-px-h);
padding-left: var(--padding-px-h);
padding-top: var(--padding-px-v);
padding-bottom: var(--padding-px-v);
}
table tr th:first-child,
table tr td:first-child {
border-left: 2px solid var(--narrow-gray);
}
/* top row a.k.a. table header */
table tr th {
border-top: 2px solid var(--narrow-gray);
text-align: left;
}
/* top-left border-radius */
table tr:first-child th:first-child {
border-top-left-radius: 3px;
}
/* top-right border-radius */
table tr:first-child th:last-child {
border-top-right-radius: 3px;
}
/* bottom-left border-radius */
table tr:last-child td:first-child {
border-bottom-left-radius: 3px;
}
/* bottom-right border-radius */
table tr:last-child td:last-child {
border-bottom-right-radius: 3px;
}
math[display="block"] {
position: relative;
left: 0;
right: auto;
text-align: center;
}
footer {
margin-top: 2rem;
}
/* This is the style for the traditional horizontal rule. */
hr:not(.thematic) {
height: 2px;
border: none;
background-color: var(--narrow-gray);
}
hr.thematic {
border: none; /* remove default line */
text-align: center;
margin-top: -0.55em;
margin-bottom: -0.55em;
}
hr.thematic::before {
font-family: "Mono";
text-align: center;
content: '◇◆◇';
letter-spacing: 0.4em;
color: var(--darker-color);
display: block;
transform: translate(0.2em, -0.05em);
}

Binary file not shown.

Binary file not shown.

93
fonts/Alegreya/OFL.txt Normal file
View file

@ -0,0 +1,93 @@
Copyright 2011 The Alegreya Project Authors (https://github.com/huertatipografica/Alegreya)&#13;
&#13;
This Font Software is licensed under the SIL Open Font License, Version 1.1.&#13;
This license is copied below, and is also available with a FAQ at:&#13;
https://openfontlicense.org&#13;
&#13;
&#13;
-----------------------------------------------------------&#13;
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007&#13;
-----------------------------------------------------------&#13;
&#13;
PREAMBLE&#13;
The goals of the Open Font License (OFL) are to stimulate worldwide&#13;
development of collaborative font projects, to support the font creation&#13;
efforts of academic and linguistic communities, and to provide a free and&#13;
open framework in which fonts may be shared and improved in partnership&#13;
with others.&#13;
&#13;
The OFL allows the licensed fonts to be used, studied, modified and&#13;
redistributed freely as long as they are not sold by themselves. The&#13;
fonts, including any derivative works, can be bundled, embedded, &#13;
redistributed and/or sold with any software provided that any reserved&#13;
names are not used by derivative works. The fonts and derivatives,&#13;
however, cannot be released under any other type of license. The&#13;
requirement for fonts to remain under this license does not apply&#13;
to any document created using the fonts or their derivatives.&#13;
&#13;
DEFINITIONS&#13;
&quot;Font Software&quot; refers to the set of files released by the Copyright&#13;
Holder(s) under this license and clearly marked as such. This may&#13;
include source files, build scripts and documentation.&#13;
&#13;
&quot;Reserved Font Name&quot; refers to any names specified as such after the&#13;
copyright statement(s).&#13;
&#13;
&quot;Original Version&quot; refers to the collection of Font Software components as&#13;
distributed by the Copyright Holder(s).&#13;
&#13;
&quot;Modified Version&quot; refers to any derivative made by adding to, deleting,&#13;
or substituting -- in part or in whole -- any of the components of the&#13;
Original Version, by changing formats or by porting the Font Software to a&#13;
new environment.&#13;
&#13;
&quot;Author&quot; refers to any designer, engineer, programmer, technical&#13;
writer or other person who contributed to the Font Software.&#13;
&#13;
PERMISSION &amp; CONDITIONS&#13;
Permission is hereby granted, free of charge, to any person obtaining&#13;
a copy of the Font Software, to use, study, copy, merge, embed, modify,&#13;
redistribute, and sell modified and unmodified copies of the Font&#13;
Software, subject to the following conditions:&#13;
&#13;
1) Neither the Font Software nor any of its individual components,&#13;
in Original or Modified Versions, may be sold by itself.&#13;
&#13;
2) Original or Modified Versions of the Font Software may be bundled,&#13;
redistributed and/or sold with any software, provided that each copy&#13;
contains the above copyright notice and this license. These can be&#13;
included either as stand-alone text files, human-readable headers or&#13;
in the appropriate machine-readable metadata fields within text or&#13;
binary files as long as those fields can be easily viewed by the user.&#13;
&#13;
3) No Modified Version of the Font Software may use the Reserved Font&#13;
Name(s) unless explicit written permission is granted by the corresponding&#13;
Copyright Holder. This restriction only applies to the primary font name as&#13;
presented to the users.&#13;
&#13;
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font&#13;
Software shall not be used to promote, endorse or advertise any&#13;
Modified Version, except to acknowledge the contribution(s) of the&#13;
Copyright Holder(s) and the Author(s) or with their explicit written&#13;
permission.&#13;
&#13;
5) The Font Software, modified or unmodified, in part or in whole,&#13;
must be distributed entirely under this license, and must not be&#13;
distributed under any other license. The requirement for fonts to&#13;
remain under this license does not apply to any document created&#13;
using the Font Software.&#13;
&#13;
TERMINATION&#13;
This license becomes null and void if any of the above conditions are&#13;
not met.&#13;
&#13;
DISCLAIMER&#13;
THE FONT SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND,&#13;
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF&#13;
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT&#13;
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE&#13;
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,&#13;
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL&#13;
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING&#13;
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM&#13;
OTHER DEALINGS IN THE FONT SOFTWARE.&#13;

75
fonts/Alegreya/README.txt Normal file
View file

@ -0,0 +1,75 @@
Alegreya Variable Font
======================
This download contains Alegreya as both variable fonts and static fonts.
Alegreya is a variable font with this axis:
wght
This means all the styles are contained in these files:
Alegreya-VariableFont_wght.ttf
Alegreya-Italic-VariableFont_wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that arent available as static fonts. Not all apps support variable fonts, and
in those cases you can use the static font files for Alegreya:
static/Alegreya-Regular.ttf
static/Alegreya-Medium.ttf
static/Alegreya-SemiBold.ttf
static/Alegreya-Bold.ttf
static/Alegreya-ExtraBold.ttf
static/Alegreya-Black.ttf
static/Alegreya-Italic.ttf
static/Alegreya-MediumItalic.ttf
static/Alegreya-SemiBoldItalic.ttf
static/Alegreya-BoldItalic.ttf
static/Alegreya-ExtraBoldItalic.ttf
static/Alegreya-BlackItalic.ttf
Get started
-----------
1. Install the font files you want to use
2. Use your app's font picker to view the font family and all the
available styles
Learn more about variable fonts
-------------------------------
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
https://variablefonts.typenetwork.com
https://medium.com/variable-fonts
In desktop apps
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
Online
https://developers.google.com/fonts/docs/getting_started
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
Installing fonts
MacOS: https://support.apple.com/en-us/HT201749
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
Android Apps
https://developers.google.com/fonts/docs/android
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
License
-------
Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
You can use them in your products & projects print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
license for all details.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,93 @@
Copyright 2013 The Alegreya Sans Project Authors (https://github.com/huertatipografica/Alegreya-Sans)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -0,0 +1 @@
b481aac1-ef8d-48d0-8d6b-b109c992addd

View file

@ -0,0 +1 @@
{"weight":350,"italic":false,"alternates":{"cv01":false,"cv02":false,"cv03":false,"cv04":false,"cv05":false,"cv06":true,"cv07":false,"cv08":true,"cv09":false,"cv10":false,"cv11":false},"features":{"ss01":false,"ss02":false,"ss03":false,"ss04":true,"ss05":true},"letterSpacing":0,"lineHeight":1,"fontName":"UnfancyDevN"}

View file

@ -0,0 +1,11 @@
A short guide for how to install and enable your shiny new version of Commit Mono.
This is taken from section 08 Install from https://commitmono.com/
#1 (Download the fonts)
#2 Unzip the fonts. You'll see 4 font files. These 4 fonts make up a 'Style Group':
* CommitMono-Regular: Base version with settings and weight of your choice.
* CommitMono-Italic: An italic version, same weight as regular.
* CommitMono-Bold: A bold version, weight 700.
* CommitMono-BoldItalic: A bold version, weight 700, that is also italic.
#3 Install all 4 fonts on your system:
* Windows: Right click the font in the folder and click "Instal

View file

@ -0,0 +1,37 @@
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the compon

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more