Reading List

The Selfish Gene
The Psychopath Test: A Journey Through the Madness Industry
Bad Science
The Feynman Lectures on Physics
The Theory of Everything: The Origin and Fate of the Universe


ifknot's favorite books »

Saturday, 24 October 2026

C SNOBOL do SNOBOL

Two 80-column punched cards forming a SNOBOL4 program that prints C SNOBOL DO SNOBOL

†

I've wanted to do this since 1988!

SNO is a small C99 library that brings the pattern matching of SNOBOL4 to C, as described in The SNOBOL4 Programming Language, 2nd edition, by R. E. Griswold, J. F. Poage and I. P. Polonsky (Prentice-Hall, 1971), better known as "The Green Book". The code is on GitHub [link to repo], and this post is the story of how it came to be the shape it is.

Where it started

I used SNOBOL at university and, quirks and all, parsing files with it was a breeze. However, and at the time, I loved C and wanted a C version of SNOBOL but the task was clearly a daunting one and so I carried on hand coding my parsing needs ad-hoc. (I still do love C btw warts and all!)

After decades of ad-hoc, but still SNOBOL inspired, C parsing using some ersatz and ultimately unsatisfying snobol-esque functions - the time has come to do the job "properly".

So the naive idea was: take the Green Book, work through it chapter by chapter, and turn each SNOBOL4 function into a C function. LEN becomes sno_len, ANY becomes sno_any, job done.

It was not job done. Almost every chapter turned up a place where SNOBOL and C disagree about something fundamental - what a string is, what failure is, what an error is, even what a column is. This post is about those clashes and how each one was resolved - the resolutions became the library.

Sunday, 19 April 2026

DOSBox_joker.png CGA mode 6 "hi res"

Getting Zed and Clangd to Be Useful for the Retro Programmer

My editor of choice is Zed. It is fast, modern and minimal. My compiler is Open Watcom (which is a modernised Watcom) ancient and idiosyncratic but correct for the IBM XT target I am writing for. The problem is that clangd, the language server that Zed uses for C and C++ code, has opinions about my code - strong opinions.

As I have argued elsewhere, modern language servers and retro compilers are not natural bedfellows. Clangd expects C17, not 8086 assembly with __fastcall calling conventions and inline __asm blocks. Open Watcom accepts all of that happily, because it was built for the retro target, but clangd was not. The two do not speak the same dialect, and the result, without configuration, is an editor full of red squiggly underlines under perfectly valid code.

Consider then, a typical function from my codebase:

void __fastcall cga_hi_res_plot(cga_coord_t x, cga_coord_t y, cga_colour_t colour) {
   __asm {
      .8086
       mov     ax, x
       
       etc...
  }
}

Clangd flags __fastcall as an unsupported calling convention. It flags __asm as unknown syntax. It has no idea what .8086 means. The code compiles without complaint under Watcom and the XT runs it beautifully, but my editor thinks I am writing nonsense.

The Solution a .clangd File

The most reduced form of a working configuration for this problem is a .clangd yaml file in the project root directory that Zed will honour:

CompileFlags:
Add: [-xc, -Wno-ignored-attributes, -fms-extensions]
Remove: [-std=c99]

The -xc flag forces clangd to treat the code as C rather than Objective-C++, which for reasons I do not fully understand is sometimes the default. The -Wno-ignored-attributes flag suppresses the warning about __fastcall being unsupported, which is a lie but a lie we can live with. The -fms-extensions flag embraces the Microsoft and Watcom dialect, which helps with the rest of the weirdness. Removing -std=c99 prevents clangd from insisting on a language standard that my code does not fully follow because neither does Watcom.

The Include That Was Not Unused

A second problem emerged after the squiggles were gone. I have a lookup table for CGA row offsets:

#include "cga_lookup_table_y.h"   // clangd says: unused and paints it with yellow squiggles

The header is used extensively, but it is used inside __asm blocks. Clangd cannot see past the veil of inline assembly, so it flags the include as dead code. This is a false positive, but it is a persistent one.

The solution is a pragma from the Include-What-You-Use (IWYU) tooling:

#include "cga_lookup_table_y.h"   // IWYU pragma: keep

This communicates to the analyser that the inclusion is intentional, even if the static analysis cannot observe its usage. The warning disappears, the header remains, and the code continues to work.

Conclusion

So, fellow retro programmers, you can have best of both worlds. With Zed and .clangd configuration you can transform a hostile language server into a useful assistant. The defining properties of such a configuration are that it accepts non-standard calling conventions with -Wno-ignored-attributes, accepts the Watcom dialect with -fms-extensions, preserves includes hidden behind assembly with // IWYU pragma: keep, and - most importantly - lets you keep code completion.