Skip to content

v0.3.0-jinx3

Pre-release
Pre-release
Compare
Choose a tag to compare
@CohenArthur CohenArthur released this 29 Dec 14:20
· 599 commits to master since this release
2f6cea7

Developer changelog

Thanks to @tanguysegarra, we now have multiple different tags over on dockerhub. If you go over to the tags tab, you'll now find the latest tag which corresponds to the latest release (or pre-release like this one) and bleeding which is updated with the most recent version of the interpreter on the default branch.

@SanderJSA worked to improve the string formatting situation and create the needed instructions during the parsing phase instead of the execution phase. Before, each string would get parsed and expanded at runtime: This is no longer the case.

Thanks to this change, we also get all other phases of the context "for free": The nodes to expand are now typechecked like any other, can use generics, andw ill get optimized once that is in.

We can also update formatting delimiters ('{' and '}') if we desire to use them by themselves: For example,

> name = "jinko"
> println("hey {jinko}")
"hey jinko"
> println("hey \{jinko\}")
"hey {jinko}"

We also now have escaped characters such as \t or \n! Meaning that we can properly print newlines from the stdlib without necessarily relying on the println builtin function.

Thanks to his work, the string interpolation feature is now complete enough that I felt confident merging it. This will make tiny scripts easier to write, without relying on multiple calls to concat(). Thanks!

@n1tram1 started working on an online REPL for the language in order to showcase it. In order to do so, @Skallwar started work on getting the jinko crate to compile with a WASM target, as well as adding an eval() function to develop easy REPLs. This meant splitting the code using rust features, to avoid platform dependent interactions such as dynamic library loading or uses of stdin and stdout. The eval() function is also now available for very easy integration of the jinko language to rust code, as showcased in the simple-repl example.

Work on generics has started in the interpreter. The changes are massive and extremely complex. I decided to focus on generic functions instead of generic types, as they are more complex but easier to test. The implementation still feels extremely fragile, and should not be relied on. The pipeline is as follows:

 ___________________
|                   |
|  TypeChecking 1   |
|___________________|
         |
         v
 ___________________
|                   |
| Generic expansion |
|___________________|
         |
         v
 ___________________
|                   |
| Generic resolving |
|___________________|
         |
         v
 ___________________
|                   |
|   TypeChecking 2  |
|___________________|

Splitting the typechecking pass in two allows for two things:

1/ Resolving generic calls and declarations after the arguments have been typechecked,
in order to generate proper versions of each declarations
2/ Declaring functions after they are being called. This is a feature of many modern
languages, and is still not present in jinko.

The type is now also cached in every instruction, to not reevaluate unecessary types during the second phase of the typechecking. This adds a lot of mutability, which I am quite sad about, but was necessary.

The generic typechecking works as follows:

If a function declaration contain generic types, it is not typehchecked during the first phase. A typical generic function looks something like this:

func call_method_on_generic[T](animal: T) -> string {
    animal.get_name()
}

type Dog;

func get_name(d: Dog) -> string {
    "doug"
}

rex = Dog;

call_method_on_generic(rex);

Since the type T does not exist, there is no point in checking that a get_name() method exists on it.

However, it makes sense for the interpreter to realize that we'd like to use the call_method_on_generic function with the Dog type, and to generate the following function declaration:

// The name comes from mangling the function name and the resolved generics. The user
// cannot directly call these mangled functions, or generate them either as they are not
// valid identifiers. But we are waaaaay paste the parsing phase when generating these, so
// the rules do not apply anymore :)
func call_method_on_generic+Dog(animal: Dog) -> string {
    animal.get_name()
}

This function can then get typechecked, and we can make sure that a get_name() method exists for the type Dog: This is just a regular function declaration!

The next step is to replace the call to call_method_on_generic(rex) with its newly generated counterpart: call_method_on_generic+Dog(rex)

The generics pass is a simple "visitor" comprised of two functions. Each instruction runs them on itself, before calling it on its subinstructions (i.e an if_else block needs to do generic expansion on its condition, its if block and its else block).

There are still a lot of issues with generic function calls and declarations, namely:

1/ No type inference. The above example would not work, as we cannot yet infer the correct call from the argument's type. This should not be hard to do, but is still unimplemented. You need to specify the generic types at all times for now, i.e: call_method_on_generic[Dog](rex)
2/ Parsing errors: We cannot call generic methods yet: rex.call_method_on_generic[Dog]() fails, while it shouldn't.

The issues haves the generic label and should not be too hard to solve. Focus should also be shifted to generic types, as they are the final step required to have a proper generic standard library and interesting types such as Maybe[T], Result[T, E] and so on.

Language changelog

  • String formatting for expressions between curly brackets
  • Character escaping
  • Generic function declarations and calls

Full Changelog: v0.3.0-jinx2...v0.3.0-jinx3