iolang

package module
v0.0.0-...-cfb4354 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 17, 2020 License: Zlib Imports: 1 Imported by: 0

README

iolang

stability-experimental Go Reference Tests

This is a pure Go implementation of Io. Much of the hard work has been done, but hard work still remains.

To embed an iolang interpreter into a Go program, one would use the NewVM() function to get a *VM, use SetSlot() to make available any extras, then feed the VM code to evaluate with its DoString() and DoReader() methods. The VM also has methods like NewNumber(), NewString(), NewObject(), &c. to create primitives. The API is currently incomplete and will change.

The cmd/io directory contains a REPL as an example of embedding iolang.

This implementation does not always behave identically to the original.

Io Primer

"Hello, world!" println

Io is a programming language primarily inspired by Self, Smalltalk, and Lisp. Execution is implemented by message passing: the phrase Object slot, when evaluated, creates a message with the text slot and sends it to Object, which is itself a message passed to the default Lobby object. Messages can also be provided with arguments, which are simply additional messages to provide more information with the message, in parentheses. Object slot(arg1, arg2) provides the arg1 and arg2 messages as arguments to the call. The only syntactic elements other than messages are numeric and string literals and comments.

When an object receives a message, it checks for a slot on the object with the same name as the message text. If the object has such a slot, then it is activated and produces a value. Slots can be added to objects using the := or ::= assignment operators; Object x := 1 creates a slot on Object named x that will produce the value 1 when activated.

Certain objects have special behavior when activated. Methods are encapsulated messages that, when activated, send that message to the receiver of the message which activated the method. For example, with println being an Object method, x println executes println's message in the context of x, meaning that the default location for slot lookups becomes x instead of Lobby. Methods also have their own locals, so new slots created within them exist only inside the method body, and they can take arguments, setting slots on the locals object for each argument.

If an object does not own a slot with the same name as a message passed to it, the object will instead check for that slot in its prototypes. Objects can have any number of protos, and the search proceeds in depth-first order without duplicates. If the slot is found in one of the protos, then it is activated, but still with the original object as the receiver. This implements an inheritance-like concept, but with "superclasses" being themselves objects. (If there isn't any proto with the slot, then the message is sent to the object's forward slot, and if that slot doesn't exist, either, then an exception is raised.)

Producing "subclasses" is done using the clone method. A clone of an object, say x, is another object which has (initially) empty slots and x as its only prototype. If we say y := x clone, then y will respond to the same messages as x via delegation; new slots can be created which will be activated in place of the proto's, providing polymorphism. The typical pattern to implement a new "type" is to say NewType := Object clone do(x := y; z := w), where do is an Object method which evaluates code in the context of its receiver. Then, "instances" of the new "class" can be created using instance := NewType clone. Notice that clone is the method used both to create a new type and an instance of that type - this is prototype-based programming.

An important aspect of Io lacking syntax beyond messages is that control flow is implemented as Object methods. if is a method taking one to three arguments: if(cond, message when cond is true, message when cond is false) evaluates its first argument, then the second if it is true or the third if it is false. Because message arguments are themselves messages, the other argument is not evaluated. When any of the arguments are not supplied, the evaluation result is returned instead, which enables alternate forms of branching: if(cond) then(message when cond is true) elseif(cond2, different message) else(last thing to try). There are also loops, including for to loop over a range with a counter, while to perform a loop as long as a condition is true, loop to perform a loop forever, and others. continue, break, and return generally do what you expect. Each of these methods is an expression, and the last value produced during evaluation of each is returned.

For a more in-depth introduction to Io, check out the official guide and reference. There are more code examples at the original implementation's GitHub repository as well.

Documentation

Overview

Package iolang implements a version of the Io programming language.

Io is a dynamic, prototype-based language inspired by Smalltalk (everything is an object), Self (prototypes - *everything* is an object), Lisp (even the program code is made up of objects), NewtonScript (differential inheritance), Act1 (concurrency via actors and futures), and Lua (small and embeddable implementations). It was originally developed in C by Steve Dekorte.

Currently, this implementation is focusing primarily on becoming a fully fledged interpreter. A read-eval-print loop exists, primarily for integration testing. There is no file interpreter, so written Io programs are not yet executable.

The interpreter can easily be embedded in another program. To start, use the NewVM function to create and initialize the interpreter. The VM object has a number of fields that then can be used to make objects available to Io code, especially the Lobby and Addons objects. Use the VM's NewNumber, NewString, or any other object creation methods to create the object, then use SetSlot to set those objects' slots to them.

Io Primer

Hello World in Io:

"Hello, world!" println

Io code executes via message passing. For example, the above snippet compiles to two messages:

  1. "Hello, world!", a string literal
  2. println

Upon evaluation, the first message, being a literal, immediately becomes that string value. Then, the println message is sent to it, activating the slot on the string object named "println", which is a method that writes the object to standard output.

Messages can also be provided with arguments, which are simply additional messages to provide more information with the message, in parentheses. We could add arguments to our program:

"Hello, world!"(an argument) println(another argument)

But the program executes the same way, because neither string literals nor (the default) println use their arguments.

When an object receives a message, it checks for a slot on the object with the same name as the message text. If the object has such a slot, then it is activated and produces a value. Slots can be added to objects using the := assignment operator; executing

Object x := 1

creates a slot on Object named x with the value 1.

Certain objects have special behavior when activated. Methods are encapsulated messages that, when activated, send that message to the object which received the message that activated the method. The println method above is an example: it is defined (roughly) as

Object println := method(File standardOutput write(self, "\n"); self)

In the Hello World example, this method is activated with the "Hello, world!" string as its receiver, so it executes in the context of the string, meaning that slot lookups are performed on the string, and the "self" slot within the method refers to the string. Methods have their own locals, so new slots created within them exist only inside the method body. They can also be defined to take arguments; for example, if the println method were instead defined as

Object println := method(x, File standardOutput write(x, "\n"); self)

then we would pass the object to print as an argument instead of printing the receiver.

If an object does not own a slot with the same name as a message passed to it, the object will instead check for that slot in its prototypes. Objects can have any number of protos, and the search proceeds in depth-first order without duplicates. If the slot is found in one of the protos, then it is activated, but still with the original object as the receiver. This implements an inheritance-like concept, but with "superclasses" being themselves objects. (If there isn't any proto with the slot, then the message is sent to the object's "forward" slot, and if that slot doesn't exist, either, an exception is raised.)

Producing "subclasses" is done using the clone method. A clone of an object is a new object having empty slots and that object as its only prototype. If we say "y := x clone", then y will respond to the same messages as x via delegation; new slots can be created which will be activated in place of the proto's, providing polymorphism. The typical pattern to implement a new "type" is to say:

NewType := Object clone do(
	x := y
	z := w
	someMethod := method(...)
)

do is an Object method which evaluates code in the context of its receiver. "Instances" of the new "class" can then be created by saying:

instance := NewType clone

Now instance is an object which responds to NewType's x, z, and someMethod slots, as well as all Object slots. Notice that clone is the method used both to create a new type and an instance of that type - this is prototype-based programming.

An important aspect of Io lacking syntax beyond messages is that control flow is implemented as Object methods. "Object if" is a method taking one to three arguments:

if(condition, message when true, message when false)
if(condition, message when true) else(message when false)
if(condition) then(message when true) else(message when false)

All three of these forms are essentially equivalent. if evaluates the condition, then if it is true (controlled by the result's asBoolean slot), it evaluates the second, otherwise it evaluates the third. If the branch to evaluate doesn't exist, if instead returns the boolean to enable the other forms, as well as to support the elseif method. Note that because message arguments are themselves messages, the wrong branches are never evaluated, so their side effects don't happen.

There are also loops, including but not limited to:

repeat(message to loop forever)
while(condition, message to loop)
for(counter, start, stop, message to loop)
5 repeat(message to do 5 times)

Each loop, being a method, produces a value, which is by default the last value encountered during evaluation of the loop. Object methods continue, break, and return also do what their equivalents in most other programming languages do, except that continue and break can be supplied an argument to change the loop result.

For a more in-depth introduction to Io, check out the official guide and reference at http://iolanguage.org/ as well as the original implementation's GitHub repository at https://github.com/IoLanguage/io for code samples.

Index

Constants

View Source
const (
	NumberTag    = internal.NumberTag
	SchedulerTag = internal.SchedulerTag
)

Tag constants for core types.

View Source
const (
	NoStop        = internal.NoStop
	ContinueStop  = internal.ContinueStop
	BreakStop     = internal.BreakStop
	ReturnStop    = internal.ReturnStop
	ExceptionStop = internal.ExceptionStop
	ExitStop      = internal.ExitStop
)

Control flow reasons.

View Source
const SeqMaxItemSize = internal.SeqMaxItemSize

SeqMaxItemSize is the maximum size in bytes of a single sequence element.

Variables

View Source
var (
	BlockTag     = internal.BlockTag
	CallTag      = internal.CallTag
	CFunctionTag = internal.CFunctionTag
	ExceptionTag = internal.ExceptionTag
	ListTag      = internal.ListTag
	MapTag       = internal.MapTag
	MessageTag   = internal.MessageTag
	SequenceTag  = internal.SequenceTag
)

Tag variables for core types.

Functions

func SliceArgs

func SliceArgs(vm *VM, locals *Object, msg *Message, size int) (start, step, stop int, exc *Object, control Stop)

SliceArgs gets start, stop, and step values for a standard slice-like method invocation, which may be any of the following:

slice(start)
slice(start, stop)
slice(start, stop, step)

start and stop are fixed in the following sense: for each, if it is less than zero, then size is added to it, then, if it is still less than zero, it becomes -1 if the step is negative and 0 otherwise; if it is greater than or equal to the size, then it becomes size - 1 if step is negative and size otherwise.

Types

type BasicTag

type BasicTag = internal.BasicTag

BasicTag is a special Tag type for basic primitive types which do not have special activation and whose clones have values that are shallow copies of their parents.

type Block

type Block = internal.Block

A Block is a reusable, lexically scoped message. Essentially a function.

NOTE: Unlike most other primitives in iolang, Block values are NOT synchronized. It is a race condition to modify a block that might be in use, such as 'call activated' or any block or method object in a scope other than the locals of the innermost currently executing block.

type CFunction

type CFunction = internal.CFunction

A CFunction is an object whose value represents a compiled function.

type Call

type Call = internal.Call

Call wraps information about the activation of a Block.

type Exception

type Exception = internal.Exception

An Exception is an Io exception.

type Fn

type Fn = internal.Fn

An Fn is a statically compiled function which can be executed in an Io VM.

type Message

type Message = internal.Message

A Message is the fundamental syntactic element and functionality of Io.

NOTE: Unlike most other primitive types in iolang, Message values are NOT synchronized. It is a race condition to modify a message that might be in use, such as 'call message' or any message object in a scope other than the locals of the innermost currently executing block.

func ForeachArgs

func ForeachArgs(msg *Message) (kn, vn string, hkn, hvn bool, ev *Message)

ForeachArgs gets the arguments for a foreach method utilizing the standard foreach([[key,] value,] message) syntax.

type Object

type Object = internal.Object

Object is the basic type of Io. Everything is an Object.

Always use NewObject, ObjectWith, or a type-specific constructor to obtain new objects. Creating objects directly will result in arbitrary failures.

type RemoteStop

type RemoteStop = internal.RemoteStop

RemoteStop is a wrapped object and control flow status for sending to coros.

type Scheduler

type Scheduler = internal.Scheduler

Scheduler helps manage a group of Io coroutines.

type SeqKind

type SeqKind = internal.SeqKind

SeqKind represents a sequence data type.

type Sequence

type Sequence = internal.Sequence

A Sequence is a collection of data of one fixed-size type.

type Slots

type Slots = internal.Slots

Slots represents the set of messages to which an object responds.

type Stop

type Stop = internal.Stop

A Stop represents a reason for flow control.

type Tag

type Tag = internal.Tag

Tag is a type indicator for iolang objects. Tag values must be comparable. Tags for different types must not be equal, meaning they must have different underlying types or different values otherwise.

type VM

type VM = internal.VM

A VM processes Io programs.

func NewVM

func NewVM(args ...string) *VM

NewVM prepares a new VM to interpret Io code. String arguments may be passed to occupy the System args slot, typically os.Args[1:].

Directories

Path Synopsis
Package addons provides easier access to common iolang types and constants.
Package addons provides easier access to common iolang types and constants.
Range
Package Range provides an efficient iterator over linear numeric sequences.
Package Range provides an efficient iterator over linear numeric sequences.
cmd
io
Package testutils provides utilities for testing Io code in Go.
Package testutils provides utilities for testing Io code in Go.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL