• Stars
    star
    138
  • Rank 264,508 (Top 6 %)
  • Language
    Rust
  • License
    MIT License
  • Created about 1 year ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Translations of a simple C program to Rust.

Translations of a simple C program to Rust

This repository contains several translations of Russ Cox's Thompson NFA C program to Rust. Cox's program features as one of a few supporting programs in his article "Regular Expression Matching Can Be Simple And Fast."

I was motivated to do this exercise by Andy Chu's (of Oil fame) questioning of whether Cox's program could be modeled using Rust's borrow checker. My belief is that the answer to that question is a firm "no." More than that, I knew it would be "no" before I started this exercise. So why do it? It's my belief that it would be interesting to see how to translate a simple C program whose use of pointers can't be modeled by the borrow checker. Indeed, I did three different translations of the original program:

  • A dumb translation where I tried to copy the original program as closely as possible, even if it meant using unsafe in Rust. This means there are raw pointers and the borrow checker does not help us (much). In this translation, I believe I've substantially preserved the character of the original program, including its elegant use of pointers to represent the NFA state graph.
  • A safe translation that follows from the dumb translation but with one extra restriction: no use of unsafe allowed. This program does not preserve the character of the original program as much as the dumb translation does, but it tries. The PtrList trick in the original program is lost for example, and now we use reference counted pointers to states. It's not so elegant.
  • An idiomatic translation that similarly tries to preserve the character of the original program, but in a way that "makes sense" for Rust. In this program, we use handles/indices to states instead of pointers to states. This bypasses the borrow checker but still uses no unsafe code. Many of the principle downsides of handles/indices do not apply to this program, because once the state graph is built, it is immutable until it is discarded. Moreover, unlike the original program and both the dumb and safe translations, this program has no memory leaks.
  • A rust-regex translation that preserves the behavior of the original program, but uses the regex crate. This is for "fun" comparison purposes only. And a way to sanity check my test suite.

Building and testing

You'll need Clang and Cargo to build the original program and its translations. After cloning this repository, you should just need to run the tests. The harness will build the programs and test them for you:

$ ./test all
=== original ===
//badsyntax ... FAILED
a|/a/badsyntax ... FAILED
a.b/a.b/badsyntax ... FAILED
=== dumb-translation(rust) ===
=== safe-translation(rust) ===
=== idiomatic-translation(rust) ===
=== regex crate ===

The failing tests for the original program are expected.

Target audience

The discussion below gets into the weeds pretty quickly. I'd suggest the following:

  • Reading at least the "implementation" sections in Cox's blog. This will give you the proper context for understanding the original program and what it's actually doing.
  • At least intermediate C and and Rust experience is assumed.
  • Some passing familiarity with the original program written by Cox.

Goal

The goal of this exercise was to explore what the Rust translations would look like. In particular, I think this may be helpful to folks looking to see how to translate pointer techniques used in C to Rust. Assuredly, this is not a general guide for doing so. However, I think it can serve nicely as a single example for a specific C program that might partially generalize to other C programs that use similar tricks. I say this with the experience of having used the same techniques in the idiomatic translation in many other Rust programs and libraries.

Observations

Interested parties will want to reivew the code themselves. Each translation has a comment at the top of the source file with some notes. With that said, I feel there are some interesting observations to discuss in a broader context.

Source lines of code

Lines of code is a not-so-good metric to measure complexity, but it can provide a very rough feeling:

$ tokei --files --type C,Rust --columns 90 \
    original/nfa.c \
    dumb-translation/nfa.rs \
    safe-translation/nfa.rs \
    idiomatic-translation/nfa.rs
==========================================================================================
 Language                       Files        Lines         Code     Comments       Blanks
==========================================================================================
 C                                  1          419          304           78           37
------------------------------------------------------------------------------------------
 original/nfa.c                                419          304           78           37
------------------------------------------------------------------------------------------
 Rust                               3         1372          923          376           73
------------------------------------------------------------------------------------------
 idiomatic-translation/nfa.rs                  488          301          165           22
 safe-translation/nfa.rs                       450          317          107           26
 dumb-translation/nfa.rs                       434          305          104           25

Overall, the original program and its translations are almost exactly the same length at around 300 source lines of code.

The parser remains invariant

The parser essentially looks the same across all translations and it matches up pretty well with the parser in the original C program. There are a few differences worth noting:

  • The translations don't use a global static buffer to store the postfix version of the pattern. I could have done this in the dumb version using unsafe, but given that Vec<u8> is in Rust's standard library and is very simple to use, I decided to just put the postfix pattern on the heap. This also has the advantage of making the parse function re-entrant.
  • The translations don't use NUL terminated strings. I could have done that, but I didn't see a good reason to. Instead I used a Vec<u8>. Technically a Vec<u8> is far more complicated than a char* since it's a dynamically growable vector on the heap, but its usage is very simple courtesy of the standard library.
  • I moved the state that tracks nested parenthetical expressions from the stack to the heap.

Overall I felt that these changes did not alter the character of the parser much if at all. I kept the same limits as the original parser even though they aren't quite as important now that both the pattern and the nesting state are on the heap.

Leaks

In addition to the original program, the dumb and safe translation both leak memory. The dumb translation leaks memory for the same reason that the original does: there is no attempt to free any of the memory allocated on the heap. It is clearly an intentional omission, likely in the name of keeping the program simple. For the use case of teaching someone about the Thompson NFA simulation via a short lived program, freeing any memory allocated is superfluous since the operating system will automatically handle it upon program termination.

In the case of the safe translation, it leaks memory because of cycles created between reference counted pointers. Rust's std::rc::Rc type in particular is documented to leak memory in the case of cycles, so this is expected behavior. Rust's Rc pointer does support creating Weak pointers that can break the cycle by not incrementing the reference count. However, I could see no simple way of adapting the use of weak reference counted pointers to this program in a way that prevents leaks.

The idiomatic translation does not have any memory leaks. Since this translation works by putting all NFA states into one single allocation that gets dropped automatically, there is no cyclic in-memory data structure. The handles/indices of course still can form a cycle, but this doesn't impact memory management.

I do think that the fact that this program is designed to leak in C in favor of simplicity does limit its usefulness somewhat as a comparison point. Namely, I think this calls into question Chu's characterization of the C program as an example of elegance. It might be elegantly simple for narrow pedagogical reasons, but it isn't reflective of robust code. In particular, the cyclic graph that is created in memory by the C program is non-trivial to free manually. Adding the necessary free logic to this program would bring it closer to something "real," but it would almost certainly detract from its simplistic elegance.

I also think that it is interesting to note that the idiomatic translation does not have leaks and yet is arguably as simple as the original. Of course, one could translate the idiomatic Rust program back into C by using handles/indices in C, but this would likely detract from its elegance when compared to Rust. For the handles/indices technique, Rust benefits significantly from having a Vec<T> in its standard library. It's likely that the elegant path to go in C is to compute the number of states one needs to allocate in advance from the postfix syntax. That would avoid needing to hand-write the code for a dynamically growable vector.

Undefined behavior

In the course of this exercise, I found 3 distinct occurrences of undefined behavior in the original program:

  • Passing an empty pattern results in the original trying to do an unchecked pop of an empty stack in post2nfa.
  • Passing a pattern that ends with an alternation symbol, e.g., a|. This pattern is considered valid and results in an unchecked pop of an empty stack in post2nfa.
  • Passing a pattern that contains a . can wreak all sorts of havoc, since . is treated as a literal in re2post but as a meta character (the concatenation operator) in post2nfa.

All of the translations to Rust, including the dumb translation, fix these bugs by rejecting the patterns that provoke the undefined behavior.

While the existence of undefined behavior in a pedagogical toy program is not necessarily significant, I do think these are legitimate bugs in the program. Namely, the program puts in some effort to reject invalid patterns. That is, it's doing some sanitization of the input, but it is not complete.

The above errors were found through unit testing (see the ./test harness program). In some cases, the C program still "behaved normally," but the Rust program tripped an assert. (The Rust translations move things like stacks to the heap and assert that the stack is not empty before popping it.)

Pointer tricks

I think the part of the original program I struggled with the most was its use of the Ptrlist union type. In short, it uses type punning to store a linked list of pointers that need to be patched to real states later in the NFA construction process. While I was able to re-create the technique used in the original in the dumb translation, the safe and idiomatic translations lack this trick.

It's worth lingering on this trick for a moment, because it's one of the two things in the original program that really inhibit the use of Rust's borrow checker and ownership system without additional abstractions. (The other being the cyclic graph of states.)

To explain this trick, let's look at the type definitions from the original C program that we need to understand the trick:

typedef struct State {
    int c;
    State *out;
    State *out1;
    int lastlist;
} State;

typedef struct Frag {
    State *start;
    Ptrlist *out;
} Frag;

typedef union Ptrlist {
    Ptrlist *next;
    State *s;
} Ptrlist;

In post2nfa, each character in the postfix expression of the pattern corresponds to an action on a stack of Frag values and the creation of a new NFA state. When a state is first created, its outgoing transitions (the out and out1 fields) aren't necessarily both known. For example, consider what happens when a + repetition operator is seen in post2nfa:

        case '+':    /* one or more */
            e = pop();
            s = state(Split, e.start, NULL);
            patch(e.out, s);
            push(frag(e.start, list1(&s->out1)));
            break;

This is popping a fragment (e has type Frag) off of a stack and then building a Split state. The Split state knows one of its outgoing transitions: out is set to e.start via the state constructor. But out1 is set to NULL. In effect, Split represents a fork in the execution of the Thompson VM: it can either repeat the previous state (that's s->out which is set to e.start) or it can go on to the next state (that's s->out1, but unknown as of yet).

So when does the next state get patched in? That happens at some indeterminate point later when patch is called. Note that the patch call in the fragment above is not patching s->out1, but rather, the outgoing transitions on the state (e.out) in the fragment (e) popped off the stack. Those outgoing transitions are set to the the state we just created.

The trick is that the list of outgoing transitions to patch is maintained as a linked list of pointers within the allocation of each State itself. The linked list is created via list1 in the above snippet. list1 is as follows:

Ptrlist* list1(State **outp) {
    Ptrlist *l;

    l = (Ptrlist*)outp;
    l->next = NULL;
    return l;
}

A list can also be created by appending two existing lists together:

Ptrlist* append(Ptrlist *l1, Ptrlist *l2) {
    Ptrlist *oldl1;

    oldl1 = l1;
    while(l1->next)
        l1 = l1->next;
    l1->next = l2;
    return oldl1;
}

The trick here is that the memory locations used by the linked list are the unset out or out1 fields in the State struct itself. This works because each State is given its own stable allocation that will never move.

Finally, patch traverses this list and updates the pointers to the next state once the next state is known:

void patch(Ptrlist *l, State *s) {
    Ptrlist *next;

    for(; l; l=next){
        next = l->next;
        l->s = s;
    }
}

Notice here that the Ptrlist given is treated as a linked list by following next and then immediately treated as a State* and updated to the next state given. This, crucially, works because the post2nfa algorithm ensures that the Ptrlist values are used exactly once. If they were followed again after patch is called you would wind up with undefined behavior.

The nice thing about this trick is that it reuses the allocations of State values so that tracking the unpatched transitions doesn't require any extra memory. Both of the safe and idiomatic translations opt to use extra heap memory here instead. The tricky part about representing this in safe Rust is that it uses interior pointers into an existing allocation. These are allowed in safe Rust, but only as borrows. While there are some tricks to make self-referencing structs work safely in Rust, the trick used this program stores the interior pointers in a function's call stack, so it likely seems even harder to do here. Perhaps a slab or bump allocator abstraction could help here, but I'm not sure.

As a comparison point, let's look at how the idiomatic translation handles this. We'll look at the types, just as we did with the C program:

enum State {
    Literal { byte: u8, out: StateID },
    Split { out1: StateID, out2: StateID },
    Match,
}

struct Frag {
    start: StateID,
    out: Vec<ToPatch>,
}

#[derive(Clone, Copy)]
enum ToPatch {
    // patch 'out' or 'out1' in given state
    Out1(StateID),
    // patch 'out2' in given state
    Out2(StateID),
}

Instead of using a linked list, we use a dynamically growable vector to represent all of the outgoing transitions that need to be patched. A source of inelegance here is that we need to keep track of which outgoing transition to patch: either the first or second. Namely, the StateID handles we use here (instead of State pointers in the C program) actually point to the state containing the outgoing transition instead of the location in memory that needs to be updated. That means we need to carry with it an instruction of which transition to update. We can see more concretely what this means by looking at our patch function:

impl NFA {
    // Perform all patch instructions such that all
    // handles point to the state given.
    fn patch(&mut self, l: &[ToPatch], s: StateID) {
        for &p in l.iter() {
            match p {
                ToPatch::Out1(sid) => match self.states[sid as usize] {
                    State::Literal { ref mut out, .. } => {
                        *out = s;
                    }
                    State::Split { ref mut out1, .. } => {
                        *out1 = s;
                    }
                    _ => unreachable!("invalid out1 patch"),
                },
                ToPatch::Out2(sid) => match self.states[sid as usize] {
                    State::Split { ref mut out2, .. } => {
                        *out2 = s;
                    }
                    _ => unreachable!("invalid out2 patch"),
                },
            }
        }
    }
}

Here, we iterate over our patch instructions, figure out what kind of patching we need to do and then inspect the State itself to get access to the outgoing transitions we need to patch. If, for example, we have a patch instruction for the second outgoing transition but a non-split state, then we panic indicating a bug. In the C program, this sort of error is still possible, but it would likely manifest as a logical bug in matching somehow.

I'm torn on which approach is "better" here. The C program definitely wins the elegance trophy for reusing the unset parts of a state as a linked list to track future work that needs to be done. But, this was definitely the part of the program that took me the longest to understand.

Performance

As Cox says in his blog, the original C program "was not written with performance in mind." I stuck to that as well and didn't bother thinking too deeply about performance. Instead, the main thing to optimize for here (in my opinion) is a working implementation that is simple enough to quickly demonstrate the Thompson NFA simulation concept.

With that said, I was still curious how fast they were. So I devised a "torture" test. The test is a little hokey because we have to work within the limits imposed by the program (the parser has a fixed limit on the size of the pattern) and the operating system (since the haystack is passed as an argument to the process). The test consists of the following pattern

(abc)*d|(abc)*d|...|(abc)*d|(abc)*Z

matched against the following haystack:

abcabcabc...abcabcZ

where ... represents repetition. The idea here is that all of the alternations are "active" throughout the search but only the last one matches. Each alternation corresponds to a "split" NFA state which all stack up on each other. This results in an enormous amount of time spent chasing epsilon transitions for each byte in the haystack.

To run the torture test, first make sure all of the programs are built:

$ SKIPTEST=1 ./test all
=== original ===
=== dumb-translation(rust) ===
=== safe-translation(rust) ===
=== idiomatic-translation(rust) ===
=== regex crate ===

And then either run the torture test for each program individually, e.g.,

$ time ./torture-test ./original/nfa
PASSED

real    0.252
user    0.246
sys     0.053
maxmem  26 MB
faults  0

$ time ./torture-test ./idiomatic-translation/target/release/nfa
PASSED

real    0.238
user    0.230
sys     0.056
maxmem  26 MB
faults  0

Or use a tool like hyperfine to bake them off against one another:

$ hyperfine -w5 \
    "./torture-test ./original/nfa" \
    "./torture-test ./dumb-translation/target/release/nfa" \
    "./torture-test ./safe-translation/target/release/nfa" \
    "./torture-test ./idiomatic-translation/target/release/nfa" \
    "./torture-test ./rust-regex/target/release/nfa"
Benchmark 1: ./torture-test ./original/nfa
  Time (mean ± σ):     179.6 ms ±   9.8 ms    [User: 178.7 ms, System: 1.4 ms]
  Range (min … max):   169.1 ms … 204.9 ms    16 runs

Benchmark 2: ./torture-test ./dumb-translation/target/release/nfa
  Time (mean ± σ):     153.5 ms ±   3.6 ms    [User: 151.8 ms, System: 2.1 ms]
  Range (min … max):   149.4 ms … 162.5 ms    19 runs

Benchmark 3: ./torture-test ./safe-translation/target/release/nfa
  Time (mean ± σ):     510.7 ms ±  11.3 ms    [User: 508.8 ms, System: 2.2 ms]
  Range (min … max):   486.1 ms … 528.9 ms    10 runs

Benchmark 4: ./torture-test ./idiomatic-translation/target/release/nfa
  Time (mean ± σ):     166.0 ms ±   4.1 ms    [User: 165.0 ms, System: 1.5 ms]
  Range (min … max):   163.0 ms … 175.4 ms    17 runs

Benchmark 5: ./torture-test ./rust-regex/target/release/nfa
  Time (mean ± σ):      12.5 ms ±   0.2 ms    [User: 5.8 ms, System: 7.2 ms]
  Range (min … max):    11.8 ms …  13.1 ms    212 runs

Summary
  './torture-test ./rust-regex/target/release/nfa' ran
   12.26 ± 0.37 times faster than './torture-test ./dumb-translation/target/release/nfa'
   13.26 ± 0.41 times faster than './torture-test ./idiomatic-translation/target/release/nfa'
   14.35 ± 0.83 times faster than './torture-test ./original/nfa'
   40.80 ± 1.20 times faster than './torture-test ./safe-translation/target/release/nfa'

In other words:

  • The original program along with the dumb and idiomatic translations have very similar performance characteristics.
  • The safe translation is a fair bit slower. Light profiling of this program suggests the difference comes from reference counting. (Via Rc::drop.)
  • The rust-regex program is quite a bit faster, but primarily because it uses a different technique for this particular regex (a lazy DFA).

More Repositories

1

ripgrep

ripgrep recursively searches directories for a regex pattern while respecting your gitignore
Rust
48,517
star
2

xsv

A fast CSV command line toolkit written in Rust.
Rust
10,377
star
3

toml

TOML parser for Golang with reflection.
Go
4,464
star
4

quickcheck

Automated property based testing for Rust (with shrinking).
Rust
2,408
star
5

erd

Translates a plain text description of a relational database schema to a graphical entity-relationship diagram.
Haskell
1,805
star
6

fst

Represent large sets and maps compactly with finite state transducers.
Rust
1,771
star
7

jiff

A date-time library for Rust that encourages you to jump into the pit of success.
Rust
1,736
star
8

rust-csv

A CSV parser for Rust, with Serde support.
Rust
1,710
star
9

walkdir

Rust library for walking directories recursively.
Rust
1,283
star
10

nflgame

An API to retrieve and read NFL Game Center JSON data. It can work with real-time data, which can be used for fantasy football.
Python
1,257
star
11

nfldb

A library to manage and update NFL data in a relational database.
Python
1,079
star
12

aho-corasick

A fast implementation of Aho-Corasick in Rust.
Rust
1,028
star
13

byteorder

Rust library for reading/writing numbers in big-endian and little-endian.
Rust
980
star
14

wingo

A fully-featured window manager written in Go.
Go
958
star
15

memchr

Optimized string search routines for Rust.
Rust
888
star
16

bstr

A string type for Rust that is not required to be valid UTF-8.
Rust
795
star
17

advent-of-code

Rust solutions to AoC 2018
Rust
479
star
18

xgb

The X Go Binding is a low-level API to communicate with the X server. It is modeled on XCB and supports many X extensions.
Go
472
star
19

termcolor

Cross platform terminal colors for Rust.
Rust
462
star
20

rust-snappy

Snappy compression implemented in Rust (including the Snappy frame format).
Rust
449
star
21

go-sumtype

A simple utility for running exhaustiveness checks on Go "sum types."
Go
421
star
22

chan

Multi-producer, multi-consumer concurrent channel for Rust.
Rust
392
star
23

regex-automata

A low level regular expression library that uses deterministic finite automata.
Rust
352
star
24

cargo-benchcmp

A small utility to compare Rust micro-benchmarks.
Rust
342
star
25

suffix

Fast suffix arrays for Rust (with Unicode support).
Rust
262
star
26

rure-go

Go bindings to Rust's regex engine.
Go
250
star
27

tabwriter

Elastic tabstops for Rust.
Rust
247
star
28

rebar

A biased barometer for gauging the relative speed of some regex engines on a curated set of tasks.
Python
227
star
29

imdb-rename

A command line tool to rename media files based on titles from IMDb.
Rust
226
star
30

critcmp

A command line tool for comparing benchmarks run by Criterion.
Rust
216
star
31

ty

Easy parametric polymorphism at run time using completely unidiomatic Go.
Go
198
star
32

xgbutil

A utility library to make use of the X Go Binding easier. (Implements EWMH and ICCCM specs, key binding support, etc.)
Go
191
star
33

pytyle3

An updated (and much faster) version of pytyle that uses xpybutil and is compatible with Openbox Multihead.
Python
181
star
34

dotfiles

My configuration files and personal collection of scripts.
Vim Script
154
star
35

rust-cbor

CBOR (binary JSON) for Rust with automatic type based decoding and encoding.
Rust
129
star
36

chan-signal

Respond to OS signals with channels.
Rust
125
star
37

goim

Goim is a robust command line utility to maintain and query the Internet Movie Database (IMDb).
Go
117
star
38

same-file

Cross platform Rust library for checking whether two file paths are the same file.
Rust
101
star
39

clibs

A smattering of miscellaneous C libraries. Includes sane argument parsing, a thread-safe multi-producer/multi-consumer queue, and implementation of common data structures (hashmaps, vectors and linked lists).
C
98
star
40

ucd-generate

A command line tool to generate Unicode tables as source code.
Rust
95
star
41

nflvid

An experimental library to map play meta data to footage of that play.
Python
91
star
42

rust-stats

Basic statistical functions on streams for Rust.
Rust
87
star
43

migration

Package migration for Golang automatically handles versioning of a database schema by applying a series of migrations supplied by the client.
Go
81
star
44

winapi-util

Safe wrappers for various Windows specific APIs.
Rust
64
star
45

xpybutil

An incomplete xcb-util port plus some extras
Python
62
star
46

graphics-go

Automatically exported from code.google.com/p/graphics-go
Go
60
star
47

rust-pcre2

High level Rust bindings to PCRE2.
C
56
star
48

blog

My blog.
Rust
52
star
49

rust-sorts

Implementations of common sorting algorithms in Rust with comprehensive tests and benchmarks.
Rust
51
star
50

openbox-multihead

Openbox with patches for enhanced multihead support.
C
46
star
51

nakala

A low level embedded information retrieval system.
Rust
45
star
52

nflfan

View your fantasy teams with nfldb using a web interface.
JavaScript
43
star
53

globset

A globbing library for Rust.
Rust
42
star
54

utf8-ranges

Convert contiguous ranges of Unicode codepoints to UTF-8 byte ranges.
Rust
42
star
55

rtmpdump-ksv

rtmpdump with ksv's patch. Intended to track upstream git://git.ffmpeg.org/rtmpdump as well.
C
40
star
56

regexp

A regular expression library implemented in Rust.
Rust
37
star
57

xdg

A Go package for reading config and data files according to the XDG Base Directory specification.
Go
35
star
58

locker

A simple Golang package for conveniently using named read/write locks. Useful for synchronizing access to session based storage in web applications.
Go
34
star
59

nflcmd

A collection of command line utilities for viewing NFL statistics and rankings with nfldb.
Python
32
star
60

notes

A collection of small notes that aren't appropriate for my blog.
31
star
61

mempool

A fast thread safe memory pool for reusing allocations.
Rust
29
star
62

gribble

A command oriented language whose environment is defined through Go struct types by reflection.
Go
28
star
63

vcr

A simple wrapper tool around ffmpeg to capture video from a VCR.
Rust
27
star
64

encoding_rs_io

Streaming I/O adapters for the encoding_rs crate.
Rust
25
star
65

rust-cmail

A simple command line utility for periodically sending email containing the output of long-running commands.
Rust
21
star
66

cluster

A simple API for managing a network cluster with smart peer discovery.
Go
19
star
67

pager-multihead

A pager that supports per-monitor desktops (compatible with Openbox Multihead and Wingo)
Python
15
star
68

cablastp

Performs BLAST on compressed proteomic data.
Go
15
star
69

rust-error-handling-case-study

Code for the case study in my blog post: http://blog.burntsushi.net/rust-error-handling
Rust
15
star
70

rg-cratesio-typosquat

The source code of the 'rg' crate. It is an intentional typo-squat that redirects folks to 'ripgrep'.
Rust
15
star
71

imgv

An image viewer for Linux written in Go.
Go
14
star
72

cmd

A convenience library for executing commands in Go, including executing commands in parallel with a pool.
Go
14
star
73

fanfoot

View your fantasy football leagues and get text alerts when one of your players scores.
Python
12
star
74

cmail

cmail runs a command and sends the output to your email address at certain intervals.
Go
12
star
75

gohead

An xrandr wrapper script to manage multi-monitor configurations. With hooks.
Go
12
star
76

burntsushi-blog

A small Go application for my old blog.
CSS
12
star
77

intern

A simple package for interning strings, with a focus on efficiently representing dense pairwise data.
Go
11
star
78

crev-proofs

My crev reviews.
10
star
79

pytyle1

A lightweight X11 tool for simulating tiling in a stacking window manager.
Python
9
star
80

cif

A golang package for reading and writing data in the Crystallographic Information File (CIF) format. It mostly conforms to the CIF 1.1 specification.
Go
9
star
81

rucd

WIP
Rust
8
star
82

qcsv

An API to read and analyze CSV files by inferring types for each column of data.
Python
8
star
83

pyndow

A window manager written in Python
Python
8
star
84

csql

Package csql provides convenience functions for use with the types and functions defined in the standard library `database/sql` package.
Go
6
star
85

freetype-go

A fork of freetype-go with bounding box calculations.
Go
6
star
86

sqlsess

Simple database backed session management. Integrates with Gorilla's sessions package.
Go
6
star
87

go-wayland-simple-shm

C
5
star
88

sqlauth

A simple Golang package that provides database backed user authentication with bcrypt.
Vim Script
4
star
89

lcmweb

A Go web application for coding documents with the Linguistic Category Model.
JavaScript
4
star
90

bcbgo

Computational biology tools for the BCB group at Tufts University.
Go
4
star
91

fex

A framework for specifying and executing experiments.
Haskell
3
star
92

present

My presentations.
HTML
3
star
93

memchr-2.6-mov-regression

Rust
3
star
94

genecentric

A tool to generate between-pathway modules and perform GO enrichment on them.
Python
3
star
95

rust-docs

A silly repo for managing my Rust crate documentation.
Python
3
star
96

pcre2-mirror

A git mirror for PCRE2's SVN repository at svn://vcs.exim.org/pcre2/code
2
star
97

xpyb

A clone of xorg-xpyb.
C
2
star
98

burntsushi-homepage

A small PHP web application for my old homepage.
PHP
2
star
99

window-marker

Use vim-like marks on windows.
Python
2
star
100

sudoku

An attempt at a sudoku solver in Haskell.
Haskell
1
star