Skip to content

Instantly share code, notes, and snippets.

@ajkachnic
Last active June 10, 2020 23:26
Show Gist options
  • Save ajkachnic/4b1445c5c54f913a6d14e0ec624e95d4 to your computer and use it in GitHub Desktop.
Save ajkachnic/4b1445c5c54f913a6d14e0ec624e95d4 to your computer and use it in GitHub Desktop.
The specification for the Kyro Language

Kyro Specification

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.

Examples

The best way explain this language is to show examples for each convention

If Statements

  • No parenthesis needed for if, for, etc
  • Braces used for scoping
  • Equality operator is is, && -> and, || -> or, and ! -> not
  • if, if else, and else are supported
if 1 is 1 {

} else if true is not false {

}

Variables

  • Comments created with --
  • Variables created with let
-- Creates a variable
let name = "andrew"

-- OR
let name: String = "andrew"

Types

  • 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

  • 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
}

Switch Statements

  • 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 {
    -- ...
  }
}

Structs

  • 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)
}

Custom Types

  • 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
}

Weird Features and Characteristics

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
  }
}

Modules

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

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