This is the spec for a programming language. I am using this purely as a learning tool. If I've built them, you can find various implementations on my GitHub.
The best way explain this language is to show examples for each convention
- No parenthesis needed for
if
,for
, etc - Braces used for scoping
- Equality operator is
is
,&&
->and
,||
->or
, and!
->not
if
,if else
, andelse
are supported
if 1 is 1 {
} else if true is not false {
}
- Comments created with
--
- Variables created with
let
-- Creates a variable
let name = "andrew"
-- OR
let name: String = "andrew"
- Static typing is prefered, but not required. Types are used after a colon
- These are the basic available types
Note that all types should start with a Capital letter
String
Int
Float
Bool
Byte
Nothing
- Union types are also allowed using the or operator
let myNumber: Int or Float
- Functions are defined with
fn
- If a function has a return value, it should be static typed
fn plus(x: Int, y: Int): Int {
return x + y
}
- No parenthesis needed
- Implict break (no
break;
needed at the end of each case) - Braces used for scoping
other
can be used as a default case
let myBool: Bool = true
switch myBool {
case true {
-- ...
} case false {
-- ...
}
}
- Defined with
struct
and then a name - The name should start upper cased
Here's an example
struct Person {
name: String,
age: Int
}
let myPerson = Person {
name: "andrew",
age: 1000
}
-- OR
let myPerson: Person = {
name: "andrew",
age: 1000
}
You can use .
to access properties on an instance of a struct, like this
fn getName(): String {
return myPerson.name
}
You can use parenthesis to access property values determined by variables, like this
fn getProperty(name: String): String {
return myPerson.(name)
}
- Defined with
type
and then a name - The name should start upper cased
Here's an example
type Age = Int or Float;
let myAge: Age = 1000.0; -- Or 1000
You can also define enums like this
type Status: Enum = Active or Idle;
Here is an example of a combination of structs, custom types, and enums
type Status: Enum = Active or Idle;
type Age = Int or Float;
struct User {
status: Status,
name: String,
age: Age
}
let myself: User = {
status: Active,
name: "andrew",
age: 1000
}
Kyro does have a null type, but it cannot be used without a Perhaps
type, which has a subtype. This is inspired to Maybe
in Elm and various other functional programming languages. Think of the subtype as how in strict typing, an array can have a type. It's easier to just show an example.
fn toFloat(num: String): Maybe(Float, Nothing) {
-- Whatever logic
if isValid {
return myFloat
} else {
return Nothing
}
}
let myString = "23"
switch myString {
case Nothing {
-- It didn't convert
}
other {
-- It works
}
}
You may have noticed that we haven't shown logging to the console yet. The reason for that is that, technically, outputing to STDOUT is included in a module, more specifically the @std/io
module. Here are some other ones. Modules will be imported in a variable context. Destructuring syntax, similar to JavaScript is also available
Name | Common use |
---|---|
@std/io |
Taking console input and output |
@std/net |
Networking via sockets |
@std/http |
Networking via http |
@std/os |
Interacting with your operating system |
So without further ado, here's how to log to the console
use '@std/io' as io;
let name: String = "Andrew";
io.log("Hello " + name)
Note how the as
statement is used. Technically you can omit this, but for readability, you should leave it. It also helps you prevent overlap if two modiules have the same name;
This code works, but with destructuring;
use '@std/io' as { log };
let name: String = "Andrew";
log("Hello " + name)
However, you might want to rename the variable, and you can do it like this
-- Note the use of the second as
use '@std/io' as { log as print};
let name: String = "Andrew";
print("Hello " + name)
Looking through this also gives a good explanation of destructuring