-
Safe Integers
-
Unit System
-
Hidden Context Arguments
-
Universal Call Syntax.
-
elaboration phase
-
language syntax is extendable
-
inline assembler
-
compile time reflection, types are values.
-
hidden dependency injection, both functions and structs.
-
enum only has named-values, it is not an integer.
-
function overloading, on both arguments (type and non-type) and return type.
All operations on integers will either:
- always result in a mathematically correct result, or
- the operation may throws an error that must be caught locally.
The int
template type automatically scales to fit the full
result of an operation:
fn foo(x : int[10..=20], y : int[2..=4]) {
// return type is infered as int[20..=80]
return x * y
}
int
is the basic integer type and the result of integer literals.
The int
with proper ranges are compatible with both signed and
unsigned integers with C and C++.
The second integer type is long
, this integer dynamically scales in size
and is allocated on the heap, and includes SIO (Short Integer Optimization).
Real, rational and decimal types can be tagged with a unit, which is used for dimensional analysis.
let speed = 100.0 (km/h)
let duration = 15.0 min
let distance = speed * duration // 25.0 km
fn convert(length : real #iso.length, dpi : real #(screen.length/iso.length)) -> real #screen.length
{
return length * dpi
}
Hidden Context Arguments
Context arguments reduces the need for global variables in many uses. It makes it easy to inject context in unit-tests as well.
fn foo(x) {
// $y is used, which makes foo() require $y to be passed in.
return x + $y
}
fn bar(x) {
// $y is required by foo(), so bar requiress $y passed in.
// $y is automatically and invisibly passed into foo()
return foo(x)
}
fn qux(x, y) {
// qux() does not allow $y to be passed in.
// Pass in $y explicitly into bar.
return bar(x, $y=y)
}
Functions and member functions may be called in two different ways:
foo(a, b)
a.foo(b)
The compiler will automatically clone and update git repositories that
are imported with the import git
statement. Repositories are
cloned into the _hkdeps
directory, by recursively scanning the
repositories.
Directory structure within a repository is free-form; the compilation order is determined after scanning the prologue of each module (a file).
Certain languages have a separate elaboration phase during compilation.
In this phase the compiler will fill in all:
- type and non-type arguments of templates.
- global variables.
- Guaranteed full constant folding and function elimination.
The process basically does full constant folding by executing functions until no more functions can be executed.
It is a reportable error if any expression is left of which the value needs to be known for the next compilation phase.
Note: all functions by default can be executed at compile time. Note: full constant folding allows generic/templated functions without a special syntax. Note: since low level functions are written as inline-assembly, inline-assembly must be executable during compile time, possibly using an interpreter/JIT.
The language allows you to define custom operators. This is done by registering a a keyword or pattern-syntax, arity, precedence and associativity, and a function that will be called when the operator is used.
The language allows you to define custom literals. This is done by registering a suffix-keyword and a function that will be called when the suffix is used with a literal.
The literal is passed to the function as a string, and the function determines the returned value and type. Since the function is called at compile time, it can dynamically create both the value and the type.
Inline assembly is supported through the llvm
directive. This allows you to write low-level
LLVM assembly code directly in your source code. The inline assembly may even be executed during
the elaboration phase.
function foo(a : __i32__, b : __i32__) -> __i32__
{
var c = a &+ b
var r = 0 : __i32__
llvm {
%tmp = mul i32 %c, %a
store i32 %tmp, i32* %r.addr
}
return r
}
The language treats types as if they are first-class values. This means types themselves are of a meta-type, recursively.
Since types are values, you can interrigate and manipulate types at compile time. This allows you to write generic code that can work with any type, and even create new types at compile time.
Meta-types are defined as built-in, by the standard library, and can be extended by the user.
There is macro system that allows you to match with tokens and replace it with a different expression.