7 July 2025

Only Exceptions for Control Flow

Where It Started
At technical unconferences I like to start (and conclude) the day with some fun coding. After all, writing code is (still, in June 2025,) at the heart of software development. During SoCraTes Linz 2024 I proposed the first session to be a "Coding Fun session: Only use exceptions for control flow." I explained that it would be impractical but fun ;-) Using exceptions like that was an anti pattern but I wanted to see what would be possible.

Coding Fun: Only Exceptions for Control Flow
I start the session with a few slides: Control flow is is the order in which individual statements, instructions or function calls of an imperative program are executed or evaluated. And the idea of the session is to only use exceptions for changing the control flow. This is a constraint, an artificial addition to a coding exercise, to make it more focused or challenging. To force us to use exceptions for control flow I need to take away all other options:
  • Not using if, while or for, the ternary operator or similar language constructs. (This is the same as the constraint Cyclomatic Complexity One.)
  • Not using any array or (Java) stream operations like map, filter, etc. (These functions are a way to deal with Cyclomatic Complexity One.)
  • Not using lookup arrays of functions. For example a map with the keys true and false and functions as values is an if-else statement. (These lookups are another way to deal with Cyclomatic Complexity One.)
  • Not using modulo, signum, minimum and maximum Math functions as well as various String translate and replace functions. (These are unrelated to Cyclomatic Complexity One, but offer ways to work around in the specified task further down.)
control room in former refinery (licensed CC BY-NC-ND by Michal Jancek)Word of Warning
This is an exercise, even more it is an "inverted" exercise because it wants us to do things we usually avoid doing. And for good reason, using exceptions this way is an anti pattern. It is basically a goto statement, not a structured break or continue style go to, but a real nasty one where we can jump across multiple stack frames. It makes code hard to understand. Often performance is bad due to the cost of creating exceptions. Don't do this in your project. Keep in mind that we are just playing...

Assignment
As a task let us create a ROT-13 "Encryption", also known as Caesar-Chiffre. Our requirements are:
  1. Write a function encode to encode a message using ROT-13.
  2. Each letter is replaced with one 13 added in the alphabet.
  3. At the end of the alphabet, it continues at the beginning.
  4. Characters other than letters are not encoded.
  5. Lowercase letters are first changed to uppercase letters.
  6. Umlauts are letter combinations for encoding, e.g. "Ä" becomes "AE" before encoding and so on.
  7. Bonus: Make rotation configurable. Now it is 13.
These are plenty of requirements. In a usual session we manage to finish item 4) which shows all the relevant parts. If we have more time we can of course do more.

Try it Yourself
Below I will show Java code of the most common solution. After you see it, you will not be able to un-see it. If you want to explore the constraint and the exercise on your own, then stop reading now! I encourage you to do that. Best way is to work with difficult constraints is to code a little bit and then step back and verify if all rules still apply.

Hands On!
I run this session as "interactive demo", which means I let participants decide what to do next, or at least I let them propose what to do next and I wait until a proposition is aligned with my plan. I make sure we stay focused on the main goal of the exercise. The whole coding takes 30 to 40 minutes. Further more I (demand to) use TDD and try to make the code readable (as possible). How do I start? I start with a (simple) failing test of course:
public class Rot13Test {

  @Test
  public void empty() {
    assertEquals("", encode(""));
  }

}
To keep things simple I add the production code (that is the function encode) at the top of the test class:
public class Rot13Test {

  public String encode(String message) {
    return "";
  }
The first test established the method with the required signature, see requirement #1. Now I am able to replace letters with one 13 added in the alphabet. A suitable test case is "A" becomes "N", i.e. assertEquals("N", encode("A")). The first challenge is how to check if there are characters in the message? I cannot loop or map them. How about:
public class Rot13Test {

  public String encode(String message) {
    try {
      message.charAt(0);
      return "N";
    } catch (StringIndexOutOfBoundsException empty) {
      return "";
    }
  }
Ok. Next I choose to go for more letters. I could also do that at the end, it depends what people say during the workshop... A suitable test case is "AA" becomes "NN".
public class Rot13Test {

  public String encode(String message) {
    try {
      char first = message.charAt(0);
      String remaining = message.substring(1);
      return encodeChar(first) + encode(remaining);
    } catch (StringIndexOutOfBoundsException empty) {
      return "";
    }
  }

  private String encodeChar(char c) {
    return "N";
  }
The recursion ends when the message is empty. Back to requirement #2. My next test case is "B" becomes "M" and I only need to modify the method encodeChar():
public class Rot13Test {
  ...

  private String encodeChar(char c) {
    int encoded = c + 13;
    return Character.toString((char) encoded);
  }
This works for the letters "A" to "M" (which becomes "Z"). Nice. Requirement #3 deals with letters "N" to "Z", so
public class Rot13Test {
  ...

  private String encodeChar(char c) {
    int encoded = c + 13;
    try {
      // if encoded <= 'Z' then jump
      int tmp = 1 / (encoded / ('Z' + 1));
      encoded -= 26;
    } catch (ArithmeticException smaller) {
      // leave encoded alone
    }
    return Character.toString((char) encoded);
  }
I will explain how that works: If encoded is less or equal to "Z" the logic is already correct. How can I create an exception if a number is less than 'Z' + 1? If encoded is smaller than this limit, the integer division will be zero which will result in a division by zero, which in Java causes an ArithmeticException. If encoded is larger than "Z", the result of the division will be 1, so dividing by it is allowed and the code continues. Now the algorithm works for all letters.

The Sluice Gate (licensed CC BY-NC-ND by Ib Aarmo)Requirement #4 tells us to leave special characters alone. Which are these? A quick look at the 7 bit ASCII table,
 !"#$%&'()*+,-./0123456789:;<=>?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
`abcdefghijklmnopqrstuvwxyz{|}~
shows the special characters. There are three ranges which I need to handle. (At the moment there are only two ranges, and I know that lowercase letters will be handled later.) I add a test for several special characters below, i.e. assertEquals("!/?@", encode("!/?@")) and later one above i.e. assertEquals("[_`{~", encode("[_`{~")), always using the first and last character of each range.
public class Rot13Test {
  ...

  private String encodeChar(char c) {
    int encoded = c;
    try {
      skipIfBelow(encoded, 'A');
      skipIfBelow('Z', encoded);
      encoded += 13;

      skipIfBelow(encoded, 'Z' + 1);
      encoded -= 26;
    } catch (ArithmeticException smaller) {
      // leave encoded alone
    }
    return Character.toString((char) encoded);
  }

  private void skipIfBelow(int codePoint, int limit) {
    int tmp = 1 / (codePoint / limit);
  }
By extracting a helper method skipIfBelow() I can check against the limits of the two ranges: Below and above. The basic algorithm is complete. Requirement #5 is more of the same, adding a toUppercase:
public class Rot13Test {
  ...

  private int toUppercase(int codePoint) {
    try {
      skipIfBelow(codePoint, 'a');
      skipIfBelow('z', codePoint);

      return codePoint - 'a' + 'A';

    } catch (ArithmeticException skipped) {
      // leave char alone
      return codePoint;
    }
  }
Control Flow "Library"
I said I try to make the code more readable. This can be done with custom exceptions and extracting the flow control logic into separate "library" functions like skipIfBelow(). During a different run of the session I ended up with
public class Rot13Test {
  ...

  static class SmallerThan extends Exception {
    public SmallerThan(Throwable cause) {
      super(cause);
    }
  }

  private void testIfSmaller(int codePoint, int limit)
               throws SmallerThan {
    try {
      int tmp = 1 / (codePoint / limit);
    } catch (ArithmeticException e) {
      throw new SmallerThan(e);
    }
  }

  static class LargerThan extends Exception {
    public LargerThan(Throwable cause) {
      super(cause);
    }
  }

  private void testIfLarger(int codePoint, int limit)
               throws LargerThan {
    try {
      testIfSmaller(limit, codePoint);
    } catch (SmallerThan e) {
      throw new LargerThan(e);
    }
  }

  static class ReplacedUmlaut extends Exception {
    public final String replacement;

    public ReplacedUmlaut(String replacement) {
      this.replacement = replacement;
    }
  }

  private void replaceUmlaut(int codePoint, int umlaut,
                             String replacement)
               throws ReplacedUmlaut {
    try {
      testIfSmallerThan(codePoint, umlaut);
      testIfLargerThan(codePoint, umlaut);
      throw new ReplacedUmlaut(replacement);
    } catch (SmallerThan | LargerThan notUmlaut) {
    }
  }
This allows me to distinguish different cases in the catch clause, which becomes a switch statement.

Closing Circle
This code is not like the code you see every day and it was definitely not the code you should write, but all participants agreed that they had a lot of fun. Several people said that I had screwed their mind and my long time peer Bernhard Woditschka said that he liked how it forced him to think outside of the box. This is probably because I am exploring negative aspects, i.e. concepts we usually try to avoid. Samuel Ytterbrink agreed saying "I think this session was like balm for the soul, nothing so freeing as being ordered to do something you are not supposed to do."

Using only exceptions for control flow, like naming all identifiers randomly, focuses on unwanted qualities of code, sharpens our awareness and (hopefully) raises our dislike of them. Ant it is a fun exercise, too. Stay tuned for my next Coding Fun session: No Composition Only Inheritance ;-)

25 June 2025

Patching Io Addons

This project is getting out of hand. I just wanted to use Regular Expressions in Io. This is the forth part of me trying different extensions of Io. First I installed addons without native code, then I compiled native code of addons and in the third part I:
  • Checked package.json and build.io for hints about needed third party libraries and headers.
  • Found ported libraries for Windows in GnuWin32, e.g. ReadLine.
  • Compiled Io addons with dependencies.
  • Fixed the undefined reference to 'IoState_registerProtoWithFunc_' error, which occurs using addons created for older versions of Io.
  • Worked around conflicting headers included by IoObject.h or IoState.h.
  • Finally compiled with dependencies on the native code of another addon.
On the way I added minor fixes to several addons, see my (forked) Io repositories and today I cover addons which needed more extensive modifications.

patch (licensed CC BY-NC-ND by Natasha Wheatland)JSON Parsing
Io has "half" JSON support, most objects provide an asJson() method to represent the contained data in JSON. Some addons (like Docio) require Sequence.parseJson() but omit a specific dependency for that and my Io (iobin-​win32-​current.zip) misses this method. These addons are for a newer version of Io, which has not been released (yet?). Writing a JSON parser is a nice exercise and there is already a JSON parser for Io. It is neither an addon nor an Eerie package. Preparing some arbitrary Io code as addon means moving the existing files into the proper folder structure, and adding the files needed to run as addon (i.e. proto and depends files). I even added a test. My Io JSON addon can be cloned directly into the addons folder %IO_HOME%\​lib\​io\​addons.

Missing Io Core Functions
I started changing addon source code to include the JSON parser addon, at the same time I wanted to keep my changes as little as possible. The Io Guide said that if you have a .iorc file in your home folder, it will be eval'ed before the interactive prompt starts.. In my %HOMEDRIVE%%HOMEPATH%\.iorc I added all definitions of missing Io core functions:
false isFalse := method( true )
true isFalse := method( false )

// load parseJson
Sequence hasSlot("parseJson") ifFalse(
    Json
)

false asJson := "false"
true asJson := "true"
nil asJson := "null"
This made me ask myself, what else was added to Io since my Windows binary was built in 2013? I cloned Io and compared the proper tag with master. There were many changes and I filtered out formatting and comments. The final result was a short list of additions and modifications like
  • Addon and AddonLoader extensions to use Eerie.
  • Core tildeExpandsTo() using UserProfile instead of HOME on Windows.
  • Sequence power() and powerEquals().
  • TestRunner run() returning the number of errors.
Docio
With JSON support and Markdown working, I can run Docio. Docio is the documentation generator for Eerie packages. It extracts tagged comments from C and Io code and generates Markdown and HTML documentation like this. To make Docio work, I had to work around several issues:
  1. Docio's repository name is lowercase. To install it as addon the folder name has to be uppercase, as for Kano:
    > cd "%IO_HOME%\lib\io\addons"
    > git clone git@github.com:IoLanguage/docio.git Docio
  2. Docio does not depend on native code, and it has Markdown as its sole dependency.
    > echo Markdown > Docio\depends
    > echo Docio > Docio\protos
  3. Docio loads Eerie eagerly in the first line of Docio.io. It uses Eerie to query its template path. I lack Eerie and the eager loading fails, so I remove that. With Eerie out of the picture, I always have to provide the full path to the template (%IO_HOME%\lib\io\addons\Docio\template). Now I can initialise Docio with io -e "Docio println". Success.
  4. Docio has a starter script in bin/docio which needs a Windows version, i.e. a bin/docio.bat which calls the original starter script,
    io "%IO_HOME%\lib\io\addons\Docio\bin\docio" %*
  5. With the starter script Docio is available as a command line tool. One thing to know is that its help is wrong. It says
    docio package=/path/to/package [template=/path/to/template]
    while it really needs two dashes for each option,
    docio --package=/path/to/package [--template=/path/to/template]
    Both paths need to be absolute, otherwise documentation files appear in strange places.
  6. There is a syntax error in DocsExtractor.io line 65 and following.
  7. Copying of binary resources, i.e. fonts, corrupts the files. You will have to copy them manually. This is because Io's low level C code does fails to set the binary flag when opening files. On Windows the C library functions consider text files to be finished on the first byte 0x1A. I am unable to fix that right now.
After fixing all that, Docio provides help, even inside the Io REPL, which comes handy:
Io> Docio printDocFor("ReadLine")
Binding to GNU readline.
Docio will generate the documentation for any package on the fly which has a package.json with a name field. Bundled addons miss that file, and I create empty ones containing each addon's name for addons where I want to generate documentation. Here is my own Docio with all the fixes.

Socket
Finally I am going for the Socket addon. I was scared of Socket, some sources stated that Io socket support has always been tricky. Socket depends on libevent 2.0.x, an event notification library. Surprisingly autogen.sh, configure and make install compile and link libevent without any problems. Now I have some libevent_*.dll files. This is way too easy.

But of course Socket's C code does not compile. The header sys/​queue.h is not available on Windows. Fortunately libevent comes with its own compat/​sys/​queue.h. Some header files, e.g. IoEvConnection.h, IoEvHttpServer.h and several others need the conditional include:
#include "Socket.h"
#if !defined(_WIN32) || defined(__CYGWIN__)
#include <sys/queue.h>
#else
#include <compat/sys/queue.h>
#endif
#include <event.h>
Further UnixPath.c fails compilation, and I drop it from the list of files to compile. Even if it would compile, it would not work as indicated by the error returned from IoUnixPath for defined(_WIN32) || defined(__CYGWIN__): Sorry, no Unix Domain sockets on Windows MSCRT.

Linking the compiled files produces an undefined reference to 'GetNetworkParams@8', which is part of the Windows GetNetworkParams interface in iphlpapi.dll, as listed on build.io together with ws2_32. It seems that after my exploration of building addons with native dependencies like ReadLine or Markdown, compiling Socket as difficult than that. Here are the important pieces of the steps:
> ...
> cd source
> gcc -fPIC -D _NO_OLDNAMES -D IO_WINSOCK_COMPAT -c ...
> gcc -liovmall -lbasekit -levent -liphlpapi -lws2_32 ^
      -shared -Wl,--out-implib,libIoSocket.dll.a ^
      ... -o libIoSocket.dll
> ...
My fork of Socket contains the required fixes for Windows.

Using the Socket addon on current Ubuntu
In my product development Coderetreat template, I have GitHub actions to verify the sample code. Based on Sandro's installation instructions I first install the required dependencies:
sudo apt-get install libpcre3-dev libevent-dev
(This might be unecessary as GitHub's Ubuntu image has both of them installed.) The current branch of libevent is 2.1.x and the none of the older 2.0.x versions do compile due to wrong dependencies. At the same time, the Linux version of Io contains Socket but needs libevent 2.0.5 specifically. I have no idea if that is the way to fix these kind of issues, but it works, so I link the installed version (2.1.7) as the required (2.0.5) one.
ls -la /usr/lib/x86_64-linux-gnu/libevent*.so.*
sudo ln -s /usr/lib/x86_64-linux-gnu/libevent-2.1.so.7.0.1 /usr/lib/x86_64-linux-gnu/libevent-2.0.so.5
(I leave the ls is the action to show the current version when the GitHub runner changes and there is a newer version of libevent.) After that I download the "latest" version of Io and install it:
wget -q http://iobin.suspended-chord.info/linux/iobin-linux-x64-deb-current.zip
unzip -qq iobin-linux-x64-deb-current.zip
sudo dpkg -i IoLanguage-2013.11.04-Linux-x64.deb
sudo ldconfig
io --version
The Linux version of Io comes with almost all addons pre-compiled, and there is no need for any compilation. Success. Because this version is without Eerie, custom addons have to be installed manually into the System installPrefix folder, e.g. adding Docio
git clone https://github.com/codecop/Docio.git
io -e "System installPrefix println"
sudo mv Docio /usr/local/lib/io/addons/
io -e "Docio println"
Now the runner is ready to execute some tests which is usually done with io ./tests/correctness/run.io. The full GitHub action for Io is here.

Alaska, Frontier Land (licensed CC BY-NC-ND by Clickrbee)Random Facts about Addons
During my exploration I learned more things about Io's addon structure:
  • The protos file does not need to contain the name of the "main" prototype, which is also the name of the addon, and often it does not. I put the name of all (exported) prototypes there to simplify my scripts. Then package.json, the Eerie manifest, does contain all prototypes.
  • In the beginning I though depends was some kind of manifest, listing dependencies. But it is only needed for dependencies of native code, so AddonLoader loads dependent addons before they are used in native code. For non native addons, Io will load whatever is needed when parsing any unknown type name. Till now the only addon I have seen which needs that feature is Regex.
  • When an addon is loaded all its files are evaluated. Only then is it registered as active. This can lead to addon initialisation loops. The initialisation order seems to be by file name. Some addons with complex inter dependencies - like Socket - prefix Io files with A_0, A_1 and so on to ensure ordered initialisation. (This is a bit annoying for tooling as the prototype name usually equals the file name in Io.)
Summary: My Feelings Towards Io
While Io is dead since more than ten years, working with it feels bleeding edge, even more it is outside the frontier. You are at your own, there is no help. Nobody is coming to save you. The latest article I found was written in 2019. There are less than ten (!) active blog posts: Blame it on Io (2006), Io language on Windows (2014), Io Basics (2015) and Io Programming Language (2019) - to list the best ones. There are a handful of Stack Overflow questions and a few repositories on GitHub - which are sometimes incompatible with the "latest" Io. ChatGPT understands the language but fantasises the libraries, so no help from AI neither. I am used to modern languages with a rich ecosystem, e.g. Java, C#, Python and this is an unfamiliar feeling. At the same time it is a refreshing puzzle. Maybe I will come back for a vacation in uncharted territory.

30 December 2024

Integrating Io Addons

Ultrathin Interconnects #11983 (licensed CC BY-NC-ND by Eldon Baldwin)This is the third time I write about making existing Io extensions work under Windows. While I still struggle with Socket - a crucial dependency - I install and compile random addons to see what could go wrong. Almost every addon I touch has a different issue that I need to resolve for my Io Windows build dating back to 5.11.2013. Till now I have covered the following situations:
  • Clone current addons right into the addons folder, see part 1, Kano.
  • Rename addon folders to start with an uppercase letter.
  • Create the required protos file with a list of all exported prototypes and depends file with a list of required prototypes, which is usually empty.
  • Create the missing Windows starter bat for tools like Kano.
  • If the name of the addon is missing in the package.json, Docio will be unable to process the package. I will cover Docio next time.
  • Compile the native code using MingW GCC, see part 2, Rainbow.
  • Fix linker issues regarding one versus two underscores in name decoration.
  • Copy native headers to _build/​headers, which I will need today.
  • Resolve initialisation recursion issue when loading addons.
I walk through the installation of addons with different needs and increasing difficulty using concrete examples. As many addons are wrappers around C libraries, which "may not be installed on your system" - I will work on integrating these libraries today.

Addons with (Pre-Compiled) Dependencies: ReadLine
The Io ReadLine addon is a binding to GNU readline. I have no use for it but it is small and a good example. I start as usual with cloning, creating the init file and required files:
> cd "%IO_HOME%\lib\io\addons"
> git clone git@github.com:IoLanguage/ReadLine.git
> io generate.io . ReadLine
> cd ReadLine
> touch depends
> echo ReadLine > protos
> cd source
> gcc -fPIC -D _NO_OLDNAMES -c IoReadLine.c IoReadLineInit.c
IoReadLine.c: fatal error: readline/readline.h: No such file or directory
This is expected as ReadLine binds to libreadline. For more complex cases it helps to look at the Eerie package manifest package.json:
{
  "name": "ReadLine",
  ...
  "dependencies": {
    "libs":     ["readline"],
    "headers":  ["readline/readline.h", "readline/history.h"],
    "protos":   [],
    "packages": []
  }
}
I need libreadline and two of its headers. The easiest way to build a Linux library for Windows is not build it. There is GnuWin32 @ SourceForge. It was last updated 2010, which is around the same time that active Io development stopped. Lovely there is readline-5.0-1-bin.zip which is all I need. Now I can continue building the addon.
> gcc -liovmall -lbasekit -lreadline5 ^
      -shared -Wl,--out-implib,libIoReadLine.dll.a ^
      IoReadLine.o IoReadLineInit.o -o libIoReadLine.dll
IoReadLine.o: undefined reference to `IoState_registerProtoWithFunc_'
Sigh, this was supposed to be the easiest case. I have seen this method call before. Older addons from the 2009 source release used this function, while addons bundled with my 2013 version use IoState_registerProtoWithId_. The required change in source/​IoReadLine.c, line 47 is
  using_history();

-  IoState_registerProtoWithFunc_((IoState *)state, self, protoId);
+  IoState_registerProtoWithId_(state, self, protoId);

  IoObject_addMethodTable_(self, methodTable);
Now compilation and linking succeeds.
> gcc -fPIC -D _NO_OLDNAMES -c IoReadLine.c IoReadLineInit.c
> gcc -liovmall -lbasekit -lreadline5 ^
      -shared -Wl,--out-implib,libIoReadLine.dll.a ^
      IoReadLine.o IoReadLineInit.o -o libIoReadLine.dll
> del *.o
> move /Y lib*.* ..\_build\dll
> copy /Y *.h ..\_build\headers
> cd ..\..
> io -e "ReadLine readLine() println"
The last step is to copy readline5.dll into the %IO_HOME%\​bin folder, where Io's DLLs are located. This makes running Io independent from my build environment. Here is my Io ReadLine with the necessary fixes. A nice surprise after installing ReadLine is that the Io CLI remembers my input.

Addons with Dependencies: Markdown
Io's Markdown addon is next in line. It is a Markdown parser for Io, based on discount. Its package.json states that it needs libmarkdown and a header mkdio.h. I need to compile the library first. The addon comes with the library's source code, and even a compiled version for Windows x64. It has a build.io, which will rewrite Eerie's AddonBuilder to represent a receipt for your package. Eerie should be able to build it. What does it say?
AddonBuilder clone do(

  srcDir := Directory with(Directory currentWorkingDirectory .. "/source/discount")

  compileDiscountIfNeeded := method(
    if((platform == "windows") or(platform == "mingw"),
      appendLibSearchPath(Path with(Directory currentWorkingDirectory, "deps/w64/lib") asIoPath)
      appendHeaderSearchPath(Path with(Directory currentWorkingDirectory, "/deps/w64/include") asIoPath)
    ,
      prefix := Directory currentWorkingDirectory .. "/_build"
      Eerie sh("cd #{srcDir path} && " ..
               "CC='cc -fPIC' ./configure.sh --prefix=#{prefix} && " ..
               "make && " ..
               "make install" interpolate)
      appendHeaderSearchPath(Path with(Directory currentWorkingDirectory, "_build/include") asIoPath)
      appendLibSearchPath(Path with(Directory currentWorkingDirectory, "_build/lib") asIoPath)
    )
  )

  compileDiscountIfNeeded

  dependsOnLib("markdown")
  dependsOnHeader("mkdio.h")
)
I have little experience with building C, still the bold line above gives me enough information to build discount. Tweaking its makefile and librarian.sh a bit does the trick of creating DLLs. It would have been nice to use Eerie instead... I follow the usual steps as for ReadLine above to build the addon and in the end io -e "Markdown toHtml(\"# A1\") println" displays <h1>A1</h1>.

conflict! (licensed CC BY-NC-ND by atomicity)Addons with (Conflicting) Dependencies: UUID
There waits a different problem when installing UUID, Io's wrapper around libuuid. To get started there is a MinGW compatible libuuid, thank you Alessandro Pilotti. The typical commands sh.exe ./configure, make && make install work. Between configure and make I had to #define HAVE_USLEEP 1 in the configured config.h. Having built libuuid, I can work on Io's UUID. The problem is that both libuuid (in uuid.h) and MinGW (in basetyps.h) define the type uuid_t differently. I dislike messing with MinGW include files.

Maybe I can avoid including basetyps.h from the code I want to compile? Io's source contains more than 40 headers, one for each Io object. Even the smallest addon needs to include IoObject.h and IoState.h, which provides access to the whole Io virtual machine - the largest headers in the source - which include other headers on the way. I start copying required includes into a stand-alone header. It is a boring and repetitive work, and brute force wins the day. I end up copying 500 lines of structs, method declarations, and macros from various headers. Using that isolated header instead of the provided ones, addon compilation and linking works as expected.

Addons with Io and Third Party Dependencies: Regex
My whole exploration of Io addons started with the Regex package. I really wanted to use Regular Expressions. I am a big fan of Regex, and Mastering Regular Expressions is one of my favourite books since 2006. I still consider it one of the most exciting technical books of all times. The Regex addon supports Perl regular expressions using the PCRE library. There is a suitable GnuWin32 PCRE build. Maybe I am lucky today.

This addon is special, it depends on a third party library libpcre3, and it depends on the native code of another Io addon, Range. It is shown inside build.io:
AddonBuilder clone do(
  dependsOnLib("pcre")
  dependsOnHeader("pcre.h")

  dependsOnBinding("Range")

  debs atPut("pcre", "libpcre3-dev")
  ebuilds atPut("pcre", "pcre")
  pkgs atPut("pcre", "pcre")
  rpms atPut("pcre", "pcre-devel")
)
I will have to consider this when building it.
> cd "%IO_HOME%\lib\io\addons"
> git clone git@github.com:IoLanguage/Regex.git
> io generate.io . Regex
> cd Regex/source
> gcc -fPIC -D _NO_OLDNAMES -I ..\..\Range\_build\headers ^
      -c IoRegex.c IoRegexInit.c IoRegexMatch.c IoRegexMatches.c Regex.c
> gcc -liovmall -lbasekit -lpcre3 -L ..\..\Range\_build\dll -lIoRange ^
      -shared -Wl,--out-implib,libIoRegex.dll.a ^
      IoRegex.o IoRegexInit.o IoRegexMatch.o IoRegexMatches.o Regex.o -o libIoRegex.dll
> del *.o
> move /Y lib*.* ..\_build\dll
> copy /Y *.h ..\_build\headers
> cd ../..
> io -e "Regex; \"11aabb\" matchesOfRegex(\"aa*\") asStrings println"
list(aa)
If there is a linker error,
Creating library file: libIoRegex.dll.a
IoRegexMatches.o: undefined reference to `_imp__IoRange_new'
IoRegexMatches.o: undefined reference to `_imp__IoRange_setRange'
it is the same name decoration problem as before. The solution is to move out libIoRange.dll.a from Range\​_build\​dll temporarily.

If io -e Regex fails during runtime with Exception: Error loading object 'lib\​io\​addons\​Regex\​_build\​dll\​libIoRegex.dll': 'Did not find module' or similar, then Range functionality was not loaded in advance. Make sure that depends contains Range and that it does not end with a newline at the end.

The next episode (part 4) will deal with patching some broken addons like Docio.