# Io — Full Documentation Io is a dynamic, prototype-based language built on a few simple ideas taken from Smalltalk (all values are objects), Self (prototypes, not classes), NewtonScript (differential inheritance), Act1 (actors and futures for concurrency), Lisp (code is a runtime-inspectable message tree), and Lua (small and embeddable). See also: /llms.txt (curated index) and /sitemap.xml. --- Source: / A small, prototype-based programming language — now targeting WASM/WASI. ## About Io is a dynamic, prototype-based language built on a few simple ideas taken from Smalltalk (all values are objects), Self (prototypes, not classes), NewtonScript (differential inheritance), Act1 (actors and futures for concurrency), Lisp (code is a runtime-inspectable message tree), and Lua (small and embeddable). The VM is written in portable C and now targets WebAssembly (WASI) by default. The same binary runs under wasmtime, Node.js, or directly in the browser via a bidirectional Io↔JavaScript bridge. ## Key Features - **Minimal Syntax** — Expressions only. No keywords, no statements, no special forms. Every expression is a message send, including assignment, operators, and control flow. - **Prototype-Based Objects** — No classes. New objects are clones of existing ones, slots are created on write, and any object can be used as a prototype for any other. - **Differential Inheritance** — Clones inherit by reference until a slot is assigned, so most objects carry only the deltas that distinguish them from their parents. - **Multiple Inheritance** — Every object has a list of protos; lookup is depth-first with runtime loop detection. - **Messages as Code** — Messages form trees that can be inspected and rewritten at runtime. Argument evaluation can be deferred, so `if`, `while`, and `for` are implementable in Io itself. - **Runtime Introspection** — Slots, protos, message trees, and block source code are all inspectable and modifiable at runtime. - **Actors and Futures** — Any object can receive `asyncSend` or `futureSend` messages and run its own coroutine. Futures are transparent (they become their result) and detect deadlocks automatically. - **Coroutines Everywhere** — Concurrency uses cooperative user-level coroutines rather than OS threads, scaling to thousands of active tasks without thread-level overhead. - **First-Class Continuations** — the VM is callcc-capable: the current computation can be captured as a first-class value and later resumed, stored, or serialized. - **Unified Sequences** — Strings, buffers, and numeric vectors share one `Sequence` primitive, with multiple item types, text encodings, and SIMD acceleration on float vectors. - **WASM/WASI Target** — The default build produces a single WebAssembly module that runs under wasmtime, Node.js, or in browsers, with full access to the DOM when hosted in a page. - **Io↔JS Bridge** — Host integration on the browser target is bidirectional: Io can call JavaScript functions and JavaScript can call Io methods, replacing the native-addon model of earlier Io releases. - **Embeddable Core** — The VM ships as a single WebAssembly module under a BSD license, embeddable in any WASM host — browsers, Node.js, or native apps via wasmtime or wasmer. - [Documentation](docs/index.html) — Guides, tutorial, language reference, and design notes. - [Source on GitHub](https://github.com/IoLanguage/io) — Io language VM source repository. - FAQ - Timeline For AI agents: [llms.txt](llms.txt) (curated index) and [llms-full.txt](llms-full.txt) (full content). --- Source: /docs/ # Documentation Guides, tutorial, language reference, and design notes. - Guide - Tutorial - Book - Reference - Style Guide - Technical Notes --- Source: /docs/Book/ # Book The original Io book, organized by chapter. - Introduction - Syntax - Objects - Control Flow - Concurrency - Primitives - Appendix --- Source: /docs/Book/Appendix/ --- heroImage: ../images/Appendix.png --- # Appendix Grammar reference and citations. ## Grammar #### messages expression ::= { message | sctpad } message ::= [wcpad] symbol [scpad] [arguments] arguments ::= Open [argument [ { Comma argument } ]] Close argument ::= [wcpad] expression [wcpad] #### symbols symbol ::= Identifier | number | Opereator | quote Identifier ::= { letter | digit | "_" } Operator ::= { ":" | "." | "'" | "~" | "!" | "@" | "$" | "%" | "^" | "&" | "*" | "-" | "+" | "/" | "=" | "{" | "}" | "[" | "]" | "|" | "\" | "<" | ">" | "?" } #### quotes quote ::= MonoQuote | TriQuote MonoQuote ::= """ [ "\"" | not(""")] """ TriQuote ::= """"" [ not(""""")] """"" #### spans 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 } #### comments Comment ::= slashStarComment | slashSlashComment | poundComment slashStarComment ::= "/*" [not("*/")] "*/" slashSlashComment ::= "//" [not("\n")] "\n" poundComment ::= "#" [not("\n")] "\n" #### numbers number ::= HexNumber | Decimal HexNumber ::= "0" anyCase("x") { [ digit | hexLetter ] } hexLetter ::= "a" | "b" | "c" | "d" | "e" | "f" Decimal ::= digits | "." digits | digits "." digits ["e" [-] digits] #### characters Comma ::= "," Open ::= "(" | "[" | "{" Close ::= ")" | "]" | "}" letter ::= "a" ... "z" | "A" ... "Z" digit ::= "0" ... "9" digits ::= { digit } Uppercase words designate elements the lexer treats as tokens. ## References 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.* --- Source: /docs/Book/Concurrency/ --- heroImage: ../images/Concurrency.png --- # Concurrency Coroutines, actors, futures, and Io's concurrency model. ## Coroutines Io uses coroutines (user level cooperative threads), instead of preemptive OS level threads to implement concurrency. This avoids the substantial costs (memory, system calls, locking, caching issues, etc) associated with native threads and allows Io to support a very high level of concurrency with thousands of active threads. Under the stackless evaluator, a coroutine is simply a chain of heap-allocated evaluation frames. Switching coroutines is a matter of changing which frame chain the eval loop is walking — there is no platform-specific assembly, no `setjmp`/`longjmp`, and no `ucontext` or fibers. The same C code works on every host, including WebAssembly, which hides the native call stack entirely. ### Scheduler The Scheduler object is responsible for resuming coroutines that are yielding. The current scheduling system uses a simple first-in-first-out policy with no priorities. ## Actors An actor is an object with its own thread (in our case, its own coroutine) which it uses to process its queue of asynchronous messages. Any object in Io can be sent an asynchronous message by placing using the asyncSend() or futureSend() messages. Examples: Synchronous: ```io result := self foo ``` Asynchronous, immediately returns a Future: ```io futureResult := self futureSend(foo) ``` Asynchronous, immediately returns nil: ```io self asyncSend(foo) ``` When 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: ```io 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: ```io HttpServer handleRequest := method(aSocket, HttpRequestHandler clone asyncSend( handleRequest(aSocket) ) ) ``` ## Futures Io's futures are transparent. That is, when the result is ready, they become the result. If a message is sent to a future (besides the two methods it implements), it waits until it turns into the result before processing the message. Transparent futures are powerful because they allow programs to minimize blocking while also freeing the programmer from managing the fine details of synchronization. ### Auto Deadlock Detection An advantage of using futures is that when a future requires a wait, it will check to see if pausing to wait for the result would cause a deadlock and if so, avoid the deadlock and raise an exception. It performs this check by traversing the list of connected futures. ### Futures and the Command Line Interface The command line will attempt to print the result of expressions evaluated in it, so if the result is a Future, it will attempt to print it and this will wait on the result of Future. Example: ```io Io> q := method(wait(1)) Io> futureSend(q) [1-second delay] ==> nil ``` To avoid this, just make sure the Future isn't the result. Example: ```io Io> futureSend(q); nil [no delay] ==> nil ``` ### Yield An object will automatically yield between processing each of its asynchronous messages. The yield method only needs to be called if a yield is required during an asynchronous message execution. ### Pause and Resume It's also possible to pause and resume an object. See the concurrency methods of the Object primitive for details and related methods. ## Continuations Because evaluation state lives in heap-allocated frames rather than on the C stack, Io supports first-class continuations. `callcc` captures the current computation — its frame chain and local state — as a first-class object that can be stored, invoked later, or even serialized and resumed in another process. The same property also makes it possible to model resumable exceptions: a handler can choose to resume the computation at the point of the raise, rather than unwinding past it, enabling Smalltalk- or Common-Lisp-style condition systems. Because `callcc` is easy to misuse, it is not exposed in the top-level Lobby. It is available where needed through the VM's reflective interface. --- Source: /docs/Book/Control%20Flow/ --- heroImage: ../images/Control Flow.png heroLayout: wide heroAspect: 1024 / 681 --- # Control Flow Branching, loops, exceptions, and scope. ## Branching ### if, then, else The if() method can be used in the form: ```io if(, , ) ``` Example: ```io 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: ```io if(y < 10, x := y, x := 0) ``` is the same as: ```io x := if(y < 10, y, 0) ``` Conditions can also be used in this form: ```io if(y < 10) then(x := y) else(x := 2) ``` elseif() is supported: ```io if(y < 10) then(x := y) elseif( y == 11) then(x := 0) else(x := 2) ``` ### ifTrue, ifFalse Also supported are Smalltalk style ifTrue, ifFalse, ifNil and ifNonNil methods: ```io (y < 10) ifTrue(x := y) ifFalse(x := 2) ``` Notice that the condition expression must have parenthesis surrounding it. #### loop The loop method can be used for "infinite" loops: ```io loop("foo" println) ``` ### repeat The Number repeat method can be used to repeat a loop a given number of times. ```io 3 repeat("foo" print) ==> foofoofoo ``` #### while Arguments: ```io while(, ) ``` Example: ```io a := 1 while(a < 10, a print a = a + 1 ) ``` ### for Arguments: ```io for(, , , , ) ``` The start and end messages are only evaluated once, when the loop starts. Example: ```io for(a, 0, 10, a println ) ``` Example with a step: ```io for(x, 0, 10, 3, x println) ``` Which would print: ```io 0 3 6 9 ``` To reverse the order of the loop, add a negative step: ```io 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. ### break, continue loop, repeat, while and for support the break and continue methods. Example: ```io for(i, 1, 10, if(i == 3, continue) if(i == 7, break) i print ) ``` Outputs: ```io 12456 ``` ### return Any part of a block can return immediately using the return method. Example: ```io Io> test := method(123 print; return "abc"; 456 print) Io> test 123 ==> abc ``` Internally, `break`, `continue`, and `return` are handled by the iterative evaluator's frame state machine: each sets a non-local control signal on the current frame, and the eval loop unwinds frames until it reaches the enclosing loop or block. ## Comparison ### true, false and nil There are singletons for true, false and nil. nil is typically used to indicate an unset or missing value. ### Comparison Methods The comparison methods: ```io ==, !=, >=, <=, >, < ``` 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. ## Exceptions ### Raise An exception can be raised by calling raise() on an exception proto. ```io Exception raise("generic foo exception") ``` ### Try and Catch To catch an exception, the try() method of the Object proto is used. try() will catch any exceptions that occur within it and return the caught exception or nil if no exception is caught. ```io e := try() ``` To catch a particular exception, the Exception catch() method can be used. Example: ```io e := try( // ... ) ``` ```io 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. #### Pass To re-raise an exception caught by try(), use the pass method. This is useful to pass the exception up to the next outer exception handler, usually after all catches failed to match the type of the current exception: ```io e := try( // ... ) ``` ```io e catch(Error, // ... ) catch(Exception, // ... ) pass ``` ### Custom Exceptions Custom exception types can be implemented by simply cloning an existing Exception type: ```io MyErrorType := Error clone ``` ### Resumable Exceptions Because evaluation state lives in heap-allocated frames, the frame that raised an exception is still a live, inspectable object when a handler runs. A handler can choose to *resume* the computation at the point of the raise — returning a value in place of the exception — instead of unwinding past it. This enables Smalltalk- or Common-Lisp-style condition handling where the handler decides whether to retry, substitute a value, or abort. ## Continuations The iterative evaluator makes first-class continuations straightforward: `callcc` snapshots the current frame chain as an ordinary Io object. That object can be stored, invoked later, or — because it's just a graph of Io values — serialized and resumed in another process. `callcc` is not exposed in the top-level Lobby because it's easy to misuse. Where continuations are needed, they're reachable through the VM's reflective interface. --- Source: /docs/Book/Introduction/ --- heroImage: ../images/Introduction.png --- # Introduction The origins, philosophy, and influences behind Io. ## Overview Io is a dynamic prototype-based programming language. The ideas in Io are mostly inspired by Smalltalk[1] (all values are objects), Self[2] (prototype-based), NewtonScript[3] (differential inheritance), Act1[4] (actors and futures for concurrency), Lisp[5] (code is a runtime inspectable / modifiable tree) and Lua[6] (small, embeddable). ## Perspective The focus of programming language research for the last thirty years has been to combine the expressive power of high level languages like Smalltalk and the performance of low level language like C with little attention paid to advancing expressive power itself. The result has been a series of languages which are neither as fast as C or as expressive as Smalltalk. Io's purpose is to refocus attention on expressiveness by exploring higher level dynamic programming features with greater levels of runtime flexibility and simplified programming syntax and semantics. 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. ## Getting Started Io is distributed as a single WebAssembly module (`io_static.wasm`) that runs under any WASM host — [wasmtime](https://wasmtime.dev), Node.js, or a browser. Source and build instructions live at [github.com/IoLanguage/io](https://github.com/IoLanguage/io). ### Interactive Mode To start the REPL, run the WASM binary under wasmtime: ```io wasmtime --dir=. build/bin/io_static ``` This opens the Io interpreter prompt. You can evaluate code by entering it directly. Example: ```io Io> "Hello world!" println ==> Hello world! ``` Expressions are evaluated in the context of the Lobby: ```io 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. #### Inspecting objects You can get a list of the slots of an object like this: ```io Io> someObject slotNames ``` To show them in sorted order: ```io Io> someObject slotNames sort ``` For a nicely formatted description of an object, the slotSummary method is handy: ```io Io> slotSummary ==> Object_0x20c4e0: Lobby = Object_0x20c4e0 Protos = Object_0x20bff0 exit = method(...) forward = method(...) ``` Exploring further: ```io Io> Protos ==> Object_0x20bff0: Core = Object_0x20c4b0 ``` Inspecting a method will print a decompiled version of it: ```io Io> Lobby getSlot("forward") ==> # io/Z_Importer.io:65 method( Importer import(call) ) ``` #### doFile and doString A script can be run from the interactive mode using the doFile method: ```io 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: ```io someObject doFile("scriptName.io") ``` The doString method can be used to evaluate a string: ```io Io> doString("1+1") ==> 2 ``` And to evaluate a string in the context of a particular object: ```io someObject doString("1 + 1") ``` ### Command Line Arguments Example of printing out command line arguments: ```io System args foreach(k, v, write("'", v, "'\n")) ``` ### launchPath The System "launchPath" slot is set to the location of the initial source file that is executed; when the interactive prompt is started (without specifying a source file to execute), the launchPath is the current working directory: ```io System launchPath ``` --- Source: /docs/Book/Objects/ --- heroImage: ../images/Objects.png --- # Objects Slots, prototypes, cloning, and differential inheritance. ## Overview Io's guiding design principle is simplicity and power through conceptual unification. ## Prototypes In Io, everything is an object (including the locals storage of a block and the namespace itself) and all actions are messages (including assignment). Objects are composed of a list of key/value pairs called slots, and an internal list of objects from which it inherits called protos. A slot's key is a symbol (a unique immutable sequence) and its value can be any type of object. ### clone and init New objects are made by cloning existing ones. A clone is an empty object that has the parent in its list of protos. A new instance's init slot will be activated which gives the object a chance to initialize itself. Like NewtonScript[3], slots in Io are create-on-write. ```io me := Person clone ``` To add an instance variable or method, simply set it: ```io 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. ### Inheritance When an object receives a message it looks for a matching slot, if not found, the lookup continues depth first recursively in its protos. Lookup loops are detected (at runtime) and avoided. If the matching slot contains an activatable object, such as a Block or CFunction, it is activated, if it contains any other type of value it returns the value. Io has no globals and the root object in the Io namespace is called the Lobby. 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 Io> Dog := Object clone ==> Object_0x4a7c0 ``` The 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 Io> Dog color := "red" Io> Dog ==> Object_0x4a7c0: color := "red" ``` #### Multiple Inheritance You can add any number of protos to an object's protos list. When responding to a message, the lookup mechanism does a depth first search of the proto chain. ## Methods A method is an anonymous function which, when called, creates an object to store its locals and sets the local's proto pointer and its self slot to the target of the message. The Object method method() can be used to create methods. Example: ```io method((2 + 2) print) ``` An example of using a method in an object: ```io 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: ```io Dog bark ``` The default return value of a block is the result of the last expression. #### Arguments Methods can also be defined to take arguments. Example: ```io add := method(a, b, a + b) ``` The general form is: ```io method(, , ..., ) ``` ### Blocks A block is the same as a method except it is lexically scoped. That is, variable lookups continue in the context of where the block was created instead of the target of the message which activated the block. A block can be created using the Object method block(). Example of creating a block: ```io b := block(a, a + b) ``` #### Blocks vs. Methods This is sometimes a source of confusion so it's worth explaining in detail. Both methods and blocks create an object to hold their locals when they are called. The difference is what the "proto" and "self" slots of that locals object are set to. In a method, those slots are set to the target of the message. In a block, they're set to the locals object where the block was created. So a failed variable lookup in a block's locals continue in the locals where it was created. And a failed variable lookup in a method's locals continue in the object to which the message that activated it was sent. #### call and self slots When a locals object is created, its self slot is set (to the target of the message, in the case of a method, or to the creation context, in the case of a block) and its call slot is set to a Call object that can be used to access information about the block activation: #### Variable Arguments The "call message" slot in locals can be used to access the unevaluated argument messages. Example of implementing if() within Io: ```io 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))) ) ``` ```io 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: ```io myif := method( call evalArgAt(0) ifTrue( call evalArgAt(1)) ifFalse( call evalArgAt(2)) ) ``` ```io myif(foo == bar, write("true\n"), write("false\n")) ``` #### Forward If an object doesn't respond to a message, it will invoke its "forward" method if it has one. Here's an example of how to print the information related lookup that failed: ```io 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") ) ) ``` #### Resend Sends the current message to the receiver's protos with self as the context. Example: ```io A := Object clone A m := method(write("in A\n")) B := A clone B m := method(write("in B\n"); resend) B m ``` will print: ```io in B in A ``` For sending other messages to the receiver's proto, super is used. #### Super Sometimes it's necessary to send a message directly to a proto. Example: ```io Dog := Object clone Dog bark := method(writeln("woof!")) ``` ```io fido := Dog clone fido bark := method( writeln("ruf!") super(bark) ) ``` Both resend and super are implemented in Io. ## Introspection Using the following methods you can introspect the entire Io namespace. There are also methods for modifying any and all of these attributes at runtime. ### slotNames The slotNames method returns a list of the names of an object's slots: ```io Io> Dog slotNames ==> list("bark") ``` ### protos The protos method returns a list of the objects which an object inherits from: ```io Io> Dog protos ==> list("Object") ``` ### getSlot The "getSlot" method can be used to get the value of a block in a slot without activating it: ```io 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: ```io otherObject newMethod := getSlot("myMethod") ``` Here, the target of the getSlot method is the locals object.’ ### code The arguments and expressions of methods are open to introspection. A useful convenience method is "code", which returns a string representation of the source code of the method in a normalized form. ```io Io> method(a, a * 2) code ==> "method(a, a *(2))" ``` --- Source: /docs/Book/Primitives/ --- heroImage: ../images/Primitives.png --- # Primitives Built-in objects: numbers, strings, lists, maps, files, and more. ## Primitives Primitives are objects built into Io whose methods are typically implemented in C and store some hidden data in their instances. For example, the Number primitive has a double precision floating point number as its hidden data and its methods that do arithmetic operations are C functions. All Io primitives inherit from the Object prototype and are mutable. That is, their methods can be changed. The reference docs contain more info on primitives. 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. ### Object #### The ? Operator Sometimes it's desirable to conditionally call a method only if it exists (to avoid raising an exception). Example: ```io if(obj getSlot("foo"), obj foo) ``` Putting a "?" before a message has the same effect: ```io obj ?foo ``` ### List A List is an array of references and supports all the standard array manipulation and enumeration methods. Examples: Create an empty list: ```io a := List clone ``` Create a list of arbitrary objects using the list() method: ```io a := list(33, "a") ``` Append an item: ```io a append("b") ==> list(33, "a", "b") ``` Get the list size: ```io a size ==> 3 ``` Get the item at a given index (List indexes begin at zero): ```io 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: ```io a atPut(2, "foo") ==> list(33, "a", "foo", "b") ``` ```io a atPut(6, "Fred") ==> Exception: index out of bounds ``` Remove an item at a given index: ```io a remove("foo") ==> list(33, "a", "b") ``` Inserting an item at a given index: ```io a atInsert(2, "foo") ==> list(33, "a", "foo", "56") ``` #### foreach The foreach, map and select methods can be used in three forms: ```io 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 Io> a foreach(i, v, write(i, ":", v, ", ")) ==> 0:65, 1:21, 2:122, ``` The second form removes the index argument: ```io Io> a foreach(v, v println) ==> 65 21 122 ``` The third form removes the value argument and simply sends the expression as a message to each value: ```io Io> a foreach(println) ==> 65 21 122 ``` #### map and select Io's map and select (known as filter in some other languages) methods allow arbitrary expressions as the map/select predicates. ```io Io> numbers := list(1, 2, 3, 4, 5, 6) ``` ```io Io> numbers select(isOdd) ==> list(1, 3, 5) ``` ```io Io> numbers select(x, x isOdd) ==> list(1, 3, 5) ``` ```io Io> numbers select(i, x, x isOdd) ==> list(1, 3, 5) ``` ```io Io> numbers map(x, x*2) ==> list(2, 4, 6, 8, 10, 12) ``` ```io Io> numbers map(i, x, x+i) ==> list(1, 3, 5, 7, 9, 11) ``` ```io 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. ### Sequence In Io, an immutable Sequence is called a Symbol and a mutable Sequence is the equivalent of a Buffer or String. Literal strings(ones that appear in source code surrounded by quotes) are Symbols. Mutable operations cannot be performed on Symbols, but one can make mutable copy of a Symbol calling its asMutable method and then perform the mutation operations on the copy. Common string operations Getting the length of a string: ```io "abc" size ==> 3 ``` Checking if a string contains a substring: ```io "apples" containsSeq("ppl") ==> true ``` Getting the character (byte) at position N: ```io "Kavi" at(1) ==> 97 ``` Slicing: ```io "Kirikuro" slice(0, 2) ==> "Ki" ``` ```io "Kirikuro" slice(-2) # NOT: slice(-2, 0)! ==> "ro" ``` ```io Io> "Kirikuro" slice(0, -2) # "Kiriku" ``` Stripping whitespace: ```io " abc " asMutable strip ==> "abc" ``` ```io " abc " asMutable lstrip ==> "abc " ``` ```io " abc " asMutable rstrip ==> " abc" ``` Converting to upper/lowercase: ```io "Kavi" asUppercase ==> "KAVI" "Kavi" asLowercase ==> "kavi" ``` Splitting a string: ```io "the quick brown fox" split ==> list("the", "quick", "brown", "fox") ``` Splitting by others character is possible as well. ```io "a few good men" split("e") ==> list("a f", "w good m", "n") ``` Converting to number: ```io "13" asNumber ==> 13 "a13" asNumber ==> nil ``` String interpolation: ```io name := "Fred" ==> Fred "My name is #{name}" interpolate ==> My name is Fred ``` Interpolate 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. ### Ranges A range is a container containing a start and an end point, and instructions on how to get from the start, to the end. Using Ranges is often convenient when creating large lists of sequential data as they can be easily converted to lists, or as a replacement for the for() method. #### The Range protocol Each object that can be used in Ranges needs to implement a "nextInSequence" method which takes a single optional argument (the number of items to skip in the sequence of objects), and return the next item after that skip value. The default skip value is 1. The skip value of 0 is undefined. An example: ```io 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: ```io 1 to(5) foreach(v, v println) ``` The above will print 1 through 5, each on its own line. ### File The methods openForAppending, openForReading, or openForUpdating are used for opening files. To erase an existing file before opening a new open, the remove method can be used. Example: ```io f := File with("foo.txt) f remove f openForUpdating f write("hello world!") f close ``` ### Directory Creating a directory object: ```io dir := Directory with("/Users/steve/") ``` Get a list of file objects for all the files in a directory: ```io files := dir files ==> list(File_0x820c40, File_0x820c40, ...) ``` Get a list of both the file and directory objects in a directory: ```io items := Directory items ==> list(Directory_0x8446b0, File_0x820c40, ...) ``` ```io items at(4) name ==> DarkSide-0.0.1 # a directory name ``` Setting a Directory object to a certain directory and using it: ```io root := Directory clone setPath("c:/") ==> Directory_0x8637b8 ``` ```io root fileNames ==> list("AUTOEXEC.BAT", "boot.ini", "CONFIG.SYS", ...) ``` Testing for existence: ```io Directory clone setPath("q:/") exists ==> false ``` Getthing the current working directory: ```io Directory currentWorkingDirectory ==> "/cygdrive/c/lang/IoFull-Cygwin-2006-04-20" ``` ### Date Creating a new date instance: ```io d := Date clone ``` Setting it to the current date/time: ```io d now ``` Getting the date/time as a number, in seconds: ```io Date now asNumber ==> 1147198509.417114 ``` Getting individual parts of a Date object: ```io 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.747125 ``` Find how long it takes to execute some code: ```io Date cpuSecondsToRun(100000 repeat(1+1)) ==> 0.02 ``` ### Networking On the WASM target, networking is handled through the host: under Node.js via the built-in `net`/`http`/`fetch` APIs, under wasmtime via WASI sockets (where granted), and in the browser via `fetch`, `WebSocket`, and related Web APIs. These are reached through the Io↔JavaScript bridge rather than a dedicated Io `Socket` addon. The examples below use the classic Io `Socket` / `Server` / `URL` interface, which is preserved as a thin Io-level wrapper over the host's networking facilities. From Io's perspective, reads and writes still appear synchronous because the calling coroutine yields until the underlying host operation completes. Creating a URL object: ```io url := URL with("[http://example.com](http://example.com)/") ``` Fetching an URL: ```io data := url fetch ``` Streaming a URL to a file: ```io url streamTo(File with("out.txt")) ``` A simple whois client: ```io 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: ```io 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 ) ) ``` ```io WebServer := Server clone do( setPort(8000) handleSocket := method(aSocket, WebRequest clone asyncSend(handleSocket(aSocket)) ) ) ``` ```io WebServer start ``` ### XML Using the XML parser to find the links in a web page: ```io xml := URL with("[http://www.yahoo.com](http://www.yahoo.com)/") fetch asXML links := xml elementsWithName("a") map(attributes at("href")) ``` ### Vector Io's Vectors are built on its Sequence primitive and are defined as: ```io Vector := Sequence clone setItemType("float32") ``` The Sequence primitive supports SIMD acceleration on float32 add, subtract, multiply, and divide. On the WASM target these dispatch to WebAssembly SIMD128 opcodes (`f32x4.add`, `f32x4.mul`, etc.) via a `simd_cp_wasm.h` backend; native builds use SSE on x86 or iwMMX on ARM. Here's a small example: ```io iters := 1000 size := 1024 ops := iters * size ``` ```io v1 := Vector clone setSize(size) rangeFill v2 := Vector clone setSize(size) rangeFill ``` ```io dt := Date secondsToRun( iters repeat(v1 *= v2) ) ``` ```io writeln((ops/(dt*1000000000)) asString(1, 3), " GFLOPS") ``` Historical note: on a 2 GHz PowerPC G5 laptop with the original AltiVec-accelerated Vector primitive, this reported around 1.25 GFLOPS — roughly 2.5× a similar C loop without SIMD acceleration. On the current WASM target running under wasmtime, the same benchmark reports around 2.3 GFLOPS, using native WebAssembly SIMD128 opcodes. ## Unicode ### Sequences In Io, symbols, strings, and vectors are unified into a single Sequence prototype which is an array of any available hardware data type such as: ```io uint8, uint16, uint32, uint64 int8, int16, int32, int64 float32, float64 ``` ### Encodings Also, a Sequence has a encoding attribute, which can be: ```io number, ascii, ucs2, ucs4, utf8 ``` UCS-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. ### Source Code Io source files are assumed to be in UTF8 (of which ASCII is a subset). When a source file is read, its symbols and strings are stored in Sequences in their minimal fixed character width encoding. Examples: ```io Io> "hello" encoding ==> ascii ``` ```io Io> "π" encoding ==> ucs2 ``` ```io Io> "∞" encoding ==> ucs2 ``` We can also inspect the internal representation: ```io Io> "π" itemType ==> uint16 ``` ```io Io> "π" itemSize ==> 2 ``` ### Conversion The Sequence object has a number of conversion methods: ```io asUTF8 asUCS2 asUCS4 ``` ## Importing The Importer proto implements Io's built-in auto importer feature. If you put each of your proto's in their own file, and give the file the same name with and ".io" extension, the Importer will automatically import that file when the proto is first referenced. The Importer's default search path is the current working directory, but can add search paths using its addSearchPath() method. --- Source: /docs/Book/Syntax/ --- heroImage: ../images/Syntax.png --- # Syntax Io's minimal grammar: expressions, messages, and operators. ## Expressions Io has no keywords or statements. Everything is an expression composed entirely of messages, each of which is a runtime accessible object. The informal BNF description: ```io 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. ## Messages Message arguments are passed as expressions and evaluated by the receiver. Selective evaluation of arguments can be used to implement control flow. Examples: ```io 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: ```io 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: ```io 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: ```io Account := Object clone Account balance := 0 Account deposit := method(amount, balance = balance + amount ) ``` ```io account := Account clone account deposit(10.00) account balance println ``` Like Self[2], Io's syntax does not distinguish between accessing a slot containing a method from one containing a variable. ## Operators An operator is just a message whose name contains no alphanumeric characters (other than ";", "_", '"' or ".") or is one of the following words: or, and, return. Example: ```io 1 + 2 ``` This just gets compiled into the normal message: ```io 1 +(2) ``` Which is the form you can use if you need to do grouping: ```io 1 +(2 * 4) ``` Standard operators follow C's precedence order, so: ```io 1 + 2 * 3 + 4 ``` Is parsed as: ```io 1 +(2 *(3)) +(4) ``` User defined operators (that don't have a standard operator name) are performed left to right. ## Assignment Io has three assignment operators: These operators are compiled to normal messages whose methods can be overridden. For example: 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. ## Numbers The following are examples of valid number formats: ```io 123 123.456 0.456 .456 123e-4 123e4 123.456e-7 123.456e2 ``` Hex numbers are also supported (in any casing): ```io 0x0 0x0F 0XeE ``` ## Strings Strings can be defined surrounded by a single set of double quotes with escaped quotes (and other escape characters) within. ```io 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. ```io s := """this is a "test". This is only a test.""" ``` ## Comments Comments of the //, /**/ and # style are supported. Examples: ```io a := b // add a comment to a line ``` ```io /* comment out a group a := 1 b := 2 */ ``` The "#" style is useful for unix scripts: ```io #!/usr/local/bin/io ``` That'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. --- Source: /docs/Guide/ # Guide Comprehensive language guide: syntax, messages, objects, control flow, concurrency, and more. ## Introduction ### Overview Io is a dynamic prototype-based programming language. The ideas in Io are mostly inspired by Smalltalk[1] (all values are objects), Self[2] (prototype-based), NewtonScript[3] (differential inheritance), Act1[4] (actors and futures for concurrency), Lisp[5] (code is a runtime inspectable / modifiable tree) and Lua[6] (small, embeddable). ### Perspective The focus of programming language research for the last thirty years has been to combine the expressive power of high level languages like Smalltalk and the performance of low level language like C with little attention paid to advancing expressive power itself. The result has been a series of languages which are neither as fast as C or as expressive as Smalltalk. Io's purpose is to refocus attention on expressiveness by exploring higher level dynamic programming features with greater levels of runtime flexibility and simplified programming syntax and semantics. 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. ### Goals To be a language that is: simple - conceptually simple and consistent - easily embedded and extended powerful - highly dynamic and introspective - highly concurrent (via coroutines and async i/o) practical - fast enough - multi-platform - unrestrictive BSD/MIT license - comprehensive standard packages in distro ### Downloading Io source and releases are available at: ``` https://github.com/IoLanguage/io ``` The project site is: ``` https://iolanguage.org ``` ### Installing Follow the README.md in the source repository to build and run Io. The default build targets WebAssembly (WASI) via [wasi-sdk](https://github.com/WebAssembly/wasi-sdk); the resulting binary runs under [wasmtime](https://wasmtime.dev/), Node.js, or in a browser via the bundled browser REPL. ### Running Scripts An example of running a script: ``` wasmtime --dir=. build/bin/io_static samples/misc/HelloWorld.io ``` There is no main() function or object that gets executed first in Io. Scripts are executed when compiled. ### Interactive Mode Running: ``` wasmtime --dir=. build/bin/io_static ``` will open the Io interpreter prompt. The same binary can also be invoked under Node.js, or loaded into a browser REPL that talks to the VM through the Io↔JavaScript bridge. 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. ### Inspecting objects You can get a list of the slots of an object like this: ``` Io> someObject slotNames ``` To show them in sorted order: ``` Io> someObject slotNames sort ``` For 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: Core = Object_0x20c4b0 ``` On the WASM target there is no `Addons` hierarchy — what were once C-level addons are now either folded into the core VM or reached through the Io↔JavaScript bridge exposed by the host. Inspecting a method will print a decompiled version of it: ``` Io> Lobby getSlot("forward") ==> # io/Z_Importer.io:65 method( Importer import(call) ) ``` #### doFile and doString A script can be run from the interactive mode using the doFile method: ``` 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") ==> 2 ``` And to evaluate a string in the context of a particular object: ``` someObject doString("1 + 1") ``` #### Command Line Arguments Example of printing out command line arguments: ``` System args foreach(k, v, write("'", v, "'\n")) ``` #### launchPath The System "launchPath" slot is set to the location of the initial source file that is executed; when the interactive prompt is started (without specifying a source file to execute), the launchPath is the current working directory: ``` System launchPath ``` ## Syntax ### Expressions Io has no keywords or statements. Everything is an expression composed entirely of messages, each of which is a runtime accessible object. The informal BNF description: ``` 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. ### Messages Message arguments are passed as expressions and evaluated by the receiver. Selective evaluation of arguments can be used to implement control flow. Examples: ``` 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 ### Operators An operator is just a message whose name contains no alphanumeric characters (other than ";", "_", '"' or ".") or is one of the following words: or, and, return. Example: @@@PRE@@@ 1 + 2 ``` This 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 + 4 ``` Is parsed as: ``` 1 +(2 *(3)) +(4) ``` User defined operators (that don't have a standard operator name) are performed left to right. ### Assignment Io has three assignment operators: 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. ### Numbers The following are examples of valid number formats: ``` 123 123.456 0.456 .456 123e-4 123e4 123.456e-7 123.456e2 ``` Hex numbers are also supported (in any casing): ``` 0x0 0x0F 0XeE ``` ### Strings Strings can be defined surrounded by a single set of double quotes with escaped quotes (and other escape characters) within. ``` 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.""" ``` ### Comments Comments of the //, /**/ and # style are supported. Examples: ``` 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/io ``` That'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. ## Objects ### Overview Io's guiding design principle is simplicity and power through conceptual unification. concept unifies scopable blocks functions, methods, closures prototypes objects, classes, namespaces, locals messages operators, calls, assigns, var access ### Prototypes In Io, everything is an object (including the locals storage of a block and the namespace itself) and all actions are messages (including assignment). Objects are composed of a list of key/value pairs called slots, and an internal list of objects from which it inherits called protos. A slot's key is a symbol (a unique immutable sequence) and its value can be any type of object. #### clone and init New objects are made by cloning existing ones. A clone is an empty object that has the parent in its list of protos. A new instance's init slot will be activated which gives the object a chance to initialize itself. Like NewtonScript[3], slots in Io are create-on-write. ``` me := Person clone ``` To 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. ### Inheritance When an object receives a message it looks for a matching slot, if not found, the lookup continues depth first recursively in its protos. Lookup loops are detected (at runtime) and avoided. If the matching slot contains an activatable object, such as a Block or CFunction, it is activated, if it contains any other type of value it returns the value. Io has no globals and the root object in the Io namespace is called the Lobby. 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_0x4a7c0 ``` The 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" ``` #### Multiple Inheritance You can add any number of protos to an object's protos list. When responding to a message, the lookup mechanism does a depth first search of the proto chain. ### Methods A method is an anonymous function which, when called, creates an object to store its locals and sets the local's proto pointer and its self slot to the target of the message. The Object method method() can be used to create methods. Example: ``` 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 bark ``` The default return value of a block is the result of the last expression. #### Arguments Methods can also be defined to take arguments. Example: ``` add := method(a, b, a + b) ``` The general form is: ``` method(, , ..., ) ``` ### Blocks A block is the same as a method except it is lexically scoped. That is, variable lookups continue in the context of where the block was created instead of the target of the message which activated the block. A block can be created using the Object method block(). Example of creating a block: ``` b := block(a, a + b) ``` #### Blocks vs. Methods This is sometimes a source of confusion so it's worth explaining in detail. Both methods and blocks create an object to hold their locals when they are called. The difference is what the "proto" and "self" slots of that locals object are set to. In a method, those slots are set to the target of the message. In a block, they're set to the locals object where the block was created. So a failed variable lookup in a block's locals continue in the locals where it was created. And a failed variable lookup in a method's locals continue in the object to which the message that activated it was sent. #### call and self slots When a locals object is created, its self slot is set (to the target of the message, in the case of a method, or to the creation context, in the case of a block) and its call slot is set to a Call object that can be used to access information about the block activation: 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 #### Variable Arguments The "call message" slot in locals can be used to access the unevaluated argument messages. Example of implementing if() within Io: ``` 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")) ``` ### Forward If an object doesn't respond to a message, it will invoke its "forward" method if it has one. Here's an example of how to print the information related lookup that failed: ``` 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") ) ) ``` ### Resend Sends the current message to the receiver's protos with self as the context. Example: ``` A := Object clone A m := method(write("in A\n")) B := A clone B m := method(write("in B\n"); resend) B m ``` will print: ``` in B in A ``` For sending other messages to the receiver's proto, super is used. ### Super Sometimes it's necessary to send a message directly to a proto. Example: ``` 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. ### Introspection Using the following methods you can introspect the entire Io namespace. There are also methods for modifying any and all of these attributes at runtime. #### slotNames The slotNames method returns a list of the names of an object's slots: ``` Io> Dog slotNames ==> list("bark") ``` #### protos The protos method returns a list of the objects which an object inherits from: ``` Io> Dog protos map(type) ==> list(Object) ``` #### getSlot The "getSlot" method can be used to get the value of a block in a slot without activating it: ``` 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. #### code The arguments and expressions of methods are open to introspection. A useful convenience method is "code", which returns a string representation of the source code of the method in a normalized form. ``` Io> method(a, a * 2) code ==> "method(a, a *(2))" ``` ## Control Flow ### true, false and nil There are singletons for true, false and nil. nil is typically used to indicate an unset or missing value. ### Comparison The comparison methods: ``` ==, !=, >=, , ### if, then, else The if() method can be used in the form: @@@PRE@@@ if(, , ) ``` 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 ### ifTrue, ifFalse Also supported are Smalltalk style ifTrue, ifFalse, ifNil and ifNonNil methods: @@@PRE@@@ (y ### loop The loop method can be used for "infinite" loops: @@@PRE@@@ loop("foo" println) ``` ### repeat The Number repeat method can be used to repeat a loop a given number of times. ``` 3 repeat("foo" print) ==> foofoofoo ``` ### while Arguments: ``` while(, ) ``` Example: ``` a := 1 while(a ### for Arguments: @@@PRE@@@ for(, , , , ) ``` 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 9 ``` To 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. ### break, continue loop, repeat, while and for support the break and continue methods. Example: ``` for(i, 1, 10, if(i == 3, continue) if(i == 7, break) i print ) ``` Output: ``` 12456 ``` ### return Any part of a block can return immediately using the return method. Example: ``` Io> test := method(123 print; return "abc"; 456 print) Io> test 123 ==> abc ``` Internally, `break`, `continue`, and `return` are handled by the iterative evaluator's frame state machine: each sets a non-local control signal on the current frame, and the eval loop unwinds frames until it reaches the enclosing loop or block. ## Importing The Importer proto implements Io's built-in auto importer feature. If you put each of your proto's in their own file, and give the file the same name with and ".io" extension, the Importer will automatically import that file when the proto is first referenced. The Importer's default search path is the current working directory, but can add search paths using its addSearchPath() method. ## Concurrency ### Coroutines Io uses coroutines (user level cooperative threads), instead of preemptive OS level threads to implement concurrency. This avoids the substantial costs (memory, system calls, locking, caching issues, etc) associated with native threads and allows Io to support a very high level of concurrency with thousands of active threads. Under the stackless evaluator, a coroutine is simply a chain of heap-allocated evaluation frames. Switching coroutines is a matter of changing which frame chain the eval loop is walking — there is no platform-specific assembly, no `setjmp`/`longjmp`, and no `ucontext` or fibers. The same C code works on every host, including WebAssembly, which hides the native call stack entirely. ### Scheduler The Scheduler object is responsible for resuming coroutines that are yielding. The current scheduling system uses a simple first-in-first-out policy with no priorities. ### Actors An actor is an object with its own thread (in our case, its own coroutine) which it uses to process its queue of asynchronous messages. Any object in Io can be sent an asynchronous message by placing using the asyncSend() or futureSend() messages. Examples: ``` result := self foo // synchronous futureResult := self futureSend(foo) // async, immediately returns a Future self asyncSend(foo) // async, immediately returns nil ``` When 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)) ) ``` ### Futures Io's futures are transparent. That is, when the result is ready, they become the result. If a message is sent to a future (besides the two methods it implements), it waits until it turns into the result before processing the message. Transparent futures are powerful because they allow programs to minimize blocking while also freeing the programmer from managing the fine details of synchronization. #### Auto Deadlock Detection An advantage of using futures is that when a future requires a wait, it will check to see if pausing to wait for the result would cause a deadlock and if so, avoid the deadlock and raise an exception. It performs this check by traversing the list of connected futures. #### Futures and the Command Line Interface The command line will attempt to print the result of expressions evaluated in it, so if the result is a Future, it will attempt to print it and this will wait on the result of Future. Example: ``` Io> q := method(wait(1)) Io> futureSend(q) [1-second delay] ==> nil ``` To avoid this, just make sure the Future isn't the result. Example: ``` Io> futureSend(q); nil [no delay] ==> nil ``` ### Yield An object will automatically yield between processing each of its asynchronous messages. The yield method only needs to be called if a yield is required during an asynchronous message execution. #### Pause and Resume It's also possible to pause and resume an object. See the concurrency methods of the Object primitive for details and related methods. ## Exceptions ### Raise An exception can be raised by calling raise() on an exception proto. ``` Exception raise("generic foo exception") ``` ### Try and Catch To catch an exception, the try() method of the Object proto is used. try() will catch any exceptions that occur within it and return the caught exception or nil if no exception is caught. ``` e := try() ``` 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. ### Pass To re-raise an exception caught by try(), use the pass method. This is useful to pass the exception up to the next outer exception handler, usually after all catches failed to match the type of the current exception: ``` e := try( // ... ) e catch(Error, // ... ) catch(Exception, // ... ) pass ``` ### Custom Exceptions Custom exception types can be implemented by simply cloning an existing Exception type: ``` MyErrorType := Error clone ``` ### Resumable Exceptions Because evaluation state lives in heap-allocated frames, the frame that raised an exception is still a live, inspectable object when a handler runs. A handler can choose to *resume* the computation at the point of the raise — returning a value in place of the exception — instead of unwinding past it. This enables Smalltalk- or Common-Lisp-style condition handling where the handler decides whether to retry, substitute a value, or abort. ## Continuations The iterative evaluator makes first-class continuations straightforward: `callcc` snapshots the current frame chain as an ordinary Io object. That object can be stored, invoked later, or — because it's just a graph of Io values — serialized and resumed in another process. `callcc` is not exposed in the top-level Lobby because it's easy to misuse. Where continuations are needed, they're reachable through the VM's reflective interface. ## Primitives Primitives are objects built into Io whose methods are typically implemented in C and store some hidden data in their instances. For example, the Number primitive has a double precision floating point number as its hidden data and its methods that do arithmetic operations are C functions. All Io primitives inherit from the Object prototype and are mutable. That is, their methods can be changed. The reference docs contain more info on primitives. 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. ### Object #### The ? Operator Sometimes it's desirable to conditionally call a method only if it exists (to avoid raising an exception). Example: ``` if(obj getSlot("foo"), obj foo) ``` Putting a "?" before a message has the same effect: ``` obj ?foo ``` ### List A List is an array of references and supports all the standard array manipulation and enumeration methods. Examples: Create an empty list: ``` a := List clone ``` Create 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 ==> 3 ``` Get 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") a atPut(6, "Fred") ==> Exception: index out of bounds ``` Remove the given item: ``` a remove("foo") ==> list(33, "a", "b") ``` Inserting an item at a given index: ``` a atInsert(2, "foo") ==> list(33, "a", "foo", "b") ``` #### foreach The foreach, map and select methods can be used in three forms: ``` 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 122 ``` The third form removes the value argument and simply sends the expression as a message to each value: ``` Io> a foreach(println) ==> 65 21 122 ``` #### map and select Io's map and select (known as filter in some other languages) methods allow arbitrary expressions as the map/select predicates. ``` 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. ### Sequence In Io, an immutable Sequence is called a Symbol and a mutable Sequence is the equivalent of a Buffer or String. Literal strings(ones that appear in source code surrounded by quotes) are Symbols. Mutable operations cannot be performed on Symbols, but one can make mutable copy of a Symbol calling its asMutable method and then perform the mutation operations on the copy. Common string operations Getting the length of a string: ``` "abc" size ==> 3 ``` Checking if a string contains a substring: ``` "apples" containsSeq("ppl") ==> true ``` Getting the character (byte) at position N: ``` "Kavi" at(1) ==> 97 ``` Slicing: ``` "Kirikuro" exSlice(0, 2) ==> "Ki" "Kirikuro" exSlice(-2) # NOT: exSlice(-2, 0)! ==> "ro" Io> "Kirikuro" exSlice(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 other characters 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 ==> nan ``` String interpolation: ``` name := "Fred" ==> Fred "My name is #{name}" interpolate ==> My name is Fred ``` Interpolate 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. ### Ranges A range is a container containing a start and an end point, and instructions on how to get from the start to the end. Using Ranges is often convenient when creating large lists of sequential data as they can be easily converted to lists, or as a replacement for the for() method. #### The Range protocol Each object that can be used in Ranges needs to implement a "nextInSequence" method which takes a single optional argument (the number of items to skip in the sequence of objects), and return the next item after that skip value. The default skip value is 1. The skip value of 0 is undefined. An example: ``` 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. ### File The methods openForAppending, openForReading, or openForUpdating are used for opening files. To erase an existing file before opening a new open, the remove method can be used. Example: ``` f := File with("foo.txt") f remove f openForUpdating f write("hello world!") f close ``` ### Directory Creating a directory object: ``` 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 := dir items ==> list(Directory_0x8446b0, File_0x820c40, ...) items at(4) name ==> DarkSide-0.0.1 # a directory name ``` Setting 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 ==> false ``` Getthing the current working directory: ``` Directory currentWorkingDirectory ==> "/cygdrive/c/lang/IoFull-Cygwin-2006-04-20" ``` ### Date Creating a new date instance: ``` d := Date clone ``` Setting it to the current date/time: ``` d now ``` Getting the date/time as a number, in seconds: ``` Date now asNumber ==> 1147198509.417114 Date now asNumber ==> 1147198512.33313 ``` Getting 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.747125 ``` Find how long it takes to execute some code: ``` Date cpuSecondsToRun(100000 repeat(1+1)) ==> 0.02 ``` ### Vector Io's Vectors are built on its Sequence primitive and are defined as: ``` Vector := Sequence clone setItemType("float32") ``` The Sequence primitive supports SIMD acceleration on float32 add, subtract, multiply, and divide. On the WASM target these dispatch to WebAssembly SIMD128 opcodes (`f32x4.add`, `f32x4.mul`, etc.) via a `simd_cp_wasm.h` backend; native builds use SSE on x86 or iwMMX on ARM. 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") ``` Historical note: on a 2 GHz PowerPC G5 laptop with the original AltiVec-accelerated Vector primitive, this reported around 1.25 GFLOPS — roughly 2.5× a similar C loop without SIMD acceleration. On the current WASM target running under wasmtime, this benchmark reports around 2.3 GFLOPS for multiply and 2.4 GFLOPS for add, using native WebAssembly SIMD128 opcodes. ## Unicode ### Sequences In Io, symbols, strings, and vectors are unified into a single Sequence prototype which is an array of any available hardware data type such as: ``` uint8, uint16, uint32, uint64 int8, int16, int32, int64 float32, float64 ``` ### Encodings Also, a Sequence has a encoding attribute, which can be: ``` number, ascii, ucs2, ucs4, utf8 ``` UCS-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. ### Source Code Io source files are assumed to be in UTF8 (of which ASCII is a subset). When a source file is read, its symbols and strings are stored in Sequences in their minimal fixed character width encoding. Examples: ``` Io> "hello" encoding ==> ascii Io> "π" encoding ==> ucs2 Io> "∞" encoding ==> ucs2 ``` We can also inspect the internal representation: ``` Io> "π" itemType ==> uint16 Io> "π" itemSize ==> 2 ``` ### Conversion The Sequence object has a number of conversion methods: ``` asUTF8 asUCS2 asUCS4 ``` ## Embedding On the default WASM target, the "embedding" surface is a single WebAssembly module: a host (wasmtime, Node.js, a browser) loads `io_static.wasm`, imports its few WASI functions, and drives it by feeding source strings or messages. The Io↔JavaScript bridge on browser/Node hosts plays the role that native addons and direct C embedding played in earlier Io releases. The C-level embedding API described below still reflects how the VM is structured internally, and is accurate if you build the VM as a plain C library rather than a WASM module. Use it as a reference for the shape of the API and the relationship between `IoState`, coroutines, and Io objects. ### Conventions Io's C code is written using object oriented style conventions where structures are treated as objects and functions as methods. Familiarity with these may help make the embedding APIs easier to understand. #### Structures Member names are words that begin with a lower case character with successive words each having their first character upper cased. Acronyms are capitalized. Structure names are words with their first character capitalized. Example: ``` typdef struct { char *firstName; char *lastName; char *address; } Person; ``` #### Functions Function names begin with the name of structure they operate on followed by an underscore and the method name. Each structure has a new and free function. 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); ``` #### File Names Each structure has its own separate .h and .c files. The names of the files are the same as the name of the structure. These files contain all the functions(methods) that operate on the given structure. ### IoState An IoState can be thought of as an instance of an Io "virtual machine", although "virtual machine" is a less appropriate term because it implies a particular type of implementation. #### Multiple states Io is multi-state, meaning that it is designed to support multiple state instances within the same process. These instances are isolated and share no memory so they can be safely accessed simultaneously by different os threads, though a given state should only be accessed by one os thread at a time. #### Creating a state Here's a simple example of creating a state, evaluating a string in it, and freeing the state: ``` #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; } ``` ### Values We can also get return values and look at their types and print them: ``` IoObject *v = IoState_doCString_(self, someString); char *name = IoObject_name(v); printf("return type is a ‘%s', name); IoObject_print(v); ``` #### Checking value types There are some macro short cuts to help with quick type checks: ``` 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. ## Bindings On the WebAssembly target, host integration goes through a bidirectional Io↔JavaScript bridge rather than native dynamic addons: Io code can call JS functions, and JS can call Io methods. See [browser/README.md](../../browser/README.md) and [agents/wasm/Bridge.md](../../agents/wasm/Bridge.md) for details and examples. ## Appendix ### Grammar #### messages ``` expression ::= { message | sctpad } message ::= [wcpad] symbol [scpad] [arguments] arguments ::= Open [argument [ { Comma argument } ]] Close argument ::= [wcpad] expression [wcpad] ``` #### symbols ``` symbol ::= Identifier | number | Operator | quote Identifier ::= { letter | digit | "_" } Operator ::= { ":" | "." | "'" | "~" | "!" | "@" | "$" | "%" | "^" | "&" | "*" | "-" | "+" | "/" | "=" | "{" | "}" | "[" | "]" | "|" | "\" | "" | "?" } ``` #### quotes ``` quote ::= MonoQuote | TriQuote MonoQuote ::= """ [ "\"" | not(""")] """ TriQuote ::= """"" [ not(""""")] """"" ``` #### spans ``` 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 } ``` #### comments ``` Comment ::= slashStarComment | slashSlashComment | poundComment slashStarComment ::= "/*" [not("*/")] "*/" slashSlashComment ::= "//" [not("\n")] "\n" poundComment ::= "#" [not("\n")] "\n" ``` #### numbers ``` number ::= HexNumber | Decimal HexNumber ::= "0" anyCase("x") { [ digit | hexLetter ] } hexLetter ::= "a" | "b" | "c" | "d" | "e" | "f" Decimal ::= digits | "." digits | digits "." digits ["e" [-] digits] ``` #### characters ``` Comma ::= "," Open ::= "(" | "[" | "{" Close ::= ")" | "]" | "}" letter ::= "a" ... "z" | "A" ... "Z" digit ::= "0" ... "9" digits ::= { digit } ``` The uppercase words above designate elements the lexer treats as tokens. ### Credits Io is the product of all the talented folks who taken the time and interest to make a contribution. The complete list of contributors is difficult to keep track of, but some of the recent major contributors include; Jonathan Wright, Jeremy Tregunna, Mike Austin, Chris Double, Rich Collins, Oliver Ansaldi, James Burgess, Baptist Heyman, Ken Kerahone, Christian Thater, Brian Mitchell, Zachary Bir and many more. The mailing list archives, repo inventory and release history are probably the best sources for a more complete record of individual contributions. ### References 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 ### License Copyright 2006-2010 Steve Dekorte. All rights reserved. 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. --- Source: /docs/Reference/ # Reference Browse every built-in object and method in Io's standard library. ## Objects - Core - [Block](Block/index.html) - [CFunction](CFunction/index.html) - [Call](Call/index.html) - [Collector](Collector/index.html) - [Compiler](Compiler/index.html) - [Continuation](Continuation/index.html) - [Coroutine](Coroutine/index.html) - [Error](Error/index.html) - [EvalFrame](EvalFrame/index.html) - [Exception](Exception/index.html) - [FileImporter](FileImporter/index.html) - [Importer](Importer/index.html) - [List](List/index.html) - [Map](Map/index.html) - [Message](Message/index.html) - [MutableSequence](MutableSequence/index.html) - [Number](Number/index.html) - [Object](Object/index.html) - [Sandbox](Sandbox/index.html) - [Scheduler](Scheduler/index.html) - [Sequence](Sequence/index.html) - [State](State/index.html) - [System](System/index.html) - [WeakLink](WeakLink/index.html) - [false](false/index.html) - [nil](nil/index.html) - [true](true/index.html) - Debug - [Debugger](Debugger/index.html) - [Profiler](Profiler/index.html) - FileSystem - [Directory](Directory/index.html) - [File](File/index.html) - [Path](Path/index.html) - Testing - [DirectoryCollector](DirectoryCollector/index.html) - [FileCollector](FileCollector/index.html) - [TestRunner](TestRunner/index.html) - [UnitTest](UnitTest/index.html) - Time - [Date](Date/index.html) - [Duration](Duration/index.html) --- Source: /docs/Style%20Guide/ # Style Guide How to write idiomatic Io — naming, formatting, comments, and the patterns used throughout the standard library. ## Naming Io distinguishes *objects* (prototypes) from *slots on objects* (methods and values) by case. The rule is simple and never varies. **Prototypes use PascalCase.** The first letter is capitalized and each subsequent word is capitalized. Examples from the core library: `Object`, `List`, `Map`, `Sequence`, `Block`, `Coroutine`, `UnitTest`. When you clone a prototype to define a new type, keep the same convention: ``` Animal := Object clone Dog := Animal clone HttpRequest := Object clone ``` **Methods and variables use camelCase.** The first letter is lowercase; subsequent words are capitalized. Examples: `asString`, `appendSeq`, `removeFirst`, `slotNames`. ``` greetingFor := method(name, "hello, " .. name) firstMatch := list select(isActive) first ``` **Constants defined on a proto also use camelCase** — Io does not have a separate SCREAMING_CASE convention. A constant is just a slot whose value you don't intend to change. ### Method name prefixes The standard library uses a small vocabulary of prefixes to convey what a method does without reading its body. Follow these when adding methods to your own protos. - **`as`** — returns a (possibly modified) copy in a different representation. The receiver is not mutated. Examples: `asString`, `asNumber`, `asUppercase`, `asList`, `asMutable`, `asJson`. - **`to`** — converts toward a target, often destructively or with a target argument. Examples: `Sequence asUTF8`, conversions that write into something else. - **`set`** — assigns a value and returns self, enabling chaining. Examples: `setName`, `setSize`, `setEncoding`. - **`is`** — boolean query. Examples: `isNil`, `isEmpty`, `isKindOf`, `isActivatable`. - **`has`** — boolean query about containment or possession. Examples: `hasSlot`, `hasProto`. - **`with`** — constructor-style class method that returns a newly-configured clone. Example: `Date withNow`, `Range with(1, 10)`. - **`for`** — returns something derived for a given key or context. Example: `slotSummaryFor`. ### In-place vs. copy When a method has both a copying and a mutating form, the mutating form ends in `InPlace`. The copying form is the plain name. ``` "abc" asUppercase // "ABC" — returns a new sequence aMutableSeq uppercase // modifies in place, returns self ``` The core `Sequence` proto follows this rigorously: `asUppercase` / `uppercase`, `asLowercase` / `lowercase`, `alignLeft` / `alignLeftInPlace`, etc. Methods that unconditionally mutate the receiver do not need the suffix — e.g. `List append`, `List removeFirst`. ### Private slots A slot whose name begins with an underscore (`_`) is a hint that it is internal and should not be relied on from outside. Io does not enforce this; it is a reader-facing convention. ## Indentation and whitespace **Use tabs for indentation** in both C and Io source files. One tab per level; do not mix tabs and spaces. **No trailing whitespace** on any line. **One blank line** between method definitions at the same nesting level. Two blank lines are reserved for separating top-level sections within a file. **Spaces around binary operators** and after commas: ``` x := a + b * c list := list(1, 2, 3) ``` **No space between a message and its argument list.** `foo(x)`, not `foo (x)`. **Chain message sends with single spaces:** ``` people select(isActive) map(name) sort join(", ") ``` Long chains can break across lines; align the continuation with the receiver or indent one level: ``` result := people \ select(isActive) \ map(name) \ sort \ join(", ") ``` ## Comments Keep comments minimal. Well-named methods and slot names should carry most of the meaning. Write a comment when the *why* is non-obvious — a constraint, an invariant, or a workaround — not to restate what the code plainly does. Io's reflection system recognizes two documentation forms that tools can extract: **`//doc`** — a single-line doc comment attached to a slot. Placed immediately before the slot definition. ``` //doc List sum Returns the sum of the items. sum := method(self reduce(+)) ``` **`/*doc ... */`** — a multi-line doc comment for longer descriptions. ``` /*doc List join(optionalSeparator) Returns a Sequence of the concatenated items with optionalSeparator between each item, or simply the concatenation of the items if no optionalSeparator is supplied. */ join := method(sep, ...) ``` **`//metadoc`** — metadata about a proto itself (one per proto, at the top of the file). ``` //metadoc List category Core //metadoc List description A mutable, ordered collection of values. ``` Ordinary `//` and `/* */` comments are still available for implementation notes that aren't intended for doc extraction. ## File organization Each proto lives in its own file named after the proto: `List.io`, `Sequence.io`, `Map.io`. The file defines and extends the proto using a `do(...)` block: ``` List do( sum := method(self reduce(+)) average := method(self sum / self size) ... ) ``` Inside the `do` block, order slots roughly from fundamental to derived: state slots first, then core operations, then conveniences built on them. Group related methods together. Bootstrap files that must load before other core protos end in `_bootstrap.io` (e.g. `List_bootstrap.io`, `Object_bootstrap.io`). They are listed explicitly at the top of the `IO_FILES` variable in the Makefile — load order is not alphabetical. ## Method patterns ### Cloning and initialization A new instance is always a clone of an existing proto. If initialization is needed, override `init`: ``` Animal := Object clone do( name ::= nil legs ::= 4 init := method( self sounds := list() ) ) ``` `::=` declares a slot and generates a matching setter (`setName`, `setLegs`). Use it for public, settable state. Use `:=` when you want the slot without a generated setter. ### Chaining Methods that modify the receiver should return `self` so they can be chained: ``` list append(1) append(2) append(3) ``` Methods that produce a derived value return that value — `asUppercase`, `map`, `select` do not return self. ### Fluent constructors When a proto is typically created with several initial settings, provide a `with` class method: ``` Range := Object clone do( with := method(start, end, r := self clone r start := start r end := end r ) ) r := Range with(1, 10) ``` ## Error handling Raise exceptions for exceptional conditions; return `nil` for ordinary "not found" results. ``` // "not found" — return nil list detect(isPrime) // nil if none // exceptional — raise Exception raise("connection lost: " .. host) ``` Catch exceptions with `try`: ``` e := try(riskyOperation) if(e, e showStack) ``` Subclass `Exception` for error categories you want callers to distinguish: ``` ParseError := Exception clone ``` ## Testing Tests live alongside the code they exercise, in files named `*Test.io`, and extend `UnitTest`: ``` ListTest := UnitTest clone do( testAppend := method( l := list(1, 2) l append(3) assertEquals(l, list(1, 2, 3)) ) testRemoveFirst := method( assertEquals(list(1, 2, 3) removeFirst, 1) ) ) ``` Each test method begins with `test`. Use `assertEquals`, `assertTrue`, `assertRaisesException`, and the other assertions on `UnitTest`. ## Commit messages Short, imperative first line describing *what changed and why*. Reference the issue or PR number if relevant. ``` Fix List removeFirst to return nil on empty list (#123) ``` Longer explanation goes in the body, separated from the subject by a blank line. Keep the subject under ~70 characters. Prefer small, focused commits over mixed ones. Each commit should leave the tree in a buildable, testable state. --- Source: /docs/Technical%20Notes/ # Technical Notes Implementation notes and design docs on WASM targeting, stackless evaluation, and future work. - WASM - Stackless - Future Work --- Source: /docs/Technical%20Notes/Future%20Work/ # Future Work Design notes and proposals for upcoming changes. - Exceptions --- Source: /docs/Technical%20Notes/Future%20Work/Exceptions/ # Exceptions Design notes on the current exception system and proposed improvements. ## Current Implementation ### try/catch (non-resumable) Standard exception handling. `try(code)` runs code in a child coroutine. If an exception is raised, it's captured on the coroutine and returned. `catch` filters by exception type. `pass` re-raises. ```io e := try(Exception raise("boom")) e catch(Exception, writeln(e error)) ``` ### signal/withHandler (resumable) Added in the `stackless` branch. `withHandler` installs a handler on the coroutine's handler stack, evaluates the body, then removes the handler. `signal` walks the handler stack to find a matching handler and calls it as a subroutine — the signaler's frames remain on the stack. ```io result := withHandler(Exception, block(exc, resume, "default value" ), Exception signal("something went wrong") ) // result is "default value" ``` The handler runs as a normal block activation with its own frame. Whatever the handler returns becomes the return value of `signal()` at the call site. No coroutine switch, no frame manipulation, no `callcc`. `_Resumption` is a placeholder object whose `invoke(v)` returns `v`. It exists for API consistency but is functionally the identity. ## Comparison with Other Languages ### Common Lisp Condition System CL separates three concerns: 1. **Signaling** — low-level code detects a problem and signals a condition 2. **Handling** — high-level code decides on a recovery policy 3. **Restarting** — low-level code offers named recovery strategies ```lisp ;; Low-level: offers restarts, doesn't choose (defun parse-entry (line) (restart-case (actually-parse line) (skip-entry () nil) (use-value (v) v))) ;; High-level: chooses policy, doesn't know recovery details (handler-bind ((malformed-entry (lambda (c) (invoke-restart 'skip-entry)))) (parse-log-file stream)) ``` Key features Io lacks: - **Restarts**: named recovery points established by the signaler. The handler picks one without knowing recovery details. This decouples policy from mechanism. - **Decline**: a `handler-bind` handler can return normally without invoking a restart, and the search continues to the next handler. In Io, the handler's return value is always used. - **Interactive debugging**: unhandled conditions present available restarts to the developer in the debugger. SLIME's debugger lets you inspect the live stack, fix data, and resume — the program continues as if the error never happened. ### Smalltalk (on:do:) Handler receives the exception object with a rich protocol: - `ex resume: value` — resume at signal site with value (non-local return from handler) - `ex return: value` — return from the protected block - `ex retry` — re-run the protected block - `ex pass` — delegate to outer handler - `ex outer` — invoke outer handler, then resume More expressive than Io's current system but lacks CL's restart separation. ## Possible Directions ### 1. Add Restarts (CL-style) The most impactful addition. Restarts let the signaler offer recovery strategies without choosing one, and the handler choose a strategy without knowing recovery details. Possible Io API: ```io // Low-level code establishes restarts parseEntry := method(line, withRestarts( list( Restart clone setName("skipEntry") setAction(block(nil)), Restart clone setName("useValue") setAction(block(v, v)) ), actuallyParse(line) ) ) // High-level code picks a policy withHandler(MalformedEntry, block(exc, resume, invokeRestart("skipEntry") ), parseLogFile(stream) ) ``` Implementation: a restart registry (List) on the Coroutine, similar to `handlerStack`. `withRestarts` pushes entries, `invokeRestart` walks the registry. Pure Io-level code, no VM changes needed. ### 2. Handler Decline Allow a handler to decline (pass to next handler) instead of always producing a value. Could use a sentinel: ```io withHandler(Exception, block(exc, resume, if(exc error containsSeq("fatal"), decline // search continues to next handler , "recovered" ) ), body ) ``` ### 3. Richer Exception Protocol (Smalltalk-style) Add methods to the exception object for `retry`, `return(value)`, `outer`. These would use the eval loop's existing stop-status mechanism or handler stack walking. ### 4. Interactive Restart Selection in REPL When an unhandled signal reaches the top level with available restarts, present them to the user: ``` Error: Malformed entry at line 42 Available restarts: 0: [skipEntry] Skip this entry 1: [useValue] Supply a replacement value 2: [abort] Abort Pick a restart: ``` This requires restarts (direction 1) and REPL integration. ### Recommendation Start with **restarts** (direction 1). They provide the biggest leverage — decoupling error policy from recovery mechanism — and map naturally to Io's prototype model. A Restart is just an Object with `name` and `action` slots. The handler/restart registries are Lists on the Coroutine. No VM changes, no new primitives, just Io-level code building on `withHandler`. Decline (direction 2) is a small addition on top. Interactive selection (direction 4) is the long-term payoff but requires restarts first. --- Source: /docs/Technical%20Notes/Stackless/ # Stackless Io's iterative evaluator, continuations, and coroutines. ## About Historically Io evaluated messages by recursive descent in C — every Io call nested a real C frame. That model is simple, but it couples the language to the host platform in ways that become painful as the VM grows: the depth of Io recursion is bounded by the host’s C stack; capturing the in-flight computation requires copying raw stack memory; coroutine switching needs platform-specific assembly (`setjmp`/`longjmp`, `ucontext`, Windows fibers); and targets like WebAssembly don’t expose the native stack at all. A **C-stackless** evaluator moves all evaluation state into heap-allocated *frames*. The C code becomes a flat loop that pops the current frame, steps its state machine, and pushes new frames for sub-expressions. The C stack stays shallow — one loop iteration deep — regardless of how deep the Io call graph gets. ### Why it matters - **First-class continuations** — `callcc` becomes a simple matter of snapshotting the frame chain. No `makecontext`, no copying raw C stack bytes, no fragile reassembly. Continuations become ordinary Io objects that can be stored and invoked later. - **Portable coroutines** — switching coroutines is just swapping which frame chain the eval loop is running. No platform-specific assembly, no ucontext, no fibers. The same C code works on macOS, Linux, Windows, ARM64, and WASM. - **Tail-call optimization** — reusing the current frame instead of pushing a new one is trivial when frames are explicit heap objects. Deep tail-recursive Io programs no longer blow the host stack. - **Robust exception unwinding** — raising an exception becomes a loop that pops frames until a handler is found. No `longjmp` hopping over C code that expected to run cleanup, and no reliance on C++-style unwinding. - **Resumable exceptions** — because the raising frame is a live, inspectable object, a handler can choose to *resume* the computation at the point of the raise instead of unwinding past it. This lets you model Smalltalk- or Common-Lisp-style condition systems where the handler decides whether to retry, substitute a value, or abort. - **Serializable execution state** — the full active computation — frame chain, local slots, pending messages — is a reachable graph of Io objects. It can be serialized to disk or sent over the network, then deserialized and resumed in another process, letting a running task migrate hosts, checkpoint and recover, or survive a VM restart. - **WASM compatibility** — WebAssembly intentionally hides the native call stack, which makes classical coroutine implementations impossible. A C-stackless design ports cleanly because it never depended on the C stack in the first place. - **Introspection and debugging** — the entire call stack is a reachable chain of Io objects. A debugger can walk it, print it, modify it, or resume from any point, without needing to reach into native memory. The cost is some throughput relative to a tightly coded recursive evaluator — the loop carries more explicit state than a C call frame would. In return, Io gets a single, portable, inspectable runtime with capabilities that would be impractical otherwise. - Report - Examples --- Source: /docs/Technical%20Notes/Stackless/Examples/ # Stackless VM Examples Patterns enabled by the heap-allocated frame-based evaluator: portable coroutines, TCO, frame introspection, and robust exception handling. ## Overview The stackless branch replaces Io's recursive C-stack evaluator with a heap-allocated frame-based iterative evaluator. This enables portable coroutines, tail call optimization, frame introspection, and robust exception handling — all without platform-specific assembly, `setjmp`/`longjmp`, or `ucontext`. ## Tail Call Optimization The stackless evaluator has two TCO mechanisms that keep frame stacks flat for recursive patterns. ### Direct Tail Recursion When a block call is the last message in a block body, the frame is reused instead of pushing a new one: ```io countdown := method(n, if(n <= 0, return n) countdown(n - 1) ) countdown(100000) // no stack overflow ``` ### TCO Through If Branches When `if()` is the last message in a chain, the selected branch evaluates in-place. This is the idiomatic Io recursion pattern: ```io factorial := method(n, acc, if(n <= 1, acc, factorial(n - 1, n * acc)) ) factorial(100000, 1) // no stack overflow sumAcc := method(n, acc, if(n <= 0, return acc) sumAcc(n - 1, acc + n) ) sumAcc(100000, 0) // => 5000050000 ``` ### Non-Tail Recursion Even without TCO, recursion is bounded by heap rather than C stack depth: ```io sumTo := method(n, if(n <= 0, 0, n + sumTo(n - 1))) sumTo(100) // => 5050, uses heap frames ``` --- ## Coroutines Coroutines work by saving and restoring the frame pointer — no C stack switching. A suspended coroutine's entire state is a single pointer to its saved frame chain. Switching is O(1). ### Async Dispatch (@@) The `@@` operator dispatches a message to a new coroutine: ```io o := Object clone o work := method( for(i, 1, 5, i println; yield) ) o @@work for(i, 1, 5, yield) ``` ### Futures (@) The `@` operator returns a future that resolves when the coroutine completes: ```io obj := Object clone obj double := method(v, v * 2) future := obj @double(2) future println // => 4 ``` ### Manual Coroutine Control Create and resume coroutines directly: ```io c := Coroutine clone c setRunTarget(Lobby) c setRunLocals(Lobby) c setRunMessage(message("from coro" println)) c resume ``` ### Cooperative Scheduling Coroutines yield control explicitly with `yield` and `pause`: ```io o := Object clone o s := Sequence clone o l := method( j := 1 loop( s appendSeq("a", j asString, ".") if(j % 2 == 0, pause) j = j + 1 ) ) o @@l for(i, 1, 4, yield o s appendSeq("b", i asString, ".") if(i == 2, o actorCoroutine recentInChain resumeLater) ) // o s => "a1.a2.b1.b2.a3.a4.b3.b4." ``` ### Scheduler The `Scheduler` manages a single shared queue called `yieldingCoros` — the list of coroutines that are ready to run. All coroutine switching goes through this queue. **yield** appends the current coroutine to the back of the queue, pops the first one off, and resumes it. If the queue is empty, yield is a no-op. If the current coroutine is the only one in the queue, it's also a no-op: ```io // Coroutine yield (simplified): yield := method( if(yieldingCoros isEmpty, return) yieldingCoros append(self) // put ourselves at the back next := yieldingCoros removeFirst // take the next one off the front if(next == self, return) // we're the only one, nothing to do next resume // switch to it ) ``` **pause** removes the current coroutine from the queue entirely and resumes the next one. The paused coroutine won't run again until something calls `resume` or `resumeLater` on it: ```io // Coroutine pause (simplified): pause := method( yieldingCoros remove(self) next := yieldingCoros removeFirst if(next, next resume, Exception raise("Scheduler: nothing left to resume") ) ) ``` **resumeLater** puts a coroutine at the front of the queue without switching to it — it will be the next one to run when the current coroutine yields: ```io someCoroutine resumeLater // insert at front of yieldingCoros ``` The scheduler loop waits for all coroutines to finish: ```io Scheduler waitForCorosToComplete // yields until yieldingCoros is empty ``` ### Waiting `Object wait(s)` is a cooperative sleep — if other coroutines are in the queue, it yields in a loop until the deadline passes. If no other coroutines exist, it falls back to `System sleep`: ```io // cooperative wait — other coroutines run while we wait wait(0.5) // equivalent to: endDate := Date clone now + Duration clone setSeconds(0.5) loop(endDate isPast ifTrue(break); yield) ``` ### Futures and Waiting on Results The `@` operator returns a `FutureProxy`. When you send any message to the proxy, the calling coroutine pauses until the result is ready. The actor's coroutine calls `setResult` when done, which `resumeLater`s all waiting coroutines: ```io obj := Object clone do( compute := method(n, n * n) ) result := obj @compute(7) // result is a FutureProxy — calling coroutine keeps running doOtherWork result println // pauses here until compute finishes, then prints 49 ``` Under the hood: ```io // Future waitOnResult (simplified): waitOnResult := method( waitingCoros append(Scheduler currentCoroutine) Scheduler currentCoroutine pause // removes us from yieldingCoros ) // Future setResult — called when the actor finishes: setResult := method(r, proxy _become(r) // proxy becomes the real value waitingCoros foreach(resumeLater) // wake up everyone who was waiting ) ``` This is the general pattern for all async operations in Io. A coroutine waiting on an async operation (socket read, file I/O, timer, etc.) is **not in the `yieldingCoros` queue at all** — `pause` removed it. The coroutine only exists as a reference in the operation's waiting list. It won't be scheduled until the operation completes and calls `resumeLater`, which puts it back at the front of `yieldingCoros`. For example, a socket read looks like: ```io // Inside a Socket read method (conceptual): streamReadNextChunk := method( waitingCoros append(Scheduler currentCoroutine) Scheduler currentCoroutine pause // off the scheduler entirely // ... execution resumes here when data arrives ... readBuffer ) // When the event loop detects data ready on the socket: onReadable := method( waitingCoros foreach(resumeLater) // back on the scheduler waitingCoros empty ) ``` The VM's `IoState_activeCoroCallback` hook (in `IoState_callbacks.c`) lets an external event loop (libevent, kqueue, epoll, etc.) drive the scheduler — it gets notified when the coroutine count changes so it can integrate polling with coroutine switching. ### Actor Pattern Objects become actors with `@` and `@@`. Each actor gets its own coroutine and message queue, processing messages one at a time with `yield` between each: ```io Database := Object clone do( store := method(key, value, // ... store data ... "stored #{key}" interpolate println ) ) // Fire-and-forget (@@) — returns nil Database @@store("a", 1) Database @@store("b", 2) // Future (@) — returns a proxy that blocks on access result := Database @store("c", 3) result println // pauses here until store completes ``` --- ## Frame Introspection Live execution frames are exposed to Io code for debugging and metaprogramming. ### Walking the Frame Stack ```io f := Coroutine currentCoroutine currentFrame while(f != nil, f description println f = f parent ) ``` ### Inspecting Frame Properties Each frame exposes its execution state: ```io f := Coroutine currentCoroutine currentFrame f state println // frame state machine state (e.g., "activate") f depth println // distance from bottom of frame stack f target println // receiver object (self) f locals println // enclosing scope f result println // accumulated result f message println // current message being evaluated f call println // call introspection object (in methods) f blockLocals println // block's local scope (in block activations) ``` ### Programmatic Stack Traces ```io stackTrace := method( frames := list f := Coroutine currentCoroutine currentFrame while(f != nil, frames append(f description) f = f parent ) frames ) ``` --- ## Exception Handling Exceptions use the frame unwinding mechanism — no `longjmp`/`setjmp`. Both C-level errors and Io-level `Exception raise` set `errorRaised` and the eval loop unwinds frames. ### Try / Catch ```io e := try(Exception raise("boom")) e error println // => "boom" "execution continues" println ``` ### Exception Pass (Re-raise) ```io e := try( try(Exception raise("inner")) pass ) e error println // => "inner" ``` ### C-Level Errors C-level errors (type mismatches, index out of bounds, etc.) are caught the same way: ```io e := try(1 unknownMethod) e error println // => "Object does not respond to 'unknownMethod'" ``` --- Source: /docs/Technical%20Notes/Stackless/Report/ # Stackless Evaluator Report Design and status notes on the heap-allocated frame-based evaluator. ## Overview The `stackless` branch replaces Io's recursive C-stack-based evaluator with a heap-allocated frame-based iterative evaluator. Every piece of execution state — message pointer, target, locals, arguments, control flow — lives in GC-managed frame objects on the heap rather than in C stack frames. ### Goals 1. **First-class continuations** — `callcc` captures the entire execution state as a serializable, network-transmittable object. Impossible with C stack recursion. **Note:** `callcc` is disabled by default (behind `#ifdef IO_CALLCC`) because undelimited continuations can rewind past cleanup code and corrupt handler stacks. Enable with `-DIO_CALLCC` at build time. 2. **Portable coroutines** — No platform-specific assembly, no `setjmp`/`longjmp`, no `ucontext`, no fibers. Coroutine switching is just swapping a frame pointer. 3. **No stack overflow** — Tail call optimization reuses frames. Deep recursion is bounded by heap, not C stack depth. 4. **Performance** — The iterative evaluator with object pooling and inline caching is 1.1x–4x faster than the recursive evaluator across all benchmarks. ### Non-Goals - Changing the Io language semantics. All existing Io code runs unmodified. - JIT compilation or bytecode. The evaluator still interprets the message tree directly. --- ## Architecture ### The Eval Loop A single C function, `IoState_evalLoop_()`, runs a `while(1)` loop that processes frames from a linked stack. Each frame has a `state` field (a state machine enum) that tells the loop what to do next. There is no C recursion for message evaluation — argument evaluation, block activation, and control flow are all driven by pushing/popping frames and transitioning states. ``` IoState_evalLoop_ while(1) { frame = state->currentFrame switch(frame->state) { START → LOOKUP_SLOT → ACTIVATE ├── CFunction: call directly └── Block: push child frame CONTINUE_CHAIN → next message or RETURN RETURN → pop frame, store result IF_*, WHILE_*, FOR_*, ... (control flow) } } ``` ### Frame Structure Frames are full IoObjects managed by the garbage collector (`typedef IoObject IoEvalFrame`). Their data payload is `IoEvalFrameData`: | Field | Purpose | |-------|---------| | `message` | Current message being evaluated (instruction pointer) | | `target` | Receiver object (`self`) | | `locals` | Enclosing scope for slot lookup | | `result` | Accumulated result of this frame | | `state` | Current state machine state | | `parent` | Parent frame (for returning results) | | `argValues` / `inlineArgs[4]` | Pre-evaluated argument values | | `blockLocals` | Block's local scope (if block activation) | | `call` / `savedCall` | Call introspection objects | | `controlFlow` | Union of per-primitive state (for/while/if/foreach/etc.) | ### Frame State Machine The evaluator has 24 states organized by function: **Core evaluation:** `START` → `LOOKUP_SLOT` → `EVAL_ARGS` → `ACTIVATE` → `CONTINUE_CHAIN` → `RETURN` **Control flow (each primitive has its own states):** - **if:** `IF_EVAL_CONDITION` → `IF_CONVERT_BOOLEAN` → `IF_EVAL_BRANCH` - **while:** `WHILE_EVAL_CONDITION` → `WHILE_CHECK_CONDITION` → `WHILE_DECIDE` → `WHILE_EVAL_BODY` - **for:** `FOR_EVAL_SETUP` → `FOR_EVAL_BODY` → `FOR_AFTER_BODY` - **loop:** `LOOP_EVAL_BODY` → `LOOP_AFTER_BODY` - **foreach:** `FOREACH_EVAL_BODY` → `FOREACH_AFTER_BODY` - **callcc:** `CALLCC_EVAL_BLOCK` - **coroutines:** `CORO_WAIT_CHILD`, `CORO_YIELDED` - **do/doString/doFile:** `DO_EVAL`, `DO_WAIT` ### No C Stack Manipulation The entire design avoids platform-specific stack tricks: - No `setjmp`/`longjmp` — errors set `state->errorRaised = 1` and return normally. The eval loop unwinds frames. - No `ucontext` or fibers — coroutines save/restore a frame pointer. - No assembly — continuations copy frame chains on the heap. This makes the VM fully portable to any platform with a C99 compiler. --- ## New Language Features ### First-Class Continuations `callcc(block)` captures the current execution state into a Continuation object: ```io result := callcc(block(escape, list(1, 2, 3) foreach(v, if(v == 3, escape invoke("found it")) ) "not found" )) result println // => "found it" ``` **Continuation API:** | Method | Description | |--------|-------------| | `invoke(value)` | Restore captured frames, return value at callcc site | | `copy` | Deep-copy the frame chain (enables multi-shot use) | | `isInvoked` | Returns true if this continuation has been invoked | | `frameCount` | Number of captured frames | | `frameStates` | List of state names per frame | | `frameMessages` | List of current messages per frame | | `asMap` / `fromMap` | Serialize/deserialize continuation state | Continuations are one-shot by default. Use `copy` to create a fresh continuation for multi-shot patterns (generators, backtracking). See `docs/IoContinuationsExamples.md` for detailed examples. ### Frame Introspection Live execution frames are exposed to Io code for debugging and metaprogramming: ```io f := Coroutine currentCoroutine currentFrame while(f != nil, f description println f = f parent ) ``` **EvalFrame methods:** `message`, `target`, `locals`, `state`, `parent`, `result`, `depth`, `call`, `blockLocals`, `description` ### Resumable Exceptions `signal` is the resumable counterpart to `raise`. A handler installed with `withHandler` can inspect an exception and supply a replacement value, resuming execution at the signal site: ```io result := withHandler(Exception, block(e, resume, resume invoke(42)), Exception signal("need value") + 1 ) result println // => 43 ``` **How it works:** - `withHandler(proto, handler, body)` pushes a handler entry onto the current coroutine's `handlerStack`, evaluates the body, then pops the handler. - `signal(error)` clones the exception, walks `handlerStack` from the current coroutine up through `parentCoroutine` (matching by `isKindOf`), and calls the first matching handler as a regular function. The handler's return value becomes signal's return value. - `raise(error)` is unchanged — non-resumable, unwinds frames via `errorRaised`. - If `signal` finds no matching handler, it falls back to `raise`. **Implementation:** Entirely in Io (`libs/iovm/io/Exception.io`), no C changes. The body is evaluated directly via `doMessage` rather than in a child coroutine, which avoids a known interaction between continuation capture and nested C eval loops. Handler lookup walks the `parentCoroutine` chain, so handlers installed outside a `try` are visible inside it. See `docs/IoContinuationsExamples.md` for detailed examples. ### Portable Coroutines Coroutines work by saving and restoring the frame pointer — no C stack switching: ```io o := Object clone o work := method( for(i, 1, 5, i println; yield) ) o @@work for(i, 1, 5, yield) ``` A suspended coroutine's entire state is a single pointer to its saved frame chain. Switching coroutines is O(1). --- ## Performance Optimizations ### Object Pooling Four pools eliminate allocation overhead in hot paths: | Pool | Size | What's Reused | |------|------|---------------| | Frame pool | 256 | GC-managed EvalFrame objects | | Block locals pool | 8 | PHash-allocated locals for block activation | | Call pool | 8 | IoCallData-allocated Call objects | | Number data freelist | 512 | IoObjectData allocations for Numbers | All pooled objects are GC-marked through `IoCoroutine_mark()` to prevent premature collection. ### Inline Argument Buffer 95% of method calls have 4 or fewer arguments. These use a stack-allocated `inlineArgs[4]` buffer instead of heap-allocating an argument array. ### Monomorphic Inline Cache Each IoMessage has a one-entry cache for slot lookups: ```c if (tag matches && slotVersion matches && no local shadow) → return cached value (skip proto chain walk) ``` The cache stores `(tag, slotValue, context, version)` and only caches proto-chain hits. A local-slot shadow guard prevents stale results when different objects of the same type have overriding local slots (e.g., `false.isTrue` shadowing `Object.isTrue`). ### Special Form Detection Each CFunction that needs lazy argument evaluation carries an `isLazyArgs` flag, set at VM init time. This includes control flow primitives (`if`, `while`, `for`, `loop`, `callcc`), block constructors (`method`, `block`), iteration (`foreach`, `foreachSlot`), and others. Since Io's `getSlot` returns the same CFunction object, aliases automatically inherit the flag (e.g., `false.elseif := Object getSlot("if")`). The result is cached per-message-site for fast subsequent lookups. ### Cached Literal Fast Paths When a control flow body is a single cached literal (nil, number, or string), the evaluator skips frame allocation entirely and uses the cached value directly. For-loops with literal bodies (`for(i, 1, 1000000, nil)`) run as tight C loops. ### Tail Call Optimization Two complementary mechanisms keep frame stacks flat: 1. **Direct TCO:** When a Block call is the last message in a block body, `activateBlockTCO_()` reuses the current frame instead of pushing a new one. 2. **TCO through if:** When `if()` is the last message in a chain, the selected branch evaluates in-place without a child frame. This enables idiomatic Io recursion: ```io factorial := method(n, acc, if(n <= 1, acc, factorial(n - 1, acc * n)) ) factorial(100000, 1) // no stack overflow ``` ### Boolean Singleton Fast Path `if` and `while` conditions that are `true`, `false`, or `nil` skip the `asBoolean` frame push — the singleton is used directly. ### Number Cache Pre-allocated Number objects for the range [-10, 1024] eliminate allocation for most loop counters and arithmetic. --- ## Hybrid Reference Counting (Optional) An optional RC layer (disabled by default, enable with `#define COLLECTOR_USE_REFCOUNT 1` in `CollectorMarker.h`) promptly reclaims short-lived objects. The existing mark/sweep GC remains as backup for cycles. When enabled, for-loop counter Numbers are reclaimed immediately via RC drain, keeping the freed list populated and avoiding `calloc`. This gives a 1.5x speedup on `for(i, 1, 1000000, i + 1)` at the cost of ~7% regression on method-heavy workloads from the refcount increment on every `IOREF`. --- ## Benchmarks All benchmarks on macOS, Release build. Times are best-of-3 wall clock. ### Stackless vs Master (Recursive Evaluator) | Benchmark | Master | Stackless | Speedup | |-----------|--------|-----------|---------| | `for(i, 1, 1M, nil)` | 0.32s | 0.08s | **4.0x** | | `for(i, 1, 1M, i+1)` | 0.69s | 0.42s | **1.6x** | | `x = x + 1` (500K) | 0.41s | 0.29s | **1.4x** | | 500K `method(y, y+1)` | 0.74s | 0.28s | **2.6x** | | `while(i < 1M)` | 0.70s | 0.64s | **1.1x** | | `fib(30)` recursive | 3.10s | 1.77s | **1.8x** | | List ops (100K) | 1.74s | 1.14s | **1.5x** | | Test suite (239 tests) | 0.93s | 0.83s | **1.1x** | Stackless is faster on every benchmark. Method calls and tight for-loops benefit most from the iterative eval loop, frame pooling, and inline caches. Recursive workloads like `fib(30)` benefit from reduced frame allocation overhead. ### With Optional RC Enabled | Benchmark | Without RC | With RC | Change | |-----------|-----------|---------|--------| | `for(i, 1, 1M, i+1)` | 0.42s | 0.35s | 1.2x faster | | 500K `method(y, y+1)` | 0.28s | 0.32s | 12% slower | | `fib(30)` | 1.77s | 1.96s | 11% slower | RC is a targeted optimization for allocation-heavy for-loops. It trades ~10% overhead on general workloads for prompt reclamation of loop temporaries. --- ## Test Results - **30/30** C tests (TCO, continuations, exceptions, coroutines, `?` operator, `asMap`) - **249/249** Io tests via `run.io` (230 original + 9 EvalFrame introspection + 10 resumable exception tests) - SwitchTest: 6 pre-existing failures (same on master, not in run.io suite) --- ## Key Files | File | Purpose | |------|---------| | `libs/iovm/source/IoState_iterative.c` | Iterative eval loop, state machine, control flow | | `libs/iovm/source/IoEvalFrame.h` / `.c` | Frame structure, state enum, introspection methods | | `libs/iovm/source/IoObject_flow.c` | Control flow primitives (if, while, for, loop, etc.) | | `libs/iovm/source/IoContinuation.h` / `.c` | Continuation capture, invoke, copy, serialization | | `libs/iovm/source/IoCoroutine.c` | Frame-based coroutine switching | | `libs/iovm/source/IoState_eval.c` | Entry points (doCString, runCLI) | | `libs/iovm/source/IoState_inline.h` | Inline helpers, pre-eval arg access | | `libs/iovm/source/IoState.h` | VM state, pools, cached symbols | | `libs/iovm/tests/correctness/EvalFrameTest.io` | Frame introspection tests | | `libs/iovm/tests/correctness/ResumableExceptionTest.io` | Resumable exception tests | | `libs/garbagecollector/source/Collector_inline.h` | RC increment/decrement (optional) | | `agents/C_STACK_ELIMINATION_PLAN.md` | Architecture design document | | `agents/CONTINUATIONS_TODO.md` | Phase tracker and implementation notes | --- Source: /docs/Technical%20Notes/WASM/ # WASM Running Io in the browser via WebAssembly. ## About Historically the Io VM was a native C binary with a per-platform build matrix (macOS, Linux, Windows, BSD), platform-specific coroutine assembly, and a native addon model that compiled C extensions against the host toolchain. That model works, but every new platform multiplies the work: new assembly for coroutines, new build recipes, new binary artifacts, new ways for addons to break. Compiling the VM to **WebAssembly** collapses that matrix to a single portable module. The same `io_static.wasm` runs under [wasmtime](https://wasmtime.dev), Node.js, and directly in the browser, with no platform-specific code paths in the VM itself. ### Why it matters - **One binary, every host** — a single WASM module replaces the per-OS and per-architecture build matrix. No cross-compilation toolchains, no CI jobs for each target, no separate releases. If your environment has a WASM runtime, it can run Io. - **Runs in the browser** — the same VM that runs on the command line loads as a script tag. Io programs get direct access to the DOM, `fetch`, Web Audio, WebGL — any capability the host page exposes — without a separate “web Io” fork. - **Bidirectional Io↔JavaScript bridge** — the old native-addon model is replaced by a symmetric bridge: Io can call any JavaScript function and receive JS objects as Io values; JavaScript can call Io methods and pass JS values as arguments. One mechanism replaces what used to require a per-library C addon. - **Access to the JavaScript ecosystem** — through the bridge, Io programs can reach the roughly two million packages on npm and every Web API the browser exposes. The classic Io distribution shipped a few dozen hand-written addons covering networking, databases, graphics, crypto, and serialization; the JS ecosystem already covers all of those, plus machine learning, 3D rendering, audio synthesis, protocol implementations, cloud SDKs, and much more — without anyone writing a line of binding code. Io inherits decades of JavaScript library work as a side effect of the port. - **Embeddable by design** — a WASM module is an embeddable artifact. Native apps can host Io through wasmtime or wasmer; server-side JS can host it through Node; the browser hosts it directly. Embedding no longer means linking C libraries and matching ABIs. - **Sandboxed by default** — WASM modules only see the capabilities their host grants. File-system and network access flow through WASI or host-supplied JS, not raw syscalls, so an Io program can’t silently reach parts of the system the host didn’t intend to expose. - **Forces a cleaner core** — the WASM target doesn’t expose the native C stack, which ruled out the old ucontext/setjmp coroutine implementations and motivated the stackless evaluator. The discipline that came with the port left the VM smaller, more portable, and easier to reason about. Trade-offs are real: the WASM target is early-access, JIT throughput depends on the host runtime, and some classic native addons (notably anything linking C libraries) don’t carry over — their roles are now filled by JavaScript libraries reached through the bridge. - Browser Target - DOM Interop --- Source: /docs/Technical%20Notes/WASM/Browser%20Target/ # Browser Target Io runs in the browser as a WebAssembly module. The browser build compiles the full VM into a WASM reactor module that JS loads and drives via exported functions. ## Building ```bash make browser # build browser/io_browser.wasm make serve # serve REPL at http://localhost:8000 make check-browser # run headless Playwright tests ``` Requires [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) for cross-compilation and [Node.js](https://nodejs.org/) + Playwright for headless tests. ## Architecture ``` ┌──────────────────────────────────────────────┐ │ Browser (JS) │ │ │ │ io.js │ │ ├── WASI shim (fd_write, clock, etc.) │ │ ├── DOM handle table (Map) │ │ ├── DOM bridge functions (dom namespace) │ │ ├── WASM loader (loadIo / ioEval) │ │ └── REPL UI wiring │ │ │ │ ┌────────────────────────────────────────┐ │ │ │ WebAssembly (io_browser.wasm) │ │ │ │ │ │ │ │ io_browser.c — init, eval, I/O │ │ │ │ io_dom.c — DOM/Element protos │ │ │ │ iovm/* — full Io VM │ │ │ └────────────────────────────────────────┘ │ └──────────────────────────────────────────────┘ ``` The WASM module is built with `-mexec-model=reactor` (no `main()`). JS calls exported functions: | Export | Purpose | |--------|---------| | `io_init()` | Initialize the Io VM (call once) | | `io_get_input_buf()` | Pointer to 64KB input buffer | | `io_eval_input()` | Evaluate code written to input buffer | | `io_get_output()` | Pointer to output string | | `io_get_output_len()` | Length of output | ## WASI Shim The browser has no filesystem or OS. `io.js` provides a minimal WASI shim: - **stdout/stderr** — captured to a JS string - **clock** — `performance.now()` in nanoseconds - **filesystem** — all path operations return `ENOTCAPABLE` - **proc_exit** — throws a JS Error - **random** — `crypto.getRandomValues()` ## Files | File | Purpose | |------|---------| | `browser/io_browser.c` | Reactor entry point: init, eval, output capture | | `browser/io_dom.c` | DOM and Element proto implementation | | `browser/io_dom.h` | Header for DOM protos | | `browser/io.js` | WASI shim, DOM bridge, WASM loader, REPL UI | | `browser/index.html` | REPL page | | `browser/test.html` | Automated test page | | `browser/run_tests.mjs` | Headless Playwright test runner | --- Source: /docs/Technical%20Notes/WASM/DOM%20Interop/ # DOM Interop Io code running in the browser can query, create, and manipulate HTML elements through the `DOM` object and `Element` instances. ## Quick Start ```io // Get the page body body := DOM body body tagName println // ==> BODY // Create and attach an element div := DOM createElement("div") div setTextContent("Hello from Io!") div setStyle("color", "blue") div addClassName("greeting") body appendChild(div) // Query existing elements el := DOM getElementById("myId") el setAttribute("data-count", "42") el getAttribute("data-count") println // ==> 42 // Chaining (setters return self) DOM createElement("p") setTextContent("chained") setStyle("font-weight", "bold") ``` ## DOM Object The `DOM` singleton provides document-level queries and element creation. | Method | Returns | Description | |--------|---------|-------------| | `querySelector(selector)` | Element or nil | First element matching CSS selector | | `querySelectorAll(selector)` | List of Elements | All elements matching CSS selector | | `getElementById(id)` | Element or nil | Element with given id attribute | | `createElement(tagName)` | Element | Create a new detached element | | `body` | Element | The document body | ```io DOM querySelector("h1") textContent println DOM querySelectorAll("li") foreach(el, el textContent println) DOM getElementById("app") setInnerHTML("

replaced

") ``` ## Element Object Element wraps a DOM element handle. Methods fall into three categories: content, attributes, and tree manipulation. ### Content | Method | Returns | Description | |--------|---------|-------------| | `tagName` | Sequence | Tag name (e.g. "DIV", "SPAN") | | `textContent` | Sequence | Text content of element and descendants | | `setTextContent(text)` | self | Set text content | | `innerHTML` | Sequence | HTML markup of children | | `setInnerHTML(html)` | self | Set inner HTML | ### Attributes & Style | Method | Returns | Description | |--------|---------|-------------| | `getAttribute(name)` | Sequence or nil | Get attribute value | | `setAttribute(name, value)` | self | Set attribute | | `removeAttribute(name)` | self | Remove attribute | | `getStyle(property)` | Sequence | Get inline style property | | `setStyle(property, value)` | self | Set inline style property | | `addClassName(name)` | self | Add CSS class | | `removeClassName(name)` | self | Remove CSS class | | `hasClassName(name)` | true/false | Check for CSS class | ```io el := DOM createElement("div") el setAttribute("id", "demo") el setStyle("background-color", "#eee") el addClassName("card") el hasClassName("card") println // ==> true ``` ### Tree Manipulation | Method | Returns | Description | |--------|---------|-------------| | `appendChild(child)` | self | Append a child element | | `remove` | self | Remove element from its parent | | `children` | List of Elements | Direct child elements | ```io list := DOM createElement("ul") 3 repeat(i, li := DOM createElement("li") li setTextContent("Item " .. (i + 1) asString) list appendChild(li) ) DOM body appendChild(list) list children size println // ==> 3 ``` ## Architecture ### Handle Table JS maintains a `Map`. WASM C code only sees integer handles (ints stored as the Element object's data pointer). Handle 0 means null/not found and maps to `nil` in Io. ``` Io code C (WASM) JS ───────── ────────── ────── DOM body → dom_getBody() → registerHandle(document.body) → 7 el tagName → dom_getTagName(7) → handles.get(7).tagName → "BODY" ``` ### String Passing - **C→JS**: `(pointer, length)` pairs. JS reads UTF-8 from WASM linear memory. - **JS→C**: JS writes into a C-exported 64KB buffer (`dom_buf`), null-terminates, returns length. C reads the buffer as a C string. ### GC Integration When the Io garbage collector frees an Element object, the Element's `freeFunc` calls `dom_release(handle)`, which removes the entry from the JS handle map. This prevents the JS-side Map from growing without bound. ## Limitations - **Max 256 results** from `querySelectorAll`, `children` (fixed-size buffer on stack) - **Max 64KB** per string transfer (shared buffer size) - **No events** — event listeners are deferred to a future phase - **No fetch/async** — network requests are deferred to a future phase - **Inline styles only** — `getStyle`/`setStyle` operate on `element.style`, not computed styles --- Source: /docs/Tutorial/ # Tutorial A short, example-driven tour of Io for folks who already know how to program. Each section is a quick REPL session. ## Getting Started Run the REPL and exit with `exit` or Ctrl-D: ``` $ io Io 2025 Io> "hello" println hello ==> hello Io> exit ``` Run a script file: ``` $ io script.io ``` Evaluate a single expression: ``` $ io -e '"hello" println' ``` ## Math ``` Io> 1+1 ==> 2 Io> 2 sin ==> 0.909297 Io> 2 sqrt ==> 1.414214 ``` ## Variables ``` Io> a := 1 ==> 1 Io> a ==> 1 Io> b := 2 * 3 ==> 6 Io> a + b ==> 7 ``` ## Conditions ``` Io> a := 2 Io> if(a == 1) then(writeln("a is one")) else(writeln("a is not one")) a is not one Io> if(a == 1, writeln("a is one"), writeln("a is not one")) a is not one ``` ## Lists ``` Io> d := List clone append(30, 10, 5, 20) ==> list(30, 10, 5, 20) Io> d size ==> 4 Io> d print ==> list(30, 10, 5, 20) Io> d := d sort ==> list(5, 10, 20, 30) Io> d first ==> 5 Io> d last ==> 30 Io> d at(2) ==> 20 Io> d remove(30) ==> list(5, 10, 20) Io> d atPut(1, 123) ==> list(5, 123, 20) Io> list(30, 10, 5, 20) select(>10) ==> list(30, 20) Io> list(30, 10, 5, 20) detect(>10) ==> 30 Io> list(30, 10, 5, 20) map(*2) ==> list(60, 20, 10, 40) Io> list(30, 10, 5, 20) map(v, v*2) ==> list(60, 20, 10, 40) ``` ## Loops ``` Io> for(i, 1, 10, write(i, " ")) 1 2 3 4 5 6 7 8 9 10 Io> d foreach(i, v, writeln(i, ": ", v)) 0: 5 1: 123 2: 20 Io> list("abc", "def", "ghi") foreach(println) abc def ghi ``` ## Strings ``` Io> a := "foo" ==> "foo" Io> b := "bar" ==> "bar" Io> c := a .. b ==> "foobar" Io> c at(0) ==> 102 Io> c at(0) asCharacter ==> "f" Io> s := "this is a test" ==> "this is a test" Io> words := s split(" ", "\t") print "this", "is", "a", "test" Io> s findSeq("is") ==> 2 Io> s findSeq("test") ==> 10 Io> s exSlice(10) ==> "test" Io> s exSlice(2, 10) ==> "is is a " ``` ## Maps ``` Io> m := Map clone ==> Map_0x... Io> m atPut("first", "Alice") Io> m atPut("age", 30) Io> m at("first") ==> Alice Io> m keys ==> list("first", "age") Io> m size ==> 2 Io> m foreach(k, v, writeln(k, " = ", v)) first = Alice age = 30 ``` ## Objects and Prototypes Every object is cloned from another. There are no classes — only prototypes. ``` Io> Dog := Object clone ==> Dog_0x... Io> Dog name := "Rex" ==> "Rex" Io> Dog bark := method("woof!" println) ==> method(...) Io> Dog bark woof! ==> "woof!" ``` Clone an instance. Slots set on the clone override slots inherited from the prototype. ``` Io> fido := Dog clone ==> Dog_0x... Io> fido name ==> "Rex" Io> fido name := "Fido" Io> fido name ==> "Fido" Io> Dog name ==> "Rex" ``` ## Methods `method(args..., body)` creates a callable with its own local scope. ``` Io> square := method(x, x * x) Io> square(5) ==> 25 Io> Dog greet := method(other, "Hi, " .. other name .. "!" println) Io> fido greet(Dog clone setName("Spot")) Hi, Spot! ``` Methods see the receiver as `self`. Assignment without a prefix writes into the receiver's slots. ``` Io> Counter := Object clone do( n := 0 bump := method(n = n + 1) ) Io> c := Counter clone Io> c bump; c bump; c bump Io> c n ==> 3 ``` ## Blocks `block` is like `method`, but lexically scoped — it closes over the context where it was defined, not the receiver of the call. ``` Io> makeAdder := method(x, block(y, x + y)) Io> add3 := makeAdder(3) Io> add3 call(4) ==> 7 ``` ## Inheritance Clone to inherit. Override slots in the clone. `proto` points back to the parent. ``` Io> Animal := Object clone do( sound := "..." speak := method(sound println) ) Io> Cat := Animal clone do(sound := "meow") Io> Cat speak meow Io> Cat proto == Animal ==> true ``` Multiple inheritance: append protos to the `protos` list. ``` Io> Swimmer := Object clone do(swim := method("swimming" println)) Io> Flyer := Object clone do(fly := method("flying" println)) Io> Duck := Object clone Io> Duck appendProto(Swimmer); Duck appendProto(Flyer) Io> Duck swim swimming Io> Duck fly flying ``` ## Introspection Every object knows its slots and protos at runtime. ``` Io> fido slotNames ==> list("name") Io> Dog slotNames ==> list("name", "bark", "greet") Io> fido getSlot("name") ==> "Fido" Io> fido proto == Dog ==> true Io> fido hasSlot("bark") ==> true ``` ## Exceptions ``` Io> try( Exception raise("something broke") ) catch(Exception, e, writeln("caught: ", e error) ) caught: something broke ``` A method can also `return` early or raise with `Exception raise`. ## File I/O ``` Io> f := File with("hello.txt") Io> f openForUpdating Io> f write("hello, world\n") Io> f close Io> File with("hello.txt") contents ==> "hello, world\n" ``` Iterate lines: ``` Io> File with("hello.txt") openForReading foreachLine(line, line println) ``` ## Coroutines Coroutines are cooperative, scheduled in-process. `yield` hands control back to the scheduler. ``` Io> task := method(n, for(i, 1, n, writeln("tick ", i); yield)) Io> task(3) @@; task(3) @@ Io> Scheduler waitForCorosToComplete tick 1 tick 1 tick 2 tick 2 tick 3 tick 3 ``` ## Actors and Futures Send a message asynchronously with `@` (returns a future) or `@@` (no return). ``` Io> slow := Object clone Io> slow compute := method(x, System sleep(1); x * x) Io> f := slow @compute(5) Io> "doing other work..." println Io> f // blocks until ready ==> 25 ``` Any object can act as an actor — just send it async messages. ## Next Steps - Read the [Book](../Book/index.html) for a deeper walkthrough of the language. - Skim the [Reference](../Reference/index.html) for a complete list of built-in objects and methods. - Browse the [Guide](../Guide/index.html) for idioms and conventions. --- Source: /FAQ/ # FAQ Common questions about Io — its origins, philosophy, and practical use. --- Source: /Timeline/ # Timeline Milestones in Io's history, from 2002 to today. - **2002-03** — Io created: Steve Dekorte begins work on Io as a minimal prototype-based language. - **2002-04** — Yahoo! Groups mailing list: The iolanguage mailing list is created on Yahoo! Groups, becoming the gathering place for early users, patch exchanges, and language discussion. - **2002-04** — First public release: The earliest public Io releases and programming guide appear, distributed through the mailing list. - **2002-08** — Actors and coroutines: Beta 2002-08-11 adds concurrency: Actors (object-oriented threads) implemented with coroutines and cooperative yield. - **2002-08** — Addon system: First addon libraries land: Directory, XML (expat), MD5, Blowfish, regex, and OpenGL/GLUT — establishing the addon model that grows rapidly over the next year. - **2002-09** — Networking: Sockets addon adds TCP servers and clients; UDP follows a week later. Async DNS arrives in November, and IoServer becomes a dedicated build target. - **2002-10** — Incremental GC: Mark-and-sweep is replaced with an incremental tri-color collector with write barrier — trading a small throughput hit for far better real-time pause behavior. - **2002-12** — Weak links: A WeakLink primitive is added, letting objects reference each other without preventing collection — useful for caches and back-pointers. - **2003-02** — Addon explosion: Rapid expansion: curses, JPEG/TIFF, zlib, Perl5 regex, Objective-C bridge, SGML, SQLite, and more — Io becomes usable for real-world scripting and embedding. - **2003-04** — Futures: @ returns a transparent future that becomes its result when touched; @@ fires and forgets. Deadlocks are detected by walking the future graph. - **2004-07** — Vector primitive (SIMD): A numeric Vector type with AltiVec acceleration lands. Strings, buffers, and numeric vectors unify under Sequence. - **2005-07** — Lua Workshop talk: Steve presents Io at the Lua Workshop, contrasting message-tree semantics with Lua’s table-based model. - **2005-10** — OOPSLA / DLS: Io is presented at the ACM Dynamic Languages Symposium, co-located with OOPSLA’05 in San Diego, bringing the language to a wider research audience. - **2005-12** — Public DARCS repository: A public source repository hosted at iolanguage.com/darcs/Io replaces the email-patch workflow. - **2007-03** — Migration to git: Source history moves from DARCS to git, hosted on iolanguage.com. - **2008-02** — Published on GitHub: The Io repository appears at github.com/IoLanguage/io, making issues and pull requests the primary contribution channel. - **2009-10** — Portable ucontext: Russ Cox’s portable ucontext replaces platform-specific coroutine code, enabling cross-platform support including 64-bit macOS. - **2010-05** — CMake build system: The build moves from hand-written Makefiles to CMake, simplifying ports and addon builds. - **2012-01** — Io Book on Apple iBooks: Steve Dekorte publishes Io on the Apple iBooks Store on 2012-01-23 — a compact introduction to the language, prototypes, messages, and concurrency. - **2017-11** — Eerie package manager: Josip Lisec’s Eerie package manager is rewritten by Ales Tsurko under the IoLanguage org, with heavy activity through 2021. - **2025-07** — ARM64 coroutines: Native ARM64 coroutine support lands for Apple Silicon and modern Linux. - **2026-02** — Stackless evaluator: The recursive C-stack evaluator is replaced with a heap-allocated frame state machine — enabling TCO, robust exception unwinding, and first-class continuations. - **2026-03** — WASM/WASI port: Io cross-compiles to WebAssembly. A bidirectional Io↔JavaScript bridge replaces the native addon model. - **2026-04** — WASM merged to master: The WASM branch becomes the default target. Book and site restructured for the new era.