Slippy Language Documentation

The Slippy Language is designed to be simple. While this might seem limiting, the lack of complexity is liberating because it allows you to focus on prototyping, rather than fighting with arcane tool-oriented problems. This isn't for writing solid, bullet-proof code. Instead, prototype an idea with Slippy, and port it to a 'real' langauge later, once you've got the idea down.

This documentation is mostly complete. It lacks coverage of some stranger things that I've not exactly nailed down, like item annotations, class (static) members, and Slippy-Java communication. Also, it is possible to run Olive in a local disk-based environment, and it is also possible to run Slippy commands from a command line, but for now those things are also undocumented.

codeset
import
variables
strings
arithmetic
if
while
loop
functions
shadow
lists
maps
lambda
define class
mixes
objects
mystery members
math-functions
non-math-functions
to_s
(end)
codeset
[Top]
codeset org.six11.sample
The codeset phrase appears at the top of a file and indicates which group of code this source file is in. It acts like Java's package keyword. Any classes defined below will be considered a member of the indicated codeset.
import
[Top]
codeset org.firstbunch

; use 'import' to make the following class available
import org.secondbunch.OtherThing

; now you can use it
ot = new OtherThing()

; since SampleThing wasn't imported, Slippy assumes it
; can be found in this codeset (org.firstbunch).
st = new SampleThing()
The 'import' keyword(s) appears just below the 'codeset' keyword. This tells Slippy where to look for other code that is referred to in the current file. There is no wildcard: you must specify class files by name.
variables
[Top]
x = 10
y = x * x
print (x, "*", x, "is", y) ; 10.0 * 10.0 is 100.0
print ("z is undefined:", z) ; z is undefined: nil
x = "Now I'm a string"
print (x) ; Now I'm a string
x = nil
print ("The value of x is now", x) ; The value of x is now nil
Variables are assigned with the = operator (that's one = character). The variable is on the left, and the value is on the right. You can start using variables whenever you like, but if you haven't given it a value, it has the default value of 'nil'. You can explicitly assign nil to a value if you like. Variables are typed, but you don't have to declare what type they are, and you can reuse the same variable for storing values of different types.
strings
[Top]
print("This is a string") ; This is a string
print("This", "is", "easy", "to", "read") ; This is easy to read
print("this" + "is" + "hard" + "to" + "read") ; thisishardtoread
print("Combining strings and numbers: " + 42) ; Combining strings and numbers: 42.0
Strings are text surrounded by double quotes. You can concatenate them with the + operator (which doesn't introduce any extra spaces), or in the special case of the 'print' function, you can use commas to separate strings and it will insert spaces for you. In general you'll probably want to use + though.
arithmetic
[Top]
; All of the following evaluates to 10.
4 + 6
(4 + 6)
12 - 2
(15 - 1) - 4
2 * 5
(1 * 2) * 5
2 * ( 1 + 4)
20 / 2
10 / 2 + 10 / 2
60 % 50
Arithmetic is straightforward, and supports addition (+), subtraction (-), multiplication (*), division (/), and modulus (%). Slippy uses the same precedence rules as languages like Java and Ruby. You can use parens to group terms together. The only nonstandard thing about numbers in Slippy is that they are all interpreted as floating point values.
if, else if, else
[Top]
t = true
f = false

if (t)
  print("Good.") ; this always runs
done

if (f)
  print("No...")
else if (t and f)
  print("No...")
else if (t or f)
  print("Yes.") ; This executes
else
  print("Hmm.") ; This won't execute because the 'Yes' one did.
done
You can have conditional branching with the 'if' construct. It will execute the first 'if' (or 'else if') clause that evaluates to true. When no clause matches, it will execute the 'else' clause, if it is present. The 'if' construct is closed with the 'done' keyword.
while
[Top]
x = 5
while (x > 0)
  print("x: ", x) ; counts from 5 down to 1
  x = x - 1
done
print("x finishes at", x) ; x finishes at 0.0
While loops will execute a block of code as long as the statement at top evaluates to some form of true. End a while loop with the 'done' keyword. This is almost equivalent to a 'loop' construct. The difference is that while loops will always try to resolve the parenthetical statement as a boolean value, even if you give it a number, string, list, or object.
loop
[Top]
; Loop 3 times
loop(3)
  print("It works three times!")
done

; Loop while the condition is true
boo = true
loop(boo)
  print("I should 'loop' one time.")
  boo = false
done

; Loop through a list with a new 'x' value each time
myList = ["John", "Paul", "George", "Ringo"]
loop(x : myList)
  print ("A Beatle:", x)
done
There are three flavors of 'loop'. The first simply counts down a set number of times by giving it a scalar number. The second type is almost equivalent to a while loop, and runs so long as its boolean expression is true. The last type has slightly different syntax, and lets you iterate through lists. A loop is closed with the 'done' keyword.
functions
[Top]
define f(x)
  x * x ; a function's value is the last value it computes
done

; This shows passing funtions and invoking them
define doSomething(aFunction, aValue)
  aFunction(aValue)
done

print(f(3)) ; prints 9.0
print(doSomething(f, 5)) ; prints 25.0
A function in Slippy is made with the 'define' keyword followed by the function's name, followed by a parenthetical list of parameters, a block of code, and ended with the 'done' keyword. You must include the parens, even if there are no arguments. The return value of a function is the value of the last thing it evaluates. Also notice you can treat functions as variables if you leave off the parens, so you can pass them into another function, and invoke them.
shadow variables
[Top]
x = "ecks"
y = "why"

define mutate(y)
  y = x ; read from the global x
  x = 3 ; don't write to the global x
  print(x, y)
done

print(x, y) ; ecks why
mutate(y)   ; 3.0 ecks
print(x, y) ; ecks why
Variables declared at a high level (like x and y here) don't get overwritten when you assign to them inside functions. This is different from some programming languages, and I made it this way because I wanted to reduce the headaches associated with global variables that change when you don't expect them to. You can still read from global primitive values, but you can't write to them inside functions.
list operations
[Top]
; Making a list
stuff = ["a", "something", nil, (4*3)]
print (stuff) ; [ a, something, nil, 12.0 ]

; n()
print ("The list has " + stuff.n() + " items") ; The list has 4.0 items

; add(val)
stuff.add(8)
print (stuff) ; [ a, something, nil, 12.0, 8.0 ]

; remove(val)
stuff.remove(nil)
print (stuff) ; [ a, something, 12.0, 8.0 ]

; removeAtIndex(idx)
stuff.removeAtIndex(0)
print (stuff) ; [ something, 12.0, 8.0 ]

; indexOf(val)
print("12 is at index: " + stuff.indexOf(12)) ; 12 is at index: 1.0

; resort(sortFunction)
s = lambda (a, b)   ; sort functions work like in Java
    if (a < b)
      -1            ; -1 means first param is < second
    else if (a > b)
      1             ; 1 means first param is > second
    else
      0             ; 0 means params are equal
    done
  done
jumble = [ 4, 2, 6, 3, 7, 5 ]
print (jumble) ; [ 4.0, 2.0, 6.0, 3.0, 7.0, 5.0 ]
ordered = jumble.resort(s)
print (ordered) ; [ 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 ]

; each
squared = []
go = lambda (v)
    squared.add(v* v)
  done
jumble.each(go)
print(squared) ; [ 16.0, 4.0, 36.0, 9.0, 49.0, 25.0 ]

; filter
is_odd = { (v) if (v % 2) v else nil done }
odds = jumble.filter(is_odd)
print(odds) ; [ 3.0, 7.0, 5.0 ]

; sum, min, and max
sumVal = jumble.sum()
minVal = jumble.min()
maxVal = jumble.max()
print("sum is " + sumVal) ; sum is 27.0
print("min is: " + minVal) ; min is: 2.0
print("max is: " + maxVal) ; max is: 7.0

; sort
orderedSquares = []
orderedSquares.sort = s
squared.each( { (v) orderedSquares.add(v) } )
print(orderedSquares) ; [ 4.0, 9.0, 16.0, 25.0, 36.0, 49.0 ]

; copy
osCopy = orderedSquares.copy()
osCopy.removeAtIndex(osCopy.n() - 1)
osCopy.removeAtIndex(0)
print(osCopy) ; [ 9.0, 16.0, 25.0, 36.0 ]
print(orderedSquares) ; [ 4.0, 9.0, 16.0, 25.0, 36.0, 49.0 ]
There are many operations built in to lists. The examples above should demonstrate them fairly well.
maps
[Top]
empty = {}
print("Empty map: " + empty) ; Empty map: { }

beatles = { 
	"Lead guitar" : "George",
	"Rythym guitar" : "John",
	"Bass"	: "Paul",
	"Drums" : "Ringo" }

print("The Beatles are: " + beatles) ; The Beatles are: { Lead guitar : George, 
                                     ; Rythym guitar : John, Drums : Ringo, 
                                     ; Bass : Paul }
print("The bass player was " + beatles["Bass"]) ; The bass player was Paul
print("The cowbell player was " + beatles["Cowbell"]) ; The cowbell player was nil
Slippy has associative arrays, which are often called maps or hashes. This data type is a little deficient, as it doesn't have any fanciful features like lists do. To make a map, use the curly-brace syntax, with key-value pairs separated by colons, and mappings separated by commas. To use a map, use square brackets with the key value inside.
lambda
[Top]
; the 'long' way to make a lambda
square = lambda(x)
    x * x
  done

; the 'short' way
cube = { (x) x * x * x }

; lambda params can be functions
runNtimes = lambda(f, p, n)
    loop (n)
      print(f(p))
    done
  done

print(square(3)) ; 9.0

runNtimes(cube, 4, 2) ; 64.0 two times

A lambda is an anonymous function that may be defined wherever you like, stored in variables and passed around, and executed. It does not guarantee that variables refered to within the closure will always work, so it wouldn't be quite right to call them closures. There are two ways to make a lambda, both of which are shown above. The long version is probably more legible, but sometimes the short version is more appropriate (even though its syntax involves lots of punctuation).
define class
[Top]
; A sample class definition including a constructor.
class SampleThingy

  name ; Thingy's name. Members are declared like this.
  num  ; A number

  define init()
    name = "Default Name"
    num = 42
  done

done
Define a class with 'class X', followed by a block of code, and close it off with a 'done' statement.
mixes
[Top]
class Named
  name
  define mix()
    print("Mixing a named thing.")
  done
  define printName()
    print("Hello my name is", name)
  done
done

class CanFly
  speed = 10
  define mix()
    print("Mixing a thing that can fly.")
  done
  define fly()
    print("I am flying!")
  done
done

class Bird mixes Named, CanFly
  define init()
    print("In Bird's constructor")
  done
done

; The following will print:
;   In Bird's constructor
;   Mixing a named thing.
;   Mixing a thing that can fly.
b = new Bird()

; Now test out the mixed in capabilities.
b.name = "Mrs. Tweetypants"
b.printName() ; Hello my name is Mrs. Tweetypants
b.fly() ; I am flying!
The 'mixes' keyword lets you get a sort of multiple-inheritance effect with your classes. A class may only extend one class, but it may mix in any number of other classes with a comma-separated list. When a class C mixes a class M, all of M's members and functions are infused with C's as though they were copied directly into the file. Further, if M has a function named 'mix', it is executed (after the constructor is called). The 'mix' function is provided as an alternative to a constructor.
object instances
[Top]
class Point
  x ; horizontal dimension, larger is to the right
  y ; vertical dimension, larger is down

  define init(x_, y_) ; constructor is always called init
    x = x_
    y = y_
  done

  define translate(dx, dy)
    x = x + dx
    y = y + dy
  done

  define report()
    print("Location: " + x + ", " + y)
  done
done

p1 = new Point(0, 0)
p2 = new Point(5, 5)
p1.report() ; Location: 0.0, 0.0
p2.report() ; Location: 5.0, 5.0
p2.translate(10, -3)
p2.report() ; Location: 15.0, 2.0

You may instantiate a class any number of times and as you expect, the instances are separate. The constructor for a class (if present) is always called 'init'. Member variables and functions (those things defined inside a class) are publicly visible, so we could easily access p1.x or p1.y to get the first point's location components.
mystery members / generic objects
[Top]
; Make a plain 'untyped' object, a global function, and a global variable
obj = new Object()
define f()
  3
done
six = 6

; Assign 'mystery members' to this object
obj.three = f      ; assign a function---note there are no parens!
obj.four = "four"  ; assign a string
obj.five = (2 + 3) ; assign the result of evaluating an expression
obj.six = six      ; assign a copy of whatever is in variable 'six'

print(obj.three(), obj.four, obj.five, obj.six) ; 3.0 four 5.0 6.0
six = "error" ; changing original variable has no effect on obj.six
print(obj.three(), obj.four, obj.five, obj.six) ; 3.0 four 5.0 6.0
You can make a generic, 'untyped' object with 'new Object()', and arbitrarily assign values to 'mystery members'. You may also assign values to mystery members of other object types (e.g. those that instantiate a class). Mystery members may be functions or variables. This is useful, for example, if you want to return several values from a function. Since Slippy doesn't support tuples, this is a suboptimal workaround. When assigning mystery members, the object gets a copy of whatever you assign (refer the last two lines of the example).
built-in math functions
[Top]
; abs(v) - get absolute value of a number
print(abs(-3)) ; 3.0

; min(a,b) - return the minimum of two numbers
print(min(-1, 2)) ; -1.0

; max(a,b) - return the maximum of two numbers
print(max(-1, 2)) ; 2.0

; sqrt(v) - return the square root of a number
print(sqrt(4.0)) ; 2.0

; pow(a, b) - return a raised to the power of b
print(pow(2,4)) ; 16.0

; sin(x) - sine trig function
print(sin(1)) ; 0.8414709848078965

; cos(x) - cosine trig function
print(cos(1)) ; 0.5403023058681398

; tan(x) - tangent trig function
print(tan(1)) ; 1.5574077246549023

; arcsin(x) - inverse sine (arc sine) trig function
print(arcsin(1)) ; 1.5707963267948966

; arccos(x) - inverse cosine (arc cosine) trig function
print(arccos(1)) ; 0.0

; arctan(x) - inverse tangent (arc tangent) trig function
print(arctan(1)) ; 0.7853981633974483

; constant represeting PI
print(PI) ; 3.141592653589793

; constant representing E
print(E) ; 2.718281828459045
There are many built-in math functions and scalar values. These are summarized above.
built-in non-math functions
[Top]
; now() - returns the current time in milliseconds
start = now()
doSomething()
end = now()
print( (end - start) + " ms elapsed") ; 56.0 ms elapsed

; getType(t) - gives the type of any expression
t = new SampleThingy()
print(getType(t)) ; Instance
print(getType(3)) ; Number
print(getType("foo")) ; String
print(getType(print)) ; Function
print(getType( [1, 2, 3] )) ; Array
print(getType( { "key" : "val" } )) ; Map

; showStacktrace() - shows the line/column where execution is
showStacktrace()
  ; Prints a stacktrace such as the following:
  ;  /org/sample/SampleThingy.slippy:saySomething:13:4
  ;  /org/sample/SampleThingy.slippy:18:15

; printMembers(obj)
printMembers(mySampleThingy)
  ; Prints symbol tables such as the following:
  ; (Table for SampleThingy instance) (hash: 796215886) (parent: -2001183084)
  ; +-------------------------------------------+  
  ; | getClass         | getClass ()         |  |
  ; | printSymbolTable | printSymbolTable () |  |
  ; +-------------------------------------------+

  ; (Symbol table for class SampleThingy) (hash: -2001183084)
  ; +------------------------------------------+
  ; | saySomething | saySomething (sumthin) |  |
  ; | init         | init ()                |  |
  ; +------------------------------------------+


Some non-math functions. These include 'now' (for getting the current system clock in milliseconds) and 'getType' (used to determine what type something is). 'getType' will not tell you which class an object instantiates, rather it will rather unhelpfully tell you it is an 'Instance'. There are two useful debugging functions: 'showStacktrace' and 'printMembers'.
to_s
[Top]
class ThingWithoutToString
 ; nothing here :(
done

class ThingWithToString
  define to_s()
    "I am fancy"
  done
done

t1 = new ThingWithoutToString()
t2 = new ThingWithToString()
print(t1) ; 
print(t2) ; I am fancy
If an object instance has a to_s function, that will be used whenever the object must be cast to a string. Otherwise it will give you a string that is vaguely helpful, since it gives you the class name and a unique hash code (which can be used to differentiate instances of the same type).