It will be Smalltalk like language, but statically typed.
Current backend is Kotlin, because you get 4 backends for free - JVM, Native, JS, Wasm, also ecosystem is rich. A lot of pet-project languages are translated into js, which is very high-level, so why not be translated into a real language.
I haven't decided on the final name yet, so far I've chosen niva because my 2 favorite static languages are nim and vala.
P.S. u can find binary releases in the releases
After running compile.sh you will get niva_compiler folder that contains jvm or native binary.
sh compile.sh jvm
- run compiler from bin folder
- install graalvm
yay -S jdk21-graalvm-bin
and set it default:sudo archlinux-java set java-21-graalvm
on Arch,nix shell nixpkgs#graalvm-ce
on nix sh compile.sh bin
Niva can eat .niva and .scala files, because Scala highlight fits well for Niva :3
niva main.niva
- compile and run
niva main.niva -с
- compile only, will create binary for native target and fat-jar for jvm
niva main.niva -i > info.md
- will generate info about all code base of the projects, -iu
- only user defined files
niva run
- run all files in current folder with main.niva as entry point
niva build
- produce binary
VS Code plugin for syntax highlighting
To get started with Niva, use the provided shell.nix file.
Navigate to the main Niva repository. Enter the following command:
nix-shell
This command will set up the necessary dependencies and run the compile script to produce a binary file.
Afterwards, you can run the Niva compiler with the following command:
./niva_compiler/niva <file>
Almost everything in this lang is message send(function call), because of that there are 3 ways of doing it(don't worry, none of them requires parentheses).
Everything is sending a message to an object.
There no such things as function(arg)
, only object message
"Hello world" echo // print is a message for String
You can send as many messages as you want
"12.08.2009" asDate days echo
// 12
Okay, but what if the message has some arguments?
Just separate them with colons, this is called a keyword message:
obj message: argument
1 to: 5 // oh, we just created nice range syntax
And what about many arguments?
Easy
1 to: 5 do: [ it echo ] // 1 2 3 4 5
aand we don't need things like hardcoded for/while/do_while loops in language anymore, the second argument here is a code block.
Here some more examples:
5 factorial //unary
5 + 5 // binary (only math symbols allowed)
map at: "key" put: "value" // keyword
Niva is statically typed language, so we need a way to declare custom types, here it is, just the syntax of keyword messages with type keyword.
Each type automatically has a constructor that represents as the same keyword message, isn't it beautiful?
// Declare type Person with 2 fields
type Person name: String age: Int
// Instantiation
person = Person name: "Bob" age: 42
To declare method for type just type Type function_signature = body
// unary method declaration for Person receiver
Person sleep = [...]
// method call
person sleep
// with arguments(keyword message)
TimeInterval from: x::Date to: y::Date = [
// using x and y
]
In the last example you can see a problem, we dont need local names for from and to. Because of that there are another way to declare keyword message:
Random from::Int to::Int = ... // `from` and `to` are local arguments
All methods are extension methods(like in Kotlin or C#), so you can add new methods for default types like Int
You can also define many methods at once
extend Int [
unary1 = []
unary2 = []
]
// instead of
Int unary1 = []
Int unary2 = []
1 < 2 ifTrue: ["yes!" echo]
1 > 2 ifFalse: ["no!" echo]
// `ifTrue:ifFalse:` is single keyword message with 2 arguments and boolean receiver
1 > 2 ifTrue: [...] ifFalse: [...]
// ifTrue:ifFalse: can be used as expression
x = 42 < 69 ifTrue: [1] ifFalse: [0] // x == 1
// Loops
{1 2 3} forEach: [it echo] // 1 2 3
1..3
// single expression
1 < 2 => "yes!" echo
// with body
1 < 2 => [
...
]
// with else
1 > 2 => "yes" echo |=> "no" echo
// as expression
x = 1 > 2 => "yes" |=> "no" // x == "no"
// multiline switch
x = 1
| x
| 1 => "switch 1" echo
| 2 => "switch 2" echo
|=> "what?" echo
// single line switch
m = |x| 1 => x inc | 2 => x dec |=> 0
// m == 2 because of x inc
You can think of that as pattern matching on true(_
) the first expression that matches will evaluate then expression.
The ball of execution flow seems to fall from above along the flow of execution onto this stick, and rolls out of one of the conditions
// multiline
_
| x > 5 => 1 // if
| x > 4 => 2 // elif
| x < 4 => 3 // else
|=> 0
single line
y = _| x > 5 => 1 |=> 2
factorial
is a message for Int
type, that returns Int
.
self
is context-dependent variable that represents Int on which factorial is called.
The whole function is one expression, we pattern matching on self with |
operator that acts as swith expression here.
// switching on this(Int receiver)
Int factorial -> Int = | this
| 0 => 1
|=> (this - 1) factorial * this
5 factorial echo
//
Int fib -> Int = _
| this < 2 => 1
|=> (this - 2) fib + (this - 1) fib
5 fib echo
Int isEven = [
| self % 2 == 0 => true
|=> false
]
5 isEven echo
4 isEven echo
There 3 types
5 factorial //unary
5 + 5 // binary (only math symbols allowed)
map at: "key" put: "value" // keyword
This feature is directly from smalltalk. Its the same as Clojure doto or Pascal With Do or Kotlin with, many langs has something like that:
;
is cascade operator, that mean the message will be send to the first receiver, not to the result of the previous one
c = {}. // empty list
c add: 1; add: 2; add: 3
is the same as
c = {}.
c add: 1
c add: 2
c add: 3
That is, imagine that you have an add function that takes 2 numbers and returns their sum C lang
int add(int a, int b) {
return a + b;
}
If you chain functions like this
add(1, add(2, add(3, 4)))
you will get 1 + 2 + 3 + 4
and if you wanna apply all functions to the same value you will need:
int a = 0;
a = add(3, 4);
a = add(a, 2);
a = add(a, 1);
Lest imagine its Java, and int itself has add method then:
int a = 0;
a = a.add(1).add(2).add(3).add(4);
will give you 1, because each call after add(1)
was applied to the result of the previous call, and not to the original variable a.
So to get fluent desing in Java like languages you need to return this or new instance of this on each method.
So the same on niva will looks like:
Int add: b -> Int = [
self + b
]
a = 0 add: 1; add: 2; add: 3; add 4 // 10
This is very convenient for chaining any calls. Since setting field values is also a message, you can do this:
person = Person name: "Alice" age: 42
person name: "Bob"; age: 43 // send message name and age to person
boxWidget = Box width: 40 height: 50
boxWidget
add: Label text: "hello";
add: Button text: "press me";
height: 100;
You can create code block like this:
block = ["hello from block" print]
To evaluate block send value message to it:
block value // hello from block printed
Block with arguments:
block::[Int -> Int] = [x -> x + 1]
block value: 1 // 2
Many args -> many value messages:
block::[Int, Int, Int -> Int] = [x y z-> x + y + z]
block value: 1 value: 2 value: 3 // 6
If you have a better ideas how to send many arguments to blocks please make an issue. I have anoter variant in mind with named args
block::[Int -> Int] = [it + 1]
block it: 1 // 2
block x: 1 y: 2 z: 3 // 6
Declaration:
union Shape area: Int =
| Rectangle => width: Int height: Int
| Circle => radius: Int
rectangle = Rectangle area: 42 width: 7 height: 6
rectangle area echo
rectangle width echo
rectangle height echo
Both Rectangle and Circle has area field. Every branch is usual type, so you can create them separately.
Rectangle area: 42 width: 7 height: 6
Every branch here has an area field.
I almost stole Rich Hickey's syntax, I hope he won't be offended ^_^
listLiteral =
| "{" spaces "}" -- emptyList
| "{" listElements "}" -- notEmptyList
listElements = primary whiteSpaces (","? spaces primary)*
hashSetLiteral =
| "#{" spaces "}" -- emptyHashSet
| "#{" listElements "}" -- notEmptyHashSet
As you can see from that grammar, commas between elements are optional.
list = {1 2 3 4}
list add: 5 //! {1 2 3 4 5}
list at: 0 //! 1
list copy at: 0 put: 5 //! {5 2 3 4 5}
list //! {1 2 3 4 5}
x = #{"sas" 1, "sus" 2}
x at: "ses" put: 3
set = #{1 2 3}
set add: 2 //! #{1 2 3}
set add: 4 //! #{1 2 3 4}
set has: 3 //! true