/kilite

Lightweight Kinx but more flexible, portable, and faster.

Primary LanguageCMIT LicenseMIT

Looks like JavaScript, feels like Ruby, and it is a script language fitting in C programmers.

Kilite

This is like a lightweight Kinx but more flexible, portable, and faster. Mainly, we will provide the following features for your help.

  • To make it flexible, portable, and faster than Kinx.
    • This program is a successor of the Kinx project, so I'll support most of Kinx features.
    • Moreover, the performance or various points will be improved.
  • To output a C source code to make an executable from that.
    • This is a new feature not in Kinx.
    • This feature will be helpful to make the executable from dynamically typed language script.
    • But you need another C compiler to make it.

Motivation

Kinx is very useful for me. It's a really important project for me and actually I love it. However, unfortunately I feel now I have some difficulities to improve it more at least as long as it is. This means I'd need to remake it from its architecture if I wanted to make it improve more.

That's why I decided to try to making a new design of a programming language that I really wanted with a new architecture, athough I am going to keep using Kinx as well also in future.

Usecases

There are some usecases that I want to try. It would be roughly 4 patterns as below.

  1. To run the script as it is on the fly.
  2. To output C source code to make an executable.
  3. To generate the executable from your script directly by calling another compiler intrnally.
  4. To compile the script separatedly and to run the code with those precompiled codes.

Running the Script

This will work with the current version.

$ ./kilite file.klt

Outputting C Code and Making the Executable

$ ./kilite --cfull file.klt > file.c
(file.c)
$ gcc -o file file.c -L. -lkilite -lm
(file)
$ cl /Fefile.exe file.c kilite.lib
(file.exe)
$ ./file

Note that you need the actual compiler like gcc on Linux, cl.exe on Windows, or something to make an actual executable. Instead, you can use the compiler you want.

Generating the Executable Directly

$ ./kilite -X file.klt
(file or file.exe)
$ ./file

Note that, also in this case, you need the actual compiler like gcc on Linux, cl.exe on Windows. It's cl.exe on Windows and gcc on Linux by default, and you could also use the MinGW gcc with the option --cc=gcc on Windows environment.

Compiling the Script Separatedly

Note that this is not implemented yet.

$ ./kilite -c file1.klt
(file1.kc)
$ ./kilite -c file2.klt
(file2.kc)
$ ./kilite file1.kc file2.kc

Benchmark

Now, I did benchmark with some script languages because even the current version of kilite can run the code like a fibonacci. That's why I'll show it below. The target program is the 38th result of a fibonacci as usual.

On Windows

Version Time Result Remark
C (-O3) gcc 8.1.0 0.14s 39088169 Simple C code.
Kilite(C compiled) (beta/gcc MinGW 8.1.0) 0.17s 39088169 Compiled the output from Kilite.
C (-O2) VS2019 0.18s 39088169 Simple C code.
C (-O2) tcc 0.9.27 0.22s 39088169 Simple C code.
Kilite(C compiled) (beta/cl VS2019) 0.22s 39088169 Compiled the output from Kilite.
luajit 2.1.0-beta3 0.35s 39088169 -
PyPy 7.3.9 0.40s 39088169 -
Kilite(C compiled) (beta/tcc 0.9.27) 0.53s 39088169 Compiled the output from Kilite.
Kinx(native) 1.1.1 0.57s 39088169 -
Kilite (beta) 0.68s 39088169 -
Lua 5.4.4 2.59s 39088169 -
Ruby 3.1.2p20 4.02s 39088169 -
Kinx 1.1.1 5.18s 39088169 -
Python 3.11.0 5.94s 39088169 -

On Linux

Version Time Result Remark
C (-O3) gcc 11.2.0 0.062s 39088169 Simple C code.
Kilite(C compiled) (beta/gcc 11.2.0) 0.110s 39088169 Compiled the output from Kilite.
luajit 2.1.0-beta3 0.318s 39088169 -
PyPy 7.3.9 0.330s 39088169 -
Kinx(native) 1.1.1 0.389s 39088169 -
Kilite (beta) 0.622s 39088169 -
Lua 5.4.4 2.526s 39088169 -
Ruby 3.0.2p107 2.576s 39088169 -
Kinx 1.1.1 4.657s 39088169 -
Python 3.10.6 7.461s 39088169 -

Result

There is some differences between on Windows and on Linux, but luajit and PyPy was very fast and amazing. That's exactly JIT as expected. As for Kilite, this is almost the result that I wanted, but it could be slower by increasing the code in the future. I will try to keep the perfrmance even if the code would be more complex.

Source Code

The source codes for each language are shown below.

Kinx/Kilite

Kilite is now already supported the System.println method as well as Kinx. And when I outputted the C source code and compiled it, that was super fast.

function fib(n) {
    if (n < 2) return n;
    return fib(n - 2) + fib(n - 1);
}
System.println(fib(38));

Kinx(native)

Kinx native was super fast, but we have to specify the native everytime.

native fib(n) {
    if (n < 2) return n;
    return fib(n - 2) + fib(n - 1);
}
System.println(fib(38));

C

This is just a simple C code.

#include <stdio.h>

int fib(int n) {
    if (n < 2) return n;
    return fib(n - 2) + fib(n - 1);
}

int main()
{
    printf("%d\n", fib(38));
}

Ruby

Ruby is now much faster than before.

def fib(n)
    return n if n < 2
    fib(n - 2) + fib(n - 1)
end
puts fib(38)

Lua/luajit

Lua is almost same as Ruby about the performance, but luajit is amazing and super fast.

function fib(n)
    if n < 2 then return n end
    return fib(n - 2) + fib(n - 1)
end
print(fib(38))

Python/PyPy

Python was slow, but PyPy was very fast.

def fib(n):
    if n < 2:
        return n
    return fib(n-2) + fib(n-1)
print(fib(38))

TODO

I will note the followings as I don't forget it.

  • Support almost all basic functionalities first.
    • Main structures of source code.
      • Make the Fibonacci benchmark work.
      • Make the factorial recursive function work with big integer.
      • All basic comparisons including <=>.
      • && and || as a shortcut operator.
        • &&= and ||= as a shortcut operator.
        • ?? as a shortcut operator.
        • ??= as a shortcut operator.
      • Binary operations.
      • String operations.
      • Array operations.
      • Object operations.
      • Range operations.
        • Fiber class.
          • Coroutine with yield.
          • Return value from yield.
        • Enumerator module.
        • Range class.
          • Range for integer.
          • Range for string.
          • Range for others.
        • Range index for String, Binary, Array, and Range.
        • Range in the case condition.
        • Range in for-in.
      • Support a switch-case statement.
      • Support the loop like for, while, and do-while.
      • for-in for Array.
      • for-in for Object.
      • for-in for Range.
      • if modifier.
      • break and continue.
        • with label.
      • Anonymous function in an expression.
      • Easy print method.
    • Namespaces, classes, modules, functions, inheritance, and mix-in mechanism.
      • Namespace works.
      • Class definition.
        • Call the initialize method when creating the instance if the method exists.
        • Support the instanceOf method.
        • Operator override like public ==.
      • The new operator.
      • Module definition.
      • The mixin statement.
      • @ is the same as the shorten word of this..
    • enum/constant variable
      • Value propagation from constant value.
        • Integer
        • Double
        • String
    • Exception.
      • throwing an exception.
      • Stack trace.
      • try, catch, and finally.
      • MethodMissing for global scope.
      • MethodMissing for class methods.
    • Special object.
      • Integer
      • Double
      • String
      • Binary
      • Array/Object
    • Regular expression.
      • The regex literal of /.../
      • Operator =~ and !~.
  • Notes for a special specifications of the language.
    • Formatter object.
    • The last argument of function call could be outside arguments list if it's a block.
    • Destructuring assignment.
      • Pattern matching in assignment.
      • Destructuring assignment in function arguments.
    • case-when expression.
    • Pipeline operators like |>.
    • __FILE__, __LINE__, __FUNC__.
    • __END__.
    • Raw String Text.
      • %{ ... }
    • System
      • System.sleep
      • System.try
      • System.halt
      • System.abort
    • Datetime class.
    • Split operators.
      • For Binary
      • For Array
      • For Object
  • Libraries support.
    • Useful Libraries like below.
      • File
        • Static methods like File.mkdir.
        • File.open/print/printf.
        • Other operations.
      • Zip
        • Simple Zip operation.
        • Load data into binary.
        • Encryption.
      • Xml
        • Simple XML Parser & Simple DOM operation.
        • Node creation.
        • XmlWriter.
        • Node insertion, deletion, or modification to DOM.
        • XPath
      • JSON
        • JSON.parse()
      • PDF
        • harupdf
  • Type system.
    • Type validation system.
    • Output control with like optimizations.
  • Others.
    • Direct calling a C code.
    • REPL.
      • eval().
        • Note that this method can be used only in the scripting mode. It means it will be difficult to use in the translated C code and compile it.
      • REPL environment itself.
    • Debugger.

There could be something I've forgotten. I'll add it when I remember it.

In addition, the followings are the task list for the current implementation.

  • Remaining items.
    • Error recovery during parsing like a panic mode.
    • Error report for reassigning a value into a variable of const.

Others

Goal

Below is the goal I wanted to achive. Some of them has been already done by Kinx, but some hasn't.

  • Flexible
    • Useful gradual typing.
      • You can use it as a dynamically typed language, like you know duck-typing.
      • However, you can use it also as a statically typed language for safety.
    • Scripting and compiling.
      • You can run it on the fly very easily.
      • On the other hand, you can compile it before running it.
  • Portable
    • The code will be run on several platforms like Windows, linux, and so on.
  • Faster
    • I don't know how faster it is even if I tried to remake it, but I will challenge it.

Design Concept

To meet the goal, I will try with followings.

  • As a backend, MIR will be used.
    • This is an amazing JIT engine.
      • Optimizations including a function inlining are highly available.
      • Execution from C source code is also already supported.
    • Various platforms are supported.
      • Not only x86_64, but also PPC64, aarch64, etc.
    • It is easy to hold the code as a library, like it's a .bmir file.
      • Compiling separatedly, you can execute it all together later.
      • You can write the library by C language if you need, and use it as a .bmir as it is.

native

Note that native keyword will be no longer supported because Kilite will work automatically with a similar way thought it's a little different. For example, as you looked at above, the Kilite(beta-x) is working with a specialized method for 64bit integers and it has been already optimized. It's like native in Kinx, but we don't have to use some keywords like native and it will work automatilly as we write it naturally.