Intro

StackTalk is a programming language inspired by Io, SmallTalk, Factor, Mirth and Nova. It pulls a sort of flexible Object Orientation from Io, Smalltalk and Ruby; quotations from Factor and Joy and multistacks from Mirth and Nova.

It can look like this:

File do[ 
    "stvm.js" "playground/stvm.js" copy
    "deps/fflate.js" "playground/fflate.js" copy
]

or this:

say: fn[ >t #transcript: $q1 t> $append/div void ]

or this:

quadratic: [ a b c -- solution1 solution2 ] func[ 
  >c >b >a 
  a a + >2a
  b neg >-b  
  b b> *  a> 4 * c> * - >discriminant
  -b discriminant sqrt + 2a div 
  -b> discriminant sqrt - 2a> div 
]

Language basics

Comments

Comments are started with NB[ and are closed with ]

NB[ A comment looks kinda like this. ]

Output in the playground

say can be used in the playground to print a value to the transcript. The transcript is cleared every time you run code in the playground, so only the output from the most recent run is displayed

Quotation & stack teaser

A quotation is started with [ and is closed with ].

If a quotation is prefixed with a word, like this: do[ ] then the word is moved after the quotation at compile time, like so:

do[ ] becomes [ ] do 
get[ thing ] becomes [ thing ] get

Order of operations

Generally speaking, code in StackTalk is executed left to right. This is usually known as reverse polish notation. The exception is quotations, which gather a list of code that can be executed later (or not), and place it on the value stack

Basic math

That means that math and expressions look like this

1 2 +    NB[ stack has 3 ]
3 4 *    NB[ stack now has 12 ]
5 6 *    NB[ stack now has 30 ]
40 2 div NB[ stack now has 20 ]

Strings

Strings can be written a couple different ways.

The first is using " to delimit the start and end of the of the string in question.

"This is a string"

The second way to write a string is to end a symbol with :

which-looks-like-this:

Working with stacks

There are 5 basic operations that can done to named stacks

PUSH takes a value, and then a stack name, and pushes the value to the named stack. To use stack effect notation, it looks like so:

[ value name -- ]

POP takes a stack name, and then puts the top of that stack on the global value stack while removing it from the named stack. The stack notation looks like so:

[ name -- value ]

PEEK takes a stack name, and copies the value at the top of that stack onto the global value stack. The stack notation looks like so:

[ name -- value ]

DROP takes a stack name, and removes the value at the top of that stack. The stack effect notation:

[ name -- ]

RUN takes a stack name and tries to run the value at the top of that stack. For quotations, this represents 'calling' the quotation. For native functions (used to connect JS and StackTalk) the function is called with the vm as an argument. There isn't -one- valid stack effect for RUNs, it depends on what is executed.

If the top of the named stack isn't a quotation or JS function then RUN behaves like PEEK, copying it to the

If the top of the value stack is not a name, then RUN attemtps to execute it directly. If that value isn't runnable, an error is thrown.

There are shortcut sytaxes for some of these operations


Scoping in StackTalk

In most programming languages, especially more modern ones, scoping is usually handled "lexically", which typically implies closures. The way this usually plays out is that if you're returning a function from a function, the variables in the containing function can be referenced by the returned fuction. JavaScript might be the most famous example of this. In StackTalk, scopes are tied to objects, not functions or blocks.

StackTalk works a bit differently. Quotations are lists of symbols, and do not capture state. Objects -do- contain state. The 'host' object a quotation is run inside of determines what stacks it can see. This also means that scope is largely dynamic.

Lobby

In the tradition of Io and Self, there is a Lobby object. It's treated specially in that it's the default scope, and also the bottom of the me stack. It is also the global namespace. Stacks populated in the Lobby are visible to all scopes, but are hidden by stacks defined in a given object.

Changing the current scope

There are a few ways that you can manipulate the scope that quotation executes in. The simplest way is to put objects on the me stack, since the top of the me stack is the first scope where stacks are looked up.

There are also some dedicated words for manipulating the me stack. The first one is get.

get takes an object and a quotation. It pushes the object to the top of the me stack and then runs the quotation. After the quotation is done, the top of the me stack is dropped. get is meant for situations where you want to extract some information from an object without keeping it around (usually because you have it stashed on a stack for easy access).

get: [ obj quotation -- ... ]

The next one is do. do works like get, except, instead of dropping the top of theme stack, it pops it onto the value stack It's good for object setup, among other things

do: [ obj quotation -- ... obj ] 

Finally, there is your. your is used to interact with the scope one layer down the stack from the current layer. This is usually the calling object for the current code This is useful for pulling information out of specific stacks in your caller, such as for treating some stacks like named arguments. Beause it uses existing stack entries, it only takes a quotation as an argument

your: [ quotation -- ... ] 

Creating objects

StackTalk has a few facilities for creating objects as well as performing code re-use.

obj

You can use obj to create a new object. It takes a quotationand executes it in the scope of the new object.

20 >spawn-x 40 >spawn-y
obj[ 
  your[ spawn-x ] >x your[ spawn-y ] >y 
  pos: fn[ x. y. ]
  move: fn[ y: += x: += ]
  put-at: fn[ y: drop x: drop >y >x ]
]

This example creates an a 2d coordinate and gives it some basic methods. You can create objects basically on demand like this.

Modules

Modules are StackTalk's current mechanism for basic code-reuse. you can define one like so:

coord: module[ 
   pos: fn[ x. y. ]
   move: fn[ x: += y: += ]
   put-at: fn[ y: drop x: drop >y >x ]
]

Then, the example above could be rewritten like so:

20 >spawn-x 40 >spawn-y
obj[ 
  use[ coord ]
  your[ spawn-x ] >x your[ spawn-y ] >y 
]

Modules work by defining an object based on the supplied quotation. Then, when you use a module in a scope, the stacks are copied from the module to the scope that is using the module. The stacks are copies, not references, so changes to the stack in one usage of a given module won't automatically propogate to other usages. If you want to share state between users of a module, you can put an object on the relevant stack.


FFI between StackTalk and Javascript

StackTalk uses the ffi command to setup up words thatcommunicate between StackTalk and JS. The stack effect is like so:

[ name effect language definition -- ]

As an example, the definition of + in StackTalk is:

+: [ @ @ -- @ ] js: "(a,b) => a+b" ffi

Taking this item by item, the first item is the name, in this case+.

The second item is a quotation of the stack effect StackTalk uses this quotation to build up wrapper code that pulls items off the stack and passes them to the JS function you provide. Right now, the JS ffi compiler only supports having 0 or 1 returned values.

The third item is a (programming) language tag. I have hopes for StackTalk to be implemented in language other than JavaScript. A given implementation can ignore ffi definitions that are tagged with language it doesn't know how to run/compile, which should allow for polyglot libraries that target multiple platforms So js-based StackTalk only creates FFI bindings that are tagged as being for JS

Finally, the fourth item is a string of the code to be bound up in an FFI call. For JS, this should be a function with the same number of arguments as inputs in the stack effects, and the same number of return values as outputs For the time being, the FFI compiler only supports 0 or 1 return values.