ok

command module
v0.14.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 4, 2020 License: MIT Imports: 10 Imported by: 0

README

GitHub release Build Status codecov

ok is a strongly-duck-typed language. If you notice a lot of similarities with Go, that is no accident. There are a lot of things I liked about Go that I brought to OK. Also, OK is written in Go until which time it can be written in itself.

Why OK?

Strongly Typed

OK is strongly typed. However, it has less types that most strongly typed languages because it doesn't directly expose underlying CPU types. These types are:

  • bool is a true or false value.
  • char is a single character. Supports all UTF-8 up to 4 bytes.
  • data is raw binary data.
  • number is a decimal value with an exact value (this is not an approximation like floating-point types) so all arithmetic and math operation are exact. An internal maximum precision is used for non-exact calculations.
  • string is a UTF-8 string of any length (including zero).

These five fundamental types can be used in:

  • arrays - an ordered sequence of values.
  • maps - an unordered collection of key-value pairs

First-class Testing

Testing is not just an added library of helpers, but rather it is built into the language itself.

Here is an example of some OK code we may want to test:

// main.ok

func add(a, b number) number {
    return a + b
}

Tests are placed in the same package (directory) as the source code, but with a different extension (.okt):

// main.okt

test "adding some numbers" {
    assert(add(3, 5) == 8)
    assert(add(3, 5) == 10)
}

The second assertion will fail. Here is how we run the tests with the output:

$ ok test tests/example-tests
tests/example-tests: adding some numbers: assert(8 == 10) failed
tests/example-tests: 1 failed 0 passed 2 asserts (60.638µs)

Everything in a Single Binary

The binary for the release (you can download it from the Releases page) contains all of the tools and standard library so there is zero installation or configuration. Just place the binary anywhere and start using it.

Installation

Precompiled Binaries

You can find ready to go binaries for Mac, Windows and Linux on the Releases page.

These do not require any dependencies.

go get

If you have Go installed, you can install or update the latest version of ok with:

go get -u github.com/elliotchance/ok

From Source

You will need to have Go 1.14+ installed to build from source.

Command Line Interface

build

The build tool will compile a package and produce a binary. The binary is completely standalone and can be run on another computer.

$ ok build tests/hello-world
$ ./hello-world
Hello, World!

doc

The doc tool will generate Github-friendly Markdown for a package. To see what this looks like you can have a look at the standard library.

ok doc my/package

run

Programs in ok are directories containing one or more .ok files. You can run a program by specifying the directory (or see the included tests/):

ok run my/program

test

Use test to run all tests in a package. Tests are placed in the same package (directory) as the code they will test, but with a different file extension (.okt):

ok test my/program

version

ok version will show the current version and the date it was built.

Learn By Example

These have been heavily influenced (copied) from gobyexample because it's such a great source!

Hello World

Our first program will print the classic "hello world" message. Here’s the full source code.

func main() {
    print("hello world")
}

To run the program, put the code in hello-world/main.ok and use ok run.

$ ok run hello-world
hello world

Now that we can run and build basic ok programs, let’s learn more about the language.

Values

ok has various value types including strings, numbers, booleans, etc. Here are a few basic examples.

func main() {
    // Strings, which can be added together with +.
    print("he" + "llo")

    // Numbers.
    print("1+1 =", 1+1)
    print("7.0/3.0 =", 7.0/3.0)

    // Booleans.
    print(true and false)
    print(true or false)
    print(not true)
}
$ ok run values
hello
1+1 = 2
7.0/3.0 = 2.3333333333333333333
false
true
false

Variables

In ok, variables are explicitly declared and used by the compiler to e.g. check type-correctness of function calls.

Every variable has a type, but that type is inferred from the expression or value being assigned to it.

func main() {

    // Declare a variable.
    a = "initial"
    print(a)

    // ok will infer the type of initialized variables.
    b = true
    print(b)

    c = 1.23
    print(c)
}
$ ok run variables
initial
true
1.23

For

for is ok's only looping construct. Here are some basic types of for loops.

func main() {

    // The most basic type, with a single condition.
    i = 1
    for i <= 3 {
        print(i)
        i = i + 1
    }

    // A classic initial/condition/after for loop.
    for j = 7; j <= 9; ++j {
        print(j)
    }

    // for without a condition will loop repeatedly until you break out of the
    // loop or return from the enclosing function.
    for {
        print("loop")
        break
    }

    // You can also continue to the next iteration of the loop.
    for n = 0; n <= 5; ++n {
        if n%2 == 0 {
            continue
        }
        print(n)
    }
}
$ ok run for
1
2
3
7
8
9
loop
1
3
5

If/Else

Branching with if and else in ok is straight-forward. In ok, there is no "if else". Instead you should use a switch if there are multiple cases.

func main() {

    // Here's a basic example.
    if 7%2 == 0 {
        print("7 is even")
    } else {
        print("7 is odd")
    }

    // You can have an if statement without an else.
    if 8%4 == 0 {
        print("8 is divisible by 4")
    }

    // Note that you don't need parentheses around conditions in ok, but that
    // the braces are required.
    num = 9
    if num < 10 {
        print(num, "has 1 digit")
    } else {
        print(num, "has multiple digits")
    }
}
$ ok run if-else
7 is odd
8 is divisible by 4
9 has 1 digit

Switch

Switch statements express conditionals across many branches.

func main() {

    // Here's a basic switch.
    i = 2
    print("Write", i, "as")
    switch i {
        case 1 {
            print("one")
        }
        case 2 {
            print("two")
        }
        case 3 {
            print("three")
        }
    }

    // You can use commas to separate multiple expressions in the same case
    // statement. We use the optional else case in this example as well.
    weekday = "sunday"
    switch weekday {
        case "saturday", "sunday" {
            print("It's the weekend")
        }
        else {
            print("It's a weekday")
        }
    }

    // switch without an expression is an alternate way to express if/else
    // logic. Here we also show how the case expressions can be non-constants.
    hour = 11
    switch {
        case hour < 12 {
            print("It's before noon")
        }
        else {
            print("It's after noon")
        }
    }
}
$ ok run switch
Write 2 as
two
It's the weekend
It's before noon

Arrays

Arrays are an ordered sequence of values.

func main() {
    // Arrays are defined within square brackets. If all the elements are the
    // same type, then the type of the array is inferred. In this case the type
    // of "a" will be "[]number".
    a = [1, 2, 3]
    print(a)

    // Arrays can contain mixed types but the type of the array must be given
    // explicitly to indicate this.
    b = []any [123, "foo", true]

    // Arrays are always printed as valid JSON. This makes it easier and more
    // natural to pass data to other systems.
    print(b)

    // Access an element in an array by using its index. Remember, 0 is the
    // index for the first element.
    print(a[0], b[1])

    // Assigning an element works the same way with the index.
    a[1] = 7
    print(a)
}
$ ok run arrays
[1, 2, 3]
[123, "foo", true]
1 foo
[1, 7, 3]

Maps

Maps store an unordered collection of key-value pairs. The keys are strings and must be unique within the same map. The values can be of any type and there is no restriction on duplicate values.

func main() {
    // Maps are defined within curly brackets. If all the elements are the
    // same type, then the type of the map is inferred. In this case the type
    // of "a" will be "{}number".
    a = {"a": 1, "b": 2, "c": 3}
    print(a)

    // Like arrays, maps can contain mixed values by it has to be declared
    // explicitly.
    b = {}any {"a": 123, "b": "foo", "c": true}

    // Maps are always printed as valid JSON with their keys sorted. This makes
    // it easier and more natural to pass data to other systems.
    print(b)

    // Access an element in an map by using its key. Maps only support strings
    // as the key.
    print(a["b"], b["b"])

    // Assigning an element works the same way with the key.
    a["b"] = 7
    print(a)
}
$ ok run maps
{"a": 1, "b": 2, "c": 3}
{"a": 123, "b": "foo", "c": true}
2 foo
{"a": 1, "b": 7, "c": 3}

Iteration

func main() {
    myArray = [7, 11, 13]

    // When iterating an array the first and second variable are assigned the
    // index and the value respectively.
    for i, v in myArray {
        print(i, v)
    }

    myMap = {"foo": 1.23, "bar": 4.56}

    // Maps work the same way but the first variable will be the key.
    for key, value in myMap {
        print(key, value)
    }

    // For both arrays and maps you may omit the second variable if you only
    // need to iterate the index or keys.
    for index in myArray {
        print(index)
    }

    for key in myMap {
        print("key is", key)
    }

    // If you also need to keep a numeric iterator while iterating a map you can
    // use another form of for.
    for i = 0; key, value in myMap; ++i {
        print(i, key, value)
    }
}
$ ok run iteration
0 7
1 11
2 13
foo 1.23
bar 4.56
0
1
2
key is foo
key is bar
0 foo 1.23
1 bar 4.56

Functions

Functions are central in ok. We'll learn about functions with a few different examples.

// Here's a function that takes two numbers and returns their sum.
func plus(a number, b number) number {
    // ok requires explicit returns, i.e. it won’t automatically return the
    // value of the last expression.
    return a + b
}

// When you have multiple consecutive parameters of the same type, you may omit
// the type name for the like-typed parameters up to the final parameter that
// declares the type.
func plusPlus(a, b, c number) number {
    return a + b + c
}

func main() {
    // Call a function just as you'd expect, with name(args).

    res = plus(1, 2)
    print("1+2 =", res)

    res = plusPlus(1, 2, 3)
    print("1+2+3 =", res)
}
$ ok run functions
1+2 = 3
1+2+3 = 6

There are several other features to Go functions. One is multiple return values, which we'll look at next.

Multiple Return Values

OK has built-in support for multiple return values.

// The (number, number) in this function signature shows
// that the function returns 2 numbers.
func vals() (number, number) {
    return 3, 7
}

func main() {
    // Here we use the 2 different return values from the
    // call with multiple assignment.
    a, b = vals()
    print(a)
    print(b)

    // If you only want a subset of the returned values,
    // use the blank identifier _.
    _, c = vals()
    print(c)
}
$ ok run multiple-return-values
3
7
7

Interpolation

import "math"

func main() {
    // Variables can be placed in {}. This is called interpolation.
    name = "Bob"
    print("Hello, {name}. How are you?")

    // Interpolation can be used with any expression. The result doesn't
    // even have to be a string.
    print("3 + 5 = {3+5}")

    // The spaces added between {} are not required, but might be easier
    // to read.
    total = 3.557
    print("The total is ${ math.Round(total, 2) }")
}
$ ok run interpolation
Hello, Bob. How are you?
3 + 5 = 8
The total is $3.56

Stateful Functions (Objects)

// When a function has a single return type that is also the same name
// as the function, this is called a constructor. Constructors are used
// to create stateful functions. Sometimes called objects, records or
// structures in other languages.
func person(name string) person {
    // When you set variables here, they will be accessible outside.
    Name = name
    Age = 42

    // A constructor does not need to have a return statement. It will
    // always return itself.
}

func main() {
    // Create an instance by calling the constructor.
    p = person("Bob")

    // You can read and write properties using the dot notation.
    p.Name = "John"
    print(p.Age)

    // Objects are always printed as JSON objects.
    print(p)
}
$ ok run objects
42
{"Age": 42, "Name": "John"}

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
ast
Package ast provides structures for representing parsed source code.
Package ast provides structures for representing parsed source code.
cmd
doc
run
Package compiler translates the ast structures that represent the parsed course code into instructions that can be executed by the VM.
Package compiler translates the ast structures that represent the parsed course code into instructions that can be executed by the VM.
Package lexer converts source code into tokens.
Package lexer converts source code into tokens.
Package number contains the implementation of the number data type.
Package number contains the implementation of the number data type.
Package parser translates the linear stream of tokens into ast structures based on the syntax of the ok language.
Package parser translates the linear stream of tokens into ast structures based on the syntax of the ok language.
Package instruction contains all the instructions created by the compiler and executed by the VM.
Package instruction contains all the instructions created by the compiler and executed by the VM.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL