Intro ¶
Niva is a simple language that takes a lot of inspiration from Smalltalk.
But leaning towards the functional side and static typed.
Everything is still an object, but instead of classes, interfaces, inheritance, and abstract classes,
we have tagged unions, which is the only way to achieve polymorphism.
For example, everything except the declaration is sending messages to objects.
1 + 2
is not a + operator, but a ... + Int
message for Int
object.
(there are no extra costs for that)
C-like: 1.inc()
Niva: 1 inc
In essence, niva is highly minimalistic, like its ancestor Smalltalk.
It introduces types, unions, and associated methods.
There are no functions.
Install:
1git clone https://github.com/gavr123456789/Niva.git
2cd Niva
3./gradlew buildJvmNiva
4# LSP here https://github.com/gavr123456789/niva-vscode-bundle
The Basics ¶
Variable ¶
Variables are immutable by default. There is no special keyword for declaring a variable.
1// this is a comment
2int = 1
3str = "qwf"
4boolean = true
5char = 'a'
6float = 3.14f
7double = 3.14
8mutltiLineStr = """
9qwf ars zxc \n \t "qwf"
10"""
11explicit_type::Int = 5
12
13
14list = {1 2 3}
15set = #(1 2 3)
16map = #{1 'a' 2 'b'}
17
18// declare a mutable variable
19mut x = 5
20x <- 6 // mutate
Messages ¶
1// hello world is a one liner
2"Hello world" echo // echo is a message for String obj
3
4
5// There are 3 types of messages
61 inc // 2 unary
71 + 2 // 3 binary
8"abc" at: 0 // 'a' keyword
9
10
11// they can be chained
121 inc inc inc dec // 3
131 + 1 + 2 - 3 // 1
141 to: 3 do: [it echo] // 1 2 3
15// the last one here to:do: is a single message
16// to chain 2 keyword messages you need to wrap it in parentheses
17("123456" drop: 1) dropLast: 2 // "234"
18the comma `,` is syntactic sugar for the same effect
19"123456" drop: 1, dropLast: 2 // "234"
20
21// you can mix them
221 inc + 3 dec - "abc" count // 2 + 2 - 3 -> 1
23"123" + "456" drop: 1 + 1 // "123456" drop: 2 -> "3456"
24
25// everything except type and message declarations are message sends in niva
26// for example `if` is a message for Boolean object that takes a lambda
271 > 2 ifTrue: ["wow" echo]
28// expression
29base = 1 > 2 ifTrue: ["heh"] ifFalse: ["qwf"]
30// same for while
31mut q = 0
32[q > 10] whileTrue: [q <- q inc]
33// all of this is zero cost because of inlining at compile time
Type ¶
New lines are not significant in niva
Type declarations look like keyword messages consisting of fields and types
1type Square side: Int
2
3type Person
4 name: String
5 age: Int
Create instance ¶
Creating an object is the same keyword message as when declaring it, but with values in place of types.
1square = Square side: 42
2alice = Person name: "Alice" age: 24
3
4// destruct fields by names
5{age name} = alice
Access fields ¶
Getting fields is the same as sending a unary message with its name to the object
1// get age, add 1 and print it
2alice age inc echo // 25
Method for type: ¶
Everything is an object, just like in Smalltalk, so everything can have a method declared.
Here, we add a double
method to Int
and then use it inside the perimeter
method of Square
.
1Int double = this + this
2Square perimeter = side double
3
4square = Square side: 42
5square perimeter // call
6
7
8// explicit return type
9Int double2 -> Int = this * 2
10
11// with body
12Int double3 -> Int = [
13 result = this * 2
14 ^ result // ^ is return
15]
Keyword message declaration ¶
1type Range from: Int to: Int
2// keyword message with one arg `to`
3Int to::Int = Range from: this to: to
4
51 to: 2 // Range
Type constructor ¶
A type constructor functions as a message for the type itself rather than to a specific instance.
1constructor Float pi = 3.14
2x = Float pi // 3.14
It can be useful for initializing fields with default values.
1type Point x: Int y: Int
2constructor Point atStart = Point x: 0 y: 0
3
4p1 = Point x: 0 y: 0
5p2 = Point atStart
6// constructor is just a usual message, so it can have params
7constructor Point y::Int = Point x: 0 y: y
8p3 = Point y: 20 // x: 0 y: 20
Conditions ¶
If, like everything else, is the usual sending of a message to a Boolean object.
It takes one or two lambda arguments.
1false ifTrue: ["yay" echo]
21 < 2 ifTrue: ["yay" echo]
31 > 2 ifTrue: ["yay" echo] ifFalse: ["oh no" echo]
4
5// `ifTrue:ifFalse:` message can be used as expression
6str = 42 % 2 == 0
7 ifTrue: ["even"]
8 ifFalse: ["odd"]
9// str == "even"
Cycles ¶
There is no special syntax for cycles.
It’s just keyword messages that take codeblocks as parameters.
(it’s zero cost thanks for inlining)
1{1 2 3} forEach: [ it echo ] // 1 2 3
21..10 forEach: [ it echo ]
3
4mut c = 10
5[c > 0] whileTrue: [ c <- c dec ]
6
7c <- 3 // reset c
8[c > 0] whileTrue: [
9 c <- c dec
10 c echo // 3 2 1
11]
whileTrue:
is a message for lambda object of the type:
[ -> Boolean] whileTrue::[ -> Unit]
Matching ¶
there is special syntax for matching, since niva heavily utilize tagged unions
1x = "Alice"
2// matching on x
3| x
4| "Bob" => "Hi Bob!"
5| "Alice" => "Hi Alice!"
6|=> "Hi guest"
7
8
9// It can be used as expression as well
10y = | "b"
11 | "a" => 1
12 | "b" => 2
13 |=> 0
14y echo // 2
Tagged unions ¶
1union Color = Red | Blue | Green
2
3// branches can have fields
4union Shape =
5 | Rectangle width: Int height: Int
6 | Circle radius: Double
7
8constructor Double pi = 3.14
9Double square = this * this
10
11// match on this(Shape)
12Shape getArea -> Double =
13 | this
14 | Rectangle => width * height, toDouble
15 | Circle => Double pi * radius square
16
17// There is exhaustiveness checking, so when you add a new branch
18// all the matches will become errors until all cases processed
19
20Shape getArea -> Double = | this
21 | Rectangle => width * height, toDouble
22// ERROR: Not all possible variants have been checked (Circle)
Collections ¶
1// commas are optional
2list = {1 2 3}
3map = #{'a' 1 'b' 2}
4map2 = #{'a' 1, 'b' 2, 'c' 3}
5set = #(1 2 3)
6
7// common collection operations
8{1 2 3 4 5}
9 map: [it inc],
10 filter: [it % 2 == 0],
11 forEach: [it echo] // 2 4 6
12
13// iteration on map
14map forEach: [key, value ->
15 key echo
16 value echo
17]
Nullability ¶
By default, variables cannot be assigned a null value.
To allow this, nullable types are used, indicated by a question mark at the end of the type.
You may already be familiar with this concept from TypeScript, Kotlin, or Swift.
1x::Int? = null
2q = x unpackOrPANIC
3
4// do something if it's not null
5x unpack: [it echo]
6
7// same but provide a backup value
8w = x unpack: [it inc] or: -1
9
10// just unpack or backup value
11e = x unpackOrValue: -1
Handling the error ¶
1// exit the program with stack trace
2x = file read orPANIC
3x = file read orValue: "no file"
Errors work like effects, look for more in Error handling
Misc ¶
Local arg names ¶
1Int from: x::Int to: y::Int = this + x + y
Syntax sugar for this ¶
1Person foo = [
2 .bar
3 this bar // same thing
4]
Compile time reflection ¶
You can get string representation of any argument from a call site.
1Int bar::Int baz::String = [
2 // getting string representation from call side
3 a = Compiler getName: 0
4 b = Compiler getName: 1
5 c = Compiler getName: 2
6 a echo // 1 + 1
7 b echo // variable
8 c echo // "str"
9]
10
11variable = 42
12// call side
131 + 1
14 bar: variable
15 baz: "str"
Links: