The language is postscript with a stack. You can also use global and local variables along with this stack. Basically, the stack is just there to pass arguments to functions, most of the time it's much better to use variables and not worry about what's going on the stack.
There are two data types: strings and lists of strings. It's not possible to make lists of lists or other complex data structures.
A program consists of words and operators. A word consists of any printable character that is not whitespace or a special character such as an operator. For instance,
foo!m<e 3.455a-343 \dgt4are all valid words. The following are the operators:
$ = % + [ ] { } @ #A comment starts with a question mark and lasts until the end of the line. A quoted string starts with a double quote and ends with another one. You may use the backslash character to escape encode a double quote inside, but no interpretation is done for escape sequences. When you write a quoted string such as:
"escaping the \"quote\" is easy"That string is exactly what's passed down the line, with the initial and final quote characters intact. This is mostly useful for passing arguments to shell commands.
Let's get on with the operators.
Name | String |
pl | + |
pc | % |
ls | [ |
rs | ] |
lc | { |
rc | } |
eq | = |
dl | $ |
at | @ |
ha | # |
qm | ? |
qu | " |
bs | \ |
nl | <Newline> |
sp | <Space> |
tb | <Tab> |
foo bar+ this list+ +The first two lines make two lists with two items in each. The last line combines them all into one list.
[this is a good way of making shell commands]results in a single string. This mechanism is the primary way of specifying shell commands because the insides of [] is still interpreted. You can make function calls, variable references etc. Everything still works until the ] character. For example:
dodo name= [Hello, name$.] print#Will work as expected.
foo.c foo.o #isnewer { [it is newer] print# } if#Here the string within {} is just passed as-is to the if function. It then decides to execute it or not depending on the condition value.
/home/dodo/lib/graphics/circle dir= init.c main.c+ /tmp/x.c+ ../polygon/init.c+ dir@The second line will produce the following list:
/home/dodo/lib/graphics/circle/init.c /home/dodo/lib/graphics/circle/main.c /tmp/x.c /home/dodo/lib/graphics/polygon/init.cWhen given as an argument to the @ operator, the word here has a special meaning. It always refers to the directory of the file it appears in. For instance, if you have the following setup:
current dir: /home/dodo/lib/build/ file name: /home/dodo/lib/graphics/circle/jjcool file contents: init.c here@The @ operator will push the following string:
/home/dodo/lib/graphics/circle/init.c
A function is defined using the define builtin. A builtin function may not be redefined, but user functions may be redefined at the cost of a warning.
The define builtin pops a function body and a function name (in this order) and then makes the association. Here is an example function:
swap { a= b= a$ b$ } define#The contents of the function body are not interpreted at all, until the point of execution.
The language has no concept of function arguments, but you can make such things by assigning arguments on the stack to variables. In the example above, the code starts with variable assignments. They will be more clear if written in this way:
swap { a= b= a$ b$ } define#Here, the value at the top of the stack is assigned to variable a. The one below gets assigned to b. It's this easy to get function arguments.
Returning values is just as easy, just look at the end of swap. We get the value of a, but do nothing with it, so it stays on the stack. The same for b. So, we got two values from the stack and returned two values.
In this context, all code appearing outside function definitions is assumed to be running in a top-level function. This top-level function is different for each included file. For instance, if you have:
file1: dodo name= greet { [hello name$] print# } define# greet# file2 include# file2: foo name= greet#In both calls to greet, it will print "hello ". This is because the definitions for name are not visible inside the function. In addition, the two definitions of name refer to different variables. The first one creates a variable in the top-function-scope of file1, whereas the second one does so in the top-function-scope of file2.
This setup makes it easy to reuse variable names without fear of clashing with included files. If you want to have some global variables to be used across files, just use the export builtin.
There is a special variable called empty. The value of this variable is always an empty list. You can use it as an initalizer for list valued variables.
foo.c foo.o isnewer# { [it is newer] print# } if# { [it isn't newer] print# } else#drop#Here, the isnewer builtin computes a value based on the given file names. A value is considered true if:
Note that code sections given to if and else can also manipulate the stack. For instance:
main.c target$ linux eq# { linux.c } if# { windows.c } else#drop# +is an example where these builtins are used in a similar way to the C ternary operator.
value print#Pops and prints the TOS. If quiet mode is on, it doesn't print anything.
dump#Prints a stack dump. Useful for debugging.
value drop#Pops the value and does nothing with it.
string run#Runs the given string as a shell command. The program exits with failure if this shell command fails.
string orun#Runs the given string as a shell command. Collects the standard output of the command as a string and pushes that. After that, it pushes either False or True, depending on the exit status of the command.
source target isnewer#If any file in source is newer than any file in target, push True. Otherwise push False. Original arguments are popped.
value search replacement rep.str#Replace the first occurence of search in value with replacement. If value is a list, then do this for each element of the list.
value suffix rep.sfx#Replace the suffix of value with suffix. A suffix is a filename extension such as .c or .class etc.
value1 value2 cat#Concatenate two strings and push the resulting string back on. If any of the values is a list, then all elements of the list is concatenated with the the other value. If both values are lists, this is an error.
varname export#Exports the given variable to the global scope.
varname getenv#Pushes the value of the given environment variable.
filename include#Executes the given input file. If it's a relative path, it's taken relative to the directory of the file the include call resides in.
string1 string2 eq#Pushes True if the two strings are equal. Pushes False otherwise.
string1 string2 neq#Pushes True if the two strings are not equal. Pushes False otherwise.
list code for.each#Runs code for each element of list. For each element, the element is pushed on the stack and then code is executed.
code count times#Execute code for count times. count should be string and is interpreted as a decimal number.
-V print version and exit -f input_file_name can be given multiple times -q quiet mode, doesn't even print messages -v verbose, prints commands before executing them var=value predefine variable var predefine variable=TrueIf no input file name is given, jjcool in the current directory is assumed.