"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.
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
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!
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.
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.
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.
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.
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.
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.
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 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.
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.
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 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.
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
}
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.
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.
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 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 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.
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).
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.
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.
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 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 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.
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 are lists of values with an easy access to the values at the top of the stack or nearby.
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.
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 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.
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).
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.
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.
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.
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.
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.
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
.
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.
“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.
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.
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 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.
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.
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.
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.
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
.
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.
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 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!
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.
None yet. That's all folk!
BTW: there are many bugs in the sample code, can you spot them?
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 |