Paradigms:
- Functional
- Object oriented
- Contract oriented
- Modular
- Imperative
- Concurrent
- Multi-paradigm
Typing:
- Static
- Strong
- Inferred
- Memory safe
- Safe type
Philosophy:
- Simplicity
- Two ways to program (Object v Functional) (e.g. you cannot use
.
(reserved to object programming) to chain function in Functional Lily, you must only use|>
) - Performance
- Low level programming access
- Safe memory and safe type
- No (explicit) lifetime
Type | Description |
---|---|
Int8 | A signed 8-bit integer |
Int16 | A signed 16-bit integer |
Int32 | A signed 32-bit integer |
Int64 | A signed 64-bit integer |
Int128 | A signed 128-bit integer |
Isize | A signed pointer sized integer |
Uint8 | An unsigned 8-bit integer |
Uint16 | An unsigned 16-bit integer |
Uint32 | An unsigned 32-bit integer |
Uint64 | An unsigned 64-bit integer |
Uint128 | An unsigned 128-bit integer |
Usize | An unsigned pointer sized integer |
Float32 | A 32-bit floating point |
Float64 | A 64-bit floating point |
Bool | Like i1 in LLVM IR (0 or 1 ) |
Never | Like _Noreturn in C |
Unit | Like void in C |
Any | Can take any type (to be used only in unsafe mode) |
Type | Description |
---|---|
CShort | Like short in c |
CUshort | Like unsigned short in c |
CInt | Like int in c |
CUint | Like unsigned int in c |
CLong | Like long in c |
CUlong | Like unsigned long in c |
CLonglong | Like long long in C |
CUlonglong | Like unsigned long long in C |
CFloat | Like float in C |
CDouble | Like double in C |
CStr | Like char* in C |
CVoid | Like void in C |
Type | Description |
---|---|
Str | A string slice |
Bytes | A string (Uint8) slice |
Self | Like Self in Rust |
Object | Used to talk about child of Self |
Char | A single character |
Byte | A single character (Uint8) |
fun add(x, y) = x + y end
fun sub(x, y) = x - y end
fun mul(x, y) = x * y end
fun div(x, y) = x / y end
fun add(x, y Int32(...2)) =
// ...
end
module X =
// ...
end
type Person record =
name Str;
age Uint8;
end
type Level enum =
A;
B;
C;
end
type PersonName alias = Str;
object Person class =
val name Str :: get, set;
val age Uint8 :: get, set;
pub fun new(name Str, age Uint8) =
@.name := name
@.age := age
end
end
You can add an instruction to generate a get
or set
or both for this property.
val name Str := "Hello" :: get, set;
Create static property with global val
.
global val name Str
The
close
keyword means that the class or trait cannot be added as an inheritance to another class.
// ...
close object Cat inherit Animal in class =
end
The
Object
type is used to talk about any objects.
fun add(x Object) = return x; end
The
Self.Object
type is used to talk about any objects implemented or inherited in the current object.
// ...
object Cat inherit Animal in class =
fun get_animal(x Self.Object) = return x; end
end
The
Self
type is used to talk about self definition.
Self.call()
object Sum trait =
fun sum(Int32) Int32;
end
object Human trait =
val name Str;
fun get_name() Str;
end
object impl Human in Work record =
name Str;
end
// Implements some function
pub fun@Person get_name(self) = self.name;
pub fun@Person get_age(self) = self.age;
pub fun@Person set_name(mut self, name) = self.name = name;
pub fun@Person set_age(mut self, age) = self.age = age;
pub fun@Person@Human get_name(self) = [email protected]; // can use object accessor in name
pub fun@Person get_name(self) = self.name;
pub fun@Person new(name Str, work_name Str) =
@[email protected] := name
@.name := work_name
end
object Letter enum =
A;
B;
C;
D;
end
pub fun@Letter to_string(ref self) =
match ref self do
A:$ => "A";
B:$ => "B";
C:$ => "C";
D:$ => "D";
end
end
The class can take an inheritance and an other class can take this class in inheritance, but the record cannot inherit of anything, but in other class can take it in inheritance. Can only take a trait as implemenetation.
I think it's important to implement that in record object, enum object or class, because it's an avandage to implement that in a programming language.
@+Derive("Eq")
object App class =
end
Test
Skip
Allow
Warn
Deny
Forbid
Deprecated
MustUse
Link
Repr
Main
NoOverload
Align
AlwaysInline
Builtin
Cold
Hot
Immarg
NoAlias
NoBuiltin
NoCallback
NoInline
NonNull
NoRecurse
NoReturn
NoSync
NoUnwind
ReadOnly
ReadNone
Speculatable
WriteOnly
WillReturn
Inline
TargetFeature
InstructionSet
Doc
NoBuiltin
NoStd
NoCore
NoSys
Os
Arch
pub error BadArgument:Str;
pub fun err() = raise BadArgument("failed");
fun main =
try do
err()
catch err do
()
end
end
pub macro create_a_function($name id, $d dt) = {
pub fun {|name|}(x {|d|}) = x;
};
create_a_function!(add1, Int32)
create_a_function!(add2, Int32)
create_a_function!(add3, Int32)
- id -> identifier:
x
- dt -> data type:
Int32
- tk -> token
- tks -> tokens:
${x Int32}
- stmt -> statement:
val x := 32;
- expr -> expression:
x + 230
- path -> path:
X.Y.Z
- patt -> pattern:
_
- block -> block:
@{ () }
NOTE (rust compiler rules):
here are additional rules regarding the next token after a metavariable:
- expr and stmt variables may only be followed by one of: => , ;
- ty and path variables may only be followed by one of: => , = | ; : > [ { as where
- pat variables may only be followed by one of: => , = | if in
- Other variables may be followed by any token.
module X do
end
// also
module X.D do
end
import "@std";
import "@std.io.*" as io;
// useful when writing a prelude
import "@std.io.write"?;
import "@std.io.*" as io;
module X =
use io.x.y.z; // like using namespace
// z.
// ...
end
module X =
end
module Y =
include X
end
package =
// [pub] .<identifier_normal.<identifier_normal... | identifier_string>
end
package =
pub .a;
.b;
.c;
.@"d";
end
val A Int32 := 30;
pub val (A Int32, B Int32) := (340, 400);
fun add(x, y) when [x < 0] = x + y;
// or
// fun add(x, y) when [x < 0] = x + y;
fun add(x, y) = raise InvalidArgument("error")
or
when $x > 0:
fun add(x, y) = x + y;
when $x < 0:
fun add(x, y) = y;
fun add(x, y) = 0;
NOTE: The dollar sign tells the compiler that the symbol it is looking for is a parameter so it must wait before analysis it.
fun div(x, y) req [y not= 0] = x / y;
fun add(x, y) req [x < 0] = x + y;
If the req
condition failed is emit an exception at compile time if is know the value in otherwise is emit a exception at runtime.
If the when
condition failed is go to the next function (with the same name) until is find a condition with successfull condition. If is not find a sucessfull condition an error at compile time will emit. Also when
can take overloading.
If the value provide to a literal expression, the resolving of contexpr at compile time is possible, but if the value provide to a function call or method call or other way, the compile time resolving will no be possible. In this case the compiler waiting to resolve this value at runtime.
The usage of undef
value will be only using at initialization of value. In otherwise the compiler was emit an error to the base usage of undef
value.
The usage of nil
value will be only expected in case with unsafe operation. In other case the compiler will must use of optional value.
fun call_it() = "hey";
module X.Y.Z =
fun call_it() = "hey";
fun hey() = global.call_it()
end
fun main =
val x Int32 := 30;
val y Int32 := 100;
end
val x := ?30;
With
!:
unary operator you can block the raising of value, to transform your exception data type in result data type.
Int32 raise Error become <Error>!Int32
match !:run() do
@ok(_) => ();
@err(_) => ();
end
The difference with the exception is that the result does not propagate the error to other functions. Also Result is better in a situation where memory consumption is a concern. Moreover, when you don't specify an error type for result, it implicitly passes the Error type, which accepts all error types, so the compiler will infer on result's error types.
!?<expr>
The exception becomes a result data type and unpacks the values to return an optional type. If the value is Ok, it returns that value directly, otherwise it returns none.
example:
Ok(value) become value
Err(value) become none
!!<expr>
The exception becomes a result data type and unwrap the Ok value, and if an error is caught, it is returned directly.
!:<expr>
The exception becomes a result data type (or vice versa). So, if we have a function that returns an Int32 and raises an error, the result data type becomes a result (Error!Int32).
?<expr>
The Ok value is unwrap and the Error value becomes none.
!<expr>
The Ok value is unwrap and the error value is returned.
- Check class
- Check trait
- Check enum
- Check record
- Check function
- Check macro expands
- Check module
- Check name conflict
- Lookup for data type
- Check implementation
- Check inheritance
- Check property conflict
- Check property data type
- Check method
- Check constructor
object impl Add in Value class =
// ^^^ -> check if is a trait
end
- Check if data type is a trait/exists.
- Check if methods are well implement.
object inherit Human in Worker class =
// ^^^^^ -> check if is a class/record
end
- Check if data type is a (class/record)/exists.
- Check if in constructor the inherit class or inherit record is well constructed.
object inherit [Human, Abc] + impl [Debug, Def] in Worker class =
// ...
end
object Foo class =
val name Str;
// ^^^^ -> check name
val name Int32;
// ^^^^ -> check name
end
object Foo class =
val name AA;
// ^^ -> check data type
end
object Foo class =
val name Str;
fun get_name(self) = self.name;
end
Constructor is any time named new
. Also you can overload the constructor.
object Foo class =
fun new() =;
end
fun main() =
val a Foo := Foo.new();
drop: val b *Foo := Foo.new();
end
fun main =
for i in 0..10 do ();
mut i := 0;
while i < 10 do i += 1;
end
fun main =
mut a := 30;
if a < 4 do
a += 40
elif a > 10 do
a -= 5
end
end
fun main =
val x := true;
val y := 10;
match x do
true ? y > 10 => true;
false ? y < 10 => true;
_ => false
end
end
Static array
fun main =
val arr [3]Int32 := [1, 2, 3];
end
Undetermined array size.
- Variant size at compile time.
- Static size at runtime.
NOTE: It's like va_arg
in C.
fun main =
val arr [?]Int32 := [1, 2, 3, 4];
end
Dynamic array
fun main =
drop: val arr [_]Int32 := [];
Array.append(ref arr, [1, 2, 3]) // append new elements
end
Multi Pointers (array)
- Cannot dereference that
NOTE: only available in unsafe mode
fun main =
val arr [*]Int32 := [1, 2, 3, 4, 5]; // [*]Int32 it same than Int32* in C
end
// {<dt>}
fun main =
val list {Int32} := {1, 2, 3, 4, 5}; // Create a simple list
mut start ?*Int32 := none;
mut @"end" ?*Int32 := none;
match list do
{s, .., e} => @{
start = ref s
@"end" = ref e
}
_ => ()
end
end
fun main =
val tuple (Int32, Int32) := (1, 2);
val x := tuple#0;
end
fun main =
val f := fun (x Int32) -> x;
end
I think move
value is better than copy value because it avoid to write program with a poor efficently in memory.
- Doesn't allow null value in safe mode
- Ref on pointer (copy of pointer (in fact is not a dropable pointer))
fun main =
drop: val x *Int32 := Ptr.new(20); // you must specifie drop except when you precise to the compiler that the drops are automaticly manage by the compiler
val y *Int32 := nil; // error in safe mode
unsafe =
val y *Int32 := nil; // ok in unsafe mode
// The compiler emit an error if the Pointer is used with nil value
// In the runtime the compiler verify if the pointer is null
end
begin =
val y *Int32 := Ptr.new(100);
drop y // drop at this point
// after this point the compiler shadows the reference of y in this scope
drop y <~ Ptr.new(200) // reassign a drop ptr value
// after this pointer the compiler enable the reference of y in this scope and drop the pointer at the end of this scope
end
// y was drop here
end
- Check if the value of the ref is available in this scope
- Doesn't allow null value in safe mode (such as pointer)
- Cannot drop a ref
fun add(x ref Int32) ref Int32 = x;
fun main =
val x Int32 := 20;
ref: val y Int32 := add(ref x); // Int32 -> ref Int32 = Int32* in C
// the value (x) is available in this scope, so the value (y) is available
// or
// val y ref Int32 := add(ref x)
end
This syntax is better than C because the embiguity with the Pointer type is very dangerous, because the difference between int*
(pass by reference) and int*
(pass by pointer). The solution of the Lily language permits to make difference between (pass by reference and pass by pointer).
- You cannot return a reference to a local variable.
fun add() ref Int32 =
val x := 30;
ref x
end
- You cannot return a reference to function parameter
fun add(x Int32) ref Int32 =
ref x
end
- Verify if the reference value and the source value is available in the actual scope.
type Person record =
name Str;
age Uint8;
end
fun get_name(p ref Person) ref Str =
ref p.name
end
fun main =
val p := Person { name: "John", age: 20 };
ref: val name := ref p |> get_name;
// both reference values are available in the actual scope
end
- Tracing pointer cannot be droped (or in other words if the pointer is already tracing you can't drop it)
- Simplely is like a reference on pointer
- Can be dereferenced
fun add(x trace *Int32) trace *Int32 = x;
fun main =
val x *Int32 := Ptr.new(100);
val y := add(trace x);
// or
val y2 := add('x);
drop x;
end
- Move value
fun add(x Int32) = x;
fun main =
val x Int32 := 30;
val y Int32 := add(x); // the value of x is moved
val z = x; // now you cannot move `x` in z variable because it has been move previously
end
The keyword cast is used to do 2 different actions. The first one is to be able to do type conversion on primaries. And the second is to access the parent or child class of a particular type.
val a := 30; // Int32
val b := a cast Int64;
object A class
fun new() =
end
end
object inherit A in B class
fun new() =
end
end
fun main =
val b := B.new(); // B
val a := b cast A; // A
// or
val a2 := b cast @B@A; // A
end
fun main =
val a := 340;
val b *Int32 := ref a;
val c := b.*;
end
You can create extern function.
@+Link("math")
lib Math =
val PI Float64;
end
- You can use raw pointer (*T)
- You can use nil value on pointer
- You can use Any data type
auto-drop
is disabled
- All pointers are traced
- Pass by reference or by trace
- All values are moved
- All values are automatically drop
comptime <stmt|expr>
Normal generic param
T
Constraint generic param
T: Abc
List (T = [Int32, Int32, Int32]) generic param
T(...)
List mut (T = [Int32, Int64, Float32]) generic param
T :: mut(...)
List generic param + constraint
T(...): Abc
List mut generic param + constraint
T :: mut(...): Abc
You can also add a minimum of given data type
T(1...2)
T(...4)
T(4...)
+
and
+=
&=
<<=
|=
>>=
/=
**=
%=
*=
-=
xor=
=
&
|
|>
/
==
**
>=
>
<<
<=
<
%
*
not=
or
>>
..
-
xor
->
<-
[n]
[n..n]
[n..]
[..n]
@sys.write(1, "Hello\n", 6)
@builtin.max(2, 30)
@len("Hello")
@len(b"Hello")
@len([1, 2, 3])
@len({1, 2, 3})
This is used to add more security at analysis time for dropping the pointer. For example, when we use the drop operator, the pointer variable is
@hideout
, so the variable is removed from scope, so analysis will report an error because it's not found in scope (this is to add more security to memory at compile-time like the borrow checker (cheaper), but it's optional).
ms: Memory security
options e.g. --ms-0, --ms-1, --ms-2, ...
val x := 30;
// we can use @hideout for use hide outsite of the scope of the function (for ref or ptr).
@hideout(x)
// by default that's a local hide
@hide(x)
// relaod variable, at level 0, this is technically an assignment
x <~ 30
- Concurrent
- Mark and sweep
@cc =
int add(int x, int y) {
return x + y;
}
@end
@cpp =
auto add(int y, int y) -> int {
return x + y;
}
@end
- Alloc
- Arch
- Collections
- Env
- Ffi
- Fmt
- Fs
- Future
- Io
- Net
- Ops
- Os
- Panic
- Path
- Process
- String
- Thread
- Time
- Vector
- Cmp
- Copy
- Int8
- Int16
- Int32
- Int64
- Isize
- Uint8
- Uint16
- Uint32
- Uint64
- Usize
- Float32
- Float64
- Fun
- Str
- Char
- BitStr
- BitChar
- Tuple
- Array
- Unit
- Pointer
- Ref
- Slice
- Bool
- Exception
- Optional
- Never
@cc
@cpp
@sys
@builtin
@hide
@hideout
@len
alias
and
as
asm
async
await
begin
break
cast
catch
class
close
comptime
defer
do
drop
elif
else
end
enum
error
false
for
fun
get
global
if
impl
include
inherit
is
lib
macro
match
module
mut
next
nil
none
not
Object
object
or
package
pub
raise
record
ref
req
return
Self
self
set
test
threadlocal
trace
trait
true
try
type
undef
unsafe
use
val
when
while
xor