Skip to content
/ Inox Public

Iɴᴏx is a concatenative script language for Edge Computing on the Internet of Things in ML times. It will run on metal, nodejs, wasm, etc.

Notifications You must be signed in to change notification settings

virteal/Inox

Repository files navigation

The Iɴᴏx programming language

"Programming with style"

"Le style, c'est l'homme" - Buffon, 1753.

Iɴᴏx is a concatenative script language. It is designed to operate in the context of edge computing, with the Internet of Things (IoT), in Machine Learning (ML) times.

It will hopefully run on nodejs, C++ targets, wasm (WIP), micro controlers (esp32), etc.

It is a forth/smalltalk/erlang inspired stack based language with a virtual machine. The basic data element is a 64 bits cell made of two parts, a typed value and a name.

This is the Typescript reference implementation. It defines the syntax and semantic of the language. Production quality versions of the virtual machine would have to be hard coded in some machine code to be more efficient.

I started working on it in june 2021. It's not working yet. The first implementation will run in a regular javascript virtual machine, nodejs, browsers, deno, etc.

Yours,

Jean Hugues Noël Robert, aka Virteal Baron Mariani. @jhr on Twitter.




Why?

Hello. Keep reading only if you care about programming language design. You've been warned. Welcome.

The grand plan is an AI driven distributed system, a computerized living organism that evolves according to the law of evolution.

The Iɴᴏx programming language explores hopefully innovative features not found in mainstream languages like Javascript, C, Python or PHP. Some of Iɴᴏx specificities do exist in some more esoteric languages like Lisp, Forth, Smalltalk, etc. Some other specifities are radically new. Well... until proven otherwise that is ;)

So, what's new?

  • Named values - values have a name attached to them
  • Reactive sets - for distributed dataflow processing
  • Actors - concurrency, asynchronicity & message passing
  • Ranges - smart references to slices of data
  • Stacks - pervasive, even objects are stacks of named values
  • Variables - in the data stack, in the control stack or in some other stack
  • Verbs - simple verbs are concatenated to make complex ones
  • Notations - prefix, infix or postfix notation, your choice
  • Dialects - multiple predefined and custom dialects for different styles

Overview

Here is a short presentation of some of the main characteristics of the Iɴᴏx programming language. There is no stable set of features yet but it gives some ideas about the general spirit of the language.

This introduction is more like a tutorial than a reference manual. That manual will come next, when design gets stable. Enjoy and stay tuned!

Control structures

to play
 random-no( 100 )
 loop: {
   out( "Guess? " )
   read-line, text-to-integer,
   dup, if not an-integer? then: { drop,                     continue };
   dup, if: >              then: { drop, out( "Too big"   ), continue };
   dup, if: <              then: { drop, out( "Too small" ), continue };
   out( "Yes!" ), break
 }
 clear

This code is a small game where the player must guess a number between 0 and 100. It illustrates two important control structures: loops and conditionnals.

dup duplicates the value on the top of the data stack whereas drop removes it, while clear empties the entire stack.

while: { ... } do: { ... }; and do: { ... } until: { ... }; are special versions of the more general loop: { ... }; structure where the loop breaks or continues depending on some condition.

Named values

Every Iɴᴏx value has a name attached to it. That name comes in addition to the classic type and value that most languages provide.

Because values are named, using a tag, it becomes possible to access them using that name. This is similar to the indirect access that pointers provide but without the notion of identity normaly associated to objects. That is so because many values can have the same name whereas the identity of an object is unique.

This is similar to the notion of property, attribute, field, instance variables, etc. But it has deep additional consequences and usages.

Among other usages, Iɴᴏx uses names to access variables in stacks. Most other languages use index instead, ie a numercial position in the stack. A position that is most often relative to the level of the stack when some function is entered/activated. These are the classical notions of activation records and local variables associated to function calls.

Because Iɴᴏx access variables by names there is no need to provide a user friendly syntax to figure out the numerical position of a variable in a stack. Hence local variables in Iɴᴏx are dynamically scoped; no lexical scope, not yet.

Iɴᴏx also uses named values to implement control structures (if, loop, etc) without the computation of complex changes to the instruction pointer. It is still possible to manipule that instruction pointer to implement diverse form of branching (goto, jump, call, exceptions, etc) ahead of time, at compile time, when verbs are defined, but this is more an optimization than a natural way of expressing things using names instead of labels like in the dark age of assembler languages.

An Iɴᴏx optimizing compiler is somewhere is the road map, we'll come to it somedays, just in time.

A tale of two stacks

Iɴᴏx don't mix the control plane with the data plane, contrary to most languages.

The good thing about that is that data can stay much longer in stacks instead of needing storage elsewhere.

For example the idiomatic solution to build an array is to push some sentinel value onto the data stack, accumulate data on it thanks to some processing and collecting all the data down to the sentinel data when the data is ready for further processing somewhere else or sometimes later.

This is incredibly usefull and when more stacks are required it is equaly simple: switch to a new stack, build the data, store the result somewhere (or leave it on the alternative stack) and get back to the previous stack, eventually the original data stack.

Sophisticated statefull machines can be expressed easly using that solution in addition to finite state machines. It's like having an instruction pointer for data instead of the usual one for code, complete, with push, pop, and return. Think 'active data'.

All objects have at least one data stack attached to them. Actors are special active objects that can have multiple data stacks so that it is possible to send them messages to different addresses, as if they had multiple mailboxes.

Sending messages to the main data stack of an actor is asking it to process the message. Sending the message to some other data stack of the actor helps the actor to identify the meaning of the messages it receives.

Sending messages to the control stack of an actor is even more strange, it's basically telling the actor what verbs to execute, ie remote control. This could for example ask the actor to change state, switch to a debug mode for example or prepare for an hot reload when some change in the code needs to occur.

Note that an actor has full control over the routing of the messages it receives, it can dispatch them to some stack or queue them until it reaches some different state.

None of this actor stuff is implemented at this time, this is a work in progress.

Reactive sets

Actors also handle enhanced stacks dedicated to the processing of a very powerfull type of structured datum called reactive sets.

Thanks to the Toubkal dataflow engine, such data sets can travel thru multiple processing steps in a fully distributed and consistent manner. This is stream processing on steroïds.

The vision is to have captors and reactors down to small devices like a smart bulb or a smoke detector in the house and up to massive AI enhanced processors that could make decisions based on changing external conditions. Conditions like the weather for example when controlling the usage of electricity in a plant factory or even in a house with solar panels.

On a large scale this is like a giant brain with neurons of diverse power that react to changing inputs by firing intelligently depending on their configuration, mamory and training capabilities, small and big.

Hints

Interpreters are slow, this is inevitable to some extend. That disadvange is compensated by some additionnal level of safety. No more dangling pointers, overflow/underflow, off by one back doors, eisenbugs that disappear when observed and all the drama of low level debugging, including core dumps, viruses and unanticipated corner cases. Less pain.

Yet, no pain, no gain. If you dare, Iɴᴏx lets you enter adventure land. You then giveup asserts, type checking, boundaries guards and maybe even dynamic memory management in exhange for speed. Up to C speed for those who are willing to take the risk.

Runtime checks are enabled/disabled at user's will, at run time potentially. This provides a speed boost that is well deserved when sufficient test coverage was conducted by the mature programmer using state of art technics.

Type checking at compile time is a mode that sustains the passage of time, it is not going to disappear soon. On the other end of the spectrum, script languages favor late binding and run time type identification. Let's try to unite these opposite styles, to the extend it is possible.

Syntax is also a matter of taste. Iɴᴏx is rather opiniated about that. To some reasonnable extend it provides mechanisms to alter the syntax of the language, sometimes radically. Thank Forth for that.

It is up to each programmer to apply the style she prefers, life is brief. There is more than one way to do it as they say in the wonderfull world of Perl. The principle of least surprise is cautious but girls love bad guys, don't they?

So, be surprised, be surprising, get inspirational if you can, endorse the Iɴᴏx spirit!

Vive Iɴᴏx ! Or else, stay calm and carry on, c'est la vie, a tale maybe.

Verbs

to hello  "hello" out.

hello

This defines a verb named hello. Then the verb is invoked and as a result hello is displayed on some output device.

Verbs take their arguments from the data stack. They can also push results onto that stack.

Verb to starts a verb definition that an often optional . (dot) terminates. Inside that definition there are other verbs and litteral values like numbers or pieces of text.

Note: the name of a verb can be made of anything, not just letters. As a result even? is a valid name for a verb, it could be the name of a verb that tests if a number is even. By convention verbs with a ? suffix are predicates, their result is a boolean value that is either true or false.

As a convenience the verb to invoke can be specified before the pushed data, using the ( ) parentheses. This is the prefix notation.

say( "hello" )  ~~ frefix style
"hello" say     ~~ postfix style

It is the responsabily of whoever invokes a verb to first push onto the stack the arguments required by that verb, in the proper order, in the proper number, etc. Each verb can define it's own protocol about that. There are a few common protocols, described below.

Prefix, infix and postfix styles

It is a matter of style often but sometimes a notation is preferable to another.

The prefix notation is the one where the verb is specified first, then the arguments. This is a common notation and it is the only one in Lisp style languages. It mimics the style of mathematical functions.

The infix notation is the one where operators are specified between operands. It also derives from the notation in mathematics. It is the most common notation in most programming languages where the concept of expression is used, it is very common.

The postfix notation is the one where the verb is specified last, after the arguments. This is the most common notation in Forth style languages. The postfix notation is also called the reverse polish notation. That notation is the one that describes the actual order of execution at the CPU level with the most fidelity.

  out( "hello" )                  ~~ prefix style
  out( &( "hello", " world!" ) )  ~~ pure prefix style
  out( "hello" & " world!" )      ~~ mixed prefix and infix style
  "hello" " world!" & out         ~~ pure postfix style
  ( "hello" & " world!" ) out     ~~ mixed infix and postfix style

When a verb is to be used as an operator, the operator verb must be invoked right after the verb definition. There is currently no precedence mechanism, all operators have the same precedence and left associativity.

to & text.join. operator ~~ this is how the & operator is actually defined

There is yet another style named keyword style. It is the one where the verb is specified using mutliple parts with a : at the end of each part and a final ;. The Smalltalk language introduced that style.

to say:to:  >dest >msg, out( "Say: " $msg & " to " & $dest );

say: "Hello" to: "the world!";

Local variables are created and initialized using the top of the data stack. The syntax is >xxx where xxx is the name of the local variable. The local variables are then accessible using either $xxx syntax or the xxx>. Both syntaxes are equivalent and means pushing the value of the local variable onto the data stack.

  msg> out ~~ postfix style
  out( $msg ) ~~ prefix style

Because using names if so frequent when programming in the Iɴᴏx language, there is a concise shortcut to invoke a verb with parameters that are names only.

  white/color/led-setup
  on/led-toggle
  small/led-toggle-delay

This is equivalent to:

  /white /color led-setup
  /on           led-toggle
  /small        led-toggle-delay

Which is also equivalent to:

  led-setup( /white, /color )
  led-toggle( /on )
  led-toggle-delay( /small )

Which style you prefer is a matter of taste. The last one is the most readable if you already know a classical programming language like C, TypeScript or Python. It is also the most verbose. The first one is the most concise and the most cryptic until you are familiar with the Iɴᴏx syntax. The second one is somewhere between the two and will please Forth programmers.

Functions

Functions are special verbs with named parameters to access arguments in an easier way than is possible from the data stack. They respect the function protocol.

to tell-to/  with /msg /dest  function: {
  out( "Tell " & $msg & " to " & $dest )
}

tell-to/( "Hello", "Alice" )

By convention the names of functions terminates with a / that means applied on. When the function is invoked, it's actual arguments are moved from the data stack onto another stack named the control stack. In the process these values get renamed so that the names of the actual arguments get's replaced by the names of the formal parameters.

The {} enclosed block that defines the function can then access the arguments, using the name of the corresponding formal parameter with a $ prefix or a > suffix. This is the syntax for local variables too.

& is an operator that joins two pieces of text found on the top of the stack.

to tell-to/  with /m /d /{ out( "Tell " & $m & " to " && $d }

This is an abbreviated syntax that is defined in the standard library. xx{ ... } is like xx( ... ) but the former invokes the xx{ verb with the block as sole argument whereas the later invokes the verb xx when ) is reached.

/{ is like {, it marks the begining of a block, a sequence of verbs and literals. There is however an important difference, only `/{' creates a new scope and fiils it with the named parameters. It is convenient to use local variables that will be automaticaly discarded when the block execution ends, ie when the variables become "out of scope".

In the case of /{ the scope is filled with local variables that are the formal parameters of the function. The scope and all the local variables in it is discarded when the function returns.

There is also a .{ that is like /{ but it creates a new scope with a single local variable named it that is the target of the operations in the block. When the target is not an object, but rather a value, then the synonym >{ makes more sense (it{ is another option, same meaning).

Some high level control structures automaticaly create a scope. This is the case for all types of loops and anything related to exceptions.

Assertions

assert{ check-something }

Assertions are conditions to expect when things go normally, ie with no bugs. Assertions before something are called pre conditions whereas assertions after something are called post conditions. This is usefull to detect bugs early.

Note: the verb assert{ does not evaluate it's block argument when running in fast mode. Hence there is little overhead involved to keep lots of assertions even when the code is ready for production. Who knows, they may prove valuable later on when some maintenance error breaks something. It's like tests, but inline instead of in some independant test suite.

The default definition of assert{ uses the inox-FATAL primitive. However it uses it via an indirection by the FATAL verb so that the behaviour can be redefined freely by redefining that verb.

Verb redefinition

to FATAL  /FATAL-hook run-by-tag.  ~~ late binding

This kind of late binding makes it easy to hook some new code to old verb definitions. Without those indirections there would be no solution for old verbs to use redefined verbs.

That's because redefined verb definitions impact verbs defined after the redefinition only, ie the existing verbs keep using the older definition.

to FATAL-hook handle-it-my-way.

The default implementation in the standard library uses inox-FATAL. That primitive displays a stack trace and then forces the exit of the Iɴᴏx process. This is brutal but safe when Iɴᴏx processes are managed by some orchestration layer. A layer that will automatically restart the dead process for example.

Blocks

Blocks are sequences of verbs and literals enclosed between balanced { and }.

to tell-sign  if-else( <0, { out( "negative" ) }, { out( "positive" ) } ).

-1 tell-sign  ~~ outputs negative

tell-sign( -1 )  ~~ idem, prefix style`

Two consecutive ~~ (tildes) introduce a comment that goes until the end of the line. Use ~| and |~ for multiple lines comments.

Exceptions

There exists two main styles about exceptions. fail fast is about aborting as soon as something exceptional occurs. It is the simple idea that there is some orchestration layer that will detect the situation and decide to automally restart the failing processus when appropriate.

The other style is about trying to recover, ie assuming that the exception is rare but not that much exceptionnal. This is good if the handling of the exception is safe because it was well anticipated.

to save-data
  try: {
    ~~ attempt to save somehow
  } catch: {
    ~~ attempt to handle something rare
  } finally: {
    ~~ things to do in both cases, success and failure
  }

Both finally and catch blocks are optional. There is some overhead when exceptions are handled because a new scope is involved, it is small but noticable when utter speed matters.

When the catch block runs, it has access to the stacks, both the data stack and the control stack. There are two options. Either the exception is recoverable and in that situation the exception does not need to propagate because the program aborts. Or the exception needs to be propagated further using raise.

Note: finally{ also exists as a standalone verb. The attached block is executed when the current scope is discarded, ie when the current block or function returns. This is usefull to clean up resources.

  open( "data.txt" ) >fd, finally{ fd> close, fd/forget }
  "" >data
  loop{
    fd> read-line >data!  ~~ read data
    if: data> not a-text? then: {break};
    data> process
  }

Keywords

to say:to:  "-" joint-text, out().

say: "Hello" to: "Bob";  ~~ outputs Bob-Hello

Keywords are multi parts verbs with a : (colon) after each part and a final ; (semi colon). This is syntactic sugar to make the source mode readable.

to tell-sign
  if: <0? then: {
    out( "negative" )
  } else: {
    out( "positive )
  }

if:then:else: is a keyword. It is predefined. If it were not, it could easly be defined using the if-else verb.

to if:then:else  ~| cond block block -- |~
~~ run first or second block depending on condition
  if-else

if-else is a verb that expects to find three arguments on the data stack: a boolean value and two blocks. Depending on the boolean, it runs either the first or the second block.

to tell-sign  <0? { "negative" out } { "positive" out } if-else.

This definition of the verb tell-sign uses a style that is unusual, it is a postfix notation. This is compact but sometimes difficult to read. Depending on your preferences you may use either that postfix style, a classical prefix function call style or the multi parts keyword style.

The form changes but the meaning keeps the same.

Variables

Global variables

variable: /global-state is: "initial state".
constant: /error-state  is: "error".

loop: {
  if: global-state =? error-state then: { break };
  ....
  if: xxx then: { "next" global-state! };
  ...
}

There are automatically two verbs that are created for each global variable. First verb is the getter verb, which is simply the name of the variable as specified when the variable was created using a tag. The second verb is the setter verb, the same name with the ! suffix.

Constants are like variables but with no setter verb. Once set, at creation, the value cannot change anymore.

Note that constant: /error-state is: "error". just means to error-state "error"., syntactic sugar again. Which form you use depends on the style you prefer.

Local variables

to say-to  >{ >msg
  out( "Say " & $msg & " to " & it )
}

say-to( "Hello", "Bob" )  ~~ outputs Say Hello to Bob

Local variables are variables stored into another stack, the control stack. Syntax >xyz creates such variables using values from the top of the data stack. It reads "create and initialize local variable x". Use either $xyz or xyz> to later retrieve the value of the local variable. It reads "get local variable x's value".

To set the value of a local variable using the top of the stack, use >xyz!. ! (exclamation point) means "set" in this context and by convention it means "some side effect or surprise involved" is a more general sense. $xyz! is a valid syntax too, it means the exact same thing.

>{ and } specify respectively the begining and the end of the scope within which local variables are created and used. These scopes can nest in such a way that a local variable created by a verb can be accessed from the other verbs invoked while the scope exists, unless that verb created another local variable with the same name.

This type of scoping for variables is named "dynamic" by opposition to the more frequent static style named "lexical" where a local variable stays purely local to the function that created it. Note: changing the value of a local variable outside the verb that created it is usually considered "harmful" and should be avoided.

There is no assignment operator in Iɴᴏx. The ! (exclamation point) is used to set the value of an already existing variable. The = (equal sign) is used to compare values. This is a common convention in many programming languages but not in all. For example in the C language, the = (equal sign) is used to assign a value to a variable and the == (double equal sign) is used to compare values.

Object variables

Object variables are stored inside the stack that belongs to an object. Every object has an identity and a value that is a stack of values. The name of that stack is the class of the object. The names of the values in the object's stack are the names of the attributes of the object.

Note: the OOP (Object Oriented Programming) literature uses many names for that concept: fields, properties, attributes, instance variables, members, etc. They all mean the same.

x:3 y:2 point:2 make-object  ~~ create a point object with two attributes.

Access to an object's variables requires two informations: the name of the variable and the identity of the object. The identity is reference to the object, not the object itself.

The make-object verb creates an object and pushes it's identity onto the data stack. It gets as parameters the class of the object and the number of object variables to initialize with values poped from the data stack.

to make-point  make-object( x:0, y:0, point:2 )

make-point, 2 _point .x!, 5 _point .y!, out( "x is " & _point.x )

With the object class and the object variable, it becomes easy to define method verbs that manipulate the object.

to point.dump  method: { out( "( x:" & it .x & ", y: " & it .y & ")" ) }.

Such method verbs are typically defined using the method: verb. It creates a local variable named it and then it runs the specified block. Some other language use self or this instead of it.

The attach verb binds a value to a runnable value, either a verb or a block typically. The verb partial binds multiple values. This is how closures are created in Iɴᴏx. If a closure needs to access a value that is out of scope, then that value must be encapsulated into a box object to provide an indirect access to it.

to schedule-action ~~| action time |~~
  >time box >action
  schedule( $time, attach( { run( >what unbox ) }, $action ) )
...


Data variables
--------------

Data variables have their value stored in the _data stack_. To retrieve such a value it should first be pushed with a proper name and then later on retrieved using that name with a `_` prefix.

``` sh
x:3 y:5
out( "Point x: " & _x & ", y: " & _y )  ~~ outputs Point x:3, y:5

10 _x!
out( "Point x: " & _x & ", y: " & _y )  ~~ outputs Point x:10, y:5

To push such a data variable onto the data stack, it's initial value can also be taken from the top of the data stack itself using the syntax xyz_. It reads "store the top of the data stack into data variable xyz" or "rename xyz the value at the top of the data stack".

3 x_
out( "Hello" & _x )  ~~ output Hello3

Note that values stays on the data stack until some verb consume them. When extra values remain, you may empty the stack down to some named value using /some-name forget-data or, slightly shorter, some-name/forget-data.

Values

Values are simple things such as 1, "hello", /msg or the identity of some object, or some more complex values, made of multiple simple values.

Every value, either simple or complex, has a type and a name attached to it. These named values are often more convenient to manipulate than the classical anonymous values found in most computer languages.

Falsy values

There is a boolean type of value with only two valid values, true and false.

There are also a few special values, falsy values, such as 0, void or "" (the empty text) that are often usefull when a boolean value is expected. To convert a value into a boolean value, use the ? operator. Note that a class can redefine .? to convert an object into a boolean value. The result is either true or false.

if: ""    then: out( "true" );  ~~ nothing, "" is falsy
if: void  then: out( "true" );  ~~ nothing, void is falsy
if: 0     then: out( "true" );  ~~ nothing, 0 is falsy
if: obj ? then: out( "true" );  ~~ nothing if method .? is defined and returns false

Verbs that expect a boolean value will usually coerce the value of an other type into a boolean value using a simple rule : everything whose value is zero is false, everthing else is true.

Constants are verbs that push a specific value onto the data stack, like true, false and void that push 1, 0 and void respectively.

void is a very special value that often means that there is no valid value available. It could be the result of the failed attempt to find something for example.

To test against void use the something? predicates. It's result is false only when the value on the top of the stack is the special void value. The opposite predicate is void? ('nothing?' is a synonym).

Tags

x:1  ~~ an integer value named x

msg:"hello"  ~~ a text value name msg

It is as if a tag were attached to the value, hence such names for values are called tags.

"rectangle" /label rename, dump out ~~ outputs label:"rectangle"

/xxx is the syntax to designate a tag. "xxx" designates a text. 1.0 or 1f designate floating numbers. -1 is the number negative one.

#xxx and xxx/ are two additional syntaxes for tags. Which styles you use is up to you.

Whereas objects have a life cycle (created, used, forgotten), tags exists forever, like numbers, like any value actually, in abstraction. To turn a text into a tag, use tag( text ). Use tag-to-text( tag ) to do the opposite. Some computer languages use a different terminology like atom or symbol but it means the same thing.

Objects have a value and an identity. The value is made of the class of the object and it's variables stored in a stack that belongs to the object. Contrary to values, object are referenced indirectly using references. A reference is a type of value that holds the identity of some object.

Data types

The usual suspects are there : boolean, integer, float, text and objects. There are also some more exotic types that make Iɴᴏx a bit more interesting.

This includes voids, tags, boxes and ranges.

Voids

In addition to the ubiquitous void value, there are also void integers. They are mostly usefull for reasons that are internal to the Iɴᴏx interpreter. However, in some cases, they can be usefull to the programmer too.

For example to signal the reason of the void. It could be an error code for example.

When the interpreter is executing some compiled code, if the instruction pointer (IP) points to a void value, then it means that the instruction is neither a verb nor a literal, it is a primitive. Primitive are special instructions that are not verbs, they are not stored in the verb table, they are not compiled, they are not interpreted. The interpreter knows that it is a primitive because the IP points to a void value. The void integer value is the primitive itself. The primitive is executed by the interpreter and the IP is incremented to point to the next instruction.

Contrary to verbs, the definition of a primitive is not made of verbs or literal, it is defined in the native language of the interpreter. When the interpreter is compiled using Typescript, the primitive is a Javascript function. When the interpreter in compiled using C++, the primitive is a C or C++ function.

The propotype of such functions is simple: they take no parameters and return no value. That's because they have access to the internal register of the Iɴᴏx virtual machine. There are 4 of them : IP, TOS, CSP and actor.

The IP is the instruction pointer, it points to the next instruction to be executed. The TOS is the top of stack, it points to the top of the data stack. The CSP is the control stack pointer, it points to the top of the control stack (named "return stack" or "call stack" usually). The actor is the current actor, it points to the current actor object, something similar to a thread, a task, a process, etc.

Boxes

Boxes are a special type of values that can hold any type of value. They are usefull when you want to pass a value by reference. For example, if you want to pass a value to a function and have the function modify the value, you must pass a box instead of the value itself. The function will then modify the value inside the box.

This is similar to pointers in C. The difference is that boxes are much more safe than pointers. When a box is copied, the copy points to the same underlying boxed value. They are dynamically allocated and only disappear when they are no longer referenced.

As a result, no more memory leaks, no more dangling pointers, no more segmentation faults, no more buffer overflows, no more memory corruption, etc. This comes at a price though, boxes are slower than values. But it is a small price to pay for the safety they provide.

Ranges

Ranges are like boxes, they reference some value. But instead of referencing a single value, they reference a range of values. They are usefull when you want to iterate over such a range of values. For example, if you want to iterate over the values from 1 to 10, you can use a range.

( 1 ... 10 ) iterate{ out( it ) } ~~ outputs 1 2 3 4 5 6 7 8 9 10

Ranges are also very efficient to reference a portion of a text, ie a slice. For example, if you want to reference the first 10 characters of a text, you can use a range. That works too with other types of values, like stacks, arrays, maps, etc.

The range can either be between two indices (including or execluding the latter one) or between a start index and a length. When an index is negative, it is relative to the end instead of the beginning. For example, if the range starts at -10 and run for 5 items, it will reference 5 items starting from 10 items before the end.

The syntax differs depending on the type of the range, either by indices only or with an index and a length. The .. binary operator creates a range using two indices. The ... binary operator creates a range too but including the upper limit. The :: binary operator creates a range using an index and a length.

There exist convenient shortcuts for specifying the end of someting or the beginning of something. The ^.. operator can be used with a single operand to specify the beginning of something, up to some limit, not included. The ::$ operator can be used with a single operand to specify the ending of something.

All the combinations are possible, including the ^...$ operator that creates a range that references the whole thing and also [^], [$] and [] to reference a single element, either at the beginning, at the end or at some specified position.

A range is either bound or free. A bound range is a range that references a specific thing. A free range is a range that does not reference something, yet.

Using a bound range, it becomes easy to either extract or replace a portion of a thing. To bound a range to something, you can use the @ operator. To replace a portion of a thing, you can use the @! operator.

  "Hello world", 0 5 .. @, out ~~ print Hello, postfix style
  out( "Hello world", @( 0 .. 5 ) ) ~~ print Hello too, prefix style
  "Hello world", ( 0 .. 5 ), "Iɴᴏx", @!, out ~~ print Iɴᴏx world
  out( "Hello world", @!( 0 ... 4, "Iɴᴏx" ) )" ~~ print Iɴᴏx world too

Ranges work over ranges too, this creates sub ranges. When such ranges are made of index ranges, it describe a path to a sub value. When such ranges are made of slice ranges, it describes a sub slice of a thing.

So a ranges can be many things that would be rather complex to implement without them. They get even more powerfull when tags are used to express limits and offsets inside complex objects, instead of integer positions (ToDo).

This range concept will be enhanced in the future to support more complex things like pattern matching, unification and maybe even backtracking, as in Prolog.

Under the hood there are 3 types of ranges: to, but and for. to ranges have an indexed upper limit. but have an indexed upper limit too but it is not included in the range. for range have a length instead of an upper limit. The .. operators create a but range, the ... operators creates a to range and the :: operators create a for range.

The class hierarchy

Every thing is something, hence thing is the base class of everything else, both values and objects. Values have a name and a type whereas objects have an identity and a value also made of more or less simple multiple values (object variables), potentially including reference values when objects references each others. By convention the name of the value of a object is named it's class.

class( xxx> )  ~~ get the class of the thing in the xxx local variable.
  • thing
    • value
      • void
      • "boolean"
      • number
        • integer
        • float
      • tag
      • verb
      • text
      • reference
      • range
    • object
      • native
      • proxy
      • stack
      • queue
      • array
      • map

Sometimes some things have a class that is the combination of multiple base classes. For example a text and an array are both iterable things even thougth one is a value whereas the other one is an object made of multiple values. To avoid extra complexity Iɴᴏx provide a single inheritance default solution.

As a consequence class( something ) produces a single tag, the name of the class of the thing considered. Verb ìmplement?( thing, /method ) tells about the existence of said method for the class of said thing. By default things implement their own methods and inherit the method of their super class, ie the class they extend.

That basic solution is extensible by defining a my_class.method that is free to lookup for the desired method the way it wants. See also .missing-method about virtual methods whose definition is determined on the fly at run time, a sometimes slow but otherwise radically flexible solution.

There are some optimizations involved to speed up the method lookup using caches to avoid multiple lookups for the same combination of class name and method name. This is optional and when it is turned on for a class it is the responsability of that class to properly invalidate the cache when appropriate.

One important distinction is when comparing two things. If both things are compared by value then the = operator should be invoked. If things are objects, it is generaly the identity that matters and the same? operator should be invoked. This is by reference instead of by value. When communicating, two entities must agree on weither they communicate informations by value or by reference.

Stacks

stacks are lists of values with an easy access to the values at the top of the stack or nearby.

The data stack

The data stack is of special importance because it is throught it that the information flows from verbs to verbs.

Most of the time verbs operate on the values at the top of stack, including the one at the very top sometimes called "TOS", short for Top Of the Stack. The next value, below the top, could be "NOS" for Next On Stack.

Operators are verbs that typically use TOS (unary operators) and sometimes TOS and NOS (binary operators) to produce a result.

3 2 + out  ~~ outputs 5

out( 3 + 2 )  ~~ Idem, with a mixed prefix and infix notation instead of postfix

It is very common and advised to break long verbs into smaller verbs with good names. This makes the source code easy to understand. Verbs must be defined before they are used. As a result it is common to first define verbs for some special vocabulary and then use these simple verbs to solve a bigger problem.

Handling the data stack

It takes some practice to get used to it and some people simply won't try: handling the data stack is like juggling with the values on the stack, it's a mental martial art.

to say:to:
  ", To: " swap join-text
  swap
  "Say:" swap join-text
  join-text
  out

say: "Hello" to: "Bob";  ~~ outputs Say: Hello, To: Bob

swap is a predefined verb that swaps the value at the top of the data stack with the next value on that stack.

to say:to:   ~| msg dest -- |~
  ", To: "   ~~ msg dest ", To:"
  swap &     ~~ msg ", To: {dest}"
  swap       ~~ ", To: {dest}" msg
  "Say: "    ~~ ", To: {dest}" msg "Say: "
  swap &     ~~ ", To: {dest}" "Say: {msg}"
  &          ~~ "Say: {msg}, To: {dest}"
  out        ~~

say: "Hello" to: "Bob";  ~~ outputs Say: Hello, To: Bob

over is like dup but it duplicate NOS instead of TOS, ie it duplicates the next value on the stack instead of the top of stack value. Juggling with values on the stack gets tricky easely. That's why it is sometimes usefull to describe the protocol of a verb with special comment about their effect on the stack.

Here are such comments for the most common verbs to handle the top values of the data stack:

dup    ~| a -- a a |~
drop   ~| x -- |~
swap   ~| a b -- b a |~
over   ~| a b -- a b a |~
rotate ~| a b c -- b c a |~

Fortunately you can avoid doing that if you want, using local variables, functions and methods.

The control stack

The control stack is for control structures like loops, conditional branches or nested verb invocations. It is also used to store local variables like for instance an ii variable that is used as an indice when adressing the elements of a list of values or similar data structures.

When the definition of a verb requires the execution of another verb, or the application of a function, as most verbs do, the position inside the current verb is stored onto the control stack. It is later retrieved there when the execution of the nested verb is finished and when control needs to get back to the previous verb.

Note: this usage of the control stack is so frequent that most languages call it the return stack instead.

Stack protocol

Verbs agree on protocols to manipulate values on the data stack. The most basic, fast and fairly accrobatic protocol is the stack protocol. With that protocol it is the order of the arguments on the stack that matters.

to fib
  dup
  if: > 2 then: {
    dup, fib( - 1 ) + fib( swap - 2 )
  }

out( fib( 10 ) )  ~~ outputs the 10th number of the fibonacci suite

In this example, dup duplicates the TOS (Top Of the Stack) and swap swaps it with the NOS (Next On Stack). Dealing this way with the stack can become rather complex quickly and using functions produces a solution that is more readable (but sligthly slower).

Function protocol

This protocol is very common in most programming languages. It states that functions get parameters thanks to arguments that the caller function provides to the callee function. That function is then expected to consume these arguments in order to produce one or more results.

to fib/  with nth/
~~ Compute the nth number of the Fibonacci suite
  function: {
    if: nth> > 2 then: {
      fib/( nth> - 1 ) + fib/( nth> - 2 )
    } else: {
      nth>
    }
  }

This definition of the fib verb is recursive because it references itself. If the current verb were the last verb of it's definition, using again instead would be a better solution because it avoids a potential overflow of the control stack. This classic optimisation is called "tail call elimination" and it is done by the Iɴᴏx compiler (ToDo).

Unfortunately it does not apply to the fidonacci function because the actual last verb of the definition is the if verb. This is clearly visible in the postfix notation only.

to fib
  dup
  2 > {
    dup,  1, -, fib
    swap, 2, -, fib
    +
  } if

Note: , (comma + space) is purely cosmetic, it is there just to make the source code clear, somehow.

Named parameters protocol

Another style is possible using named values in the data stack, ie data variables instead of local variables.

to fib  ~| nth:n ... -- nth:n ... fib:n |~
  ( if: _nth > 2 then: {
    fib( _nth - 1 :nth ) + fib( _nth - 2 :nth ) )
  } )fib

out( fib( nth:10 ) )  ~~ Now the parameter needs to be named

In this example :nth renames the TOS (Top Of the Stack). It is necessary to do so because verb fib uses _nth to get it's parameter. That's a different verb protocol than the ones of the previous definitions of fib, it's the named parameters protocol.

Verbs often name their result. That way, it becomes easy to get theses results later on from the data stack. Syntax ( ... )something makes it easy to rename a single result.

When a verb returns multiple results, it should name each of them to respect the named protocol, this is just a convention however.

When the results are no longer needed, they can be forgetten, ie removed from the data stack. Syntax something/forget does that, it removes all the values from the top of the data stack up to the one named something included.

Note: The function protocol uses a special version of forget, named forget-control, that operates on the control stack instead of the data stack. That's because function parameters and local variables are stored in the control stack, not the data stack.

Other stacks

A stack is a fairly usefull data structure and it is easy to create one using an array of values whose size grows and shrinks when values are pushed onto the stack and popped from it.

make-stack( 100 ) ~~ at most 100 values
"hello " _stack.push
"world!" _stack.push
out( _stack.pop & _stack.pop )

~~ it outputs world!hello , out( _stack.pop _stack.pop prefix ) would produces hello world! instead.

Note: the result of make-stack is a reference value named stack, this is the reason why _stack easely finds it inside the data stack.

Method protocol

Methods are verbs that operate on something, often an object. They do very little but what they do is essential. They figure out the name of a verb based on their own name and the class of the target value that they find on the stack.

Because the name of final verb is determined at run time, when the verb is run, not compile time, when the verb is defined, this is called late binding.

Iɴᴏx is somehow special about methods because methods work both with objects and with values. So much that it is fairly easy to implement the value semantic using objects, the user don't see the difference. A few methods needs to be implemented to handle cloning and value equality.

Cloning is about duplicating a value whereas value equality is about determining if two values are actually the same value even if their object representation is different.

The opposite is true also, it is easy to implement the objet semantic of any value, it just needs to be boxed, ie put in some object.

to text.box              :box text-box:1 make-object.
to text-box.super-class  /value.
to text-box.push         dup >R ( .box swap & .box! ) R>.
to text-box.value        .box.
to text-box.out          .box out.

"Hello" .box       ~~ make a text-box object
.push( " world" )  ~~ add text to it
.push( " !" )
out( .value() )    ~~ output it's value

This is a fairly inefficient version of a text-box buffer class but it works. Using it one can add text to the buffer until the result is used to output it using .out() or to get it's text value using .value().

A more efficient version may accumulate pieces of text inside a list and then join the members of the list when the text value is eventually needed.

There will exist higher level verbs to help define such boxed values and much more, in the standard librairy. However, whatever the implementation, at the end it will always be about applying method verbs to objects.

>R and R> move the TOS forth and back to the control stack, this is a simple solution to save something and restore it later. In the example, it is the reference to the text-box object that is saved and restored. By convention method verbs that don't provide a result should simply provide back the reference they were provided. That reference is the target of the method. This is part of the method protocol.

Note: the target of a method does not need to be a reference to an object, it can also be any other type of value. As a result methods are ok for both types of values, builtin types and user defined object classes.

Stack shorthands

Because accessing variables from stacks is so frequent, there are shorthands to do it, both to get values and to set values.

Here are the short forms and the corresponding longer forms:

"Hi" >c   ~~ "Hi" /c make-control ~~ initialize a new c local variable
c>        ~~ /c control           ~~ get the value of the c local variable
$c                                ~~ idem
"Hi" >c!  ~~ "Hi" /c set-control  ~~ change the value of the local variable
"Hi" $c!                          ~~ idem
( "Hi" -> /c )                    ~~ idem
( "Hi" >-> /c )                   ~~ idem

_d        ~~ /d data              ~~ get the value of the d data variable
"Hi" _d!  ~~ "Hi" /d set-data     ~~ change the value of the d data variable
( "Hi" _-> /d )                   ~~ idem
d:"Hi"                            ~~ idem

To assign the value of a local variable to another one, one of the shorthands is a> >b!. The ! is there to remind that this is a side effect. Whenever possible, it is better to create a new local variable instead of changing an existing one, this is easy: a> >b. The absence of ! signals that the assignment does not mutate anything. Avoiding mutations is often a good idea to avoid bugs.

Modules

There is no concept of module per see at this point but this will come later. For now the solution is to encapsulate verbs into some pseudo class and use some-module.some-verb() to avoid collisions with verbs named identically in other modules. Alternatively one may use some-module_some-verb or any other separator like ``some-module::some-verb`. Until better.

Note that using class.some-verb()and class.some-verb do not produce the same result because the second form only returns the verb, it does not exeute it. To execute the verb, use syntax class.some-verb definition run or shorter class.some-verb run-verb.

File formats

Source code is the prefered format, utf-8 encoded unless otherwise specified. Extended characters are valid in text literals only. All identifiers should be visible ASCII, ie in the 33 to 126 range.

The default dialect is the simple forth-dialect unless some initial comment tells otherwise. The two very first characters must be treated differently, in the unix tradition, ie shebang style with #!.

File names should use the .nox extension.

Compiled code file should starts with a small header that is compatible with the shebang unix tradition if the file is an executable file. The version number comes next, in text format, on a new line. Then comes the type of the file and the name of the encoding, on new lines too.

Compiled code files should use the .xnx extensions.

Special header forth-dialect, in place of the version number, means that the forth dialect needs be used to run the file so that it is the file itself that will determine what to do with the rest of itself. Rational: on small embedded systems only the forth-dialect may be available, for space reasons.

Versioning

“The only constant in life is change.”- Heraclitus

There will be multiple versions of the Iɴᴏx programming language and it is better to anticipate about that in order to ensure backward compatibily of any old source code when a new major version of the language is introduced.

This is why all source code should start with an assertion about the minimal version of the language it expects.

inox-1

The inox-2 verb will be predefined when the version 2 of the language is released. Using it with a version one Iɴᴏx machine will raise a fatal error.

To know about the current version of some Iɴᴏx virtual machine, use inox-version. It provides a version number in the major.minor.patch format. Use inox-version-time to get the time when the version was released. It can be compared with the current time inox-time-now or any other date in that format, the date of the last change to a source code file for example.

Using inox-assert-version and/or inox-assert-version-time the source code can assert the older version it was tested against or the date of that version. It is expected that all new versions of the Iɴᴏx machine will do their best to adapt to the version of the source code.

None of this is available as of today, this is inox-0.

Immediate verbs

To improve speed, use syntax [ /class.some-verb definition ] literal call as this will compute the address of the verb definition at compile time instead of run time, ie early binding instead of late binding.

Or, shorter, use quote class.some-verb verb-literal.

' class.some-verb verb-literal is even shorter but the quote character is less readable, at least until you get used to it.

When defining a verb, typically after to some-thing the Iɴᴏx interpretor switches to a special compile mode. In that mode the following verbs are added to the definition of verb being compiled instead of beeing immediately executed.

However, some verbs, immediate verbs, also called _defining verbs, are still immediately executed. These special verbs are typically usefull to compute stuff immediately instead of later on when the verb is invoked.

Together with verb-literal and other similar defining verbs, this concept of compile mode versus run time makes it easy to define verbs using the result of some computation instead of just adding plain verb names to the definition.

literal is one such verb, it adds a literal to the definition. A literal is something like a number, a piece of text, a tag, etc. Ie, it's a simple value. As usual, the literal to add is found on the top of the stack.

To define an immediate verb, simply invoke the immediate verb right after the normal verb definition.

variable: /profiling is: true;
to profile increment-call#.
to with-profiling
  if: profiling then: {
    inox-verb tag-literal
    /profile  verb-literal
  }
immediate

~~ simple profiling, counts numbers of calls, usage:
true profiling!
to do-it with-profiling do-something.

Because with-profiling is an immediate verb, it is called when the verb do-it is defined, at compile time, not when it is called, not at run time.

It then gets the name of the current verb, which is /do-it,using ìnox-verb, and then add some code to the definition of that current verb.

Said code will call profile with the name of the verb as parameter. That profile verb would typically increment some counter associated with the verb, this is not shown in the example.

If global variable profiling is false, nothing happens, ie no profiling code is added.

Reminder: variable: xxx is: yyy; creates a global variable with some initial value. It is then possible to get and set the value of that variable. Using xxx to get it and xxx! to change it.

to global-state   "initial-state".
to global-state!  [ /initial-state definition literal ] @!.

This example is a tricky way to do what variable:is: does safely. It gets the address of the definition of the global-state verb and changes it so that it provides a new value. ``@!```is a super powerfull verb that can change any value anywhere you know of, ie with the proper address. It's not a safe verb, use it at your own risk.

Note: changing the definition of a verb this ways is a kind of self modifying code. This can be convenient sometimes, rarely, for optimizations typically. Remember the advice: avoid premature optimization. First make it work, then you can make it better.

Dialects

Iɴᴏx was designed to be extensible. It supports multiple dialects in addition to it's own dialect. The Forth language was a primary source of inspiration. This is why a Forth dialect is proposed.

forth-dialect ( speaks Forth )  : HELLO ." Hello" ; HELLO

inox-dialect ~| speaks Iɴᴏx |~  to Hello  out( "Hello" ). Hello

Forth dialect

Forth is an old language designed by Charles H. "Chuck" Moore in the early seventies. It is a fascinating language, very simple, yet very powerfull.

Much like the Lisp language, also a simple and powerfull language, Forth gave birth to a multide of dialects. Where Lisp dialects are usually based on lists, Forth dialects are based on concatenated words.

In that sense, Iɴᴏx is a Forth dialect, with verbs instead of words. The Iɴᴏx parser is way more complex than the Forth one and consequently the Forth syntax is very simple, basic.

A valid Forth definition for a verb is simply a list of verbs separated by some space. Whatever is not a verb is expected to be a litteral value, an integer typically.

The Iɴᴏx Forth dialect is slighly more sophisticated because it manipulates named value instead of memory bits. Besides that significant difference the Iɴᴏx Forth dialect is very close to the latest "standard" dialect, currently Forth 2012.

Iɴᴏx dialect

This is the dialect used by most Iɴᴏx source files. It should be available in all Iɴᴏx Machines but the smaller ones where only the Forth dialect is present, for space reasons.

Note that once compiled, verbs defined with the Iɴᴏx dialect are available in the Forth dialect and vice versa.

As a result a compiled Iɴᴏx file can run on an Iɴᴏx machine with no Iɴᴏx dialect, a small machine, maybe a micro controler with limited ressources.

The Iɴᴏx compiler can also generate a C source file that includes both the Forth dialect and the desired subset of primitives and user defined verbs. Once compiled that C source file can then be run anywhere thanks to the extreme portability of the C language.

As a result, the normal development style is to add primitives and verb definitions to some Iɴᴏx machine and then ask it to generate a kind of clone of itself that can run on some predefined target processor. The generating machine holds the genotype whereas the generated machine is the phenotype.

The grand plan is for some AI to generate such genotypes based on very highlevel specifications using combinational technics to improve on existing genetic programming solutions. Using a concatenative language is an obvious advantage in such a situation.

None of this is ready right now, it's just on the roadmap.

Aliases

Whenever a word is detected, if some alias for it exists then the word is replaced by it. This is a very basic form of macro processing of the source code, with substitution. A full macro system, like the one of m4, a super set of C's one, is somewhere in the road map; that or something else.

Until then only alias is defined.

alias( "Define", "to" )  ~~ assuming I prefer to use Define instead of "to".

Each dialect has it's own dictionary of aliases that make it easy to change the source code appearence when needed.

inox-dialect, alias( "defn", "to" )
defn Hello-world  ( "Hello" out )
( hello )

Contrary to aliases, verbs defined in one dialect are available is all the other ones.

To create a new dialect, simply swith to it. Then it is a matter of verbs, methods of object, syntaxic aliases and some advanced technics yet to be fully described.

/MyDialEct dialect, alias( "Fun", "to" )
Fun HeLlO  "WorLD" out
HeLlo

Note: adding aliases to an existing dialect is not safe, better use your own style, your own dialect. If it is nice enought other may copy it, copying is the best compliment they can make to you.

The same is true for reusable verbs, it's better to encapsultate them in some module. If they are good names, they may eventually end up in the standard library.

missing-verb

When some dynamic construct attempts to execute an undefined verb, missing-verb is invoked instead. The stack contains either a tag or a text, depending on how the verb was invoked, by text name or by tag name.

It is then possible to dynamiccaly implement the proper behaviour of the undefined verb.

missing-method

A method is a verb of a certain class, the class of the thing it is applied against. Such verbs have a syntax with a dot that separates the name of the class from the rest of the name of the verb.

When the target thing does not not understand a verb, missing-method is invoked instead. The stack contains the target thing plus a tag or a text about the verb, much like with missing-verb.

missing-operator

Operators are special verbs that help to write code in the infix notation. At this time (january 2023) there is no precedence and only left association. But this is expected to evolve with multiple precedences, right associativity and possibly ternary operators.

Memory management

The initial implementation of Iɴᴏx uses reference counters to free the memory associated to an object when that object is no longer referenced. This is simple.

There are cases where a different solution is preferable or even necessary. That's why other solutions can be implemented to either extend or replace the default solution. This is done at the class level (ToDo).

The memory is made of cells stored in a unique global array.

Each cell is a named value. There are verbs to read and write the content of these cells to determine the type, name and value of each one of them: value to read the value. value! to change it, type to get the type, type! to change it, name to read the name and name! to change it. Read verbs require the address of a cell, an integer number. Write verbs require an additional parameter, a value, a type or a name.

Using these verbs is very low level and unsafe. It may even be implementation dependent in some cases. There is a strict mode that blocks these verbs, on a per actor basis. This introduces the notion of trusted actors (ToDo).

Actors

Actors are active objects that communicate the ones with others using messages.

Whereas a passive objects execute locally a verb definition when told to do so and suspend the invoker until done, active objects run verbs in parallel. Sometimes it is inside the same machine, either a virtual Iɴᴏx machine or a physical machine. Sometimes it is inside distant machines, with messages transmitted over a network.

Actors are necessary for distributed computing and usefull for asynchronous programming. They are utilized for both purposes often. Actors receive messages from a queue, their data stack, and send messages to other actors that they know about either because they created them or because they knew about them by querying some registry.

Like objects, actors have an identity, a class and variables that define their state. However each actor runs in a different thread of execution. Using objects you play solo, with actors it's an orchestra!

Orchestration

The grand plan is to built an AI driven orchestration solution on top of Iɴᴏx defined actors. Such a control plane would automaticcaly restart failing actors, allocate ressources wisely, control hot reloads and migrations, etc.

This is not at all available yet. Please don't use Iɴᴏx in production.

Conclusion

None yet. That's all folk!

BTW: there are many bugs in the sample code, can you spot them?

Iɴᴏx primitives

This is a list of the primitives that are currently implemented in the Iɴᴏx compiler. This list is automatically generated from the source code.

Primitive Code
a-list? true if TOS is a list
nil? true if TOS is an empty list
list make a new list, empty
list.cons make a new list, with TOS as head and NOS as tail
list.car get the head of a list
list.length number of elements in a list
list.append append two lists
list.reverse reverse a list
list.= true if two lists have the same elements in the same order
little-endian? true if the machine is little endian
a-primitive? true if TOS tag is also the name of a primitive
return jump to return address
actor push a reference to the current actor
l9 push a reference to the l9 task of the current actor
set-current-l9-task set the l9 task of the current actor
switch-actor non preemptive thread switch
make-actor create a new actor with an initial IP
breakpoint to break into the debugger
memory-dump output a dump of the whole memory
cast change the type of a value, unsafe
rename change the name of the NOS value
goto jump to some absolue IP position, a branch
a-void? true if TOS was a void type of cell
a-boolean? true if TOS was a boolean
an-integer? true if TOS was an integer
is-a-float? check if a value is a float
a-tag? true if TOS is a tag
a-verb? true if TOS was a verb
a-text? true if TOS was a text
a-reference? true if TOS was a reference to an object
a-proxy? true if TOS was a reference to proxied object
a-flow? true if TOS was a flow
a-list? true if TOS was a list
a-box? true if TOS was a box
push push the void on the data stack
drop remove the top of the data stack
drops remove cells from the data stack
dup duplicate the top of the data stack
2dup duplicate the top two cells of the data stack
?dup duplicates the top of the data stack if TOS is non zero
dups duplicate cells from the data stack
overs push cells from the data stack
2over push the third and fourth cells from TOS
nip removes the second cell from the top of the stack
tuck pushes the second cell from the top of the stack
swap swaps the top two cells of the data stack
swaps swaps the top cells of the data stack
2swap swaps the top four cells of the data stack
over push the second cell from the top of the stack
rotate rotate the top three cells of the data stack
roll rotate cells from the top of the stack
pick pushes the nth cell from the top of the stack
data-depth number of elements on the data stack
clear-data clear the data stack, make it empty
data-dump dump the data stack, ie print it
control-depth number of elements on the control stack
clear-control clear the control stack, make it empty
FATAL display error message and stacks, then clear stacks & exit eval loop
control-dump dump the control stack, ie print it
text.quote turn a text into a valid text literal
text.as-integer convert a text literal to an integer
text.hex-to-integer convert a text literal to an integer
text.octal-to-integer convert a text literal to an integer
text.binary-to-integer converts a text literal to an integer
intege.as-hexadecimal converts an integer to an hexadecimal text
integer.as-octal convert an integer to an octal text
integer.as-binary converts an integer into a binary text
text.unquote turns a JSON text into a text
text.pad pads a text with spaces
text.trim trims a text
debugger invoke host debugger, if any
debug activate lots of traces
normal-debug deactivate lots of traces, keep type checking
log enable/disable traces and checks
fast! Switch to "fast mode", return previous state
fast? Return current state for "fast mode"
noop No operation - does nothing
assert-checker internal
assert assert a condition, based on the result of a block
type-of Get type of the TOS value, as a tag
name-of Get the name of the TOS value, a tag
value-of Get the raw integer value of the TOS value
info-of Get the packed type and name of the TOS value
pack-info Pack type and name into an integer
unpack-type Unpack type from an integer, see pack-info
unpack-name Unpack name from an integer, see pack-info
class-of Get the most specific type name (as a tag)
if run a block if condition is met
if-not run a block if condition is not met
if-else run one of two blocks depending on condition
on-return run a block when the current block returns
while while condition block produces true, run body block
break exit loop
sentinel install a sentinel inside the control stack
loop-until loop until condition is met
loop-while loop while condition is met
+ addition operator primitive
integer.+ add two integers
= value equality binary operator
equal? like = but it is not an operator, value equality
<> value inequality, the opposite of = value equality
not= value inequality, the opposite of = value equality
inequal? like <> and not= but it is not an operator
same? true if two objects or two values are the same one
identical? like same? but it is not an operator
different? true unless two objects or two values are the same one
? operator
something? operator, true unless void? is true
void? operator - true when TOS is of type void and value is 0.
true? operator
false? operator
not unary boolean operator
or binary boolean operator
and binary boolean operator
to-float convert something into a float
to-float convert something into a float
float.to-integer convert a float to an integer
float.as-text convert a float to a text
float.add add two floats
float.subtract subtract two floats
float.multiply multiply two floats
float.divide divide two floats
float.remainder remainder of two floats
float.power power of two floats
float.sqrt square root of a float
float.sin sine of a float
float.cos cosine of a float
float.tan tangent of a float
float.asin arc sine of a float
float.acos arc cosine of a float
float.atan arc tangent of a float
float.log natural logarithm of a float
float.exp exponential of a float
float.floor floor of a float
float.ceiling ceiling of a float
float.round round a float
float.truncate truncate a float
text.join text concatenation operator
& text concatenation binary operator, see text.join
text.cut extract a cut of a text, remove a suffix
text.length length of a text
text.some? test if a text is not empty
text.none? test if a text is empty
text.but remove a prefix from a text, keep the rest
text.mid extract a part of the text
text.at extract one character at position or "" if out of range
text.low convert a text to lower case
text.up convert a text to upper case
text.= compare two texts
text.<> compare two texts
text.not= compare two texts
text.find find a piece in a text, return first position or -1
text.find-last find a piece in a text, return last position or -1
text.start? operator, test if a text starts another text
text.start-with? test if a text starts with a piece
text.end? operator, test if a text ends another text
text.end-with? test if a text ends with a piece
text.line extract a line from a text at some position
text.line-no extract a line from a text, given a line number
as-text textual representation
dump textual representation, debug style
""? unary operator
""? unary operator - true if TOS is the empty text
named? operator - true if NOS's name is TOS tag
definition-to-text decompile a definition
verb.to-text-definition decompile a verb definition
verb.from convert into a verb if verb is defined, or void if not
primitive.from convert into a primitive if primitive is defined, or void
peek get the value of a cell, using a cell's address
poke set the value of a cell, using a cell's address
make-constant using a value and a name, create a constant
define-verb using a definition and a name, create a verb
tag.defined? true if text described tag is defined
verb.defined? true if text described verb is defined
tag.to_verb convert a tag to a verb or void
make-global create a global variable and verbs to get/set it
make-local create a local variable in the control stack
forget-parameters internal, return from function with parameters
run-with-parameters run a block with the "function" protocol
parameters create local variables for the parameters of a verb
get-local copy a control variable to the data stack
inlined-get-local copy a control variable to the data stack, internal
set-local assign a value to a local variable
data lookup for a named value in the data stack and copy it to the top
set-data change the value of an existing data variable
size-of-cell constant that depends on the platform, 8 for now
lookup find a variable in a memory area.
data-index find the position of a data variable in the data stack
upper-local non local access to a local variable
upper-data non local access to a data variable
set-upper-local set a local variable in the nth upper frame
set-upper-data set a data variable in the nth upper frame
forget-data remove stack elements until a previous variable, included
make-fixed-object create a fixed size object
make-object create an object of the given length
make-extensible-object create an empty object with some capacity
extend-object turn a fixed object into an extensible one
object.get access a data member of an object
object.set! change a data member of an object
stack.pop pop a value from a stack object
stack.push push a value onto a stack object
stack.drop drop the top of a stack object
stack.drop-nice drop the tof of a stack object, unless empty
stack.fetch get the nth entry of a stack object
stack.fetch-nice get the nth entry of a stack object, or void
stack.length get the depth of a stack object
stack.capacity get the capacity of a stack object
stack.dup duplicate the top of a stack object
stack.clear clear a stack object
stack.swap swap the top two values of a stack object
stack.swap-nice like swap but ok if stack is too short
stack.enter swith stack to the stack of an object
stack.leave revert to the previous data stack
data-stack-base return the base address of the data stack
data-stack-limit upper limit of the data stack
control-stack-base base address b of the control stack
control-stack-limit upper limit s of the control stack
grow-data-stack double the data stack if 80% full
grow-control-stack double the control stack if 80% full
queue.push add an element to the queue
queue.length number of elements in the queue
queue.pull extract the oldest element from the queue
queue.capacity maximum number of elements in the queue
queue.clear make the queue empty
array.put set the value of the nth element
array.get nth element
array.length number of elements in an array
array.capacity return the capacity of an array
array.remove remove the nth element
array.index return the index of a value in an array or -1
array.tag-index return the index of a variable in an array or -1
map.put put a value in a map
map.get get a value from a map
map.length number of elements in a map
set.put put a value in a set
set.get access a set element using a tag
set.length number of elements in a set
set.extend extend a set with another set
set.union union of two sets
set.intersection intersection of two sets
box boxify the top of the data stack
@ unary operator to access a boxed value, work with bound ranges too
at like @ unary operator but it is not an operator
@! binary operator to set a boxed value, works with bound ranges too
at! like the @! binary operator but it is not an operator
range-to create a range from a low to a high index, included
range-but create a range from a low to a high index, excluded
range-for create a range from a low index and a length
range.over bind a range to some composite value
forget-control clear the control stack downto to specified local
return-without-locals like return but with some cleanup
with-locals prepare the control stack to handle local variables
return-without-it internal, run-with-it uses it
with-it prepare the control stack to handle the 'it' local variable
it access to the it local variable
it! change the value of the it local variable
run-method-by-name using a text to identify the method
run-method using a tag to identify the method
class-method-tag get the tag of a method for a class
run-with-it like run but with an "it" local variable
words_per_cell plaftorm dependent, current 1
CSP Constrol Stack Pointer, address of the top of the control stack
set-CSP move the top of the control stack
TOS address of the top of the data stack
set-TOS move the top of the data stack to some new address
IP access to the instruction pointer where the primitive was called
set-IP jump to some address
ALLOT allocate some memory by moving the HERE pointer forward
HERE the current value of the HERE pointer
ALIGN See Forth 2012, noop in Inox
ALIGNED See Forth 2012, noop in Inox
CHAR+ Forth, increment a character address
STATE Forth 2012, the current state of the interpreter
inox-dialect switch to the Inox dialect
dialect query current dialect text name
forth-dialect switch to the Forth dialect
set-dialect set current dialect
alias add an alias to the current dialect
dialect-alias add an alias to a dialect
import-dialect import a dialect into the current one
literal add a literal to the Inox verb beeing defined,
machine-code add a machine code id to the verb beeing defined,
inox add next token as code for the verb beeing defined,
quote push next instruction instead of executing it.
immediate make the last defined verb immediate
hidden make the last defined verb hidden
operator make the last defined verb an operator
inline make the last defined verb inline
last-token return the last tokenized item
last-token-info return the last tokenized item info, including it's
as-tag make a tag, from a text typically
tag.run run a verb by tag, noop if verb is not defined
text.run run a verb by text name
verb.run run a verb
definition get the definition of a verb, default is noop
block.run run a block object
destructor internal, clear a reference and return from current verb
scope create a new scope
run depending on type, run a definition, a primitive or do nothing
block.return jump to a block and then return from current verb
partial attach values to a runnable value to make a new block
block.partial attach values to a block, making a new block
attach attach a single value to a block, a target object typically
as-block convert a runnable value into a block
block.join join two blocks into a new block
make-it initialize a new "it" local variable
jump-it run a definition with a preset "it" local variable
drop-control drop the top of the control stack
block.run-it run a block with a preset "it" local variable
bind-to make a block object with an "it" preset local variable
run-definition run a verb definition
block push the start address of the block at IP
block push the start address of the block at IP.
start-input start reading from a given source code
input get next character in source code, or ""
input-until get characters until a given delimiter
pushback-token push back a token in source code stream
whitespace? true if TOS is a whitespace character
next-character get next character in source code, or ""
digit? true if the top of stack is a digit character
eol? true if the top of stack is an end of line character
next-token read the next token from the default input stream
set-literate set the tokenizer to literate style
integer-text? true if text is valid integer
parse-integer convert a text to an integer, or /NaN if not possible
compiler-enter Entering a new parse context
compiler-leave Leaving a parse context
compile-definition-begin Entering a new verb definition
compile-definition-end Leaving a verb definition
compiling? Is the interpreter compiling?
debug-info set debug info about the instruction beeing executed
compiler-expecting? Is the compiler expecting the verb to define?
debug-info-set-file set debug info file name about the current source code
debug-info-get-file get debug info file name about the current source code
debug-info-set-line set line number about the current source code
debug-info-get-line get line number about the current source code
debug-info-set-column set column number about the current source code
compile-literal Add a literal to the verb beeing defined
compile-verb add a verb to the beeing defined block or new verb
compile-quote avoid executing the next token, just compile it
compile-block-begin Start the definition of a new block
compile-block-end Close the definition of a new block
eval run source code coming from the default input stream
eval evaluate some source code
trace output text to console.log(), preserve TOS
inox-out output text to the default output stream
trace-stacks dump user friendly stacks trace
ascii-character return one character text from TOS integer
ascii-code return ascii code of first character of TOS as text
now return number of milliseconds since start
instructions number of instructions executed so far
the-void push the void, typed void, named void, valued 0
_ synonym for the-void, push the void, typed void, named void, valued 0
memory-visit get a view of the memory
source evaluate the content of a file