# 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). Its guiding design principle is simplicity and power through conceptual unification — a handful of primitives stand in for features most languages keep separate: conceptunifiesscopable blocksfunctions, methods, closuresprototypesobjects, classes, namespaces, localsmessagesoperators, calls, assigns, var accessThe 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. - FAQ - [GitHub Repository](https://github.com/IoLanguage/io) — Source code, issues, and contributions. - 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 - Implementation - 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 nextSectionLink: true --- # 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 nextSectionLink: true --- # 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 nextSectionLink: true --- # 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 nextSectionLink: true --- # 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 nextSectionLink: true --- # Objects Slots, prototypes, cloning, and differential inheritance. ## 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. ```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 nextSectionLink: true --- # 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 nextSectionLink: true --- # 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/Implementation/ # Implementation How the Io VM is put together, and how to extend it from C. - Overview - How to Write C Bindings - Reference --- Source: /docs/Implementation/How%20to%20Write%20C%20Bindings/ # How to Write C Bindings How to write Io-visible primitives in C — the macros, the argument-extraction API, proto registration, and GC-safe slot updates. ## Method Signature Every C-implemented slot has the same three arguments: the receiver, the locals object of the caller, and the unevaluated message node. The `IO_METHOD` macro (defined in `IoObject_struct.h`) writes the prototype for you: ```c #define IO_METHOD(CLASS, NAME) \ IoObject *CLASS##_##NAME(CLASS *self, IoObject *locals, IoMessage *m) ``` So `IO_METHOD(IoList, at)` expands to: ```c IoObject *IoList_at(IoList *self, IoObject *locals, IoMessage *m) ``` Three things to remember about the arguments: - **`self`** — the receiver. Its primitive data is reached through `DATA(self)`, a thin macro over `IoObject_dataPointer(self)`. - **`locals`** — the scope of the calling method or block. You need it to evaluate the message's arguments, because each argument is itself a message that has to run in the caller's scope. - **`m`** — the live `IoMessage` node. Its name, argument messages, and next pointer are all accessible for introspection. ## Extracting Arguments You never reach into `m` directly. The VM provides `IoMessage_locals_*ArgAt_` helpers that take `(m, locals, index)`, evaluate the argument message in the caller's scope, type-check the result, and raise an Io-level exception on a mismatch. The common ones: | Call | Returns | |---|---| | `IoMessage_locals_intArgAt_` | `int` | | `IoMessage_locals_longArgAt_` | `long` | | `IoMessage_locals_sizetArgAt_` | `size_t` | | `IoMessage_locals_doubleArgAt_` | `double` | | `IoMessage_locals_floatArgAt_` | `float` | | `IoMessage_locals_boolArgAt_` | `int` | | `IoMessage_locals_numberArgAt_` | `IoNumber *` | | `IoMessage_locals_symbolArgAt_` | `IoSymbol *` | | `IoMessage_locals_seqArgAt_` | `IoSeq *` | | `IoMessage_locals_cStringArgAt_` | `const char *` | | `IoMessage_locals_blockArgAt_` | `IoBlock *` | | `IoMessage_locals_listArgAt_` | `IoList *` | | `IoMessage_locals_mapArgAt_` | `IoMap *` | | `IoMessage_locals_messageArgAt_` | `IoMessage *` | | `IoMessage_locals_dateArgAt_` | `IoDate *` | Arity and raw access: - `IoMessage_assertArgCount_receiver_(m, n, self)` — raise if the caller passed fewer than `n` arguments. - `IoMessage_argCount(m)` — number of argument messages. - `IoMessage_locals_valueArgAt_(m, locals, i)` — the untyped result of evaluating arg `i`. ### Deferred arguments Control-flow-style methods sometimes want the unevaluated argument message instead of its value (so they can choose whether, when, or how many times to evaluate it). Grab it from `m` directly with `IoMessage_rawArgAt_(m, i)` and evaluate it later with `IoMessage_locals_performOn_(arg, locals, target)`. ## Constructing Results Return values are Io objects. A handful of macros produce the common ones: | Macro | Produces | |---|---| | `IONUMBER(x)` | `IoNumber` wrapping a C `double` | | `IOBOOL(self, b)` | the shared `true` / `false` singletons | | `IONIL(self)` | the shared `nil` singleton | | `IOSYMBOL(s)` | interned `IoSeq` symbol from a C string | | `IOSEQ(bytes, len)` | mutable `IoSeq` | | `IOLIST(state, ...)` | literal `IoList` | Returning `self` is fine — chainable setters do it constantly. ## Registering a Proto Primitives install themselves at VM init with a `{name, cfunc}` table and a single call. The pattern from `IoList.c`: ```c IoList *IoList_proto(void *state) { IoMethodTable methodTable[] = { {"at", IoList_at}, {"atPut", IoList_atPut}, {"append", IoList_append}, {"size", IoList_size}, {NULL, NULL}, }; IoObject *self = IoObject_new(state); IoObject_tag_(self, IoList_newTag(state)); IoObject_setDataPointer_(self, List_new()); IoState_registerProtoWithId_((IoState *)state, self, protoId); IoObject_addMethodTable_(self, methodTable); return self; } ``` Steps: 1. **Allocate** a fresh object and give it your tag. The tag (from `IoList_newTag`) carries the clone/mark/free/compare function pointers the GC and `clone` will call. 2. **Stash primitive state** in the data pointer. For `IoList` that's a basekit `List`; for your type it's whatever C struct the slot methods will read through `DATA(self)`. 3. **Register** the proto under a unique id (usually `static IoTag *protoId = "IoList";`). Other C code reaches this proto with `IoState_protoWithId_`. 4. **Install** the method table. Every `{name, cfunc}` pair becomes a slot on the proto. A matching `IoList_rawClone` is registered as the tag's clone function so each clone gets its own primitive data (otherwise mutation of one instance would leak into the proto). Finally, call your `IoList_proto` from `IoState.c`'s init so the proto is installed before Io code runs. ## GC Safety The collector runs incrementally, so any time you store an `IoObject *` into another live object you must tell the collector you did so. Use `IOREF`: ```c IoObject_setSlot_to_(self, IOSYMBOL("name"), IOREF(newValue)); ``` `IOREF` is the write barrier. Miss it and a collection between the store and the next safe point can reclaim the value you just installed. Every slot helper (`setSlot_to_`, `updateSlot_to_`, protos, listeners) takes `IOREF`'d values. Local `IoObject *` variables inside a method are fine without `IOREF` — they're already reachable through the frame chain that the collector walks. ## Raising Errors Io-level exceptions are raised from C with `IoState_error_`: ```c if (index < 0 || index >= List_size(DATA(self))) { IoState_error_(IOSTATE, m, "index %d out of range", index); return IONIL(self); // unreachable in practice, but keeps compilers happy } ``` `IoState_error_` sets `state->errorRaised = 1` and the iterative eval loop unwinds frames until an `Io`-level `try`/`catch` runs. There is no longjmp — just flag-and-return. That's why helpers that hit an error still return a value; the caller propagates by checking `errorRaised` or, more commonly, the eval loop handles it a step later. The argument-extraction helpers do this for you: `IoMessage_locals_intArgAt_` raises a type error and returns `0` if the argument isn't a number. ## A Complete Example ```c // A pretend IoPoint with x and y doubles stored in data.ptr. typedef struct { double x, y; } PointData; static IoTag *protoId = "IoPoint"; IO_METHOD(IoPoint, distanceTo) { /*doc Point distanceTo(other) Euclidean distance to other Point. */ IoObject *other = IoMessage_locals_valueArgAt_(m, locals, 0); IOASSERT(ISPOINT(other), "argument must be a Point"); PointData *a = DATA(self); PointData *b = DATA(other); double dx = a->x - b->x; double dy = a->y - b->y; return IONUMBER(sqrt(dx*dx + dy*dy)); } IoPoint *IoPoint_proto(void *state) { IoMethodTable methodTable[] = { {"distanceTo", IoPoint_distanceTo}, {NULL, NULL}, }; IoObject *self = IoObject_new(state); IoObject_tag_(self, IoPoint_newTag(state)); IoObject_setDataPointer_(self, io_calloc(1, sizeof(PointData))); IoState_registerProtoWithId_((IoState *)state, self, protoId); IoObject_addMethodTable_(self, methodTable); return self; } ``` That's the shape of every primitive in `libs/iovm/source/` — `IoNumber`, `IoSeq`, `IoList`, `IoMap`, `IoBlock`, and the rest. Read any of them as a reference when implementing your own. --- Source: /docs/Implementation/Overview/ # Overview A tour of the Io VM source tree — object representation, the message tree, the evaluator, the standard library loader, and the garbage collector. ## Layout The VM lives under `libs/iovm/` and is about 25k lines of portable C, with no threading, no assembly, and no platform-specific syscalls beyond what WASI provides. ``` libs/iovm/source/ Core VM (C) libs/iovm/io/ Standard library (.io) libs/iovm/tests/ C test files tools/source/main.c REPL / CLI entry point ``` The public C API is in `IoState.h`, `IoObject.h`, `IoMessage.h`, and the per-primitive headers (`IoNumber.h`, `IoSeq.h`, `IoList.h`, `IoMap.h`, `IoBlock.h`, `IoCoroutine.h`, etc.). Every file is prefixed `Io` so the C namespace stays tidy when embedded. ## Objects Every Io value is a `CollectorMarker` — the header that the collector uses to link live objects on its gray/black/white lists. The marker's `object` field points to an `IoObjectData`: ```c struct IoObjectData { unsigned int markerCount; union { void *ptr; double d; uint32_t ui32; } data; // primitive payload IoTag *tag; // vtable PHash *slots; // cuckoo hash List *listeners; IoObject **protos; // parent list /* ... flag bits: isActivatable, isLocals, isSymbol, ... */ }; ``` A few things to notice: - **No classes.** A `tag` names the primitive kind (`Number`, `Sequence`, `List`, …) and carries function pointers for clone, mark, free, and compare. Behavior lives in slots, not in the tag. - **Slots are a PHash** — a cuckoo hash table keyed by interned symbol pointers. Lookup is a single hash and an identity compare; no string compare, no collision chain. - **Protos are a plain C array.** Lookup walks the array depth-first with loop detection via the `hasDoneLookup` bit. Multiple inheritance falls out naturally. - **The `data` union holds the primitive payload.** A Number stores its `double` inline; a List's `ptr` points at a basekit `List`; a Block's `ptr` points at the compiled message tree. Everything else — identity, identity-compare, GC — goes through the same header. Assignment operators desugar to slot sends: `:=` → `setSlot`, `=` → `updateSlot`, `::=` → `newSlot`. Slot creation is a write; slot lookup is the proto walk. ## The Message Tree Parsing produces a tree of `IoMessage` nodes, each holding a `name` symbol, an argument list of messages, and a `next` pointer to the next message in the chain. Operators are ordinary messages at this stage. Then `IoMessage_opShuffle.c` rewrites the tree by operator precedence using the table in `libs/iovm/io/OperatorTable.io`. After shuffling, an expression like `1 + 2 × 3` has become `1 +(2 ×(3))` — the same tree shape you'd get from an explicit method call. Because messages are first-class objects, the shuffle is just tree rewriting on live data. Programs can read, rewrite, and evaluate messages the same way. This is what makes `if`, `while`, `for`, and `method` implementable in Io itself rather than as compiler built-ins. ## Evaluation `IoState_iterative.c` implements a single `while(1)` eval loop. Each iteration pulls the current heap-allocated `IoEvalFrame` off the state's `currentFrame`, steps its state machine one click, and loops. There is no C recursion for message evaluation; argument evaluation, block activation, control flow, and coroutine switching all happen by pushing frames and transitioning states. A frame carries everything a recursive evaluator would keep on the C stack: ``` target the receiver (self) locals the enclosing scope message the current message being evaluated argN pre-evaluated arguments result accumulated chain result state state-machine enum parent previous frame ``` This layout buys four properties: 1. **First-class continuations.** `callcc` captures the current frame chain as an ordinary Io object. Invoking it later just swaps `currentFrame`. (Disabled by default behind `-DIO_CALLCC`.) 2. **Portable coroutines.** A suspended coroutine is a saved frame pointer. Switching is pointer assignment — no setjmp/longjmp, no ucontext, no fibers. The same C code runs on every target, including WASM. 3. **Tail-call optimization.** When a call is the last message in a block body, the eval loop reuses the current frame instead of pushing a new one. 4. **Clean exception unwinding.** `IoState_error_` sets `state->errorRaised` and the loop pops frames until a handler is found. No longjmp hopping over C code that expected to run cleanup. `IoMessage_locals_performOn_` (the old recursive evaluator) still exists for bootstrap. Once the first eval loop starts, it redirects to the iterative path. ## Special Forms Most messages pre-evaluate their arguments before the receiver's method runs. A handful must not, because they implement control flow or introspect the unevaluated message itself: ``` if while loop for callcc method block foreach reverseForeach foreachLine ``` The iterative evaluator checks for these by name in two places in `IoState_iterative.c` and skips pre-evaluation. The method then evaluates its argument messages explicitly with the evaluator APIs. ## The Standard Library The files in `libs/iovm/io/` are loaded in the explicit order listed by the `IO_FILES` variable in the root `Makefile`: ``` List_bootstrap.io, Object_bootstrap.io, OperatorTable.io, Object.io, List.io, Exception.io, , CLI.io, Importer.io ``` Order matters — the bootstrap files install the minimum slots needed to load everything else. `make regenerate` runs `io2c` to compile these files into string literals inside `libs/iovm/source/IoVMInit.c`, which the VM evaluates at startup. That means the shipped binary carries its own standard library; there is no filesystem lookup at init. ## Garbage Collector The collector is an incremental, tri-color, mark-and-sweep design living under `libs/garbagecollector/`. Roots are the lobby, the active coroutine's frame chain, and any object the VM has pinned with `IoState_retain_`. Write barriers (`IoObject_shouldMark`, invoked from `IOREF`) keep the gray set consistent when mutators store new references into already-scanned objects. Because frames are ordinary heap objects, the live stack is itself reachable through `state->currentFrame`. Stack traces, debuggers, and serializers all walk the same graph the collector marks. ## Coroutines A coroutine is just a saved `currentFrame` plus a small bit of bookkeeping. `Coroutine resume` sets the VM's `currentFrame` to the coroutine's saved pointer; the eval loop picks up where it left off. `yield` and `pause` cooperate with `Scheduler` to pick the next coroutine. See [Stackless](../../Stackless/index.html) for a deeper treatment of the evaluator, the scheduler, the actor pattern, and the performance characteristics of the frame machine. --- Source: /docs/Implementation/Reference/ # Reference C-internal implementation details extracted from `cdoc` and `cmetadoc` comments. ## Objects - Core - [BigInt](BigInt/index.html) - [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) - [Date](Date/index.html) - [Debugger](Debugger/index.html) - [Directory](Directory/index.html) - [Duration](Duration/index.html) - [Error](Error/index.html) - [EvalFrame](EvalFrame/index.html) - [File](File/index.html) - [Lexer](Lexer/index.html) - [List](List/index.html) - [Map](Map/index.html) - [Message](Message/index.html) - [Number](Number/index.html) - [Object](Object/index.html) - [Profiler](Profiler/index.html) - [Sandbox](Sandbox/index.html) - [Sequence](Sequence/index.html) - [State](State/index.html) - [System](System/index.html) - [Tag](Tag/index.html) - [Token](Token/index.html) - [WeakLink](WeakLink/index.html) --- Source: /docs/Implementation/Reference/BigInt/ # BigInt C implementation of arbitrary-precision integers. Each BigInt is an IoObject whose dataPointer is an IoBigIntData that owns a libtommath mp_int (variable-size digit array on the heap). Unlike Number, BigInt has a real payload: rawClone allocates and mp_init's a fresh mp_int, and the tag's freeFunc must call mp_clear before freeing the wrapper. Operands must both be BigInts — there is no silent coercion from Number (matching JavaScript's BigInt rules), which is enforced by the IoBigInt_argData helper that raises an error on non-BigInt args. Every arithmetic method follows the same mp_init/op/mp_copy-into-new- BigInt/mp_clear pattern to keep libtommath's ownership discipline correct even in the face of GC-driven allocation. --- Source: /docs/Implementation/Reference/Block/ # Block C implementation of Io blocks and methods (they share the same struct; block() binds a lexical scope, method() leaves scope NULL so activation uses the message target). IoBlockData carries the message tree, an argNames List, the optional captured scope, passStops (whether return/continue/break propagate to the caller), and profiler timing. Activation happens via IoBlock_activate, which is installed as the tag's activateFunc — any getSlot of an activatable Block thus runs the block without explicit message dispatch. IoBlock_activate builds a fresh locals object (cloned from state->localsProto), populates it with call/self/updateSlot plus the named arguments, and recursively evaluates the body via IoMessage_locals_performOn_. With frame-based coroutines and the iterative evaluator, that inner perform will usually redirect into the eval loop rather than recurse on the C stack. The optional profiler wraps IoBlock_activate with clock() bookkeeping. --- Source: /docs/Implementation/Reference/Call/ # Call C implementation of the Call object created once per block/method activation. IoCallData captures the activation's sender (caller's locals), the target receiver, the triggering message, the slotContext (where the slot was found during lookup — can differ from target when the slot lives on a proto), the activated Block/CFunction itself, the coroutine that is running, and the stopStatus used to signal return/continue/break. Each Call is exposed through the block's locals as the "call" slot so Io code can read sender, message argAt, etc. IoCall_rawStopStatus is the primary C-level read path; the iterative evaluator (IoState_iterative.c) and IoBlock_activate both poke the status here when an early exit is requested. --- Source: /docs/Implementation/Reference/CFunction/ # CFunction C implementation of the CFunction primitive. An IoCFunctionData holds a C function pointer (IoUserFunction: target/locals/message -> result), an optional typeTag gate (only activate if target's tag matches, else raise a typed error), a uniqueName symbol for introspection, and profiler timing. Like Block, CFunction is self-activating: the tag's activateFunc is IoCFunction_activate, so fetching a CFunction from an activatable slot calls through immediately. This is how every C-backed Io method (List append, Number +, Object if/while/for, etc.) is installed — IoObject_addMethodTable_ creates one CFunction per entry and stores it in the target proto. CFunctions do not pre-evaluate their arguments; the recipient uses IoMessage_locals_valueArgAt_ or the pre-eval fast path (IoState_preEvalArgAt_) to fetch them lazily. --- Source: /docs/Implementation/Reference/Collector/ # Collector Thin Io-facing wrapper around the underlying C Collector (see basekit/source/Collector.c). There is no dedicated IoCollector struct — the proto is just an IoObject carrying a method table; every method forwards to Collector_* on IOSTATE->collector. Because of that, the file has no tag/newTag/rawClone/free/mark functions: lifecycle of the collector itself is owned by IoState. The Io-level methods exposed here (collect, setMarksPerAlloc, allObjects, dirtyObjects, etc.) let scripts tune and inspect the incremental tri-color mark-sweep at runtime. COLLECTOR_FOREACH iterates every live marker and is used by the introspection methods. --- Source: /docs/Implementation/Reference/Compiler/ # Compiler Thin Io-facing facade over the C lexer (IoLexer) and the C recursive- descent parser (IoMessage_parser). Exposes three Io-visible entry points — tokensForString, messageForString, messageForString2 — that run a source string through IoLexer_lex, surface any errorToken as an Io "compile error" Exception, and either hand back a list of token description objects or the root IoMessage of the parsed chain. The Compiler proto itself is just a slot-holder; there is no compiled bytecode representation in Io, so "compiling" means producing a message tree. --- Source: /docs/Implementation/Reference/Continuation/ # Continuation C implementation of first-class continuations. A continuation owns a deep copy of the IoEvalFrame chain that was current at `callcc` time, plus the locals of the capturing block. Invocation is one-shot and destructive: the stored chain is installed as `state->currentFrame`, the continuation's pointer is nulled, and `state->continuationInvoked` tells the eval loop to restart from the new top. `copy` makes a fresh deep copy so the same point can be re-entered more than once. Deep copy is required because popped frames are zeroed by the pool in IoState_iterative_fast.c — a grab-pointer capture would see empty frames by the time `callcc` returns. Frames stay GC-reachable through IoEvalFrame's own mark walk of the parent chain; `capturedLocals` is marked here. `asMap` / `fromMap` serialize a continuation to a plain Map (state enum name, message source text, primitive fields, per-state controlFlow payload) so continuations can cross process or network boundaries and be rebuilt from text — the design target that motivated the stackless rewrite. --- Source: /docs/Implementation/Reference/Coroutine/ # Coroutine C implementation of Io coroutines built on the heap-allocated IoEvalFrame chain instead of platform C-stack switching (no ucontext, fibers, setjmp, or assembly). A coroutine owns its own IoEvalFrame parent chain (DATA(self)->frameStack), a retain stack (ioStack), and bookkeeping for stop status, return value, and frame depth. Switching coroutines is a pure pointer swap: IoCoroutine_saveState_ parks state->currentFrame and friends into the outgoing coro, then IoCoroutine_restoreState_ pulls the incoming coro's saved frame chain back into the live IoState. The same single eval loop (IoState_evalLoop_) keeps running on the main C stack and simply resumes iterating on whichever frame chain is now current. The GC walks each coroutine's saved frame chain via IoCoroutine_mark so suspended coroutines stay alive and reachable. --- Source: /docs/Implementation/Reference/Date/ # Date IoObject wrapper around the plain-C Date struct (a timeval plus a timezone). All real calendar math — year/month/day/hour/minute/second decomposition, strftime-style formatting, strptime-style parsing, UTC/local/zone conversion — lives in Date.c and PortableStrptime.c; this file just bridges Io message sends to those C routines, manages lifecycle (tag, clone, free, compare), and handles arithmetic with IoDuration (Date + Duration -> Date, Date - Date -> Duration). A "format" slot on the proto holds the default strftime pattern ("%Y-%m-%d %H:%M:%S %Z") that asString falls back to when no explicit format argument is supplied. --- Source: /docs/Implementation/Reference/Debugger/ # Debugger C side of the Debugger object is intentionally tiny — it is a plain Io object with only a "type" slot stamped here; all stepping logic lives in Io-level code. The real wire-up happens in IoCoroutine.c: when a coroutine has debuggingOn, IoObject_performWithDebugger populates slots like message, messageSelf, messageLocals, messageCoroutine on this proto and resumes the coroutine stored in the Io-level `debuggerCoroutine` slot. That makes the debugger a regular Io coroutine paused in a receive loop, woken once per message send of the target coroutine. --- Source: /docs/Implementation/Reference/Directory/ # Directory C implementation of Directory — a thin Io wrapper around POSIX opendir/readdir/closedir. IoDirectoryData holds a single symbol (the path); each items/at/size call reopens the directory rather than keeping a persistent DIR* open, which keeps clone semantics trivial and avoids leaking descriptors on GC. Under WASI the evaluator must have been started with --dir= for any path to be openable; otherwise opendir fails and Io sees an "unable to open directory" exception. isDirectory prefers the d_type fast path when the host dirent exposes it, falling back to a stat call otherwise. --- Source: /docs/Implementation/Reference/Duration/ # Duration Thin IoObject wrapper around the plain-C Duration struct (years, days, hours, minutes, seconds). All arithmetic and formatting live in Duration.c; this file only bridges Io message sends to the C API, manages lifecycle (tag / clone / free / compare), and registers the proto. Durations interoperate with IoDate for +/- arithmetic — see IoDate_add / IoDate_subtract, which read the C Duration via IoDuration_duration — and with IoNumber through asNumber / fromNumber, treating seconds as the canonical scalar form. --- Source: /docs/Implementation/Reference/Error/ # Error Minimal C scaffolding for the Error proto. The object carries no C payload of its own — error information lives entirely in Io-level slots such as "message" — so this file only supplies the tag, proto registration, and a pair of convenience constructors used by C code that wants to hand an Error up through the VM. The richer exception flow (raise, catch, pass) is implemented in Io on top of Exception; IoError is the lightweight structural representative returned by primitives that report failure without raising. --- Source: /docs/Implementation/Reference/EvalFrame/ # EvalFrame Heap-allocated frame state machine that replaces C-stack recursion in the evaluator. Each frame is a GC-managed IoObject carrying an IoEvalFrameData payload: the message being evaluated, its target/locals, an evaluation state (see IoFrameState), evaluated-argument storage with a 4-slot inline buffer, and a discriminated-union of control-flow continuations (if/while/ for/foreach/callcc/coroutine/await) live only for the states that use them. Frames form a parent chain rooted at the coroutine's bottom frame; the GC walks that chain via the tag's markFunc. This module owns frame lifecycle (alloc, clone, reset, free) and state-name ↔ enum conversion; the actual step-by-step state transitions live in IoState_iterative.c. --- Source: /docs/Implementation/Reference/File/ # File Stat-related methods for File — split out from IoFile.c so the stat(2) surface can be built out (or stubbed) independently of the core stream API. IoFile_statInit is called from IoFile_proto and bolts these methods onto the File proto. The struct stat buffer is lazily allocated and cached in IoFileData->info on first use, then shared by every subsequent protectionMode / userId / size / isDirectory query until the file is freed. The S_ISLNK and S_ISSOCK macros are stubbed to 0 under WASI, where the type bits are unavailable; lstat falls back to stat for the same reason. --- Source: /docs/Implementation/Reference/Lexer/ # Lexer Hand-written UTF-8 aware tokenizer that turns an Io source string into a List of IoTokens. The lexer is driven by IoLexer_lex which kicks off a recursive IoLexer_readMessage loop; each sub-reader (readIdentifier, readOperator, readNumber, readQuote, readComment, ...) uses a position-saving pushPos / popPos / popPosBack convention to implement try-then-backtrack parsing without a separate parse state. Emitted tokens are appended both to the flat tokenStream List (parallel consumption order by the parser) and chained via nextToken links inside IoToken itself. Nested `/*...*\/` comments, triple-quoted strings, hex and decimal literals with exponents, backslash line continuations, and synthesised group-name tokens for `[...]` and `{...}` are all handled here. The parser in IoMessage_parser.c pops tokens off the stream and routes them through IoMessage_ifPossibleCache Token_. --- Source: /docs/Implementation/Reference/List/ # List Thin IoObject wrapper around the shared basekit List container (DATA(self) is a plain List*). The tag installs clone/mark/free/compare function pointers; marking walks every element so the GC keeps contained objects alive. Iteration primitives (each, foreach, reverseForeach) have two execution paths: when state->currentFrame is set they stamp the frame's controlFlow.foreachInfo union and return immediately so the iterative eval loop drives the iteration (see FRAME_STATE_FOREACH_* in IoState_iterative.c); otherwise a LIST_SAFEFOREACH recursive fallback is used, which is exercised during VM bootstrap before the eval loop starts. Mutation methods set IoObject_isDirty_ so Store/persistence layers can notice change. asEncodedList / fromEncodedList implement a compact binary round-trip used by the object serialization machinery. --- Source: /docs/Implementation/Reference/Map/ # Map Thin IoObject wrapper around basekit's PHash (cuckoo hash). DATA(self) is the PHash*; mark walks every key and value so contained IoObjects remain live. Unlike Io's own Object slots (which are stored in a PHash owned by the Object), Map is a separate user-visible collection with ordering-free semantics and symbol keys. The foreach implementation has two paths: on the iterative evaluator it materializes keys via IoMap_rawKeys and hands the List off to the foreach frame state machine (controlFlow.foreachInfo.mapSource carries the original Map so the loop can look the value up per iteration). The recursive fallback PHASH_FOREACH is used only during VM bootstrap before state->currentFrame exists. --- Source: /docs/Implementation/Reference/Message/ # Message C implementation of Io's first-class Message. An IoMessage is the AST node and the runtime perform record rolled into one: it holds a name (IoSymbol), an args List of sub-message trees, an optional cachedResult (literal or inline-cache value), a `next` pointer to the rest of the chain, an inline slot-lookup cache (inlineCacheValue / inlineCacheContext, marked with the containing tag version), and source-location fields (label, lineNumber). Evaluation goes through IoMessage_locals_performOn_, which delegates to the iterative evaluator in IoState_iterative.c whenever a frame exists; a bootstrap-only recursive fallback handles the short window before the first eval loop starts. Most Io-visible getters/setters are thin wrappers around IoMessage_raw*_ helpers; the raw helpers are also how IoMessage_parser and IoMessage_opShuffle build and rewrite trees without running the perform machinery. --- Source: /docs/Implementation/Reference/Number/ # Number C implementation of Io's Number proto. A Number is an IoObject whose inline data union stores a double — no separate heap payload, no dataPointer. The CNUMBER / DATA macros just reinterpret the inline slot, and IoNumber_free is a no-op because there is nothing to release. Most arithmetic methods are thin shims that read the double out of self (and any operand), compute, and return a freshly minted Number via IONUMBER. Number is registered as a primitive proto on the state so IONUMBER can clone without going through the message machinery. Bitwise and character-class methods cast the double to long / int on the fly; the limits methods expose the host's / constants. --- Source: /docs/Implementation/Reference/Object/ # Object C implementation of the root Object prototype — the ancestor of every Io value. An IoObject is a CollectorMarker whose `object` field points to an IoObjectData carrying the tag (vtable), a PHash of slots, an inline-or-heap-allocated protos array, and a small data union used by primitive subtypes. This file owns allocation (IoObject_justAlloc / _alloc / _proto), cloning (raw / primitive / IOCLONE with init), proto chain manipulation (rawAppendProto_, rawPrependProto_, rawRemoveProto_ with inline-or-realloc'd storage), slot read/write, lookup loop detection, the whole Io-visible method table installed by IoObject_protoFinish, and the localsProto variant used for block locals. Most of the `IO_METHOD(IoObject, ...)` functions here already carry doc blocks and are part of the Io-visible API; only the internal C helpers (raw*, alloc/dealloc, activateFunc, lookup primitives) are annotated below. --- Source: /docs/Implementation/Reference/Profiler/ # Profiler C implementation of Profiler — walks the full heap via the collector to reset or report per-Block timing data. Profiler owns no state of its own; the profiler timings live on individual IoBlocks (IoBlock_rawProfilerTime / IoBlock_rawResetProfilerTime). This module just provides the bulk reset and bulk query entry points. A commented-out CFunction branch is kept as a placeholder for extending profiling to native functions. --- Source: /docs/Implementation/Reference/Sandbox/ # Sandbox C implementation of Sandbox — a wrapper that lazily spins up an entire nested IoState and runs strings of Io code inside it. The inner IoState is stored in the IoObject's data pointer (see DATA(self)) and created lazily by IoSandbox_boxState so empty Sandbox clones stay cheap. Quota-style protection (messageCountLimit, timeLimit) is enforced by the inner IoState's evaluator, not by this module; setters here just poke those fields. Output from code run in the sandbox is intercepted via IoState_printCallback_ and re-sent as a printCallback message back to the outer VM, so the host can decide what to do with sandboxed stdout. --- Source: /docs/Implementation/Reference/Sequence/ # Sequence C implementation of the Sequence / String / Symbol proto. A Sequence is an IoObject whose dataPointer is a UArray — a typed, resizable byte array from the basekit runtime that tracks itemType, encoding, and item size in addition to raw bytes. A single proto serves three roles: mutable Sequence, immutable Symbol (interned; pointer-equality for equal strings), and typed numeric vector (uint8, int32, float32, etc.) used by the fast math methods. The split across IoSeq.c, IoSeq_immutable.c, IoSeq_mutable.c, and IoSeq_vector.c is purely a code-organization partition: this file owns lifecycle (clone/free/ compare), the constructor API, and cross-partition helpers; IoSeq_immutable.c holds read-only methods also usable on Symbols; IoSeq_mutable.c holds the in-place-mutating methods; IoSeq_vector.c holds the SIMD/vector math. Symbols are detected via IoObject_isSymbol; rawClone short-circuits on them (returning the original) so every "clone" of a Symbol is the same interned object. --- Source: /docs/Implementation/Reference/State/ # State Top-level entry points for evaluating Io source from C. Every "run some code" path embedders expose eventually funnels through IoState_tryToPerform, which spawns a fresh try-coroutine so uncaught exceptions are captured instead of reaching the host. Sandbox-mode state (message count limit, deadline endTime) is reset here before the evaluation starts so the iterative evaluator can enforce the limits per-run. This file is intentionally small — the actual step machinery lives in IoState_iterative.c. --- Source: /docs/Implementation/Reference/System/ # System C implementation of Io's System object — a slot-only object (no dedicated data pointer or tag) that exposes VM- and environment-level hooks: process exit, errno string, env var get/set, platform name, sleep, symbol table, lobby root, and GC recycler tuning. Under WASI the host-provided surface is minimal: daemon and system() raise errors, thisProcessPid is always 1, activeCpus is 1, and platform/ platformVersion return "wasm"/"wasi-0.1". IO_VERSION_STRING and INSTALL_PREFIX are stamped onto System as slots in IoSystem_proto so Io code can query the build identity. --- Source: /docs/Implementation/Reference/Tag/ # Tag Per-type vtable shared by every IoObject of a given primitive class. An IoTag carries the type's name, a set of function pointers (clone, free, mark, activate, compare, perform, write/read, notification, cleanup) and is reference-counted across the proto and all its clones. Each IoObject_new... constructor attaches exactly one tag (installed by the corresponding IoXxx_newTag helper) and the collector calls markFunc / freeFunc through the tag rather than switching on type. Keeping the dispatch here lets IoObject stay a fixed-size marker regardless of how many primitive subtypes the VM defines. --- Source: /docs/Implementation/Reference/Token/ # Token Plain-C token node produced by IoLexer as it scans source text. A token carries its spelling (name/length), an IoTokenType classification, a lineNumber/charNumber for diagnostics, an optional error string, and a forward-link `nextToken` that the lexer uses to splice tokens into a singly-linked chain parallel to the per-frame List tokenStream. Tokens are owned by the lexer and freed via IoToken_free; they are not IoObjects and are not GC-managed — the parser in IoMessage_parser.c consumes them one at a time and translates each into an IoMessage. --- Source: /docs/Implementation/Reference/WeakLink/ # WeakLink C implementation of WeakLink. Holds a single non-owning pointer (DATA(self)->link) that is deliberately NOT marked by the GC, so the referent can be collected even while a WeakLink names it. The mechanism for notification is the IoObject listener list plus the tag's notificationFunc: on setLink, the WeakLink registers as a listener on the target via IoObject_addListener_; when the target is freed, the collector fires IoWeakLink_notification which clears the pointer back to NULL. There is no markFunc registered — that is the whole point. The commented-out writeToStream/readFromStream pair persists WeakLinks by PID (IoObject_pid) so they can be rehydrated through IoState_objectWithPid_. --- 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/Reference/Block/ # Block An anonymous function (method or block) that captures messages and arguments. Activated by sending it a message. --- Source: /docs/Reference/Call/ # Call Call stores slots related to activation. --- Source: /docs/Reference/CFunction/ # CFunction A wrapper around a C function exposed to Io as an activatable slot. --- Source: /docs/Reference/Collector/ # Collector A singleton containing methods related to Io's garbage collector. Io currently uses a incremental, non-moving, generational collector based on the tri-color (black/gray/white) algorithm with a write-barrier. Every N number of object allocs, the collector will walk some of the objects marked as gray, marking their connected white objects as gray and turning themselves black. Every M allocs, it will pause for a sweep where it makes sure all grays are marked black and io_frees all whites. If the sweepsPerGeneration is set to zero, it will immediately mark all blacks as white again and mark the root objects as gray. Otherwise, it will wait until the sweepsPerGeneration count is reached to do this. By adjusting the allocsPerSweep and sweepsPerGeneration appropriately, the collector can be tuned efficiently for various usage cases. Generally, the more objects in your heap, the larger you'll want this number. --- Source: /docs/Reference/Compiler/ # Compiler Contains methods related to the compiling code. --- Source: /docs/Reference/Continuation/ # Continuation A first-class continuation captured by `callcc`. Represents a resumable snapshot of execution state; invoking it re-enters the captured context. --- Source: /docs/Reference/Coroutine/ # Coroutine Coroutine is an primitive for Io's lightweight cooperative C-stack based threads. --- Source: /docs/Reference/Date/ # Date An absolute point in time, represented as seconds since the epoch. --- Source: /docs/Reference/Debugger/ # Debugger Contains methods related to the IoVM debugger. --- Source: /docs/Reference/Directory/ # Directory A handle to a directory on disk. Lists contents, creates subdirectories, and walks file trees. --- Source: /docs/Reference/DirectoryCollector/ # DirectoryCollector An object to collect multiple UnitTests defined in *Test.io files within a given directory (System launchPath directory by default). --- Source: /docs/Reference/Duration/ # Duration A container for a duration of time. --- Source: /docs/Reference/Error/ # Error Error-handling helpers for returning error values from methods instead of raising exceptions. --- Source: /docs/Reference/EvalFrame/ # EvalFrame --- Source: /docs/Reference/Exception/ # Exception The Exception proto is used for raising exceptions; instances hold exception-related info. 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 --- Source: /docs/Reference/false/ # false The false singleton — Io's representation of boolean falsity. --- Source: /docs/Reference/File/ # File A handle to a file on disk. Supports reading, writing, seeking, and metadata queries. --- Source: /docs/Reference/FileCollector/ # FileCollector An object to collect multiple UnitTests defined in the current file. --- Source: /docs/Reference/FileImporter/ # FileImporter An Importer for local source files. --- Source: /docs/Reference/Importer/ # Importer A simple search path based auto-importer. --- Source: /docs/Reference/List/ # List A mutable, ordered collection of values indexed by integer position. --- Source: /docs/Reference/Map/ # Map A mutable dictionary mapping Sequence keys to arbitrary values. --- Source: /docs/Reference/Message/ # Message A parsed message in Io's AST. Carries a name, argument messages, the next message in the chain, and cached values for evaluation. --- Source: /docs/Reference/MutableSequence/ # MutableSequence A Sequence whose contents can be modified in place. MutableSequence is the receiver for the mutating methods of Sequence (append, insert, remove, etc.). --- Source: /docs/Reference/nil/ # nil nil is a singleton object that is used as a placeholder and to mean false in Io. --- Source: /docs/Reference/Number/ # Number A double-precision floating-point value. All numeric literals are Numbers. --- Source: /docs/Reference/Object/ # Object The root proto. All other protos clone from Object; it provides core slot management, message dispatch, introspection, and control flow primitives. --- Source: /docs/Reference/Path/ # Path Path manipulation: joining, splitting, absolute/relative conversion, and tilde expansion. --- Source: /docs/Reference/Profiler/ # Profiler A sampling profiler for timing message execution. --- Source: /docs/Reference/Sandbox/ # Sandbox Runs Io code in an isolated child state with restricted resource limits. --- Source: /docs/Reference/Scheduler/ # Scheduler Io's coroutine scheduler. --- Source: /docs/Reference/Sequence/ # Sequence An ordered, homogeneous collection of bytes or numbers. Strings are Sequences with a character encoding. --- Source: /docs/Reference/State/ # State High-performance iterative evaluator with aggressive optimizations: - Frame pooling (no malloc/free in hot path) - Computed gotos (faster than switch) - Inline fast paths for common cases - Local variable caching --- Source: /docs/Reference/System/ # System Process-level operations: command-line arguments, environment variables, platform info, and exit. --- Source: /docs/Reference/TestRunner/ # TestRunner Core testing object responsible for running tests, collected by UnitTests and TestSuites. --- Source: /docs/Reference/true/ # true The true singleton — Io's representation of boolean truth. --- Source: /docs/Reference/UnitTest/ # UnitTest An object for organizing and running unit tests validated by assertions. --- Source: /docs/Reference/WeakLink/ # WeakLink A WeakLink is a primitive that can hold a reference to an object without preventing the garbage collector from collecting it. The link reference is set with the setLink() method. After the garbage collector collects an object, it informs any (uncollected) WeakLink objects whose link value pointed to that object by calling their "collectedLink" method. --- 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 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. ## Who created Io, and when? Io was created by Steve Dekorte, starting in 2002, as an experiment in seeing how small a language could be while still expressing ideas borrowed from Smalltalk, Self, NewtonScript, Act1, and Lisp. ## What makes Io different from other languages? Io has no keywords and no statements — only expressions, and every expression is a message send. Assignment, conditionals, and loops are ordinary methods that receive their arguments as unevaluated message trees, which keeps the core tiny and makes control flow and DSLs first-class citizens of the language. Every object is a prototype. There are no classes, no distinction between instances and templates, and no hidden machinery for inheritance — any object can be cloned and any clone can be used as a proto for further objects. Concurrency is built around actors and futures rather than threads. Any object can receive `asyncSend` or `futureSend` messages and run its own coroutine; futures are transparent (they become their result when touched) and the VM detects deadlocks by walking the future graph. The evaluator is stackless — execution runs on a heap-allocated frame state machine instead of the C stack — enabling first-class continuations (`callcc`), portable coroutines without platform-specific assembly, and robust exception unwinding without `setjmp`/`longjmp`. ## Is Io production-ready? The classic Io VM is small, stable, and has been used in shipping products, embedded devices, and research. It is not trying to compete with JavaScript or Python on ecosystem size — it is a language for people who value expressiveness, simplicity, and runtime flexibility over a large standard library or raw throughput. The new WASM target (and the stackless evaluator it is built on) is not yet well tested — treat it as early-access until it has more mileage. ## How is Io different from JavaScript, which is also prototype-based? JavaScript has classes, `new`, statements, keywords, and a sharp distinction between primitives and objects. Io has none of these. Inheritance is differential — clones share their proto's slots until written — and multiple inheritance falls out naturally because every object carries a list of protos. Control flow is not built in. In Io, `if`, `while`, and `for` are regular methods written in Io itself, made possible because messages receive their arguments as unevaluated message trees. Concurrency is coroutine-based rather than callback- or `async`/`await`-based. There is no function coloring — any method can yield, and any object can be sent asynchronously with `@` (futures) or `@@` (fire-and-forget). Code that waits on a future reads like straight-line code; there is no separate async function type or await keyword to propagate through call sites. The evaluator is stackless — execution runs on heap-allocated frames rather than the C stack — so Io also has first-class continuations (`callcc`), something JavaScript does not expose at all. ## Why a WASM/WASI target? WebAssembly gives Io a single portable binary that runs under wasmtime, Node.js, or a browser — without platform-specific build matrices, coroutine assembly, or native addons. Hosting Io in a browser unlocks direct DOM access, and the bidirectional Io↔JavaScript bridge replaces the old native-addon model with something that works the same everywhere the host does. ## Can Io still embed in a C or native application? Yes — a WebAssembly module is an embeddable binary by design. You can run the Io VM inside a browser, inside Node.js, or inside a native application via a WASM runtime like wasmtime or wasmer. The VM is BSD-licensed. ## How does concurrency work? Every object can receive async messages (`@@`) or future messages (`@`) that run in their own coroutine. Coroutines are cooperative user-level tasks — not OS threads — so a single Io process can run thousands of them at once. Futures are transparent: the caller keeps running until it tries to use the result, then blocks only if the result isn't ready yet. ## Where does the name "Io" come from? A combination of searching for short two-letter names, childhood memories of the Voyager photos of Jupiter's moon Io, and the I/O towers from the movie *TRON*. ## How do I contribute? The source lives at [github.com/IoLanguage/io](https://github.com/IoLanguage/io). Open issues, send pull requests, or just clone and experiment. --- 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** — Mailing list: The iolanguage Yahoo! Group becomes the gathering place for early users and patch exchanges. - **2002-04** — First public release: The earliest public Io releases and programming guide appear, distributed through the mailing list. - **2002-08** — Actors and coroutines: Actors (object-oriented threads) arrive, built on 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** — Resumable exceptions: Common Lisp–style signal / withHandler: handlers can resume, fall through, or unwind — made practical by the stackless evaluator. - **2026-03** — WASM/WASI port: Io cross-compiles to WebAssembly. A bidirectional Io↔JavaScript bridge replaces the native addon model. - **2026-03** — Async/await bridge: Io futures tie into JavaScript Promises: asyncSend and future touches yield the eval loop across the JS boundary, and unrecognized messages on a future implicitly await the result. - **2026-03** — BigInt support: A BigInt primitive arrives with arbitrary-precision arithmetic and transparent round-tripping across the Io↔JS bridge, aligning Io numerics with modern JavaScript. - **2026-04** — SIMD128 Vector backend: The Vector primitive gains a WebAssembly SIMD128 backend, restoring the AltiVec-era vector acceleration in the browser and on modern hardware via WASM. - **2026-04** — WASM merged to master: The WASM branch becomes the default target. Book and site restructured for the new era.