I guess this isn't really the best place, but I don't know any better, so I'll do with suboptimal...
When I had postfix notation in school I got the idea of writing a postfix language (Postfiksa Lingvo in Esperanto, short "Poslin"). I hadn't heard of Forth back then and I only had little experience with Lisp, so I just kind of cobbled together a specification (which is lost now) and tried to implement it in Common Lisp but ultimately failed.
In April I got my hands on "Let over Lambda" and after working through that book I realized I now had the tools for implementing it.
So I did. Although I sincerely hope that Poslin is useful for anything practical, my main motivation is curiosity.
You can find it on bitbucket under
https://bitbucket.org/thomas_bartscher/poslin/
In the subfolder poslin there are files that make up the standard library of poslin. You can look up definitions there. To the right of almost any line in a definition (between unquoted square brackets) there is a comment showing how the stack looks after doing the operation on that line.
Comments looking like this:
; ( ... -- ... )
describe how the operation that is defined next should modify the stack when called.
; ( n -- n n )
For example is the signature for §, the duplication operation.
A few notes on it's design:
The names I use for operations defined until now are mostly exceedingly short and most likely unreadable. They are intended to be very low level and so they should not take up names that might be useful for users of the language.
Poslin was intended to maintain a tree of stacks where any stack could be called. On that tree any stack would have a name and you could move through that tree, pushing data around.
Defining operations would have been a pure matter of putting operations and values on a stack and then calling that stack to modify the now active stack.
This was inspired by Lisps way of doing this. While implementing Poslin using the approach from "Let over Lambda" I abandoned my original idea of a tree of stacks and did something that felt familiar instead.
As it is possible to open a stack somewhere else but in it's parent stack (by using it as a data structure, moving it somewhere else and then opening it there) I need to keep track of a stack of stacks (called the "path") currently opened, where the top of the path is the stack on which one is currently working. I just got rid of the tree and only retained the path. The hierarchy of stacks is now only implicitly available as stacks saved in variables.
Most operations aren't immediately called when entered but need to be called explicitly. The canonical way to do that is via the operator !.
So to add 1 and 2 you would write
1 2 + !
Defining operations is done via opening a new stack, putting on it all the things you want to do when calling the operation, closing the stack and converting it into an operator.
You cannot use ! to put threads on a stack, for this purpose & is used. &, like !, takes effect immediately. & converts a callable object into a thread, so
+ &
puts the thread associated with '+' onto the stack.
To save an operator one uses @o, which takes three arguments off the stack: An operation environment (I will get to that next), a symbol and a callable object. It converts the callable into a thread and then saves it in the operation environment under the given symbol. The symbol @~o is defined to save the operation in the current operation environment.
The operator [ pushes a fresh stack onto the path, ] pops it off the path and the pushes it onto the new current stack. Both are immediate. ]@ is defined to close a stack and immediately define a new operation.
An example:
foo [ 1 2 + & ]@
Defines an operation foo in the current operation environment that adds the numbers 1 and 2.
If you want to define an immediate operation, use ]i
You can also define operations in a more... common style like this:
bar
{ n arg ; Get one value from the calling stack and save it locall under n
n &v ; Get the value of n
baz & ; call baz
}@ ; define bar
Any stack has two lexical environments: A variable environment, used to save variables (surprise!) and an operation environment. The variable environment basically is a hash table and a parent environment. The operation environment consists of two hash tables, one for associating symbols with operations and one for associating symbols with a boolean value indicating whether the operation associated with that symbol should be called immediately when the symbol is encountered.
Environments are first class objects and can be accessed with ?ev and ?eo respectively. Both operators take a stack as argument. To set the environment of a stack, use @ev and @eo. To look up a variable or operator in an environment, use ?v and ?o, to set it @v and @o. To do the same things with the current environments instead of explicitely needing to supply one, just insert a ~ between the @/? and the rest of the operation name. So, for getting the current variable environment, use @~ev.
To delay an immediate operator, use a quote. Unfortunately I don't understand the interplay between quoting, immediateness and !/& not fully myself (I need to retrace every time), but if I remember correctly right now, the interpreter unquotes, "toplevel" quotes in a stack are removed when converting the stack to a thread and !/& remove one level of quoting (so any quoted object is a callable whose thread returns the quoted object itself).
As poslin libraries are read by the Lisp reader, Lisp read macros should be usable in *.poslin files.
To load a poslin file, use
"path/to/file.poslin" >> !
Poslin has it's own home directory. The path to that home directory currently needs to be set in the environment variable $POSLIN_HOME.
Any running poslin keeps track of it's current directory. This is the value of $POSLIN_HOME at startup and should be a directory containing the files found in the "poslin" folder.
The primary operations of Poslin are defined in startup/prims.lisp
Primary operators are defined via DEFPRIM and DEFNPRIM. If you don't know how Forth handles the program counter and the return stack, don't use DEFNPRIM and stick to DEFPRIM, as a primary operator defined with DEFNPRIM might easily throw Poslin into an infinity loop when defined incorrectly.
Any definition in startup/prims.lisp has at least two lines of comment: One describing what the operation does and one with a stack effect.
The documentation in the doc subfolder is... incomplete and most likely unusable.
Poslin already has a package system. It is implemented in poslin itself, using operation environments as packages and the variable environment to save those packages under a name.
Look into the poslin/package.poslin file for some ideas how to use it. &p and !p are the package equivalents to & and !. p* is used to define a new package. ]@p and }@p are the equivalents to ] and }. p>@ can be used to import an operation from a package into the current operation environment. <p< can be used to import an operation from one package into another.
So, of course I would like some feedback on the language itself.
What kind of documentation do you need and how would it be supplied best?
Are there any features in the language that might be useful?
There is also a problem with the POSLIN-REPL function: When I use
(sb-ext:save-lisp-and-die "poslin" :executable t :toplevel #'poslin-repl)
It seems to forget anything about immediateness, so when entering
1 2 + !
I don't get 3 but
1 2 + !
on the stack. As you can imagine this is quite annoying as it would be very nice to have a standalone REPL. It's also quite strange, as it works just fine when run from the sbcl REPL.
The definition of POSLIN-REPL is in repl/repl.lisp
I know, it's not beatiful, but maybe someone can find out what's wrong.