Recently I was reading Antirez's piece TCL the Misunderstood again, which is a nice defense of the utility and value of the TCL language.
TCL is one of those scripting languages which used to be used a hell of a lot in the past, for scripting routers, creating GUIs, and more. These days it quietly lives on, but doesn't get much love. That said it's a remarkably simple language to learn, and experiment with.
Using TCL always reminds me of FORTH, in the sense that the syntax consists of "words" with "arguments", and everything is a string (well, not really, but almost. Some things are lists too of course).
A simple overview of TCL would probably begin by saying that everything is a command, and that the syntax is very free. There are just a couple of clever rules which are applied consistently to give you a remarkably flexible environment.
To get started we'll set a string value to a variable:
set name "Steve Kemp"
=> "Steve Kemp"
Now you can output that variable:
puts "Hello, my name is $name"
=> "Hello, my name is Steve Kemp"
OK, it looks a little verbose due to the use of set, and puts is less pleasant than print or echo, but it works. It is readable.
Next up? Interpolation. We saw how $name expanded to "Steve Kemp" within the string. That's true more generally, so we can do this:
set print pu
set me ts
$print$me "Hello, World"
=> "Hello, World"
There "$print" and "$me" expanded to "pu" and "ts" respectively. Resulting in:
puts "Hello, World"
That expansion happened before the input was executed, and works as you'd expect. There's another form of expansion too, which involves the [ and ] characters. Anything within the square-brackets is replaced with the contents of evaluating that body. So we can do this:
puts "1 + 1 = [expr 1 + 1]"
=> "1 + 1 = 2"
Perhaps enough detail there, except to say that we can use { and } to enclose things that are NOT expanded, or executed, at parse time. This facility lets us evaluate those blocks later, so you can write a while-loop like so:
set cur 1
set max 10
while { expr $cur <= $max } {
puts "Loop $cur of $max"
incr cur
}
Anyway that's enough detail. Much like writing a FORTH interpreter the key to implementing something like this is to provide the bare minimum of primitives, then write the rest of the language in itself.
You can get a usable scripting language with only a small number of the primitives, and then evolve the rest yourself. Antirez also did this, he put together a small TCL interpreter in C named picol:
My code runs the original code from Antirez with only minor changes, and was a fair bit of fun to put together.
Because the syntax is so fluid there's no complicated parsing involved, and the core interpreter was written in only a few hours then improved step by step.
Of course to make a language more useful you need I/O, beyond just writing to the console - and being able to run the list-operations would make it much more useful to TCL users, but that said I had fun writing it, it seems to work, and once again I added fuzz-testers to the lexer and parser to satisfy myself it was at least somewhat robust.
Feedback welcome, but even in quiet isolation it's fun to look back at these "legacy" languages and recognize their simplicity lead to a lot of flexibility.
Recently I've been getting much more interested in the "retro" computers of my youth, partly because I've been writing crazy code in Z80 assembly-language, and partly because I've been preparing to introduce our child to his first computer:
An actual 1982 ZX Spectrum, cassette deck and all.
No internet
No hi-rez graphics
Easily available BASIC
And as a nice bonus the keyboard is wipe-clean!
I've got a few books, books I've hoarded for 30+ years, but I'd love to collect some more. So here's my request:
If you have any books covering either the Z80 processor, or the ZX Spectrum, please consider dropping me an email.
I'd be happy to pay €5-10 each for any book I don't yet own, and I'd also be more than happy to cover the cost of postage to Finland.
I'd be particularly pleased to see anything from Melbourne House, and while low-level is best, the coding-books from Usbourne (The Mystery Of Silver Mountain, etc, etc) wouldn't go amiss either.
I suspect most people who have collected and kept these wouldn't want to part with them, but just in case ..
Back in April 2021 I introduced a simple text-based adventure game, The Lighthouse of Doom, which I'd written in Z80 assembly language for CP/M systems.
To recap my game is a simple text-based adventure game, which you can complete in fifteen minutes, or less, with a bunch of Paw Patrol easter-eggs.
You enter simple commands such as "up", "down", "take rug", etc etc.
You receive text-based replies "You can't see a telephone to use here!".
My code is largely table-based, having structures that cover objects, locations, and similar state-things. Most of the code involves working with those objects, with only a few small platform-specific routines being necessary:
Clearing the screen.
Pausing for "a short while".
Reading a line of input from the user.
Sending a $-terminated string to the console.
etc.
My feeling was that I could replace the use of those CP/M functions with something custom, and I'd have done the 99% of the work. Of course the devil is always in the details.
Let's start. To begin with I'm lucky in that I'm using the pasmo assembler which is capable of outputting .TAP files, which can be loaded into ZX Spectrum emulators.
I'm not going to walk through all the code here, because that is available within the project repository, but here's a very brief getting-started guide which demonstrates writing some code on a Linux host, and generating a TAP file which can be loaded into your favourite emulator. As I needed similar routines I started working out how to read keyboard input, clear the screen, and output messages which is what the following sample will demonstrate..
First of all you'll need to install the dependencies, specifically the assembler and an emulator to run the thing:
# apt install pasmo spectemu-x11
Now we'll create a simple assembly-language file, to test things out - save the following as hello.z80:
; Code starts here
org 32768
; clear the screen
call cls
; output some text
ld de, instructions ; DE points to the text string
ld bc, instructions_end-instructions ; BC contains the length
call 8252
; wait for a key
ld hl,0x5c08 ; LASTK
ld a,255
ld (hl),a
wkey:
cp (hl) ; wait for the value to change
jr z, wkey
; get the key and save it
ld a,(HL)
push af
; clear the screen
call cls
; show a second message
ld de, you_pressed
ld bc, you_pressed_end-you_pressed
call 8252
;; Output the ASCII character in A
ld a,2
call 0x1601
pop af
call 0x0010
; loop forever. simple demo is simple
endless:
jr endless
cls:
ld a,2
call 0x1601 ; ROM_OPEN_CHANNEL
call 0x0DAF ; ROM_CLS
ret
instructions:
defb 'Please press a key to continue!'
instructions_end:
you_pressed:
defb 'You pressed:'
you_pressed_end:
end 32768
Now you can assemble that into a TAP file like so:
$ pasmo --tapbas hello.z80 hello.tap
The final step is to load it in the emulator:
$ xspect -quick-load -load-immed -tap hello.tap
The reason I specifically chose that emulator was because it allows easily loading of a TAP file, without waiting for the tape to play, and without the use of any menus. (If you can tell me how to make FUSE auto-start like that, I'd love to hear!)
I wrote a small number of "CP/M emulation functions" allowing me to clear the screen, pause, prompt for input, and output text, which will work via the primitives available within the standard ZX Spectrum ROM. Then I reworked the game a little to cope with the different screen resolution (though only minimally, some of the text still breaks lines in unfortunate spots):
The end result is reasonably playable, even if it isn't quite as nice as the CP/M version (largely because of the unfortunate word-wrapping, and smaller console-area). So now my repository contains a .TAP file which can be loaded into your emulator of choice, available from the releases list.
Here's a brief teaser of what you can expect:
Outstanding bugs? Well the line-input is a bit horrid, and unfortunately this was written for CP/M accessed over a terminal - so I'd assumed a "standard" 80x25 resolution, which means that line/word-wrapping is broken in places.
That said it didn't take me too long to make the port, and it was kinda fun.
In the past I used to run a number of virtual machines, or dedicated hosts. Currently I'm cut things down to only a single machine which I'm planning to remove.
Email
Email used to be hosted via dovecot, and then read with mutt-ng on the host itself. Later I moved to reading mail with my own console-based email client.
Eventually I succumbed, and now I pay for Google's Workspace product.
Git Repositories
I used to use gitbucket for hosting a bunch of (mostly private) git repositories. A bad shutdown/reboot of my host trashed the internal database so that was broken.
I replaced the use of gitbucket, which was very pretty, with gitolite to perform access-control, and avoid the need of a binary database.
I merged a bunch of repositories, removed the secret things from there where possible, and finally threw them on a second github account. GPG-encryption added where appropriate.
Static Hosts
Static websites I used to host upon my own machine are now hosted via netlify.
There aren't many of them, and they are rarely updated, I guess I care less.
Dynamic Hosts
That leaves only dynamic hosts. I used to have a couple of these, most notably the debian-administration.org, but that was archived and the final commercial thing I did was retired in January.
I now have only one dynamic site up and running, https://api.steve.fi/, this provides two dynamic endpoints:
One to return data about trams coming to the stop near my house.
One to return the current temperature.
Both of these are used by my tram-display device. Running these two services locally, in Docker, would probably be fine.
However there is a third "secret" API - blog-comment submission.
When a comment is received upon this blog it is written to a local filesystem, and an email is sent to me. The next time my blog is built rsync is used to get the remote-comments and add them to the blog. (Spam deleted first, of course).
Locally the comments are added into the git-repository this blog is built from - and the remote files deleted now and again.
Maybe I should just switch from writing the blog-comment to disk, and include all the meta-data in the email? I don't wanna go connecting to Gmail via IMAP, but I could probably copy and paste from the email to my local blog-repository.
I can stop hosting the tram-APIs publicly, but the blog comment part is harder. I guess I just need to receive incoming FORM-submission, and send an email.
Maybe I host the existing container on fly.io, for free?
Maybe I write an AWS lambda function to do the necessary thing?
Or maybe I drop blog-comments and sidestep the problem entirely? After all I wrote five posts in the whole of last year ..
So in my previous post I mentioned that we were going to spend the Christmas period in the UK, which we did.
We spent a couple of days there, meeting my parents, and family. We also persuaded my sister to drive us to Scarborough so that we could hang out on the beach for an afternoon.
Finland has lots of lakes, but it doesn't have proper waves. So it was surprisingly good just to wade in the sea and see waves! Unfortunately our child was a wee bit too scared to ride on a donkey!
Unfortunately upon our return to Finland we all tested positive for COVID-19, me first, then the child, and about three days later my wife. We had negative tests in advance of our flights home, so we figure that either the tests were broken, or we were infected in the airplane/airport.
Thankfully things weren't too bad, we stayed indoors for the appropriate length of time, and a combination of a couple of neighbours and online shopping meant we didn't run out of food.
Since I've been back home I've been automating AWS activities with aws-utils, and updating my simple host-automation system, marionette.
Marionette is something that was inspired by puppet, the configuration management utility, but it runs upon localhost only. Despite the small number of integrated primitives it actually works surprisingly well, and although I don't expect it will ever become popular it was an interesting research project.
The aws-utilities? They were specifically put together because I've worked in a few places where infrastructure is setup with terraform, or cloudformation, but there are always the odd thing that is configured manually. Typically we'll have an openvpn gateway which uses a manually maintained IP allow-list, or some admin-server which has a security-group maintained somewhat manually.
Having the ability to update a bunch of rules with your external IP, as a single command, across a number of AWS accounts/roles, and a number of security-groups is an enormous time-saver when your home IP changes.
I'd quite like to add more things to that collection, but there's no particular rush.
I realize it has been quite some time since I last made a blog-post, so I guess the short version is "I'm still alive", or as Granny Weatherwax would have said:
I ATE'NT DEAD
Of course if I die now this would be an awkward post!
I can't think of anything terribly interesting I've been doing recently, mostly being settled in my new flat and tinkering away with things. The latest "new" code was something for controlling mpd via a web-browser:
This is a simple HTTP server which allows you to minimally control mpd running on localhost:6600. (By minimally I mean literally "stop", "play", "next track", and "previous track").
I have all my music stored on my desktop, I use mpd to play it locally through a pair of speakers plugged into that computer. Sometimes I want music in the sauna, or in the bedroom. So I have a couple of bluetooth speakers which are used to send the output to another room. When I want to skip tracks I just open the mpd-web site on my phone and tap the button. (I did look at android mpd-clients, but at the same time it seemed like installing an application for this was a bit overkill).
I guess I've not been doing so much "computer stuff" outside work for a year or so. I guess lack of time, lack of enthusiasm/motivation.
So looking forward to things? I'll be in the UK for a while over Christmas, barring surprises. That should be nice as I'll get to see family, take our child to visit his grandparents (on his birthday no less) and enjoy playing the "How many Finnish people can I spot in the UK?" game
frodo ~ $ pass age
6 years ago GPG/root@localhost.gpg
6 years ago GPG/steve@steve.org.uk.OLD.gpg
..
4 years, 8 months ago Domains/Domain.fi.gpg
4 years, 7 months ago Mobile/dna.fi.gpg
..
1 year, 3 months ago Websites/netlify.com.gpg
1 year ago Financial/ukko.fi.gpg
1 year ago Mobile/KiK.gpg
4 days ago Enfuce/sre.tst.gpg
..
Anyway today's work involved writing another plugin, named env. I store my data in pass in a consistent form, each entry looks like this:
username: steve
password: secrit
site: http://example.com/login/blah/
# Extra data
The keys vary, sometimes I use "login", sometimes "username", other times "email", but I always label the fields in some way.
Recently I was working with some CLI tooling that wants to have a username/password specified and I patched it to read from the environment instead. Now I can run this:
That's ideal, because now I can source that from within a shell:
$ source <(pass env internal/cli/tool-name)
$ echo username
steve
Or I could directly execute the tool I want:
$ pass env --exec=$HOME/ldap/ldap.py internal/cli/tool-name
you are steve
..
TLDR: If you store your password entries in "key: value" form you can process them to export $KEY=$value, and that allows them to be used without copying and pasting into command-line arguments (e.g. "~/ldap/ldap.py --username=steve --password=secrit")
In my previous post I wrote about how I'd been running CP/M on a Z80-based single-board computer.
I've been slowly working my way through a bunch of text-based adventure games:
The Hitchhiker's Guide To The Galaxy
Zork 1
Zork 2
Zork 3
Along the way I remembered how much fun I used to have doing this in my early teens, and decided to write my own text-based adventure.
Since I'm not a masochist I figured I'd write something with only three or four locations, and solicited facebook for ideas. Shortly afterwards a "plot" was created and I started work.
I figured that the very last thing I wanted to be doing was to be parsing text-input with Z80 assembly language, so I hacked up a simple adventure game in C. I figured if I could get the design right that would ease the eventual port to assembly.
I had the realization pretty early that using a table-driven approach would be the best way - using structures to contain the name, description, and function-pointers appropriate to each object for example. In my C implementation I have things that look like this:
{name: "generator",
desc: "A small generator.",
use: use_generator,
use_carried: use_generator_carried,
get_fn: get_generator,
drop_fn: drop_generator},
A bit noisy, but simple enough. If an object cannot be picked up, or dropped, the corresponding entries are blank:
{name: "desk",
desc: "",
edesc: "The desk looks solid, but old."},
Here we see something that is special, there's no description so the item isn't displayed when you enter a room, or LOOK. Instead the edesc (extended description) is available when you type EXAMINE DESK.
Anyway over a couple of days I hacked up the C-game, then I started work porting it to Z80 assembly. The implementation changed, the easter-eggs were different, but on the whole the two things are the same.
Certainly 99% of the text was recycled across the two implementations.
Anyway in the unlikely event you've got a craving for a text-based adventure game I present to you:
In the past, I've talked about building a Z80-based computer. I made some progress towards that goal, in the sense that I took the initial (trivial steps) towards making something:
I built a clock-circuit.
I wired up a Z80 processor to the clock.
I got the thing running an endless stream of NOP instructions.
No RAM/ROM connected, tying all the bus-lines low, meaning every attempted memory-read returned 0x00 which is the Z80 NOP instruction.
But then I stalled, repeatedly, at designing an interface to RAM and ROM, so that it could actually do something useful. Over the lockdown I've been in two minds about getting sucked back down the rabbit-hole, so I compromised. I did a bit of searching on tindie, and similar places, and figured I'd buy a Z80-based single board computer. My requirements were minimal:
It must run CP/M.
The source-code to "everything" must be available.
I want it to run standalone, and connect to a host via a serial-port.
With those goals there were a bunch of boards to choose from, rc2014 is the standard choice - a well engineered system which uses a common backplane and lets you build mini-boards to add functionality. So first you build the CPU-card, then the RAM card, then the flash-disk card, etc. Over-engineered in one sense, extensible in another. (There are some single-board variants to cut down on soldering overhead, at a cost of less flexibility.)
The advantage of this design is that it loads code from a USB stick, making it easy to transfer files to/from it, without the need for a compact flash card, or similar. The downside is that the system has only 64K RAM, meaning it cannot run CP/M 3, only 2.2. (CP/M 3.x requires more RAM, and a banking/paging system setup to swap between pages.)
When the system boots it loads code from an EEPROM, which then fetches the CP/M files from the USB-stick, copies them into RAM and executes them. The memory map can be split so you either have ROM & RAM, or you have just RAM (after the boot the ROM will be switched off). To change the initial stuff you need to reprogram the EEPROM, after that it's just a matter of adding binaries to the stick or transferring them over the serial port.
In only a couple of hours I got the basic stuff working as well as I needed:
A z80-assembler on my Linux desktop to build simple binaries.
An installation of Turbo Pascal 3.00A on the system itself.
The Zork trilogy installed, along with Hitchhikers guide.
I had some fun with a CP/M emulator to get my hand back in things before the board arrived, and using that I tested my first "real" assembly language program (cls to clear the screen), as well as got the hang of using the wordstar keyboard shortcuts as used within the turbo pascal environment.
I have some plans for development:
Add command-line history (page-up/page-down) for the CP/M command-processor.
Add paging to TYPE, and allow terminating with Q.
Nothing major, but fun changes that won't be too difficult to implement.
Since CP/M 2.x has no concept of sub-directories you end up using drives for everything, I implemented a "search-path" so that when you type "FOO" it will attempt to run "A:FOO.COM" if there is no file matching on the current-drive. That's a nicer user-experience at all.
I also wrote some Z80-assembly code to search all drives for an executable, if not found in current drive and not already qualified. Remember CP/M doesn't have a concept of sub-directories) that's actually pretty useful:
B>LOCATE H*.COM
P:HELLO COM
P:HELLO2 COM
G:HITCH COM
E:HYPHEN COM
I've also written some other trivial assembly language tools, which was surprisingly relaxing. Especially once I got back into the zen mode of optimizing for size.
I forked the upstream repository, mostly to tidy up the contents, rather than because I want to go into my own direction. I'll keep the contents in sync, because there's no point splitting a community even further - I guess there are fewer than 100 of these boards in the wild, probably far far fewer!
Nothing too much has happened recently, largely as a result of the pandemic killing a lot of daily interests and habits.
However as a result of Brexit I'm having to do some paperwork, apparently I now need to register for permanent residency under the terms of the withdrawal agreement, and that will supersede the permanent residency I previously obtained.
Of course as a UK citizen I've now lost the previously-available freedom of movement. I can continue to reside here in Helsinki, Finland, indefinitely, but I cannot now move to any other random EU country.
It has crossed my mind, more than a few times, that I should attempt to achieve Finnish citizenship. As a legal resident of Finland the process is pretty simple, I just need two things:
Prove I've lived here for the requisite number of years.
Pass a language test.
Of course the latter requirement is hard, I can understand a lot of
spoken and written Finnish, but writing myself, and speaking a lot is
currently beyond me. I need to sit down and make the required effort to
increase my fluency. There is the alternative option of learning Swedish, which is a hack a lot of immigrants use:
Learning Swedish is significantly easier for a native English-speaker.
But the downside is that it would be learning a language solely to
"cheat" the test, it wouldn't actually be useful in my daily life.
Finland has two official languages, and so the banks, the medical world,
the tax-office, etc, are obliged to provide service in both. However
daily life, ordering food at restaurants, talking to parents in the
local neighborhood? Finnish, or English are the only real options. So
if I went this route I'd end up in a weird situation where I had to
learn a language to pass a test, but then would continue to need to
learn more Finnish to live my life. That seems crazy, unless I were
desperate for a second citizenship which I don't think I am.
Learning Finnish has not yet been a priority, largely because I work in
English in the IT-world, and of course when I first moved here I was
working (remotely) for a UK company, and didn't have the time to attend
lessons (because they were scheduled during daytime, on the basis that
many immigrants are unemployed). Later we had a child, which meant that
early-evening classes weren't a realistic option either.
(Of course I learned a lot of the obvious things immediately upon
moving, things like numbers, names for food, days of the week were
essential. Without those I couldn't have bought stuff in shops and
would have starved!)
On the topic of languages a lot of people talk about how easy it is for
children to pick up new languages, and while that is broadly true it is
also worth remembering just how many years of correction and repetition they
have to endure as part of the process.
For example we have a child, as noted already, he is spoken to by
everybody in Finnish. I speak to him in English, and he hears his
mother and myself speaking English. But basically he's 100% Finnish
with the exception of:
Me, speaking English to him.
His mother and I speaking English in his hearing.
Watching Paw Patrol.
If he speaks Finnish to me I pretend to not understand him, even when I
do, just for consistency. As a result of that I've heard him tell
strangers "Daddy doesn't speak Finnish" (in Finnish) when we've been
stopped and asked for directions. He also translates what some other
children have said into English for my benefit which is adorable
Anyway he's four, and he's pretty amazing at speaking to everybody in the
correct language - he's outgrown the phase where he'd mix different
languages in the same sentence ("more leipä", "saisinko milk") - when I
took him to the UK he surprised and impressed me by being able to
understand a lot of the heavy/thick accents he'd never heard before.
(I'll still need to train him on Rab C.
Nesbitt when he's a wee
bit older, but so far no worries.)
So children learn languages, easily and happily? Yes and no. I've
spent nearly two years correcting his English and he still makes the
same mistake with gender. It's not a big deal, at all, but it's a
reminder that while children learn this stuff, they still don't do it as
easily as people imagine. I'm trying to learn and if I'd been corrected
for two years over the same basic point you'd rightly think I was
"slow", but actually that's just how it works. Learning languages
requires a hell of a lot of practice, a lot of effort, and a lot of
feedback/corrections.
Specifically Finnish doesn't have gendered pronouns, the same word is
used for "he" and "she". This leads to a lot of Finnish people, adults
and children, getting the pronouns wrong in English. In the case of our
child he'll say "Mommy is sleeping, when he wake up?" In the case of
adults I've heard people say "My girlfriend is a doctor, he works in a
hospital", or "My dad is an accountant, she works for a big firm". As I
say I've spent around two years making this correction to the child, and
he's still nowhere near getting it right. Kinda adorable actually: