An experimental fully stack-based reverse-polish-notation functional and object-oriented programming language concieved and implemented by kleinesfilmröllchen.
/əs.ʊu.ˈəf/
in IPA. If you want to make me angry, you can also pronounce it 'sohf'.This is an experimental programming language. If you cause a nuclear war and the inevitable destruction of mankind by using this software, I am not to blame.
SOF is written in Java 16 and requires org.reflections and java.logging to run. It leverages the module system (you may use it in your project as well!) and uses JUnit Jupiter for testing the codebase. (See above for coverage reports as of the latest commit.)
This is a Gradle 7 project using the Java Application plugin with the module system and JUnit Jupiter tests. The usual Gradle tasks for these situations exist and have not been renamed/added to. As a quick reference: Use gradle build
to run the full build including tests. Run gradle test
to run the tests, and gradle coverage
for tests and coverage (reports are in build/jcc-report/test/html
). Use gradle javadoc
to build the javadoc. All building happens into the build/
subfolders.
Use gradle run
to execute the SOF CLI. However, Gradles obnoxious build output will obscure a bunch of the program output. Therefore, you should use gradle install
and then run the binaries from build/install/sof-language
. This works on Windows and Linux.
The command line tool currently supports the following arguments and options (taken from help output):
sof - Interpreter for Stack with Objects and
Functions (SOF) Programming Language.
usage: sof [-hvdpP] [-c COMMAND]
FILENAME [...FILENAMES]
positional arguments:
filename Path to a file to be read and
executed. Can be a list of files that
are executed in order.
options:
--help, -h
Display this help message and exit.
--version, -v
Display version information and exit.
-d Execute in debug mode. Read the manual
for more information.
-p Run the preprocessor and exit.
-P Do not run the preprocessor before
executing the input file(s).
--command, -c COMMAND
Execute COMMAND and exit.
--performance
Run performance tests and show results
When used without execution-starting arguments (-c
or filename), sof is started in interactive mode.
Quit the program with ^C.
A basic Visual Studio language support extension has been written, which can be found on the marketplace and here on github.
The documentation is browsable in-repo in docs or online here and can be built into website-compatible HTML with mdbook:
cd docs; mdbook build
"Hello World!" writeln
SOF, the (ugly but useful & pronouncible) acronym for Stack with Objects and Functions, is a programming language first concieved in June of 2019 by kleinesfilmröllchen. It has exactly one goal, which is also its name:
Create a pure stack-based programming language with object-oriented and functional capabilities.
This means that SOF has the following main features:
{ }
(cmp. PostScript). This is the only nesting/recursive token of the entire language, which powers turing-completeness (branching & conditional execution) as well as FP and OOP.To get a taste of SOF, here are several sample programs written in SOF. Also checkout the examples folder.
# fibbonachi
{
n def
0 i def
1 x def 1 y def 1 z def
{
z .
x . y . + z def # z := x + y
y . x def # x := y
z . y def # y := z
writeln # z old
i . 1 + i def
} { i . n . < } while
} 1 function fib def
"enter a number: " write input convert:int : fib :
# alternative function definition: heavier stack use, runs faster
{
1 x def 0 y def # counters, z is only defined later
{
y . dup x . + z def # z = y + x, y (old) on the stack
z . y def x def # y = z, x = y (old)
z . writeln # write z, counter on the stack
} { 1 - dup 0 > } while # while counter > 0
} 1 function fib def
# factorial
{
dup dup # n*3 on the stack
{ 1 return } swap 2 < if # return 1 if n < 2, n*2 on the stack
1 - fact : * return # return factorial of n-1 times n
} 1 function fact def
"enter a number: " write input convert:int : fact : writeln
# play a guessing game
random:int number def
0 guess def
{
input convert:int : guess def
switch:: {
"You are correct!" writeln
} { number . guess . = }
{
"You are too low!" writeln
} { number . guess . < }
{
"You are too high!" writeln
} switch
} { guess . number . /= } while
# compute the sum of the first one hundred natural numbers, using a loop and a function
100 N def
# loop
1 i def
0 result def
{
result . i . + result def
i . 1 + i def
} { i . N . <= } while
"The result (loop) is: " result . cat writeln
# function
{
n def # arg1
# gauss formula
n . n . 1 + * 2 / return
} 1 function sum def
N . sum :
"The result (function) is: " swap cat writeln
An SOF program consist of a list of "tokens", input elements that can be one of three basic syntatic types:
{
and end with a close curly bracket }
.Tokens are separated by at least one arbitrary whitespace character, e.g. space, newline, tab. This means that SOF is extremely tolerant in terms of formatting, but even non-word tokens like arithmetic operators have to be separated visually.
Every token can be seen as an action that operates on the stack. Some basic tokens include:
+
), subtract (-
), multiply (\*
) and divide (/
) (as well as some others), which will pop two values off the stack, compute a result and push that back onto the stack. It should be noted right now that all operators and functions consider the lower elements on the stack further to the left, as this is the most natural for programming. For example, the SOF code 3 4 /
is equivalent to the mathematical operation 3 / 4
, as the operator /
treats the lower value (3
in this case) as the left operand of the division, the dividend.dup
, which duplicates the topmost value on the stack, pop
, which discards one value of the stack, def
, which defines a variable and write
and writeln
, which output text. The most basic flow-controlling special tokens are the well-known if
, while
, elseif
etc..
calls functions and code blocks, retrieves variable values and creates objects. Quite special, that little guy! As it turns out, this operator is fairly simple in behavior, but at the same time can handle several apparently distinct language features. The Double Call operator :
is just an abbreviation for two calls head-to-head, useful for invoking named functions.SOF is a minimalist language.
SOF is a tool for understanding programming languages. It is very usable, the learning curve is not very steep regardless of whether you have programming experience or not, and it is turing-complete and universal. At the same time, it is so simple that I can write its interpreter or even a compiler without knowing how 'real' compilers work or how to write a 'real' compiler. Even very primitive environments should be able to parse and run SOF.
Just to be pretentious; Antoine de Saint-Exupéry is famously quoted on
Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
I think that SOF is an excellent example of this. I cannot currently think of anything that you can take away from SOF without making it inferior in capability (or, at least, severely less usable, like removing the function PT would do, for example). It is very surprising indeed that such a simple thing as a reverse-polish notation language with three token types and literally less than 30 lines of syntax definition could be as feature-rich and capable as any other modern programming language, even if with odd syntax and inferior speed. (The latter is down to me being simply not capable of writing an efficient interpreter or any sort of compiler. PostScript and Forth prove that postfix languages can be extremely performant.)
SOF kind of runs on SerenityOS. There's a helper script in the repo for the time being, and if you want to try and get it to run, please get in touch.