Creating API docs for assembly language projects
Tags: asmdoc, api, github, actions
Published: 2022-05-21 05:24:00

> This article originally appeared on my personal dev blog: [Byte High, No Limit](https://andrewowen.net/blog/).

This week, I'm releasing what I hope will be the penultimate beta of [SE Basic IV](https://cheveron.github.io/sebasic4/ "SE Basic IV") (an open source classic BASIC interpreter). The last beta should contain the missing sound and graphics functionality, and then it should go to a release candidate.

One of the things I've spent a lot of time considering is the public API. The underlying operating system has its own API, but it's rather primitive and hard to change for reasons (because the OS was reverse engineered and is barely documented).

Back when I was a full-time API writer, I used [Swashbuckle](https://github.com/domaindrivendev/Swashbuckle.AspNetCore "Swashbuckle") to generate [OpenAPI](https://swagger.io/specification/ "OpenAPI") docs from comments in the [C#](https://docs.microsoft.com/en-us/dotnet/csharp/ "C#") source code. I'd previously used [Doxygen](https://www.doxygen.nl/index.html "Doxygen") to do the same for [C++](https://isocpp.org/ "C++") and [Javadoc](https://docs.oracle.com/en/java/javase/13/javadoc/javadoc.html "Javadoc") for [Java](https://www.java.com/ "Java"). I wanted something that would do the same for [assembly language](https://en.wikipedia.org/wiki/Assembly_language "assembly language"). And I found it, in the shape of a [Perl](https://www.perl.org/ "Perl") script called _asmdoc_ by Bogdan Drozdowski.

The script parses a set of assembly files looking for comments in a particular format, with a known set of tags such as _@param_, _@return_, _@throws_. It generates a set of HTML files including a table of contents, index, list of constants, a help page and so on.

The script was written a long time ago and the default appearance is very web 1.0. But the code includes an embedded CSS file that I was able to edit to give the output a much more modern look, inspired by [ReDoc](https://redocly.github.io/redoc/ "ReDoc"). I only had to make a single change to the code to get it to work with the current version of Perl (at time of writing).

The script is self-contained and will produce all the required output files. You can grab it from: [https://github.com/cheveron/sebasic4/blob/main/api/asmdoc.pl](https://github.com/cheveron/sebasic4/blob/main/api/asmdoc.pl "https://github.com/cheveron/sebasic4/blob/main/api/asmdoc.pl"). And it's free to use in your own projects. I contacted Bogdan to thank him for his work and show him what I'd done. He was surprised that I hadn't needed to alter his code.

I created a Git Action to build the API docs and publish them whenever there's a code change in the main branch. The API docs are hosted on the project's GitHub Pages (which use Gatsby). Here's the script:

```
    name: API
    
    on:
      push:
        branches: [ main ]
      workflow_dispatch:
    
    jobs:
      asmdoc:
        runs-on: ubuntu-latest
    
        steps:
          - uses: actions/checkout@v2
    
          - name:
            run: |
              cd api
              perl asmdoc.pl -author -version ../basic/basic.inc ../basic/modules/*.asm
              cd ..
              mkdir ../temp
              cp api/* ../temp        
              git config user.name github-actions
              git config user.email github-actions@github.com
              git fetch
              git checkout gh-pages
              cp ../temp/* api
              git add .
              git commit -m "asmdoc"
              git push origin gh-pages
```

I've covered Git Actions in a previous blog post. The crucial information here is on line 18 where the files to include are specified (the `basic.inc` file and all the files with the `.asm` extension).

After the Perl script is run the rest of the action grabs the output, and then pushes it to the gh-pages repository, which has the effect of publishing it here: [https://cheveron.github.io/sebasic4/api/](https://cheveron.github.io/sebasic4/api/ "https://cheveron.github.io/sebasic4/api/").

Because the project is open source, I've created stubs for the most common routines. However, these should be considered part of the private API. The address of the code may change in a later release, and so the only program that should call these APIs is the firmware itself.

To provide a public API for applications to call where the addresses won't change, I've used a [vector table](https://github.com/cheveron/sebasic4/blob/main/basic/modules/04_vectors.asm "vector table"). This is a long list of `JP` instructions (immediate jumps to other addresses). While the destination of the jumps can change from release to release, the location in the table is fixed in memory. This means that applications can safely call it without worrying that the address will change in future.

With all the underpinnings in place, the next task was to come up with a useful set of functions. I looked at the CP/M and MS-DOS APIs, but they were, understandably, simplistic, and not in a logical order (having grown over time as new versions were released). I eventually took inspiration from [OpenStep](http://www.gnustep.org/resources/documentation/Developer/Base/Reference/index.html "GNUstep") and [BeOS](https://www.haiku-os.org/docs/api/ "Haiku").

For avoidance of confusion, namespaces should always use American English. Then I use:

1. Two letters (`SE`).
2. The domain. For example, `File`, `Folder`, `Screen`, `Keyboard` and so on.
3. \[Optional\] A subdomain. For example, `Palette`.
4. The action. For example, `Open`, `Close`, `Read`, `Write` and so on.
5. \[Optional\] A qualifier. For example, `Exists`.

Here are some example endpoints:

* `SEFileOpenExists`
* `SEScreenPaletteSet`

And here's the comment in the code that generates the docs for the first example:

```
    ;;
    ; open a file from disk for reading if it exists
    ; @param IX - pointer to ASCIIZ file path
    ; @return file handle in <code>A</code>
    ; @throws sets carry flag on error
    ;;
    SEFileOpenExists:
    	jp v_open_r_exists;					// 
```

And in the API docs, this results in something like this:

> **SEFileOpenExists**  
> open a file from disk for reading if it exists  
> **Parameter**:  
> `IX` - pointer to ASCIIZ file path  
> **Returns**:  
> file handle in `A`  
> **Throws**:  
> sets carry flag on error

Now I had better get back to preparing the release.