home.social

#rocketlang — Public Fediverse posts

Live and recent posts from across the Fediverse tagged #rocketlang, aggregated by home.social.

fetched live
  1. #RocketLang update:

    I DID IT! MY UNIT TESTING LIBRARY IS WORKING!

    *ahem* - sorry for the all-caps. Did I mention my unit-testing library is working?
    Well... It can auto-discover tests if you point it at a directory:
    * It traverses the directory tree, loads any modules it can find (as long as the filename starts with "test_" and ends with ".rocket"), checks their contents for structures inheriting from `unittest.TestCase` and any free-floating test_... functions, collects all of them into a list and runs them.

    So far it's very basic - need to implement many assert* functions. Also, I don't have try-catch statements yet, so any error means the entire test suite crashes :blobsmilesweat:

  2. #RocketLang update:

    I added support for "Union types": You can now declare variables (and function parameters of types like `int32 | None`. The variables can hold values of both types (and functions can be called with parameters of both types). The functionality is still a bit rough around the edges (not as comfortable to use as I'd like), but the core functionality is there.

    This is actually on the "critical path" towards my current goal of implementing a unit testing library: When discovering tests (iterating through folders, finding test files, loading them and then finding the test cases in them), I need a *list* of test-cases. Currently only `ArrayList`s are (kind-of) implemented. But they require... arrays - and if a type doesn't have a default value, you can't initialise the array.
    Concretely: When I'm building a list of test cases, that's a `ArrayList[func() -> None]`. But `func() -> None` doesn't have a default value, so creating a `RawArray[func() -> None]` fails, so there's no list.
    Now, with union types I can create an `ArrayList[func() -> None]` that uses a `RawArray[(func() -> None) | None]` as backing storage.

  3. #RocketLang update:

    I implemented a lot of small features recently.
    * binary and octal integers (`0b0101` and `0o644`)
    * `DirEntry.inode()` and `DirEntry.is_link()`
    * `is_instance()`
    * `Slice[T].find()` using the Knuth-Morris-Pratt #algorithm
    * `ByteString.find()` (just delegates to `Slice[Byte].find()`)
    * `ByteString.replace()` (using `.find()` internally)
    * `ByteString.split()` can now deal with separators longer than one character, also thanks to the `.find()`
    * `ByteString.join()`

    I think I learned the KMP algorithm a long, long time ago back at university and have been trying to piece it together in my head for a while now. In the end I just looked it up on #Wikipedia :blob_grinning_sweat:

  4. Project #RocketLang update:

    While working towards my current goal of having a unit-testing library in #Rocket, I came across a few bugs:

    1. `string_1 + string_2` actually didn't work when one or both of the strings where empty. Fixed and added test cases.
    2. `os.scandir(path)` actually wasn't working properly. Most of my test cases used mocking, but the function errored out when run without mocks. The test cases without mocking didn't properly check the results. Now I implemented a proper testcase that creates a temporary directory, puts some files there and checks for those.

    The `os.scandir()` test case above actually turned out to be a bit flaky: It worked locally, but failed in my CI/CD pipeline. The reason was that `os.scandir()` returned the files in a different order on the CI/CD server. So I tried sorting them by name which ... requires supporting `<`, `<=`, `>`, and `>=` for strings. So I implemented that, too (lexicographic sorting).

    And `ByteString` got a new method: `ends_with`.

    #Programming

  5. Another update on #RocketLang (my pet #programming language):

    I just implemented the `dir()` function for modules, so that you can discover what's declared in a module. (Really, I had most of it stashed already.)

    Another step towards implementing a unit-testing library for #Rocket, more specifically: auto-discovery of test cases.

  6. #RocketLang update:

    Yesterday I implemented typecasting (with runtime checks), so you can now write:
    ```
    x = cast_to[T](y)
    ```
    where `T` is the target type.

    This required first implementing function templates (generic functions) at all, that's another new feature.

    As a consequence, using the `cast_to` functions, it's now possible to call `dir()` on all types, including `Type` itself.

    That's one step closer to my current goal of implementing a unit-testing library: When you get a test class (inheriting from `unittest.TestCase`) you have to find it's methods. That's where `dir()` comes in.

  7. #RocketLang update:
    I implemented some additional library functions, namely:
    - `dynamic_type(x)`: returns the actual runtime type of the object. E.g., if the variable x is declared as `SomeProtocol` but you assign an instance of a structure implementing that protocol, it will give you the structure type.
    - `is_subtype(T1, T2)` tests if T1 is a subtype of T2, similar to Python's `issubclass()`
    - `os.scandir()` for getting the contents of a directory

    I also added a generic iterator that works with `ArrayList`s and array `Slice`s. All it needs is that the underlying container can be queried by `container[index]` and can be queried for its length.
    (I actually already had this implemented for ArrayLists, just made it more generic to make it work for Slices, too.)

    This actually required fixing a "bug" in checking whether a type implements a protocol. Implemented naively, that lead into an infinite recursion. (I was aware of that and had a FIXME in my code.) Now I accidentally triggered that and figured it was a good time to actually fix that.

  8. After a few months of break, over the last week or so I finally had some time to work on my pet programming language #Rocket again.

    My main goal was to fix a bug that prevented some built-in types (ints, booleans, ...) from being used as instances of a protocol (as in #Python - think interfaces in #Java, traits in #Rust or ...). The reason was that for these types I didn't have any runtime type info. If they were stored in a variable (or function parameter) of type `MyProtocol` that was that - there was no more information on the type except just that: It implements the protocol `MyProtocol`. No way of knowing the actual type or finding the implementation of the methods required by the protocol.

    So I had to refactor how ints and booleans were represented internally. As you can imagine, that's a change quite deep in the language. It affected arrays (which store their length - an integer), strings (which under the hood ultimately are arrays of integers) and some other stuff. Changing the representation of booleans required adjustments in parts of the language that deal with booleans: lazily evaluating logical `and`s and `or`s, `if` statements, `while` loops, etc.

    Anyway, after the refactoring I think the code is a bit cleaner. And once I had this, fixing the bug was literally a two line change (plus imports and tests).

    Also found and fixed another bug: When importing two submodules of the same top-level module (e.g. `import mymodule.submodule_1; import mymodule.submodule_2`) the second import statement used to fail because the name `mymodule` already existed (was already taken) in the code doing the imports (it was created by the first import statement).

    And then there was a third bug I introduced in the refactoring of the representation of ints. It lead to `-some_unsigned_int` to be treated as another unsigned int (rather than a signed one) in some regards. My test case converting `-9223372036854775808` (the minimum value a signed 64 bit integer can hold) to a string caught it.

    On a side node: I'm so grateful I started writing lots of test cases for this project. The amount of bugs they've caught that would have gone unnoticed otherwise is worth a million. Always write test cases if you care about your software.

    #RocketLang

  9. Today I committed and pushed my code implementing "for" loops for my pet #programminglanguage, #rocket.

    So, if an object has an `__iter__` method and the object returned by that has a `__next__` method, you can now write:
    ```
    for x in my_object:
    ...
    ```

    So far I haven't implemented the `else` branch (that's supported by #Python), because I'm not sure how useful/common that actually is.
    Also, so far the syntax is limited to a single variable, unpacking something (e.g. `for i, x in enumerate(my_object)`) is not yet supported.

    I feel like half of the effort/code went into handling the new variable `x`. It can optionally be declared with a type (e.g. `for x: int64 in my_object`), in which case it will be treated as a *new* variable (that exists only inside the loop). If you *omit* the type, it imitates the behaviour of an assignment statement (i.e. `x = ...`).
    In that case, it either
    - uses an existing variable or
    - creates a new variable or
    - throws a `TypeError`.

    While writing test-cases I also discovered a bug elsewhere in my code:
    Two of my new test cases (for the for loops) check that the for loop can work with `__iter__` and `__next__` methods of a different type. E.g. if you have a type `A`, type `B` inherits from type `A`, and the `__iter__` method was declared with a parameter of type `A` (rather than `B`), you should still be able to write `for x in B(): ...`, because the object of `B` is also an instance of `A`, so you should be able to call `__iter__` with a parameter of type `B`, too.
    (As another example, `__iter__()` could have an optional, second parameter, and it should still work in a for loop.)

    Anyway. While writing these test cases, I discovered the bug that (currently) you cannot actually declare a variable with a fixed type if that type is a `protocol`. The reason is that so far my code assumed that every type has a *default value*. (E.g.: the default value of ints is 0, the default value of boolean is `False`.) However, that doesn't make sense for *protocols*, since
    a) there's no guarantee there is a type implementing the protocol when the variable is created (there's not even a guarantee there will ever be a type implementing the protocol) and
    b) even if there where many types implementing the protocol, it wouldn't make sense to arbitrarily choose one of them and initialise the variable with the default value of that type. That would be completely random guesswork.

    I guess I'll have to drop the assumption that every type has a default value, but...
    That was a nice assumption, because it made life *so much* easier.
    Consider for example creating an array of type `T`. If `T` has a default value, there's no issue, you just allocate the array and initialise all the items in the array with the default value, then let the programmer do with it as (s)he pleases. There is no risk the programmer might use uninitialised memory. If `T` does *not* have a default value... You'd have to make sure that every item in the array gets initialised *by the programmer*.
    But what if the programmer wants to create an `ArrayList[T]`? In that case the programmer *naturally* wants to over-allocate a bit, e.g. create an array (backing the list) with space for 16 (or so) elements, even when the list doesn't actually contain any items, yet. Again, if `T` has a default value, you can handle it safely, just initialise all the array items immediately after allocation. If `T` doesn't have a default value, you can't really initialise the array items when allocating the array, you have to rely on the programmer to do so. But then you want to make sure that every no array item is *read* before it has been initialised *by the programmer*, *somehow*. (I don't really have an idea how.)

    #programming #programminglanguages #RocketLang

  10. My pet programming language, #Rocket, now supports a simple "raise" statement by which you can raise exceptions.

    E.g.:
    ```
    raise StopIteration()
    ```

    The parenthesis can be omitted, too, in which case a new instance of the exception will be created with no parameters.

    So far, exceptions could only be thrown by built-in functionality such as integer division (e.g. `1 \\ 0`). To make exception properly accessible in user-written code, I had to replace some "language magic" to make stuff properly accessible in the code.

    #programming #programminglanguage #programming_languages #programminglanguages #RocketLang