Skip to content

Instantly share code, notes, and snippets.

@kittech0
Last active June 18, 2023 08:54
Show Gist options
  • Save kittech0/4e615e4e5df1cf355cb55a8c10297957 to your computer and use it in GitHub Desktop.
Save kittech0/4e615e4e5df1cf355cb55a8c10297957 to your computer and use it in GitHub Desktop.
The Prismatica Programming Language idea

Welcome to the idea of language v2 based around Kotlin and Rust!

Comments

Comments in this language use C-like syntax.

// One-liner
function(/* Insides */) // Description of it! 
/*
   Multiple lines!
*/

Variables and mutability

Language uses rust-like declaring variables that means every variable is immutable by default. Also instead of let it is val like in kotlin. Declaring variable:

val variable: i32 = 2

Declaring mutable variable:

val mut variable: i32 = 2

Declaring constant (only primitive values can be in constant):

const CONSTANT: i32 = 2

Types

Uses rust-like types

  1. Signed integers: i8,i16,i32,i64,i128 and isize
  2. Unsigned integer: u8,u16,u32,u64,u128 and usize
  3. Floating-point numbers: f8,f16,f32,f64,f128 and fsize
  4. Characters: char (to create character use '' example: 'a')
  5. Boolean: bool (true or false)
  6. Tuples

Tuples have a Rust-like syntax. They are groups with multiple types of values inside and have a fixed size. Declaring a tuple:

val tuple: (fsize,u16,bool) = (1.0, 24, true)

To access a tuple use array-like syntax. Accessing a tuple:

val tuple: (fsize,u16,bool) = (1.0, 24, true)

val float: fsize = tuple[0] //1.0
val positiveNumber: u16 = tuple[1] //24
val boolean: bool = tuple[3] //true

Supports also deconstructing syntax into multiple variables. Deconstracting tuple:

val tuple: (fsize,u16,bool) = (1.0, 24, true)

val (float: fsize, positiveNumber: u16, boolean: bool) = tuple

Arrays

Arrays also use kotlin-like syntax. type[size], type can be anything, size has usize type. Declaring array:

val array: char[5] = ['0','1','a','c','n'] //declares array

Getting value from array:

val array: char[5] = ['0','1','a','c','n']

val letter: char = array[2] // 'a'

Set

Sets in this language have a math-like syntax. They are collection of elements of the same type and have dynamic size. They work like hashset in other languages. Delcaring set:

val set: char{} = {'a','b','c','d','e','f','g','h'}

Adding new value to set:

val mut set: char{} = {'a','b','c','d','e','f','g','h'}
set.push('i')

Removing value:

val mut set: char{} = {'a','b','c','d','e','f','g','h','i'}
set.remove('i')

Map

Maps in this language has js-like syntax. They work like HashMaps, having key-value pairs, keys are unique. Declaring is done between {type:type}, first type is of key, second type is of value. Declaring map:

val map: {char:i32} = {
    'a': 2,
    'b': 3,
    'g': 3,
    'o': 0
}

Modifying/getting value from map has array-like syntax but instead of usize, you pass the key type. Modifying map:

val mut map: {char:i32} = {
    'a': 2,
    'b': 3,
    'g': 3,
    'o': 0
}

map['o'] = 10

Getting value:

val map: {char:i32} = {
    'a': 2,
    'b': 3,
    'g': 3,
    'o': 0
}

val value: i32 = map['a'] //2

Table

Tables are similiar to maps but allows multiple values per key. The syntax for creating a table is the same as for maps, but with additional types specified inside the curly braces. Type {key:value,value} Declaring table:

val table: {char:i32,bool} {
    'a': 64, true
    'b': 128, true
}

Getting value from table:

val table: {char:i32,bool} {
    'a': 64, false
    'b': 128, true
}

val value: (i32,bool) = table['b'] // (128,true)

List

List is similiar to array, but it can be resized. Declaring a list:

val list: [[char]] = [['a','a','g','f']]

Functions

Function syntax is similiar to kotlin functions, difference is replacement of fun with fn Declaring a function:

fn function(x: i32): i32 = x^2 //short syntax
fn function(x: i32): i32 {
    <- x^2 //instead of return there is <-
} //long syntax

Functions also support default values in arguments. Example default value:

fn function(x: i32 = 2): i32 = x^2 //4 by default :p

Lambdas

Language also supports lambdas, the lambda syntax is kotlin-like.

val lambda: () -> () = {}
val lambda: (i32) -> i32 = { x -> x^2 }

Modules and Packages

Supports kotlin-like package syntax. Instead of public there is pub, all declared stuff are private (file specific), also supports inter which means internal, it makes declared stuff only accessible inside the application/library. Package example:

package MyPackage

pub fun publicated(x: i32): i32 = x^2
fun private(x: u32): u32 = x^2
inter fun internal(x: i32, y: i32): i32 = x^y

Importing a package: MyPackage file:

package MyPackage

pub fun publicated(x: i32): i32 = x^2
fun private(x: u32): u32 = x^2
inter fun internal(x: i32, y: i32): i32 = x^y

OtherFile:

import MyPackage //imports a package that you can use MyPackage@
import MyPackage.publicated //imports specific thing from package
import MyPackage.* //imports everything from package

Structures

Structures are used to create more complex data types from existing types. The language combines Rust's structure syntax with Kotlin's implementation of function syntax. (pub inside structures means that the value can be used outside the structure). Structure values also support default values. Declaring structure:

import std

struct Person {
    pub name: str@string = "Jane" //you dont need std@, you can just import `string` manually.
}

Implementing a function:

import std

struct Person {
    pub name: str@string = "Jane"
}

Person.getName(): std@string = $.name

You can also implement a static value by putting * before Person Implementing a static function:

import std

struct Person {
    pub name: str@string = "Jane"
}

fn *Person.new(name: std@string): It = It(name) //It returns itself

References

This language has references with rust-like syntax, modifying mutable references is done using :=. Use of references:

import std
fun referenceMutate(x: &mut i32) = x := x ^ 2

fun function() {
    val mut x: i32 = 100
    referenceMutate(&mut x)
    std@println("{x}") //prints 10000
}

DSL

DSL is builder syntax that can be found in kotlin. Uses kotlin-like syntax. Type is Struct.(additional)->(return) Declaring DSL function:

import std

struct Builder {
    pub mut str: std@string,
    pub mut integer: i32
}

fn builder(str: std@string, integer: i32, build: Builder.()->()) {
    Builder(str,integer).apply(build)
}

Using a DSL function:

import std

struct Builder {
    pub mut str: std@string,
    pub mut integer: i32
}

fn Builder.callMe() = std@println("{$.str}: {$.integer}")

fn builder(str: std@string, integer: i32, build: Builder.()->()) {
    Builder(str,integer).apply(build) //apply applies all modification and stuff done in builder
}
//...
builder("aaa", 100) {
    $.str = "bbb"
    $.integer = -100
    $.callMe() //prints "bbb: -100"
} //this is lambda shorting that can be found in kotlin too.

Initializer

Uses a DSL builder called init which is replacement for main function in kotlin/rust. This is done to give users way more stuff :p A hello world example:

init {
    println("Hello world!")
}

Using arguments:

init {
    println("{$.args}") // prints list of arguments
}

Operators

  1. Addition: +
  2. Subtraction: -
  3. Multiplication: *
  4. Power: ^
  5. Modulo: %
  6. Root: ^^
  7. Approximate: ~
  8. Equal: ==
  9. Not equal: !=
  10. Greater than: >
  11. Lesser than: <
  12. Greater or equal than: >=
  13. Lesser or equal than: <=
  14. Division: \
  15. Bitwise logic: ~, &, |, **
  16. Bitwise shifts: <<, >>,
  17. Or: ||
  18. And: &&

Loops, If and When

Welcome to the idea of language based around Kotlin and Rust!

Comments

Comments in this language use C-like syntax.

// One-liner
function(/* Insides */) // Description of it! 
/*
   Multiple lines!
*/

Variables and mutability

There are multiple ways of declaring variables in this language. By default, every variable is immutable, and you need to specify mutability. The syntax for declaring variables is Rust-like, with an optional let keyword. Example of an immutable variable:

variable: i32 = 2
let variable: i32 = 2 // `let` is optional and not required; it will work the same in the example above

Example of mutable variable:

mut variable: i32 = 2 //you just put `mut` before name!
let mut variable: i32 = 2 // same here but after `let`

There are also constant values, which are strictly immutable and only allow primitive values. They require the const keyword before the name. They are automatically inlined by the compiler.

const CONSTANT_VARIABLE: i32 = 2

Types

Uses rust-like types

  1. Signed integers: i8,i16,i32,i64,i128 and isize
  2. Unsigned integer: u8,u16,u32,u64,u128 and usize
  3. Floating-point numbers: f8,f16,f32,f64,f128 and fsize
  4. Characters: char (to create character use '' example: 'a')
  5. Boolean: bool (true or false)

Tuples

Tuples have a Rust-like syntax. They are groups with multiple types of values inside and have a fixed size. Tuples can be destructured. Example way of creating:

tuple: (fsize:u16:bool) = (1.0, 24, true)

Accessing values is different from Rust. Instead of tuple.i (where i is the index/place of the value in the tuple), it uses array-like syntax for accessing elements. Example way of accessing value:

//tuple: (fsize, u16, bool) = (1.0, 24, true)
value: fsize = tuple[0] //returns 1.0 of type fsize
// remember `tuple` variable is (1.0, 24, true)

You can also destructure tuples using a different variable syntax, which destructures the tuple into multiple variables. Example of destruction:

//tuple: (fsize, u16, bool) = (1.0, 24, true)
(float: fsize, number: u16, answer: bool) = tuple 
float //returns 1.0 with type fsize
number //returns 24 with type u16
answer //returns true with type bool 

Array

Arrays in this language have a Kotlin-like syntax. They are collections of elements of the same type and have a fixed size. Example way of creating:

array: char[5] = ['h','e','l','l','o']

In the above example, char[5] specifies the type of the array (char) and its fixed size (5).

You can access values inside an array using the indexing syntax, similar to tuples. Example of accessing value:

array: char[5] = ['h','e','l','l','o']
letter: &char = array[3] //returns `l` with reference of char

You can also modify values in array.

mut array: char[5] = ['h','e','l','l','o']
array[2] := 'w'

Set

Sets in this language have a math-like syntax. They are collections of elements of the same type and have a dynamic size. Sets only allow unique values. Example use:

set: char{} = {'a','b','c','d','e','f','g','h'} 

Example of adding new values:

mut set: char{} = {'a','b','c','d','e','f','g','h'} 
set.add('i') // adds one value on the end of set
set.push('j') //adds one value on the start of set
set[1] := 'l' //replaces value in this index

Example of removing values:

mut set: char{} = {'j','a','b','c','d','e','f','g','h','i'} 
set.removeFirst() //removes first value in set
set.removeLast() //removes last value in set
set.remove(2) //removes value with index 2 which is 'a'

Getting values from a set is done in the same way as accessing values in an array Example of getting value:

set: char{} = {'a','b','c','d','e','f','g','h'} 
letter: &char = set[0] //'a'

Map

Maps in this language have a JavaScript-like syntax. They have key-value pairs, where the keys are unique and the values can be of any type. Maps are created by specifying the key and value types inside curly braces. Example use:

map: {char:i32} = { //char is key type, i32 is value type
	'a' : 1
	'b' : 2
	'c' : 3
	'g' : 1
}

You can also modify values in map.

mut map: {char:i32} = {
	'a' : 1
	'b' : 2
	'c' : 3
	'g' : 1
}

map['g'] := 2 //modifying value based on key!
map.remove('a') //removes value from map by key

Table

Tables in this language are similar to maps but allow multiple values per key. The syntax for creating a table is the same as for maps, but with additional types specified inside the curly braces. Example use:

table: {char:i32:bool} = {
	'a' : 64 : true
	'b' : 32 : false
	'c' : 16 : true
}

When getting a value from a table, you receive a reference to a tuple containing the values associated with the key. Example of getting value:

table: {char:i32:bool} = {
	'a' : 64 : true
	'b' : 32 : false
	'c' : 16 : true
}
value: &(&i32,&bool) = table['a'] //returns (64,true)

Example of modifying value:

table: {char: i32: bool} = {
    'a' : 64 : true,
    'b' : 32 : false,
    'c' : 16 : true
}
table['a'] := (18, false) // modifies the entire tuple associated with the key 'a'
table['a'][0] := 24 // modifies a specific value in the tuple associated with the key 'a'

List

List is similiar to set, but instead it doesn't require values to be unique. Getting value is exactly the same. Example use:

list: [[char]] = [['a','a','g','f']]

Lambda

Lambdas are a replacement for functions in this language. They use a different syntax from other languages. To create a lambda, you use ||. By default, they return nothing. Lambdas are executed like C functions. They also support compact one-line execution, similar to Kotlin. Example use of lambda in variable

lambda: || = something()

Lambdas can span multiple lines by using {}. Example use:

lambda: || = {
	something()
	something()
}

Lambdas can also have a return type, which is specified after ||, like || -> i32. Example use:

lambda: || -> i32 = 2 //will return `2`

You can also specify when the lambda should return, if required; otherwise, it will return the last statement in the block. Instead of using return as in other languages, you use <- to indicate a return. Example use:

lambda: || -> i32 = {
	2+2 //won't be returned
	<- 4+4 //will return 8 and stop lambda here
	5-5 //won't be reached
}

Lambdas also support arguments. Arguments are specified inside ||, similar to other languages' function syntax. Example use:

lambda: |x: i32| -> i32 = x^2 //if executed `lambda(2)` will return `4`
//You can also specify which argument you pass value by using `lambda(x=2)`

Arguments also support default values, as you would see in Kotlin. Example use:

lambda: |x: i32 = 2| -> i32 = x^2 //If not passed anything by doing `lambda()` it will return 4, if did for example `lambda(6)` it will return 36.

Lambdas can be used as arguments, and there is a shorthand syntax for it. Example use:

lambda: |lam: || | = lam()
//without shorting syntax
lambda(|| = std@println("Hello world!"))
//with shorting syntax
lambda || = std@println("hello world!")

When multiple arguments are used, the shorthand syntax can be applied to each argument. Example of a lambda with multiple arguments and shorthand syntax:

lambda: |x: i32, lam: |y: i32|| = lam(x)

lambda(32) |x| = std@println("Hello {x}") //you do exactly same thing but instead you add the value in `()`

Modules and Packages

This language supports creating modules and importing them. The standard library is included by default and can be used. Modules use a syntax similar to Rust, and the namespace specifier is @. Example module:

mod MyModule

pub powerMe: |x: i128| -> i128 = x^2 // Use `pub` keyword to make something public!
//to use it you would write MyModule@powerMe(2) (returns 4)
//Also before using module, you need to import it using `import MyModule`

Structure

Structures are used to create more complex data types from existing types. The language combines Rust's structure syntax with Kotlin's implementation of function syntax. Example usage:

struct Person {
    mut name: std@String = "Jane" // To make a mutable variable in a structure, use `mut`
    pub age: u16 // By default, values in a structure are only available for implemented functions. Using `pub` makes them accessible internally and externally.
} // You can initialize it using `Person("Kittech", 20)` or `Person(age = 16)`

Person.sayHello: || = std@out.println("{$.name}: Hello!") // Implements a function for the structure
// To access values from inside the structure, use `$` (similar to `self` in Rust or `this` in Kotlin)

mut Person.setName: |name: std@String| = $.name = name // To mutate any value in the structure, the implemented lambda also needs to be mutable

*Person.new: |name: std@String, age: u16| -> it = it(name, age)
// This is a static variable on Person. You create it by putting `Person` between `||`.
// `it` is used for returning itself and only stores the constructor and static variables.
// Static variables are immutable and cannot be changed.

DSL

A DSL is a building pattern commonly found in Groovy or Kotlin. It makes it easy for developers to create builder functions, and for users to use them without problems. In this language, we can implement a builder using lambda-like syntax with some differences. Example implementation:

// Example structure
struct ExampleBuild {
    mut number: isize
}

mut ExampleBuild.addNumber: |num: isize| = $.number += num

// DSL builder implementation
builder: |num: isize, example: ExampleBuild.|| | = ExampleBuild(num).apply(example)
// `ExampleBuild.||` is similar to a lambda but provides DSL abilities

// Example usage 
builder(100) || = {
    $.addNumber(1)
    $.addNumber(100)
} // Returns ExampleBuild with `number` equal to 201

Every struct has an .apply function that can be called by passing the builder DSL lambda as an argument.

Initializer

In this language, the entry point of a program is defined using the init keyword which is DSL builder This is similar to the main function in other languages.

Example of an initializer:

init || = {
	std@println("Hello world!")
}

The code inside the initializer block will be executed when the program starts.

Operators

  1. Addition: +
  2. Subtraction: -
  3. Multiplication: *
  4. Power: ^
  5. Modulo: %
  6. Root: ^^
  7. Approximate: ~
  8. Equal: ==
  9. Not equal: !=
  10. Greater than: >
  11. Lesser than: <
  12. Greater or equal than: >=
  13. Lesser or equal than: <=
  14. Division: \
  15. Bitwise logic: ~, &, |, **
  16. Bitwise shifts: <<, >>

References

This language supports references that can be found in Rust programming language. They have exactly the same syntax like you would found in rust. To modify reference, use :=. Example use:

foo: |x: &mut i128| = x := x^2
bar: |x: &i128| = std@println("{x}")
init || = {
	mut fooBar: i128 = 1234567890
	std@println("{fooBar}") //prints 1234567890
	bar(&fooBar) //prints 1234567890
	foo(&mut foodBar) //modifes the reference
	bar(&fooBar) //prints 1524157875019052100
	std@println("{fooBar}") //prints 1524157875019052100
}

Nullability

This language by default is not nullable, but it has nullability syntax exact the same for kotlin ?. Example use:

foo: |x: i32| -> i32? = null
bar: |x: i32?| -> i32 = x!! //will panic if x is null
init || = {
	num: i32 = 10
	nullableNum: i32? = foo(num)
	notNullableNum: i32 = bar(nullableNum) //panics.
}

Loops, If and When

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment