Cassette is a small functional programming language. It looks like this:

import IO, Net, Value, String (crlf), List

; keeps reading from a connection while there's any data
def read_resp(conn) do
  def loop(received) do
    let chunk = IO.read_chunk(conn, 1024)   ; get the next chunk

    if #chunk == 0 do
      received    ; no more data
    else
      loop(received <> chunk)
    end
  end

  loop("")
end

; open a network connection
let conn = Net.connect("cassette-lang.com", "80")

; form an HTTP request
let req = List.join([
  "GET / HTTP/1.0",
  "Host: cassette-lang.com",
  "",
  ""
], crlf)

; send the request
IO.write(conn, req)

; read the response and print it
IO.print(read_resp(conn))

iThis is version 2 of Cassette. Version 1 is described here.

I made Cassette as a simple language for personal programming. It's designed for solo programmers working on non-enterprise projects. It's DIY, roll your own, batteries-not-included. It's for fun.

Here are some features of Cassette:

Getting Started

This project requires a C toolchain and SDL2.

  1. Get the project's dependencies
    • On macOS with Homebrew, run brew install llvm git sdl2
    • On Debian, run apt install build-essential clang git libsdl2-dev
  2. Build Cassette
    • Clone Cassette with git clone https://github.com/protestContest/Cassette (and then cd Cassette)
    • Run make to build the project. This creates the executable bin/cassette.
  3. Try the example with ./bin/cassette test/test.ct.

Syntax

Values

Cassette has only four value types: integers, pairs, tuples, and binaries.

Integers are signed, 30-bit numbers (-536,870,912 to 536,870,911). Integers can be written in decimal, hexadecimal, or a literal byte value. The keyword true is shorthand for 1 and the keyword false is shorthand for 0

1                 ; decimal integer
0x1F              ; hex integer
$a                ; => 0x61
true              ; => 1
false             ; => 0

Symbols are arbitrary values. (Some languages call them atoms.) At runtime, these become the integer hash value of the symbol name.

:hello
:ok
:not_found_error

Pairs are Lisp-style cons cells, which are used to create linked lists. The special value nil is a pair which is the empty list. Note that the pair operator, :, is right-associative.

100 : 200         ; pair
nil               ; empty list
[1, 2, 3]         ; list, same as 1 : 2 : 3 : nil

Tuples are fixed-size arrays. They're less flexible than lists, but they use less memory and are more efficient to access. The maximum size of a tuple is the maximum integer size.

{1, 2, 3}

Binaries are byte vectors. Strings are represented as UTF-8 encoded binaries. The maximum size of a binary is the maximum integer size.

"Hello!"

Operators

Cassette supports several built-in operators on values. Most operators only work on certain types.

Basic arithmetic and comparison operators work on integers.

-24               ; unary negation
73 + 4            ; addition
87 - 41           ; subtraction
43 * 12           ; multiplication
17 / 4            ; division (truncating)
400 % 12          ; modulus
256 >> 3          ; bit shift
1 << 27
0xAA | 1          ; bitwise or
1036 & 0xFF       ; bitwise and
~7                ; bitwise not
12 < 3            ; comparison
12 <= 3
12 > 3
12 >= 3

Some operators only work with pairs, tuples, or binaries.

; get the head of a pair
@(1 : 2)          ; => 1
@[1, 2, 3]        ; => 1

; get the tail of a pair
^(1 : 2)          ; => 2
^[1, 2, 3]        ; => [2, 3]

; join two tuples or binaries
{1, 2} <> {3, 4}  ; => {1, 2, 3, 4}
"ab" <> "cd"      ; => "abcd"

; get the length of a tuple or binary
#{:foo, :bar}      ; => 2
#"hello"          ; => 5

; get an element of a tuple or binary
{1, 2, 3}[0]      ; => 1
"test"[2]         ; => $s (an integer)

; slice a tuple or binary
{1, 2, 3, 4}[1,3] ; => {2, 3}
"hello"[1,4]      ; => "ell"

Logic and equality operators work on any type. Only the values 0 (a.k.a. false) and nil evaluate as false. Logic operators short-circuit and evaluate to one of their operands. Equality is compared structurally, and returns true or false.

false or :ok      ; => :ok
true and nil      ; => nil
not nil           ; => true
not {0, 0}        ; => false
3 == 3            ; => true
[1, 2] == [1, 2]  ; => true

Blocks

Everything in Cassette is an expression. To combine multiple statements into a single expression, a do block is used. do blocks also introduce a new lexical scope for variables. The last statement in a block is the value of the block.

do
  let x = 3,
      y = 2
  x + y           ; executed, but ignored
  x - y           ; block result
end

if blocks allow conditional execution. If the else part is omitted, it defaults to nil.

if true do
  :ok
else
  :error
end

cond blocks are syntactic sugar for nested if blocks. Each clause predicate is evaluated in order until one is true, then that clause value is evaluated as the block value. If none are true, the value is nil.

cond
  x > 10    -> :ten
  x > 1     -> :one
  true      -> :less  ; default
end

Functions

Function calls look pretty standard. Lambdas can be created and assigned. The def keyword assigns a function to a variable, and is mostly syntactic sugar for let. However, def functions can be recursive and have block scope.

let foo = \a, b -> a + b
foo(1, 2)

def bar(x) {:bar, x}

def inf_loop() do
  inf_loop()
end

Modules

Cassette programs are composed of modules. A module can export some of its defined variables, and import other modules. A module can reference imported values by qualifying them with the module name or alias. Module declaration, import, and export statements must appear first in a module.

module Foo
import Bar (bar_fn), LongModuleName as LMN
export foo, foo2

def foo(x) do
  let y = Bar.parse(x)
  LMN.run(y)
end

def foo2(x) :unimplemented

bar_fn(x)    ; no qualifier needed

Primitives

Built-in functions are executed via the trap pseudo-function. These trap calls are usually wrapped in a module function at runtime for convenience. A reference of currently-implemented traps is available here.

trap(:print, "Test")
trap(:sdl_line, 10, 20, 100, 200, window)

Standard Library

Cassette provides some standard modules for common tasks. These modules are documented here.