Home

Awesome

Sad-Script

Project status: This project is finished and the author has moved on, but it works as described and serves as an interesting exercise in programming language implementation. Commit f5c7a2c implements the language as documented below, with subsequent commits adding undocumented support for immutability and type annotations.

Try Sad-Script now in your browser!

A dynamically-typed, interpreted, garbage-collected language with 8 data types: Int, Double, Bool, String, List, Function, Error, and Type. There are no semicolons, and whitespace is insignificant. Identifiers can contain anything except parentheses, braces, brackets, double quotes, colons, backslashes, the lambda symbol (λ), and a small handful of reserved keywords. Identifiers must contain at least one character that isn't a digit and isn't a period, in order to distinguish identifiers from integer and double literals. Variables must be declared with var before they are used. A default value must always be provided.

Control flow structures are JavaScript-like, with mandatory braces. Function calls and expressions are Lisp-like, with parentheses around prefix-style function calls and brackets around infix-style function calls. The two styles are equivalent and can be mixed and matched to taste.

var a = 5
var x = [6 + a]   // calling the "+" function with infix notation
var y = (+ 6 a)   // calling the "+" function with prefix notation

Pattern matching is supported through the switch and match keywords. A list of values (or, implicitly, the arguments to the containing function) can be matched by type or by value.

for i from 1 to 100 {
  var multiple-of-3 = (zero? [i % 3])
  var multiple-of-5 = (zero? [i % 5])
  
  (println match multiple-of-3 multiple-of-5 {
    case true true: "FizzBuzz"
    case true false: "Fizz"
    case false true: "Buzz"
    default: i
  })
}

Partial function application, closures, and variable argument lists are supported. Combining these features allows the standard library to include a pipe function which pipes the output of one function call into the next. This allows a function chaining style that will be familiar to C# programmers who use that language's Enumerable extension methods. Also like C#, the list manipulation functions operate on lazy sequences (with support for infinite sequences) rather than producing a complete list after each call.

function fibonacci (n) = match {
   case 0: 0
   case 1: 1
   default: [(fibonacci [n - 1]) + (fibonacci [n - 2])]
}

function even? (x) = (zero? [x % 2])

var first-twenty-even-terms = (pipe [0 ...] (map fibonacci) (filter even?) (take 20))

Multiple return values from a function are supported in a simple way: a special assignment syntax allows a list to be broken out into separate variables for each element. The function can return a normal list.

function returns-list () = (list 1 2 3)
var (a b c) = (returns-list)  // a=1, b=2, c=3

The interpreter is written in ANSI C (C89) and compiles cleanly with -ansi -pedantic -Wall -Wextra -Werror compiler flags. A wide variety of compilers are supported: gcc, clang, Microsoft Visual C++ 2013, Emscripten (emcc), TinyCC (tcc), LCC-Win (lc), Borland C++ 5.5 (bcc32), Open Watcom (owcc). Both 32-bit and 64-bit builds are supported.

It has been tested in Windows 7, OS X 10.9, and Debian Linux 7.7. The interpreter can be embedded in client applications simply by including sad-script.c and sad-script.h file in the client project or Makefile. No need to build or link a separate library. Since the bindings are in C, they can be accessed easily from any language with a FFI. For instance .NET can access it via P/Invoke.