In Io, all values are objects (of which, anything can change at runtime, including slots, methods and inheritance), all code is made up of expressions (which are runtime inspectable and modifiable) and all expressions are made up of dynamic message sends (including assignment and control structures). Execution contexts themselves are objects and activatable objects such as methods/blocks and functions are unified into blocks with assignable scope. Concurrency is made more easily manageable through actors and implemented using coroutines for scalability.
simple
http://iolanguage.org
make vm sudo make install
su -c "sudo make aptget"or:
su -c "make emerge"or:
sudo make portDepending on which package installer you use. (port is for OSX)
makeBinaries will be placed in the _build/binaries subfolder. To install:
sudo make installor, if you'd like the install to simply link to your development folder:
sudo make linkInstalland to run all unit tests:
make testDon't worry if some of the addons won't build unless it's a particular addon that you need. Addons are just optional libraries.
make AddonNameAfter doing a pull from the source control repo, be sure to do:
make clean; makeTo test just the vm:
make testvmAnd to update the reference documentation (found in docs/IoReference.html) from the source code:
make doc
io_static ioThe io_static executable contains the vm with a minimal set of primitives all statically linked into the executable. The io executable contains just enough to load the iovm dynamically linked library and is able to dynamically load io addons when they are referenced.
io samples/misc/HelloWorld.ioThere is no main() function or object that gets executed first in Io. Scripts are executed when compiled.
./_build/binaries/ioOr, if Io is installed, running:
iowill open the Io interpreter prompt.
You can evaluate code by entering it directly. Example:
Io> "Hello world!" println ==> Hello world!Expressions are evaluated in the context of the Lobby:
Io> print [printout of lobby contents]If you have a .iorc file in your home folder, it will be evaled before the interactive prompt starts.
Io> someObject slotNamesTo show them in sorted order:
Io> someObject slotNames sortFor a nicely formatted description of an object, the slotSummary method is handy:
Io> slotSummary ==> Object_0x20c4e0: Lobby = Object_0x20c4e0 Protos = Object_0x20bff0 exit = method(...) forward = method(...)Exploring further:
Io> Protos ==> Object_0x20bff0: Addons = Object_0x20c6f0 Core = Object_0x20c4b0 Io> Protos Addons ==> Object_0x20c6f0: ReadLine = Object_0x366a10Only ReadLine is seen in the Addons since no other Addons have been loaded yet.
Inspecting a method will print a decompiled version of it:
Io> Lobby getSlot("forward") ==> # io/Z_Importer.io:65 method( Importer import(call) )
doFile("scriptName.io")The evaluation context of doFile is the receiver, which in this case would be the lobby. To evaluate the script in the context of some other object, simply send the doFile message to it:
someObject doFile("scriptName.io")The doString method can be used to evaluate a string:
Io> doString("1+1") ==> 2And to evaluate a string in the context of a particular object:
someObject doString("1 + 1")
System args foreach(k, v, write("'", v, "'\n"))
System launchPath
exp ::= { message | terminator } message ::= symbol [arguments] arguments ::= "(" [exp [ { "," exp } ]] ")" symbol ::= identifier | number | string terminator ::= "\n" | ";"For performance reasons, String and Number literal messages have their results cached in their message objects.
for(i, 1, 10, i println) a := if(b == 0, c + 1, d)In the above code, "for" and "if" are just normal messages, not special forms or keywords.
Likewise, dynamic evaluation can be used with enumeration without the need to wrap the expression in a block. Examples:
people select(person, person age < 30) names := people map(person, person name)Methods like map and select will typically apply the expression directly to the values if only the expression is provided:
people select(age < 30) names := people map(name)There is also some syntax sugar for operators (including assignment), which are handled by an Io macro executed on the expression after it is compiled into a message tree. Some sample source code:
Account := Object clone Account balance := 0 Account deposit := method(amount, balance = balance + amount ) account := Account clone account deposit(10.00) account balance printlnLike Self[2], Io's syntax does not distinguish between accessing a slot containing a method from one containing a variable.
1 + 2This just gets compiled into the normal message:
1 +(2)Which is the form you can use if you need to do grouping:
1 +(2 * 4)Standard operators follow C's precedence order, so:
1 + 2 * 3 + 4Is parsed as:
1 +(2 *(3)) +(4)User defined operators (that don't have a standard operator name) are performed left to right.
operator | action |
---|---|
::= | Creates slot, creates setter, assigns value |
:= | Creates slot, assigns value |
= | Assigns value to slot if it exists, otherwise raises exception |
These operators are compiled to normal messages whose methods can be overridden. For example:
source | compiles to |
---|---|
a ::= 1 | newSlot("a", 1) |
a := 1 | setSlot("a", 1) |
a = 1 | updateSlot("a", 1) |
On Locals objects, updateSlot is overridden so it will update the slot in the object in which the method was activated if the slot is not found the locals. This is done so update assignments in methods don't require self to be an explicit target.
123 123.456 0.456 .456 123e-4 123e4 123.456e-7 123.456e2Hex numbers are also supported (in any casing):
0x0 0x0F 0XeE
s := "this is a \"test\".\nThis is only a test."Or for strings with non-escaped characters and/or spanning many lines, triple quotes can be used.
s := """this is a "test". This is only a test."""
a := b // add a comment to a line /* comment out a group a := 1 b := 2 */The "#" style is useful for unix scripts:
#!/usr/local/bin/ioThat's it! You now know everything there is to know about Io's syntax. Control flow, objects, methods, exceptions are expressed with the syntax and semantics described above.
concept | unifies |
---|---|
scopable blocks | functions, methods, closures |
prototypes | objects, classes, namespaces, locals |
messages | operators, calls, assigns, var access |
me := Person cloneTo add an instance variable or method, simply set it:
myDog name := "rover" myDog sit := method("I'm sitting\n" print)When an object is cloned, its "init" slot will be called if it has one.
Since there are no classes, there's no difference between a subclass and an instance. Here's an example of creating the equivalent of a subclass:
Io> Dog := Object clone ==> Object_0x4a7c0The above code sets the Lobby slot "Dog" to a clone of the Object object; the protos list of this new object contains only a reference to Object, essentially indicating that a subclass of Object has been created. Instance variables and methods are inherited from the objects referenced in the protos list. If a slot is set, it creates a new slot in our object instead of changing the protos:
Io> Dog color := "red" Io> Dog ==> Object_0x4a7c0: color := "red"
method((2 + 2) print)An example of using a method in an object:
Dog := Object clone Dog bark := method("woof!" print)The above code creates a new "subclass" of object named Dog and adds a bark slot containing a block that prints "woof!". Example of calling this method:
Dog barkThe default return value of a block is the result of the last expression.
add := method(a, b, a + b)The general form is:
method(<arg name 0>, <arg name 1>, ..., <do message>)
b := block(a, a + b)
slot | returns |
---|---|
call sender | locals object of caller |
call message | message used to call this method/block |
call activated | the activated method/block |
call slotContext | context in which slot was found |
call target | current object |
myif := method( (call sender doMessage(call message argAt(0))) ifTrue( call sender doMessage(call message argAt(1))) ifFalse( call sender doMessage(call message argAt(2))) ) myif(foo == bar, write("true\n"), write("false\n"))The doMessage() method evaluates the argument in the context of the receiver. A shorter way to express this is to use the evalArgAt() method on the call object:
myif := method( call evalArgAt(0) ifTrue( call evalArgAt(1)) ifFalse( call evalArgAt(2)) ) myif(foo == bar, write("true\n"), write("false\n"))
MyObject forward := method( write("sender = ", call sender, "\n") write("message name = ", call message name, "\n") args := call message argsEvaluatedIn(call sender) args foreach(i, v, write("arg", i, " = ", v, "\n") ) )
A := Object clone A m := method(write("in A\n")) B := A clone B m := method(write("in B\n"); resend) B mwill print:
in B in AFor sending other messages to the receiver's proto, super is used.
Dog := Object clone Dog bark := method(writeln("woof!")) fido := Dog clone fido bark := method( writeln("ruf!") super(bark) )Both resend and super are implemented in Io.
Io> Dog slotNames ==> list("bark")
Io> Dog protos ==> list("Object")
myMethod := Dog getSlot("bark")Above, we've set the locals object's "myMethod" slot to the bark method. It's important to remember that if you then want use the myMethod without activating it, you'll need to use the getSlot method:
otherObject newMethod := getSlot("myMethod")Here, the target of the getSlot method is the locals object.
Io> method(a, a * 2) code ==> "method(a, a *(2))"
==, !=, >=, <=, >, <return either the true or false. The compare() method is used to implement the comparison methods and returns -1, 0 or 1 which mean less-than, equal-to or greater-than, respectively.
if(<condition>, <do message>, <else do message>)Example:
if(a == 10, "a is 10" print)The else argument is optional. The condition is considered false if the condition expression evaluates to false or nil, and true otherwise.
The result of the evaluated message is returned, so:
if(y < 10, x := y, x := 0)is the same as:
x := if(y < 10, y, 0)Conditions can also be used in this form:
if(y < 10) then(x := y) else(x := 2)elseif() is supported:
if(y < 10) then(x := y) elseif(y == 11) then(x := 0) else(x := 2)
(y < 10) ifTrue(x := y) ifFalse(x := 2)Notice that the condition expression must have parenthesis surrounding it.
loop("foo" println)
3 repeat("foo" print) ==> foofoofoo
while(<condition>, <do message>)Example:
a := 1 while(a < 10, a print a = a + 1 )
for(<counter>, <start>, <end>, <optional step>, <do message>)The start and end messages are only evaluated once, when the loop starts. Example:
for(a, 0, 10, a println )Example with a step:
for(x, 0, 10, 3, x println)Which would print:
0 3 6 9To reverse the order of the loop, add a negative step:
for(a, 10, 0, -1, a println)Note: the first value will be the first value of the loop variable and the last will be the last value on the final pass through the loop. So a loop of 1 to 10 will loop 10 times and a loop of 0 to 10 will loop 11 times.
for(i, 1, 10, if(i == 3, continue) if(i == 7, break) i print )Output:
12456
Io> test := method(123 print; return "abc"; 456 print) Io> test 123 ==> abcInternally, break, continue and return all work by setting a IoState internal variable called "stopStatus" which is monitored by the loop and message evaluation code.
Examples:
result := self foo // synchronous futureResult := self futureSend(foo) // async, immediately returns a Future self asyncSend(foo) // async, immediately returns nilWhen an object receives an asynchronous message it puts the message in its queue and, if it doesn't already have one, starts a coroutine to process the messages in its queue. Queued messages are processed sequentially in a first-in-first-out order. Control can be yielded to other coroutines by calling "yield". Example:
obj1 := Object clone obj1 test := method(for(n, 1, 3, n print; yield)) obj2 := obj1 clone obj1 asyncSend(test); obj2 asyncSend(test) while(Scheduler yieldingCoros size > 1, yield)This would print "112233". Here's a more real world example:
HttpServer handleRequest := method(aSocket, HttpRequestHandler clone asyncSend(handleRequest(aSocket)) )
Io> q := method(wait(1)) Io> futureSend(q) [1-second delay] ==> nilTo avoid this, just make sure the Future isn't the result. Example:
Io> futureSend(q); nil [no delay] ==> nil
Exception raise("generic foo exception")
e := try(<doMessage>)To catch a particular exception, the Exception catch() method can be used. Example:
e := try( // ... ) e catch(Exception, writeln(e coroutine backTraceString) )The first argument to catch indicates which types of exceptions will be caught. catch() returns the exception if it doesn't match and nil if it does.
e := try( // ... ) e catch(Error, // ... ) catch(Exception, // ... ) pass
MyErrorType := Error clone
This document is not meant as a reference manual, but an overview of the base primitives and bindings is provided here to give the user a jump start and a feel for what is available and where to look in the reference documentation for further details.
if(obj getSlot("foo"), obj foo)Putting a "?" before a message has the same effect:
obj ?foo
Create an empty list:
a := List cloneCreate a list of arbitrary objects using the list() method:
a := list(33, "a")Append an item:
a append("b") ==> list(33, "a", "b")Get the list size:
a size ==> 3Get the item at a given index (List indexes begin at zero):
a at(1) ==> "a"Note: List indexes begin at zero and nil is returned if the accessed index doesn't exist.
Set the item at a given index:
a atPut(2, "foo") ==> list(33, "a", "foo", "b") a atPut(6, "Fred") ==> Exception: index out of boundsRemove an item at a given index:
a remove("foo") ==> list(33, "a", "b")Inserting an item at a given index:
a atInsert(2, "foo") ==> list(33, "a", "foo", "56")
Io> a := list(65, 21, 122)In the first form, the first argument is used as an index variable, the second as a value variable and the 3rd as the expression to evaluate for each value.
Io> a foreach(i, v, write(i, ":", v, ", ")) ==> 0:65, 1:21, 2:122,The second form removes the index argument:
Io> a foreach(v, v println) ==> 65 21 122The third form removes the value argument and simply sends the expression as a message to each value:
Io> a foreach(println) ==> 65 21 122
Io> numbers := list(1, 2, 3, 4, 5, 6) Io> numbers select(isOdd) ==> list(1, 3, 5) Io> numbers select(x, x isOdd) ==> list(1, 3, 5) Io> numbers select(i, x, x isOdd) ==> list(1, 3, 5) Io> numbers map(x, x*2) ==> list(2, 4, 6, 8, 10, 12) Io> numbers map(i, x, x+i) ==> list(1, 3, 5, 7, 9, 11) Io> numbers map(*3) ==> list(3, 6, 9, 12, 15, 18)The map and select methods return new lists. To do the same operations in-place, you can use selectInPlace() and mapInPlace() methods.
"abc" size ==> 3Checking if a string contains a substring:
"apples" containsSeq("ppl") ==> trueGetting the character (byte) at position N:
"Kavi" at(1) ==> 97Slicing:
"Kirikuro" slice(0, 2) ==> "Ki" "Kirikuro" slice(-2) # NOT: slice(-2, 0)! ==> "ro" Io> "Kirikuro" slice(0, -2) # "Kiriku"Stripping whitespace:
" abc " asMutable strip ==> "abc" " abc " asMutable lstrip ==> "abc " " abc " asMutable rstrip ==> " abc"Converting to upper/lowercase:
"Kavi" asUppercase ==> "KAVI" "Kavi" asLowercase ==> "kavi"Splitting a string:
"the quick brown fox" split ==> list("the", "quick", "brown", "fox")Splitting by others character is possible as well.
"a few good men" split("e") ==> list("a f", "w good m", "n")Converting to number:
"13" asNumber ==> 13 "a13" asNumber ==> nilString interpolation:
name := "Fred" ==> Fred "My name is #{name}" interpolate ==> My name is FredInterpolate will eval anything with #{} as Io code in the local context. The code may include loops or anything else but needs to return an object that responds to asString.
Number nextInSequence := method(skipVal, if(skipVal isNil, skipVal = 1) self + skipVal )With this method on Number (it's already there in the standard libraries), you can then use Numbers in Ranges, as demonstrated below:
1 to(5) foreach(v, v println)The above will print 1 through 5, each on its own line.
f := File with("foo.txt") f remove f openForUpdating f write("hello world!") f close
dir := Directory with("/Users/steve/")Get a list of file objects for all the files in a directory:
files := dir files ==> list(File_0x820c40, File_0x820c40, ...)Get a list of both the file and directory objects in a directory:
items := Directory items ==> list(Directory_0x8446b0, File_0x820c40, ...) items at(4) name ==> DarkSide-0.0.1 # a directory nameSetting a Directory object to a certain directory and using it:
root := Directory clone setPath("c:/") ==> Directory_0x8637b8 root fileNames ==> list("AUTOEXEC.BAT", "boot.ini", "CONFIG.SYS", ...)Testing for existence:
Directory clone setPath("q:/") exists ==> falseGetthing the current working directory:
Directory currentWorkingDirectory ==> "/cygdrive/c/lang/IoFull-Cygwin-2006-04-20"
d := Date cloneSetting it to the current date/time:
d nowGetting the date/time as a number, in seconds:
Date now asNumber ==> 1147198509.417114 Date now asNumber ==> 1147198512.33313Getting individual parts of a Date object:
d := Date now ==> 2006-05-09 21:53:03 EST d ==> 2006-05-09 21:53:03 EST d year ==> 2006 d month ==> 5 d day ==> 9 d hour ==> 21 d minute ==> 53 d second ==> 3.747125Find how long it takes to execute some code:
Date cpuSecondsToRun(100000 repeat(1+1)) ==> 0.02
Creating a URL object:
url := URL with("http://example.com/")Fetching an URL:
data := url fetchStreaming a URL to a file:
url streamTo(File with("out.txt"))A simple whois client:
whois := method(host, socket := Socket clone \ setHostName("rs.internic.net") setPort(43) socket connect streamWrite(host, "\n") while(socket streamReadNextChunk, nil) return socket readBuffer )A minimal web server:
WebRequest := Object clone do( handleSocket := method(aSocket, aSocket streamReadNextChunk request := aSocket readBuffer \ betweenSeq("GET ", " HTTP") f := File with(request) if(f exists, f streamTo(aSocket) , aSocket streamWrite("not found") ) aSocket close ) ) WebServer := Server clone do( setPort(8000) handleSocket := method(aSocket, WebRequest clone asyncSend(handleSocket(aSocket)) ) ) WebServer start
SGML // reference this to load the SGML addon xml := URL with("http://www.yahoo.com/") fetch asXML links := xml elementsWithName("a") map(attributes at("href"))
Vector := Sequence clone setItemType("float32")The Sequence primitive supports SIMD acceleration on a number of float32 operations. Currently these include add, subtract, multiple and divide but in the future can be extended to support most math, logic and string manipulation related operations. Here's a small example:
iters := 1000 size := 1024 ops := iters * size v1 := Vector clone setSize(size) rangeFill v2 := Vector clone setSize(size) rangeFill dt := Date secondsToRun( iters repeat(v1 *= v2) ) writeln((ops/(dt*1000000000)) asString(1, 3), " GFLOPS")Which when run on 2Ghz Mac Laptop, outputs:
1.255 GFLOPSA similar bit of C code (without SIMD acceleration) outputs:
0.479 GFLOPSSo for this example, Io is about three times faster than plain C.
uint8, uint16, uint32, uint64 int8, int16, int32, int64 float32, float64
number, ascii, ucs2, ucs4, utf8UCS-2 and UCS-4 are the fixed character width versions of UTF-16 and UTF-32, respectively. A String is just a Sequence with a text encoding, a Symbol is an immutable String and a Vector is a Sequence with a number encoding.
UTF encodings are assumed to be big endian.
Except for input and output, all strings should be kept in a fixed character width encoding. This design allows for a simpler implementation, code sharing between vector and string ops, fast index-based access, and SIMD acceleration of Sequence operations. All Sequence methods will do automatic type conversions as needed.
Io> "hello" encoding ==> ascii Io> "π" encoding ==> ucs2 Io> "∞" encoding ==> ucs2We can also inspect the internal representation:
Io> "π" itemType ==> uint16 Io> "π" itemSize ==> 2
asUTF8 asUCS2 asUCS4
typdef struct { char *firstName; char *lastName; char *address; } Person;
Example:
List *List_new(void); void List_free(List *self);All methods (except new) have the structure (the "object") as the first argument the variable is named "self". Method names are in keyword format. That is, for each argument, the method name has a description followed by an underscore. The casing of the descriptions follow that of structure member names.
Examples:
int List_count(List *self); // no argument void List_add_(List *self, void *item); // one argument void Dictionary_key_value_(Dictionary *self, char *key, char *value);
#include "IoState.h" int main(int argc, const char *argv[]) { IoState *self = IoState_new(); IoState_init(self); IoState_doCString_(self, "writeln(\"hello world!\""); IoState_free(self); return 0; }
IoObject *v = IoState_doCString_(self, someString); char *name = IoObject_name(v); printf("return type is a ‘%s', name); IoObject_print(v);
if (ISNUMBER(v)) { printf("result is the number %f", IoNumber_asFloat(v)); } else if(ISSEQ(v)) { printf("result is the string %s", IoSeq_asCString(v)); } else if(ISLIST(v)) { printf("result is a list with %i elements", IoList_rawSize(v)); }Note that return values are always proper Io objects (as all values are objects in Io). You can find the C level methods (functions like IoList_rawSize()) for these objects in the header files in the folder Io/libs/iovm/source.
expression ::= { message | sctpad } message ::= [wcpad] symbol [scpad] [arguments] arguments ::= Open [argument [ { Comma argument } ]] Close argument ::= [wcpad] expression [wcpad]
symbol ::= Identifier | number | Operator | quote Identifier ::= { letter | digit | "_" } Operator ::= { ":" | "." | "'" | "~" | "!" | "@" | "$" | "%" | "^" | "&" | "*" | "-" | "+" | "/" | "=" | "{" | "}" | "[" | "]" | "|" | "\" | "<" | ">" | "?" }
quote ::= MonoQuote | TriQuote MonoQuote ::= """ [ "\"" | not(""")] """ TriQuote ::= """"" [ not(""""")] """""
Terminator ::= { [separator] ";" | "\n" | "\r" [separator] } separator ::= { " " | "\f" | "\t" | "\v" } whitespace ::= { " " | "\f" | "\r" | "\t" | "\v" | "\n" } sctpad ::= { separator | Comment | Terminator } scpad ::= { separator | Comment } wcpad ::= { whitespace | Comment }
Comment ::= slashStarComment | slashSlashComment | poundComment slashStarComment ::= "/*" [not("*/")] "*/" slashSlashComment ::= "//" [not("\n")] "\n" poundComment ::= "#" [not("\n")] "\n"
number ::= HexNumber | Decimal HexNumber ::= "0" anyCase("x") { [ digit | hexLetter ] } hexLetter ::= "a" | "b" | "c" | "d" | "e" | "f" Decimal ::= digits | "." digits | digits "." digits ["e" [-] digits]
Comma ::= "," Open ::= "(" | "[" | "{" Close ::= ")" | "]" | "}" letter ::= "a" ... "z" | "A" ... "Z" digit ::= "0" ... "9" digits ::= { digit }The uppercase words above designate elements the lexer treats as tokens.
1 |
Goldberg, A et al.
Smalltalk-80: The Language and Its Implementation Addison-Wesley, 1983 |
2 |
Ungar, D and Smith,
RB. Self: The Power of Simplicity OOPSLA, 1987 |
3 |
Smith, W.
Class-based NewtonScript Programming PIE Developers magazine, Jan 1994 |
4 |
Lieberman
H. Concurrent Object-Oriented Programming in Act 1 MIT AI Lab, 1987 |
5 |
McCarthy, J et al.
LISP I programmer's manual MIT Press, 1960 |
6 |
Ierusalimschy, R, et al.
Lua: an extensible extension language John Wiley & Sons, 1996 |
Redistribution and use of this document with or without modification, are permitted provided that the copies reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
This documentation is provided "as is" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the authors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this documentation, even if advised of the possibility of such damage.