Compare commits

...

13 commits

20 changed files with 836 additions and 217 deletions

View file

@ -1,7 +1,7 @@
{
"title": "Stranger Systems",
"placeholder-id": 0,
"about-id": 2,
"base-url": "https://www.stranger.systems",
"placeholder-id": 0,
"tagline": "Making software better by making it weird",
"about-id": 2
"title": "Stranger Systems"
}

View file

@ -1,12 +1,12 @@
{
"edited-at": [
],
"hidden": true,
"placeholder": true,
"posted-at": "2025-02-05T04:54:41.218425-05:00",
"slugs": [
],
"source": "/dev/null",
"edited-at": [
],
"tags": [
],
"posted-at": "2025-02-05T04:54:41.218425-05:00",
"hidden": true,
"placeholder": true
]
}

View file

@ -1,12 +1,14 @@
{
"tags": [
],
"slugs": [
],
"edited-at": [
],
"posted-at": "2025-02-05T06:00:49.553777-05:00",
"hidden": false,
"markdown": true,
"posted-at": "2025-02-05T06:00:49.553777-05:00",
"slugs": [
],
"source": "/home/nathan/Projects/Blog/projects/Markdown/MyNewBlog.md",
"markdown": true
"tags": [
"meta",
"raku"
]
}

View file

@ -1,11 +1,11 @@
{
"posted-at": "2025-02-05T06:01:16.693698-05:00",
"markdown": true,
"edited-at": [
],
"hidden": true,
"markdown": true,
"posted-at": "2025-02-05T06:01:16.693698-05:00",
"slugs": [
],
"hidden": true,
"source": "/home/nathan/Projects/Blog/projects/Markdown/About.md",
"tags": [
]

View file

@ -1,13 +1,13 @@
{
"hidden": false,
"posted-at": "2021-11-29T00:00:00Z",
"tags": [
"cryptography"
],
"source": "/home/nathan/Projects/Blog/projects/Markdown/CryptoSuite.md",
"edited-at": [
],
"hidden": false,
"markdown": true,
"posted-at": "2021-11-29T00:00:00Z",
"slugs": [
],
"markdown": true
"source": "/home/nathan/Projects/Blog/projects/Markdown/CryptoSuite.md",
"tags": [
"cryptography"
]
}

14
db/posts/4.json Normal file
View file

@ -0,0 +1,14 @@
{
"edited-at": [
],
"hidden": false,
"markdown": true,
"posted-at": "2025-02-07T06:45:08.536663-05:00",
"slugs": [
],
"source": "/home/nathan/Projects/Blog/projects/Markdown/2025/01-Jan/AdventOfBugs.md",
"tags": [
"idris",
"advent-of-code"
]
}

View file

@ -41,6 +41,12 @@ sub post-to-item(BlogMeta:D $meta, Int:D $id, Post:D $post --> XML::Element) {
XML::Element.new(:name<link>, :attribs({:href($link), :rel<alternate>}));
$xml.append:
XML::Element.new(:name<summary>, :nodes([$desc])) if $desc;
if $post.tags {
for $post.tags -> $tag {
$xml.append:
XML::Element.new(:name<category>, :attribs({:term($tag)}));
}
}
$xml
}

View file

@ -1,181 +1,24 @@
use v6.e.PREVIEW;
use HTML::Functional;
use Render::Util;
use Render::Head;
use Render::Post;
use DB::BlogMeta;
use DB::Post;
unit class Config;
method generate-head($title, BlogMeta:D $meta, $description?) {
head [
meta :charset<utf-8>;
meta :name<viewport>, :content<width=device-width, initial-scale=1>;
meta :author :content<Nathan McCarty>;
do if $title ~~ Str:D {
title "$title {$meta.title}";
} else {
title $meta.title;
}
# Add description, if one exists
do if $description ~~ Str:D {
meta :description :content($description)
} else {
[]
}
# Preconnect to all our resource sources
link :rel<preconnect> :href<https://static.stranger.systems>;
link :rel<preconnect> :href<https://fonts.googleapis.com>;
link :rel<preconnect> :href<https://fonts.gstatic.com> :crossorigin;
link :rel<preconnect> :href<https://unpkg.com>;
# Load fonts, Iosevka for code, Open Sans for content, and boxicons for
# icons
link :rel<stylesheet>,
:href<https://static.stranger.systems/fonts/Iosevka/Iosevka.css>;
link :rel<stylesheet>,
:href<https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap>;
link :rel<stylesheet>,
:href<https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css>;
# Link our style sheets
link :rel<stylesheet>,
:href</resources/colors.css>;
link :rel<stylesheet>,
:href</resources/main.css>;
link :rel<stylesheet>,
:href</resources/code.css>;
];
}
method site-header(BlogMeta:D $meta) {
header :class<site-header>, [
div :class<site-logo>, [
# TODO: Use a real image here
$meta.title
];
div :class<site-tagline>, [
$meta.tagline
];
div :class<header-links>, [
a :href</index.html>, [
icon 'home';
' ';
span [
'Home';
];
];
a :href</archive.html>, [
icon 'archive';
' ';
span [
'Archive';
];
];
a :href</about.html>, [
icon 'info-circle';
' ';
span [
'About';
];
];
a :href</atom.xml>, [
icon 'rss';
' ';
span [
'Feed';
];
];
];
]
}
method post-date(Post:D $post) {
my $datetime = $post.posted-at;
my $timestamp = sprintf(
"%s %02d:%02d%s",
$datetime.yyyy-mm-dd,
($datetime.hour % 12) || 12,
$datetime.minute,
$datetime.hour < 12 ?? 'am' !! 'pm'
);
div :class<post-time>, :title("Posted At $timestamp"), [
icon 'time';
'&nbsp;';
$timestamp
]
}
method post-edit(Post:D $post) {
return [] unless $post.edited-at.elems;
my $datetime = $post.edited-at.max;
my $timestamp = sprintf(
"%s %02d:%02d%s",
$datetime.yyyy-mm-dd,
($datetime.hour % 12) || 12,
$datetime.minute,
$datetime.hour < 12 ?? 'am' !! 'pm'
);
div :class<post-edit>, :title("Laste Edited At $timestamp"), [
icon 'edit';
'&nbsp;';
$timestamp
]
}
sub mins-to-string($mins) {
if $mins < 60 {
$mins.Str ~ "m"
} else {
my $h = $mins div 60;
my $m = $mins mod 60;
$h.Str ~ "h" ~ $m.Str ~ "m"
}
}
method post-read-time(Post:D $post) {
my ($slow, $average, $fast) = $post.readtimes;
div :class<post-read-time>, :title<Estimated read time at 140/180/220 WPM>, [
icon 'timer';
'&nbsp;';
mins-to-string $slow;
'&nbsp;';
'/';
'&nbsp;';
mins-to-string $average;
'&nbsp;';
'/';
'&nbsp;';
mins-to-string $fast;
]
}
method post-info(Post:D $post) {
div :class<post-info>, [
self.post-date: $post;
self.post-edit: $post;
self.post-read-time: $post;
# TODO: Add tags once we have support for that
];
}
method post-header(Post:D $post) {
header :class<post-header>, [
div :class<post-title>, [
h1 $post.title;
];
self.post-info: $post;
]
}
# TODO: Support GFM admonitions
method generate-post(Post:D $post, BlogMeta:D $meta) {
my $content = $post.render-html;
my $head = self.generate-head($post.title, $meta, $post.description);
my $head = generate-head($meta, $post.title, $post.description);
my $body =
body [
self.site-header: $meta;
site-header $meta;
article :class<post>, [
self.post-header: $post;
post-header $post;
div :class<post-body>, [
$content;
]
@ -195,20 +38,14 @@ method generate-post(Post:D $post, BlogMeta:D $meta) {
method generate-blurb(Int:D $id, $db) {
my $post = $db.posts{$id};
my $desc = $post.description;
my @slugs = $post.all-slugs;
# Use the primary slug if there is one, the id if there isn't
my $link = do if @slugs.elems {
"/posts/by-slug/{@slugs[*-1]}.html"
} else {
"/posts/by-id/$id.html"
}
my $link = post-link $id, $post;
div :class<post-blurb>, [
div :class<post-blurb-title>, [
a :href($link), span [
h2 $post.title;
];
];
self.post-info: $post;
post-info $post;
if $desc ~~ Str:D {
div :class<post-blurb-description>, [
p $post.description;
@ -228,9 +65,9 @@ method generate-index($db) {
self.generate-blurb: $pair.key, $db
});
my $head = self.generate-head(Nil, $db.meta);
my $head = generate-head($db.meta);
my $body = body [
self.site-header: $db.meta;
site-header $db.meta;
div :class<post-blurbs>, [
h1 "Recent Posts"
], @most-recent;
@ -253,9 +90,9 @@ method generate-archive($db) {
self.generate-blurb: $pair.key, $db
});
my $head = self.generate-head(Nil, $db.meta);
my $head = generate-head($db.meta);
my $body = body [
self.site-header: $db.meta;
site-header $db.meta;
div :class<post-blurbs>, [
h1 "All Posts"
], @most-recent;
@ -270,6 +107,76 @@ method generate-archive($db) {
"<!doctype html>$html"
}
sub icon($icon) {
i(:class("bx bx-$icon"))
method generate-tag-blurb($db, $tag, $limit?) {
sub post-to-link($id, $post) {
my $desc = $post.description;
my $link = post-link $id, $post;
div :class<tag-blurb-post>, [
div :class<tag-blurb-post-title>, [
a :href($link), span [
h3 $post.title;
];
post-info $post;
if $desc ~~ Str:D {
div :class<tag-blurb-post-description>, [
p $post.description;
];
} else {
[]
}
];
]
}
my @posts = $db.sorted-posts.grep(-> $a { $tag (elem) $a.value.tags });
if $limit {
@posts.=head($limit);
}
if @posts {
div :class<tag-blurb>, [
span :class<tag-blurb-title>, [
a :href("/tags/$tag.html"), [
icon 'hash';
$tag;
];
];
div :class<tag-blurb-links>,
@posts.map(-> $a {post-to-link $a.key, $a.value});
]
} else {
[]
}
}
method generate-tags-page($db, @tags) {
my $head = generate-head($db.meta);
my $body = body [
site-header $db.meta;
div :class<tags>, [
h1 "Tags";
], @tags.map(-> $tag {self.generate-tag-blurb($db, $tag, 4)});
];
my $html =
html :lang<en>, [
$head,
$body
];
"<!doctype html>$html"
}
method generate-tag-page($db, $tag) {
my $head = generate-head($db.meta);
my $body = body [
site-header $db.meta;
self.generate-tag-blurb($db, $tag, 4);
];
my $html =
html :lang<en>, [
$head,
$body
];
"<!doctype html>$html"
}

View file

@ -62,10 +62,10 @@ class PostDB {
mkdir $posts-dir unless $posts-dir.e;
# Write out metadata
# TODO: Track changes and only write changed files
$dir.add('meta.json').spurt: $!meta.to-json;
$dir.add('meta.json').spurt: $!meta.to-json(:sorted-keys);
# Write out posts (ids are the filename)
for %!posts.kv -> $key, $value {
$posts-dir.add("$key.json").spurt: $value.to-json;
$posts-dir.add("$key.json").spurt: $value.to-json(:sorted-keys);
}
}
@ -105,6 +105,15 @@ class PostDB {
my $about-path = $out-dir.add('about.html');
$about-path.unlink if $about-path.l;
$by-id.add("{$!meta.about-id}.html").symlink: $about-path;
# Generate the tags pages
my @tags = %!posts.values.map(*.tags).flat.unique.sort;
$out-dir.add('tags.html').spurt: $config.generate-tags-page(self, @tags);
my $tags-dir = $out-dir.add('tags/');
mkdir $tags-dir unless $tags-dir.e;
for @tags -> $tag {
$tags-dir.add("$tag.html").spurt:
$config.generate-tag-page(self, $tag);
}
# Render the rss/atom feed
my $atom-path = $out-dir.add('atom.xml');
my $atom = posts-to-atom self;

View file

@ -1,9 +1,13 @@
use v6.e.PREVIEW;
use Pandoc;
use JSON::Class:auth<zef:vrurg>;
use Pygments;
use DB::Post;
use JSON::Class:auth<zef:vrurg>;
use File::Temp;
#| A plain markdown post
unit class MarkdownPost does Post is json(:pretty);
@ -20,7 +24,17 @@ method title(--> Str:D) {
# Simply provide our source file to pandoc
method render-html(--> Str:D) {
markdown-to-html $!source
# Test to see if this posts contains any fenced code blocks, if so,
# pygmentize it through a temporary file
my $contents = $!source.slurp;
if $contents ~~ /'```'/ {
my $output = highlight-code $contents;
my ($filename, $filehandle) = tempfile;
$filehandle.spurt: $output, :close;
markdown-to-html $filename.IO
} else {
markdown-to-html $!source
}
}
# Return our summary, if we have one, otherwise extract the first paragraph of

View file

@ -6,7 +6,7 @@ use JSON::Fast;
#| Run pandoc with the given arguments, dieing on failure
sub pandoc(*@args --> Str:D) {
# Call into pandoc
my $pandoc = run 'pandoc', @args, :out, :err;
my $pandoc = run 'pandoc', '--no-highlight', @args, :out, :err;
# Collect the output
my $output = $pandoc.out.slurp: :close;
@ -88,6 +88,7 @@ sub markdown-first-paragraph(IO::Path:D $file --> Str:D) is export {
$para ~= "\n";
}
when "Link" {
# TODO: Properly descend into links
$para ~= $component<c>[1][0]<c>;
}
default {

40
lib/Pygments.rakumod Normal file
View file

@ -0,0 +1,40 @@
#| Interaction with pygments
unit module Pygments;
my token fence { '```' }
my token info-string { \w+ }
# TODO: Be more precise about this so we can handle backticks in code blocks
my token code { <-[`]>+ }
my token code-block {
<&fence> <info-string> \h* \v
<code>
<&fence>
}
sub pygment(Str:D $code, Str:D $lang --> Str:D) {
my $pygments = run <pygmentize -f html -l>, $lang, :out, :in;
$pygments.in.spurt: $code, :close;
$pygments.out.slurp: :close;
}
sub highlight-code(Str:D $input --> Str:D) is export {
my $text = $input;
# TODO: Figure out a way to exclude idris code so we can process both in the
# same file
while $text ~~ &code-block {
my $match = $/;
# Extract the match and have pygments colorize the code
my $code = $match<code>.Str;
my $lang = $match<info-string>.Str;
my $out = pygment $code, $lang;
## Mangle the html to meet our needs
# Delete the existing div and construct a <code></code> inside the <pre>
$out ~~ s:g/'<' \/? 'div' <-[>]>* '>'//;
$out ~~ s:g/'<pre>'/<pre><code class="{$lang}-code">/;
$out ~~ s:g/'</pre>'/<\/code><\/pre>/;
# Rename the classes to match our needs
$out ~~ s:g/'span class="' (\w+) '"'/span class="hl-{$/[0].lc}"/;
$text = $match.replace-with: $out;
}
$text
}

68
lib/Render/Head.rakumod Normal file
View file

@ -0,0 +1,68 @@
use v6.e.PREVIEW;
unit module Render::Head;
use HTML::Functional;
use Render::Util;
use DB::BlogMeta;
sub generate-head(BlogMeta:D $meta, $title?, $description?) is export {
head [
meta :charset<utf-8>;
meta :name<viewport>, :content<width=device-width, initial-scale=1>;
meta :author :content<Nathan McCarty>;
do if $title ~~ Str:D {
title "$title {$meta.title}";
} else {
title $meta.title;
}
# Add description, if one exists
optl $description ~~ Str:D, -> {meta :description :content($description)};
# Preconnect to all our resource sources
link :rel<preconnect> :href<https://static.stranger.systems>;
link :rel<preconnect> :href<https://fonts.googleapis.com>;
link :rel<preconnect> :href<https://fonts.gstatic.com> :crossorigin;
link :rel<preconnect> :href<https://unpkg.com>;
# Load fonts, Iosevka for code, Open Sans for content, and boxicons for
# icons
link :rel<stylesheet>,
:href<https://static.stranger.systems/fonts/Iosevka/Iosevka.css>;
link :rel<stylesheet>,
:href<https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap>;
link :rel<stylesheet>,
:href<https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css>;
# Link our style sheets
link :rel<stylesheet>,
:href</resources/colors.css>;
link :rel<stylesheet>,
:href</resources/main.css>;
link :rel<stylesheet>,
:href</resources/code.css>;
]
}
sub site-header(BlogMeta:D $meta) is export {
sub header-link($name, $path, $icon) {
a :href("$path"), [
icon $icon;
'&nbsp;';
span $name;
]
}
header :class<site-header>, [
div :class<site-logo>, [
# TODO: Use a real image here
$meta.title
];
div :class<site-tagline>, [
$meta.tagline
];
div :class<header-links>, [
header-link 'Index', '/index.html', 'home';
header-link 'Archive', '/archive.html', 'archive';
header-link 'Tags', '/tags.html', 'purchase-tag-alt';
header-link 'About', '/about.html', 'info-circle';
header-link 'Feed', '/atom.xml', 'rss';
];
]
}

100
lib/Render/Post.rakumod Normal file
View file

@ -0,0 +1,100 @@
use v6.e.PREVIEW;
unit module Render::Post;
use Render::Util;
use DB::Post;
use HTML::Functional;
sub post-date(Post:D $post) is export {
my $datetime = $post.posted-at;
my $timestamp = sprintf(
"%s %02d:%02d%s",
$datetime.yyyy-mm-dd,
($datetime.hour % 12) || 12,
$datetime.minute,
$datetime.hour < 12 ?? 'am' !! 'pm'
);
div :class<post-time>, :title("Posted At $timestamp"), [
icon 'time';
'&nbsp;';
$timestamp
]
}
sub post-edit(Post:D $post) is export {
return [] unless $post.edited-at.elems;
my $datetime = $post.edited-at.max;
my $timestamp = sprintf(
"%s %02d:%02d%s",
$datetime.yyyy-mm-dd,
($datetime.hour % 12) || 12,
$datetime.minute,
$datetime.hour < 12 ?? 'am' !! 'pm'
);
div :class<post-edit>, :title("Last Edited At $timestamp"), [
icon 'edit';
'&nbsp;';
$timestamp
]
}
sub post-read-time(Post:D $post) is export {
my ($slow, $average, $fast) = $post.readtimes;
div :class<post-read-time>, :title<Estimated read time at 140/180/220 WPM>, [
icon 'timer';
'&nbsp;';
mins-to-string $slow;
'&nbsp;';
'/';
'&nbsp;';
mins-to-string $average;
'&nbsp;';
'/';
'&nbsp;';
mins-to-string $fast;
]
}
sub post-tag(Str:D $tag) is export {
span :class<post-tag>, [
a :href("/tags/$tag.html"), [
icon 'hash';
$tag;
]
]
}
sub post-tags(Post:D $post) is export {
my @tags = $post.tags.sort;
if @tags {
@tags.=map(*.&post-tag);
div :class<post-tags>, [
icon 'purchase-tag-alt';
'&nbsp;';
intersperse(', ', @tags);
]
} else {
[]
}
}
sub post-info(Post:D $post) is export {
div :class<post-info>, [
post-date $post;
post-edit $post;
post-read-time $post;
post-tags $post;
];
}
sub post-header(Post:D $post) is export {
header :class<post-header>, [
div :class<post-title>, [
h1 $post.title;
];
post-info $post;
]
}

54
lib/Render/Util.rakumod Normal file
View file

@ -0,0 +1,54 @@
use v6.e.PREVIEW;
unit module Render::Util;
use DB::Post;
use HTML::Functional;
sub opt($test, $item) is export {
if $test {
$item
} else {
[]
}
}
sub optl($test, &item) is export {
if $test {
item
} else {
[]
}
}
#| Link to the post by the primary slug, if there is one, linking to it by id
#| otherwise
sub post-link(Int:D $id, Post:D $post --> Str:D) is export {
my @slugs = $post.all-slugs;
if @slugs {
"/posts/by-slug/{@slugs[*-1]}.html"
} else {
"/posts/by-id/$id.html"
}
}
sub icon($icon) is export {
i(:class("bx bx-$icon"))
}
sub mins-to-string($mins) is export {
if $mins < 60 {
$mins.Str ~ "m"
} else {
my $h = $mins div 60;
my $m = $mins mod 60;
$h.Str ~ "h" ~ $m.Str ~ "m"
}
}
sub intersperse (\element, +list) is export {
gather for list {
FIRST .take, next;
take slip element, $_;
}
}

View file

@ -0,0 +1,98 @@
# Advent of bugs
Near the start of january, after bumping into some ecosystem issues and lack of
a personal support library while working on the 2024
[Advent of Code](https://adventofcode.com/) in Idris, I started working on a
project to solve all of the Advent of Code problems from all the years in a
single massive entirely literate Idris project and publish it as an mdbook. I'm
calling it
[Idris 2 by Extremely Contrived](https://static.stranger.systems/idris-by-contrived-example/)
example.
## The Good
This has been an amazingly fun project so far. AoC problems are nice and bite
sized, giving really good material to work with for incrementally introducing
more and more complex concepts. I have been following a sort of "weirdness
budget" for each day's solutions, letting myself build on the already
established weirdness from previous days. So far I'm 13 days in, and I've
already had excuses to introduce all sorts of fun concepts, effects, dependent
pattern matching, indexed type families, and refinement types, just to name a
few.
Functional programming languages are a lot of fun to model puzzle problems in to
start with, but the new design space afforded by dependent typing offers a new,
wonderfully fun challenge of figuring out just what to show off while I'm
solving the puzzle. There have already been a few problems that I've wound up
spending a few days on just tweaking and refining until I was pleased with the
balance between weirdness expenditure and showing off what I want to show off in
a reasonably approachable way.
A couple of personal favorites of mine so far have been the
[JSON parser](https://static.stranger.systems/idris-by-contrived-example/Parser/JSON.html),
and the
[`Digits` views](https://static.stranger.systems/idris-by-contrived-example/Util/Eff.html).
The JSON parser is written in a bespoke mini-library for doing effectively
parsing that I created just for this project, and refining the library to
optimize for readability of the parsers written in it was an absolute joy. The
digits views allowing pattern matching on normal integers as if they were lists
of digits was less fun to write[^1], they are amazingly fun to use.
## The Bad
I've had to write a lot of support code already. I can't really fault the
language, its a pretty new language in its pre-1.0 stage, in a pretty niche
area, dependent types are still quite scary to the majority of programmers, and
ecosystem improvement was a major goal of the project going in. I have already
had to write several "basic" data structures, and there's no sign the need to do
so will let up anytime soon[^6].
Honestly the most painful part has been the lack of support for Idris in
external tooling. Essentially every syntax highlighting library has _completely
ass_ support for Idris. I managed to eventually sneak proper semantic
highlighting into mdbook, it even plays nice with mdbook themes, but I had to
commit horrible, _horrible_ crimes to get this working[^2].
## The Ugly
This isn't exactly a complaint, after all, ecosystem improvement was a goal
after all, but I've already run into 2 or 3 compiler bugs, a bug in the `pack`
package manager, and a weird behavior in katla that's a _maybe_ bug.
I busted the part of the compiler that automatically inserts `force` and `delay`
calls to make interaction between lazy and strict code Just Work™:
[idris-lang/Idris2#3461](https://github.com/idris-lang/Idris2/issues/3461). I
had initially thought I had discovered just _one_ compiler bug, but it turns out
I also stumbled into an unrelated issue in the termination checker![^8]
I broke the [pack](https://github.com/stefan-hoeck/idris2-pack) package manager
by, apparently, being the first person to try and upload a library to `pack-db`
with library code in literate files, in my
[Structures](https://git.sr.ht/~thatonelutenist/Structures) package, checking in
a new data structure motivated by my advent project, completely breaking the
automated docs generation:
[stefan-hoeck/idris2-pack!319](https://github.com/stefan-hoeck/idris2-pack/pull/319).
This one was especially wild to me, with how popular literate programs are in
the community, I would have never expected to be the first person to try this.
## Looking Forward
Despite the challenges, this has been a lovely experience so far. I greatly look
forward to pressing through to completion, whatever it may bring, and have a
trophy case full of bugs identified/fixed and new libraries to bring to the
ecosystem.
[^1]: While perfectly understandable, it's not reasonable to expect the compiler
to be able to reason about primitive "machine" integers like this on its
own, needing to resort to so much `believe_me` on this one did make me a bit
sad
[^6]: Well well well, if it isn't the consequences of my actions
[^2]: Do not look at the `build-book` script if you value your sanity
[^8]: Before anyone gets upset here, despite the name, the termination checker
doesn't actually solve the halting problem. It only automatically accepts a
subset of the language for which termination is a 'trivial' property, which
includes most code I've written in the language so far, but not nearly all
of it.

View file

@ -0,0 +1,205 @@
# Rustposting
Some example code with some potential problem characters:
```rust
let newline_string = "hello \n world";
let thing = *newline_string;
```
Here is some example rust code:
```rust
fn main() {
// Statements here are executed when the compiled binary is called.
// Print text to the console
println!("Hello World!");
}
```
And a slightly less trivial example:
```rust
fn main() {
// Variables can be type annotated.
let logical: bool = true;
let a_float: f64 = 1.0; // Regular annotation
let an_integer = 5i32; // Suffix annotation
// Or a default will be used.
let default_float = 3.0; //
let default_integer = 7; //
// A type can also be inferred from context.
let mut inferred_type = 12; // Type i64 is inferred from another line.
inferred_type = 4294967296i64;
// A mutable variable's value can be changed.
let mut mutable = 12; // Mutable
mutable = 21;
// Error! The type of a variable can't be changed.
mutable = true;
// Variables can be overwritten with shadowing.
let mutable = true;
/* Compound types - Array and Tuple */
// Array signature consists of Type T and length as [T; length].
let my_array: [i32; 5] = [1, 2, 3, 4, 5];
// Tuple is a collection of values of different types
// and is constructed using parentheses ().
let my_tuple = (5u32, 1u8, true, -5.04f32);
}
```
Toss in some type definitions to
```rust
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
// A unit struct
struct Unit;
// A tuple struct
struct Pair(i32, f32);
enum WebEvent {
// An variant may either be
PageLoad,
PageUnload,
// like tuple structs,
KeyPress(char),
Paste(String),
// or c-like structures.
Click { x: i64, y: i64 },
}
struct Point {
x: f64,
y: f64,
}
// Implementation block, all associated functions & methods go in here
impl Point {
// This is an "associated function" because this function is associated with
// a particular type, that is, Point.
//
// Associated functions don't need to be called with an instance.
// These functions are generally used like constructors.
fn origin() -> Point {
Point { x: 0.0, y: 0.0 }
}
// Another associated function, taking two arguments:
fn new(x: f64, y: f64) -> Point {
Point { x: x, y: y }
}
}
```
Modules and imports
```rust
#![allow(unused_variables)]
use deeply::nested::function as other_function;
use std::fs::File;
fn function() {
println!("called function()");
}
mod deeply {
pub mod nested {
pub fn function() {
println!("called deeply::nested::function()");
}
}
}
struct Val {
val: f64,
}
struct GenVal<T> {
gen_val: T,
}
// impl of Val
impl Val {
fn value(&self) -> &f64 {
&self.val
}
}
impl<T> GenVal<T> {
fn value(&self) -> &T {
&self.gen_val
}
}
let a = Box::new(5i32);
macro_rules! say_hello {
() => {
// The macro will expand into the contents of this block.
println!("Hello!")
};
}
macro_rules! calculate {
(eval $e:expr) => {
{
let val: usize = $e; // Force types to be unsigned integers
println!("{} = {}", stringify!{$e}, val);
}
};
}
fn give_adult(drink: Option<&str>) {
// Specify a course of action for each case.
match drink {
Some("lemonade") => println!("Yuck! Too sugary."),
Some(inner) => println!("{}? How nice.", inner),
None => println!("No drink? Oh well."),
}
}
impl Person {
// Gets the area code of the phone number of the person's job, if it exists.
fn work_phone_area_code(&self) -> Option<u8> {
// It would take a lot more code - try writing it yourself and see which
// is easier.
self.job?.phone_number?.area_code
}
}
#[cfg(target_family = "unix")]
#[link(name = "m")]
extern {
// this is a foreign function
// that computes the square root of a single precision complex number
fn csqrtf(z: Complex) -> Complex;
fn ccosf(z: Complex) -> Complex;
}
fn main() {
let raw_p: *const u32 = &10;
unsafe {
assert!(*raw_p == 10);
}
}
```

View file

@ -11,7 +11,6 @@ code {
/* Styling for fenced code blocks */
pre > code {
display: block;
white-space: pre-wrap;
padding: 1rem;
border-radius: 0.55rem / 0.5rem;
word-wrap: normal;

View file

@ -53,23 +53,29 @@ a:visited {
.site-tagline {
color: var(--dim-0);
}
.post-body, .post-header, .post-blurbs {
.post-body, .post-header, .post-blurbs, .tags, .tags .tag-blurb-post {
background-color: var(--bg-0);
}
.post-blurb {
.post-blurb, .tags .tag-blurb {
background-color: var(--bg-1);
}
.post-title, .post-blurbs > h1 {
:not(.tags) .tag-blurb {
background-color: var(--bg-0);
}
:not(.tags) .tag-blurb-post {
background-color: var(--bg-1);
}
.post-title, .post-blurbs h1 {
color: var(--green);
}
.post-body > h2, .post-body > h3, .post-body > h4 {
.post-body h2, .post-body h3, .post-body h4 {
color: var(--fg-1);
}
blockquote {
background-color: var(--bg-1);
}
/* Colorization for code blocks */
/* Colorization for idris code blocks */
code {
color: var(--code-fg-0);
background-color: var(--code-bg-0);
@ -96,3 +102,35 @@ code {
.hl-data {
color: var(--code-red);
}
/* Colorization for pygments code blocks */
.hl-kd, .hl-k, .hl-kc, .hl-bp {
color: var(--code-green);
}
.hl-n, .hl-nn {
color: var(--code-violet);
}
.hl-s, .hl-se {
color: var(--code-cyan);
}
.hl-nf, .hl-fm {
color: var(--code-blue);
}
.hl-c1, .hl-cm {
color: var(--code-dim-0);
}
.hl-mf, .hl-mi {
color: var(--code-magenta);
}
.hl-kt, .hl-nb, .hl-nc {
color: var(--code-orange);
}
.hl-cp {
color: var(--code-red);
}
.hl-se {
font-style: italic;
}
.hl-fm, .hl-k, .hl-o, .hl-kp {
font-weight: bold;
}

View file

@ -2,6 +2,7 @@
font-family: "Open Sans", sans-serif, serif;
/* Variables */
--content-width: 60rem;
--blurb-width: 45%;
--header-width: 35rem;
--box-padding-vert: 1rem;
--box-padding-horz: 1rem;
@ -18,6 +19,13 @@
}
}
/* slightly larger than blurb-width to account for padding/margins */
@media screen and (max-width: 40rem) {
:root {
--blurb-width: 100%;
}
}
/* Main Body and Post Flexboxs */
body, .post {
display: flex;
@ -25,6 +33,9 @@ body, .post {
align-items: center;
gap: var(--box-gap);
}
.post {
width: 100%;
}
/* Style the site header */
.site-header {
@ -75,11 +86,11 @@ body, .post {
border-radius: var(--box-radius);
box-sizing: border-box;
}
.post-body > p {
.post-body p {
margin: auto var(--box-margin-horz);
align-self: stretch;
}
.post-title > h1 {
.post-title h1 {
margin-top: 0px;
margin-bottom: 0px;
}
@ -94,7 +105,7 @@ body, .post {
.post-read-time {
text-decoration: underline dotted;
}
.post-body > h2, .post-body > h3, .post-body > h4 {
.post-body h2, .post-body h3, .post-body h4 {
text-align: center;
}
.post-blurbs {
@ -124,3 +135,56 @@ blockquote {
border-radius: var(--box-radius);
padding: var(--box-padding-vert) var(--box-padding-horz);
}
/* Style the tags blurbs and page */
.tags {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--box-gap);
max-width: var(--content-width);
/* min-width: var(--blurb-width); */
padding: var(--box-padding-vert) var(--box-padding-horz);
border-radius: var(--box-radius);
box-sizing: border-box;
}
.tag-blurb {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: var(--box-gap);
border-radius: var(--box-radius);
box-sizing: border-box;
}
.tag-blurb-links {
display: block;
border-radius: var(--box-radius);
border-radius: var(--box-radius);
display: flex;
flex-flow: row wrap;
gap: var(--box-gap);
align-items: stretch;
box-sizing: border-box;
gap: var(--box-gap);
padding: var(--box-padding-vert) var(--box-padding-horz);
}
.tag-blurb-post {
font-size: 0.8rem;
width: var(--blurb-width);
display: flex;
flex-direction: column;
align-items: center;
gap: var(--box-gap);
box-sizing: border-box;
flex-shrink: 1;
flex-grow: 1;
border-radius: var(--box-radius);
padding: var(--box-padding-vert) var(--box-padding-horz);
}
.tag-blurb-title {
margin-top: var(--box-margin-vert);
margin-bottom: 0;
font-size: 1.5em;
font-weight: bold;
}