Stacker is a powerful Reverse Polish Notation (RPN) calculator built with Python, featuring basic mathematical operations and extensibility through plugins.
- Installation
- Dependencies
- Feedback and Contributions
- Usage
- Running Scripts
- VSCode Syntax Highlighting
- Command Line Execution
- Settings
- REPL commands
- Configuration File
- Creating Plugins
- Running on Python
- Supported Operations
- Constants
git clone git@github.com:remokasu/stacker.git
cd stacker
pip install .For syntax highlighting support in VSCode:
mkdir -p ~/.vscode/extensions/stacker-language-0.1.0
cp -r .vscode-extension/* ~/.vscode/extensions/stacker-language-0.1.0/Reload VSCode (Ctrl+Shift+P → "Developer: Reload Window") and .stk files will be highlighted.
Python Prompt Toolkit is required for Stacker. Install it using the following command:
pip install prompt_toolkitFeedback and contributions are welcome. Please submit issues or suggestions on the Issues page.
Run Stacker:
stackerOr:
python -m stackerStacker supports standard arithmetic operations (+, -, *, /) and advanced functions (sin, cos, tan, etc.). Users can input commands in RPN format and extend functionality using custom plugins.
Stacker allows for straightforward RPN input. For example:
-
Single-line input:
stacker:0> 3 4 + [7] -
Multi-line input:
stacker:0> 3 [3] stacker:1> 4 [3 4] stacker:2> + [7]
-
The Stacker command allows you to directly push integers, floating-point numbers, and complex numbers onto the stack. This facilitates easy management of various types of numerical data.
-
Integers:
stacker:0> 3 [3]In this example, the integer 3 is added to the stack.
-
Floating-Point Numbers:
stacker:1> 3.14 [3.14]Here, the floating-point number 3.14 is added to the stack.
-
Complex Numbers:
stacker:2> 1+2j [(1+2j)]In this case, the complex number 1+2j (with a real part of 1 and an imaginary part of 2) is added to the stack. Complex numbers are denoted by combining the real and imaginary parts with a +, and the imaginary part is indicated using j.
-
-
- syntax:
"Hello, World!" - example:
In this example, the string "Hello, World!" is added to the stack. Strings are written with double quotes and shown on the stack with single quotes. A backslash starts an escape:
stacker:0> "Hello, World!" ['Hello, World!']
\n\t\r\0\\\"\'\xNN\uNNNN.stacker:0> "say \"hi\"\tnow" echo say "hi" now
- syntax:
-
-
syntax:
value :name set # or value :name =
-
example:
stacker:0> 3 :x set stacker:1> x [3] # Using = operator (equivalent to set) stacker:0> 5 :y = stacker:1> y [5]
In this example, we assign
3toxusingset, and5toyusing=. Both operators work identically.Note: The
=operator is an alias forsetand can be used interchangeably. Use whichever feels more natural for your coding style.Three ways to write a name. A name can be written bare (
x), as a symbol (:x) or quoted ({x}). The rule is short: a bare name is a reference,:xand{x}are the name itself, and"x"is a string, never a name.- A bare name is looked up: as a macro, then a variable, then a function or operator. A macro is expanded,
a variable's value is pushed (a lambda too; call it with
eval), a function or operator is run. A name that is none of these is an error right there, so a typo stops where it is written. :xpushes the symbolxitself as a value, whatever a variablexholds. A symbol is a type of its own::x "x" ==is false,symbol?tells it from a string,strgives its text andsymbolmakes one from text ("x" symbol).:must be followed by a name (:1and:{x}are errors). Symbols are how a name is handed to any word that takes one (set,defun,alias, a macro, ...), and[:a :b]is a list of names.{x}(a block holding only the name) means "this name" everywhere a name is read, and as code it means the same as:x::x evaland{x} evalboth do what writingxdoes.- Tokens are read one at a time, left to right, and nothing looks ahead: a bare name is a reference
wherever it stands, even right before
set. So the name a word binds is always written:nameor{name}:3 :x set,0 10 :i {i echo} do,{a b} {a b +} :add defun,[1 2 3] 0 :acc :x {acc x +} reduce. Written bare,3 x setlooksxup and stops with "xis not defined", and3 dup setrunsdupand thensetcomplains that it got3instead of a name. The same spelling rebinds any name, a built-in word's included (3 :dup set), and redefining a prelude word such asaliaslogs a warning, as does adefunthat redefines a word of your own from another file. A bare name that holds a name binds that name: after:y :x set,7 x setbindsy. A literal is never a name (5 3 setand5 true setare errors) and neither is a string (5 "x" setis an error too).
- A bare name is looked up: as a macro, then a variable, then a function or operator. A macro is expanded,
a variable's value is pushed (a lambda too; call it with
-
-
Lists:
-
Single-line list:
stacker:0> [1 2 3; 4 5 6] [[[1 2 3] [4 5 6]]]
-
Multi-line list:
stacker:0> [1 2 3; ... > 4 5 6] [[[1 2 3] [4 5 6]]]
-
-
Code blocks are fundamental structures in Stacker that enable deferred evaluation and control flow management. They are enclosed in curly braces
{}.Syntax:
{code_elements}Key Characteristics:
- Deferred Evaluation: Code blocks are not executed immediately when encountered
- Stack Interaction: Pushed onto the stack as single units in their raw form
- Delayed Execution: Can be executed later when needed (via
eval,if,times, etc.)
Common Use Cases:
- Conditional statements (
if,ifelse) - Loop controls (
times,do,dolist) - Function definitions (
defun,lambda) - Lazy evaluation patterns
Examples:
# Create a code block stacker:0> {1 2 +} [{1 2 +}] # Execute with eval stacker:0> {1 2 +} eval [3] # Use in function definitions stacker:2> {x y} {x y *} :multiply defun
Note: Code blocks are stored but not executed until explicitly triggered. This allows for flexible program control, lazy evaluation, and higher-order programming patterns.
Quoted names: a block holding a single name, such as
{double}, is the way to pass a name as a value. Every word that takes a name (set,=,global,defun,defmacro,alias) accepts{name}as well as:name. Macros that define things take their names in this form. This is also how a variable name is passed to a macro:{n} incr!and:n incr!both hand over the namen, whereasn incr!hands over the variable's current value. -
A list never changes. When you need to change elements in place, make an array or a dictionary. Words that change them end in
!.stacker:0> [1 2 3] array :a set [] stacker:1> 9 a 0 at! [] stacker:2> 4 a append! [] stacker:3> a [[9 2 3 4]] stacker:4> dict :d set [[9 2 3 4]] stacker:5> 1 d "x" put! [[9 2 3 4]] stacker:6> d "x" get [[9 2 3 4] 1]
An array is shown like a list (the REPL gives it a color of its own;
array?tells for sure) and a dictionary as{'x' 1}. An array or a dictionary is one thing wherever it goes:dup, a variable, a function argument or a list element refers to the same one, so a change made through one is seen through all.a b same?tells whether two are the same one;==compares contents. Words that only read take an array as they take a list, and the ones that make a new sequence (map,sorted,cdr, ...) make a list. -
Stacker provides two main types of control structures: conditionals and loops. These allow for dynamic program flow based on conditions and repetitive execution of code blocks.
-
Conditionals in Stacker enable execution of code based on specified conditions.
-
The
ifstatement executes a code block if a condition is true.Syntax:
condition <true-expr> if
The condition is a value, or a block that runs on the current stack and leaves the value to test on top (so
7 {0 >} {...} iftests7 0 >, and an empty block leaves the top as it is). A block that leaves nothing is an error.Example:
stacker:0> 0 :x set stacker:1> x 0 == {3 4 +} if [7]
Result: Pushes
7onto the stack asxequals0. -
The
ifelsestatement provides branching based on a condition, executing one of two code blocks.Syntax:
condition <true-expr> <false-expr> ifelse
Example:
stacker:0> 0 :x set stacker:1> x 0 == {3 4 +} {3 4 -} ifelse [7]
Result: Pushes
7onto the stack asxequals0. -
The
condstatement evaluates condition-result pairs in order and executes the first matching branch.Syntax:
{condition1} {result1} {condition2} {result2} ... n condnis the number of condition-result pairs.Example:
stacker:0> 5 :x set stacker:1> {x 0 >} {"positive"} {x 0 <} {"negative"} {true} {"zero"} 3 cond ['positive']
Result: Pushes
"positive"onto the stack asxis greater than0. Use{true}as the last condition to provide a default branch. -
Loops in Stacker allow for repeated execution of code blocks.
-
The
doloop iterates over a range of values.Syntax:
start_value end_value :name {body} doExample:
stacker:0> 1 10 :i {i echo} do 1 2 3 4 5 6 7 8 9 10
Result: Prints numbers from 1 to 10.
-
The
dolistloop iterates over a list of values.Syntax:
[value1 value2 ... valueN] :name {body} dolistExample:
stacker:0> [1 2 3 4 5] :i {i echo} dolist 1 2 3 4 5Result: Prints numbers 1 through 5.
Note: When expressing a list of consecutive values, the concise notation value1 valueN
seqcan be used instead of[value1 value2 ... valueN]to efficiently describe a sequence with a constant step size. -
The
timesloop repeats a code block a specified number of times.Syntax:
{body} n timesExample:
stacker:0> 1 {dup ++} 10 times [1 2 3 4 5 6 7 8 9 10 11]
Result: Pushes numbers 1 through 11 onto the stack by repeatedly duplicating and incrementing.
-
The
whileloop repeats a code block while a condition is true.Syntax:
{condition} {body} whileExample:
stacker:0> 1 :i set 0 :s set stacker:2> {i 6 <=} {s i + :s set i ++ :i set} while stacker:2> s [21]
Result: Sums numbers from 1 to 6.
Note: Use
{condition}as a code block that leaves a boolean on the stack. The loop terminates when the condition evaluates to false. -
looprepeats a code block forever untilbreakis executed. It is the primitive every other loop can be built from; the body runs on the current stack, in the current scope.Syntax:
{body} loopExample:
stacker:0> 0 {1 + dup 10 > {break} if} loop [11]
-
breakimmediately exits the innermost loop (do,dolist,times,while,loop).Syntax:
{break}Example:
stacker:0> 0 :s set 1 10 :i {i 5 == {break} if s i + :s set} do s [10]
Result: Sums 1 through 4, then exits when
ireaches 5. Note thatbreakinsideifimmediately stops execution of the current block —s i + :s setis not executed wheniequals 5.
-
-
- syntax:
{arg1 arg2 ... argN} {body} :name defun - example:
This defines a function named
stacker:0> {x y} {x y *} :multiply defun stacker:1> 10 20 multiply [200]
multiplythat takes two argumentsxandyand multiplies them together. The function pushes every value its body leaves, in order; a body that leaves nothing pushes nothing:stacker:0> {a b} {b a} :swap2 defun stacker:1> 1 2 swap2 [2 1]
- syntax:
-
- syntax:
{body} {name} alias - example:
An alias names a code snippet. Calling the name runs the body right there, on the caller's stack and in the caller's scope, exactly as if the body had been written in place. (Before 1.15.0 this was what
stacker:0> {2 ^ 3 * 5 +} {calculatePowerAndAdd} alias stacker:1> 5 calculatePowerAndAdd [80]
defmacrodid.)
- syntax:
-
-
syntax:
{args} {body} :name defmacro -
example:
stacker:0> {cond body} { {cond eval not {body eval} if} } {unless} defmacro stacker:1> -3 {0 >} {"not positive" echo} unless not positive
A macro receives its arguments as code (blocks are not evaluated) and runs its body when it is called to build the code that will actually run. Inside a block literal of the body, an argument name is replaced by the argument passed in, so the body above produces
{{0 >} eval not {{"not positive" echo} eval} if}, which is then evaluated on the caller's stack. This lets you define new control structures in Stacker itself.The rules:
- The body runs on its own stack, in a scope of its own where the arguments are bound. It must leave exactly one value: a code block is evaluated in the caller's context, any other value is pushed as is.
- Every name bound in that scope (the arguments, and variables assigned with
=/setinside the body) is substituted into the body's block literals at the moment each literal is pushed: a symbol (:name, agensymname) becomes a bare name in the generated code, a string becomes a string literal, a block stays a block and any other value is spliced as a value. A:namewritten in the body is replaced by the symbol of the name thatnameholds (:tfor agensymname held int,:doublefor the quoted name{double}), which is how a generated temporary is bound:{ x eval :t set t t + }. The substituted values are not scanned again; string literals, list literals and unbound:names are left untouched. - Assignments inside the body are local to the macro; they never change the caller's variables.
- Argument names may be anything, including built-in names; every occurrence in the block literals is replaced, so choose them with care.
gensymreturns a fresh unique name (prefixed__g; do not use that prefix yourself) for temporaries in generated code:stacker:0> {x} { gensym :t = { x eval :t set t t + } } {double!} defmacro stacker:1> {3 4 +} double! [14]
{name} macroexpandpushes the expansion without running it, which is the easiest way to debug a macro.
-
-
Lambda functions are anonymous functions that can be defined and executed on the fly. They are useful for creating temporary functions without the need for a formal definition.
-
syntax:
{arg1 arg2 ... argN} {body} lambda -
example:
stacker:0> {x y} {x y *} lambda [λxλy.{x y *}]
-
example:
stacker:0> {x y} {x y *} lambda :multiply set stacker:1> 3 4 multiply [3 4 λxλy.{x y *}] stacker:3> eval [12]
This example defines a lambda function that multiplies two numbers and assigns it to the variable
multiply. A lambda held in a variable is a value: writingmultiplypushes it, andevalcalls it with the arguments3and4(3 4 multiply eval). To call a function by its bare name, define it withdefuninstead. Like a function, a lambda pushes every value its body leaves when it is called.
-
-
"file.stk" includeruns another script right here, in the interpreter and the scope it is written in, as if its text stood in place: the variables, functions and macros it defines are the ones you see afterwards, and its functions can call yours. It runs every time it is included. What the file leaves on its stack is dropped.stacker:0> "my_script.stk" include
"file.stk" requiredoes the same the first time, and nothing when the same file is required again. Read a library that keeps state withrequire, so that reading it from a second file does not set it up again.Relative paths are looked up next to the including file first, then in the current directory. An error inside an included file is reported at its own line, followed by the lines that included it (
included from ...). -
Some words are written in Stacker itself and loaded into every interpreter at start-up (
aliasandmeanfor now). They behave like any other word;macrosandfuncsmark them with(prelude).
Stacker scripts can be created in .stk files. To run a script, simply execute it with Stacker. For example:
-
my_script.stk:
0 :p set 0 100000 :k { -1 k ^ 2 k * 1 + / p + :p set } do 4 p * :p set p echo
Running the script:
stacker my_script.stk
Stacker provides syntax highlighting support for .stk files in Visual Studio Code, making it easier to read and write Stacker code.
Install the syntax highlighting extension by copying it to your VSCode extensions directory:
# For VSCode Server (Remote SSH)
mkdir -p ~/.vscode-server/extensions/stacker-language-0.1.0
cp -r .vscode-extension/* ~/.vscode-server/extensions/stacker-language-0.1.0/
# For local VSCode
mkdir -p ~/.vscode/extensions/stacker-language-0.1.0
cp -r .vscode-extension/* ~/.vscode/extensions/stacker-language-0.1.0/Then reload VSCode (Ctrl+Shift+P → "Developer: Reload Window").
- Comments (
#and#| ... |#), strings ("...",'...', triple quotes), numbers (42,3.14,1.5e3,0xFF,0b1010,2+3j) - Symbols (
:x) - Every word of the language, by kind: control flow (
if,do,while, ...), definitions (defun,defmacro,lambda,alias), assignment (set,=,global), code words (eval,parse,macroexpand, ...), arithmetic, comparison, logic, bit operations, math, stack words, list words, string words, type words, I/O and files, system words, prelude words and constants - Auto-closing brackets (
{,[,(,",') and code folding
The grammar is generated from the interpreter's own word lists by scripts/generate_vscode_grammar.py, so it lists exactly the words the interpreter knows. The colors come from your VSCode theme.
You can directly execute a specified RPN expression from the command line.
stacker -e "3 4 + echo"stacker [-h] [--addplugin path] [--version] [--debug] [--no-compile] [-e E] [--recursion-limit N] [script [arg ...]]
python -m stacker takes the same options as stacker. With neither script nor -e, the REPL starts. Put the options before the first positional argument (the script name, or the first argument with -e); everything after it belongs to the script.
| Option | Description |
|---|---|
script [arg ...] |
The .stk file to run. Stacker runs it and exits without opening the REPL; the arguments after the file name are what the word args reads inside the script. |
-e E |
Runs the expression E and exits. Every positional argument becomes one of args. |
--debug |
Debug mode: logs at the DEBUG level, and the REPL and scripts print Python's traceback with an error. With -e, only the log level changes. |
--recursion-limit N |
How deep a program may nest its calls, blocks and loops: the depth of the interpreter's return stack. 100000 when omitted; 0 for no limit, when memory is the only bound. A call made by the last word of a function's body uses no depth. |
--no-compile |
Runs every block in the interpreter instead of compiling the ones that run often. The result is the same; use it to compare the two or to narrow a problem down. |
--addplugin path |
Copies the plugin file to the plugin directory of the installed Stacker and exits. |
--version |
Prints the version, in the form Stacker X.Y.Z, and exits. A script reads it with the word version. |
-h, --help |
Prints the help and exits. |
stacker script.stk 200 100 # args is ['200' '100'] inside the script
stacker script.stk -x # an argument after the file name is passed through, even one starting with -
stacker -e "args echo" -- -x # with -e, write -- before an argument that starts with -
stacker --recursion-limit 0 deep.stk # no limit: as deep as memory allows-
disable_plugin Disable a specified plugin:
stacker:0> "hoge" disable_plugin
This command deactivates the
hogeoperator added as a plugin. Note that it cannot be used on non-plugin operators. -
disable_all_plugins Disable all plugins at once.
stacker:0> disable_all_plugins
These commands work only at the REPL's prompt, typed alone on a line in lowercase (spaces around them are ignored). They do not touch the stack.
They are reserved words of the language: a program cannot name a function, variable, macro, argument or loop variable after them, and one written in a program, or after a value on the same line, raises ReservedWordError. Names with capitals, such as HELP, are ordinary names.
- help Shows how to use Stacker and lists every operator.
- about Introduces Stacker.
- delete_history Deletes the history file (~/.stacker_history).
- enable_disp_stack / disable_disp_stack Shows the stack after each input (the default) / does not.
- enable_disp_ans / disable_disp_ans
Shows the top value (ans) on a line of its own after each input / does not (the default).
stacker:0> enable_disp_ans stacker:0> 3 4 + 7 [7]
- enable_disp_logo / disable_disp_logo Shows the logo at startup (the default) / does not. The logo is shown before the first prompt, so write this one in ~/.stackerrc.
You can automatically load settings at startup. The configuration file should be placed in ~/.stackerrc. For example, if you write the following contents in ~/.stackerrc, the disable_disp_logo and disable_disp_stack will be automatically activated at startup.
disable_disp_logo
disable_disp_stack
enable_disp_ansCreate custom plugins for Stacker using Python:
-
Creating the Plugin: In the
pluginsdirectory, create a new Python file for your plugin (e.g.,my_plugin.py).stacker/ │ ├── stacker/ │ ├── plugins/ │ │ ├── my_plugin.py │ │ └── ... │ │ │ ├── data/ │ ├── stacker.py │ └── ... │ └── ...Adding your plugin here and reinstalling Stacker will apply the plugin permanently.
-
Defining Functions and Classes: Define the necessary functions and classes in
my_plugin.py. -
Defining the
setupFunction: Inmy_plugin.py, define asetupfunction that takesstackeras its only argument. -
Registering Custom Commands and Parameters:
Within the
setupfunction, use theregister_pluginmethod ofstackerto register custom commands. Additionally, you can also register custom parameters using theregister_parametermethod. This allows for greater flexibility and customization in your plugin's behavior.Here's an example that registers a custom command:
Example:
from stacker.stacker import Stacker def function(a, b): return a + b def setup(stacker: Stacker): stacker.register_plugin("command", function, desc="Add two numbers")
You can specify the command description for the help command using desc. This field is optional.
A plugin word takes precedence over a built-in word of the same name: registering
+replaces the built-in+. This is how a plugin can give an operator a new meaning, for example for matrices.The register_parameter method is used to add a custom parameter to the Stacker environment, allowing for additional customization and control within your plugin.
-
Reinstalling Stacker: Run the following command to reinstall Stacker:
> pip install .Note: If you want to apply the plugin only temporarily, create a
pluginsdirectory in the directory where Stacker is executed and add your plugin there. The method for creating it is the same as described above. This method does not require reinstalling Stacker. -
Using the Plugin: When Stacker is launched, the plugin will automatically be loaded, and the custom commands will be available for use.
-
Disabling Plugins: Use operatorName disable_plugin to disable a specific plugin.
Use disable_all_plugins to disable all plugins.
You can also run Stacker as a Python module. For example:
from stacker import Stacker
stacker = Stacker()
print(stacker.eval("3 4 +"))Stacker() loads the prelude like the command line does. Pass Stacker(prelude=False) for a bare interpreter.
Stacker(compile=False) runs every block in the interpreter instead of compiling the ones that run often, as --no-compile does on the command line; the result is the same.
stacker.run_file("script.stk") runs a script file as the command line does, and Stacker(console=StreamConsole(stdout=...)) sends what the words print and read elsewhere.
| Operator | Description | Example |
|---|---|---|
| + | Add | 3 5 + |
| - | Subtract | 10 3 - |
| * | Multiply | 4 6 * |
| / | Divide | 12 4 / |
| // | Integer divide | 7 2 // |
| % | Modulus | 9 2 % |
| ^ | Power | 3 2 ^ |
| == | Equal | 1 1 == |
| != | Not equal | 1 0 != |
| < | Less than | 1 2 < |
| <= | Less than or equal to | 3 3 <= |
| > | Greater than | 2 1 > |
| >= | Greater than or equal to | 3 3 >= |
| neg | Negate | 5 neg |
| and | Logical and | true false and |
| or | Logical or | true false or |
| not | Logical not | true not |
| band | Bitwise and | 3 2 band |
| bor | Bitwise or | 3 2 bor |
| bxor | Bitwise xor | 3 2 bxor |
| >> | Right bit shift | 8 2 >> |
| << | Left bit shift | 2 2 << |
| bit-length | The bits needed to write an integer | 255 bit-length |
| popcount | The number of one bits in an integer | 255 popcount |
| trailing-zeros | Zero bits below the lowest one bit | 40 trailing-zeros |
| ~ | Bitwise not | 5 ~ |
| bin | Binary representation (result is a string) | 5 bin |
| oct | Octal representation (result is a string) | 10 oct |
| dec | Decimal representation (result is an integer) | 0b101010 dec |
| hex | Hexadecimal representation (result is a string) | 255 hex |
| Operator | Description | Example |
|---|---|---|
| abs | Absolute value | -3 abs |
| exp | Exponential | 3 exp |
| real | Real part (a real number is its own) | 3+4j real |
| imag | Imaginary part (0 for a real number) | 3+4j imag |
| conj | Complex conjugate | 3+4j conj |
| arg | Angle of a number, from -pi to pi | 1j arg |
| polar | Complex number of length r and angle t: r t polar |
1 pi 2 / polar |
| log | Natural logarithm | 2 log |
| log10 | Common logarithm (base 10) | 4 log10 |
| log2 | Logarithm base 2 | 4 log2 |
| sin | Sine (argument in radians) | pi 2 / sin |
| cos | Cosine (argument in radians) | pi cos |
| tan | Tangent (argument in radians) | pi 4 / tan |
| asin | Arcsine (result in radians) | 0.5 asin |
| acos | Arccosine (result in radians) | 0.5 acos |
| atan | Arctangent (result in radians) | 1 atan |
| sinh | Hyperbolic sine | 1 sinh |
| cosh | Hyperbolic cosine | 1 cosh |
| tanh | Hyperbolic tangent | 1 tanh |
| asinh | Inverse hyperbolic sine | 1 asinh |
| acosh | Inverse hyperbolic cosine | 2 acosh |
| atanh | Inverse hyperbolic tangent | 0.5 atanh |
| sqrt | Square root | 9 sqrt |
| ceil | Ceiling | 3.2 ceil |
| floor | Floor | 3.8 floor |
| round | Round | 3.5 round |
| roundn | Round to specified decimal places | 3.51 1 roundn |
| gcd | Greatest common divisor | 4 2 gcd |
| ! | Factorial | 4 ! |
| radians | Convert degrees to radians | 180 radians |
| rand | Generate a random floating-point number between 0 and 1 | rand |
| randint | Generate a random integer within a specified range | 1 6 randint |
| uniform | Generate a random floating-point number within a specified range | 1 2 uniform |
| frac | Fraction | 3 6 frac |
| dice | Roll dice (e.g., 3d6) | 3 6 dice |
| Operator | Description | Example |
|---|---|---|
| drop | Drops the top element of the stack. | drop |
| drop2 | Drops the top two elements of the stack. | drop2 |
| dropn | Drops the top n elements of the stack. | 2 dropn |
| dup | Duplicate the top element of the stack. | dup |
| dup2 | Duplicate the top two elements of the stack. | dup2 |
| dupn | Duplicate the top n elements of the stack. | 2 dupn |
| swap | Swap the top two elements of the stack. | swap |
| rev | Reverse the stack. | rev |
| rot | Move the third element to the top of the stack. | rot |
| unrot | Move the top element to the third position of the stack. | unrot |
| roll | Moves the nth element to the top of the stack. | 2 roll |
| over | Copy the second element from the top of the stack. | over |
| pick | Copies the nth element to the top of the stack. | 2 pick |
| nip | Remove the second element from the top of the stack. | nip |
| depth | Returns the depth of the stack. | depth |
| ins | Insert a value below the top n elements: n value ins |
1 2 3 2 99 ins |
| count | Pop a value and count its occurrences in the rest of the stack. | 5 5 7 5 count |
| clear | Clear the stack. | clear |
| disp | Display the stack. | disp |
| Operator | Description | Example |
|---|---|---|
| if | Conditional statement | true {...} if |
| ifelse | Conditional statement with an else block | true {"yes"} {"no"} ifelse |
| iferror | Run the catch block if the first block raises an error, with the stack put back as it was | {1 0 /} {"caught"} iferror |
| try | Like iferror, with the error pushed as a dictionary ('kind', 'message', 'stack') before the handler runs | {1 0 /} {"kind" get} try |
| throw | Raise your own error: kind message throw |
{"E" "boom" throw} {"message" get} try |
| cond | Multi-branch conditional (first matching pair wins) | 5 :x set {x 0 >} {"pos"} {true} {"other"} 2 cond |
| do | Loop | 0 10 :i {i echo} do |
| dolist | Loop over the elements of a list | [1 2 3] :x {x echo} dolist |
| times | Loop a specified number of times | {dup ++} 10 times |
| while | Loop while a condition is true | 0 :i set {i 10 <} {i ++ :i set} while |
| loop | Repeat a block until break |
0 {1 + dup 10 > {break} if} loop |
| break | Break out of a loop | break |
| Operator | Description | Example |
|---|---|---|
| defun | Define a function | {x y} {x y *} :multiply defun |
| alias | Name a code snippet (prelude) | {2 ^ 3 * 5 +} {calculatePowerAndAdd} alias |
| defmacro | Define a macro | {c b} {{c eval not {b eval} if}} {unless} defmacro |
| macroexpand | Push a macro's expansion without running it | {0 >} {"neg" echo} {unless} macroexpand |
| code-text | The text of a block, without running it (parse eval reads it back) |
{1 2 +} code-text |
| code= | Whether two blocks are the same code, token by token, without running them | {1 2 +} {3} code= |
| gensym | Push a fresh unique name for macro temporaries | gensym :t = |
| symbol | Make a symbol from a string or a quoted name | "x" symbol |
| lambda | Create a lambda function | {x y} {x y *} lambda |
| set | Assign a value to a variable | 3 :x set |
| = | Assign a value to a variable (alias for set) |
3 :x = |
| global | Assign a value to a global variable | 42 :answer global |
| Operator | Description | Example |
|---|---|---|
| map | Apply a function to each element of a list | [1 2 3] {2 *} map |
| zip | Pair up the elements of two lists: [[1 4] [2 5] [3 6]] |
[1 2 3] [4 5 6] zip |
| filter | Filter a list based on a condition | [1 2 3 4 5] {2 % 0 ==} filter |
| at | Push element i, taking the container off (nth leaves it) |
[10 20 30] 1 at |
| slice | Elements a up to b as a new value of the same kind (list, array or string) | [10 20 30 40] 1 3 slice |
| all | Check if all elements of a list are true | [true true false] all |
| any | Check if any element of a list is true | [true false false] any |
| car | Return the first element of a list | [1 2 3] car |
| cdr | Return the list without its first element | [1 2 3] cdr |
| cons | Prepend an element to a list | 1 [2 3] cons |
| pair? | Return true if the value is a non-empty list | [1 2 3] pair? |
| apply | Expand list elements onto the stack and apply a function | [3 4] {+} apply |
| collect | Run a block on an empty stack and push what it left as one list | {1 2 3} collect |
| expand | Push the elements of a list, or each token of a code block as a one-token block | [1 2 3] expand, {a b} expand |
| concat-block | Join two code blocks | {a b} {c} concat-block |
| Operator | Description | Example |
|---|---|---|
| array | Make an array (changeable in place) from a list, a string or a block | [1 2 3] array |
| array-of | Make an array of n slots that all hold x | 5 0 array-of |
| at! | Store v at index i of an array: v a i at! |
9 [1 2 3] array 0 at! |
| append! | Add v at the end of an array: v a append! |
4 [1 2 3] array append! |
| pop! | Take the last element off an array and push it | [1 2 3] array pop! |
| dict | Make an empty dictionary | dict |
| get | Value of key k, or null when k is not there: d k get |
dict "k" get |
| put! | Store v under key k: v d k put! |
1 dict "k" put! |
| has? | True if the dictionary has key k | dict "k" has? |
| del! | Remove key k (an error when it is not there) | dict :d set 1 d "k" put! d "k" del! |
| keys | The keys as a list, in the order they were added | dict keys |
| values | The values as a list | dict values |
| same? | True if two arrays or dictionaries are the same one | dict dup same? |
Note: Blocks, lambdas and functions passed to map and filter must leave exactly one value for each element, and the block of reduce / fold exactly one value per step (collect several with listn). For side-effect-only iteration (e.g. printing each element), use dolist instead.
| Operator | Description | Example |
|---|---|---|
| int | Convert to int | 3.7 int |
| float | Convert to float | 3 float |
| str | Convert to string | 42 str |
| bool | Convert to bool | 1 bool |
| complex | Convert to complex number | 3 complex |
| type | The kind of a value as a symbol (:int, :str, :array, :null, ...) |
42 type |
| int? | Returns true if the value is an int (not bool) | 42 int? |
| float? | Returns true if the value is a float | 3.14 float? |
| str? | Returns true if the value is a string | "hello" str? |
| symbol? | Returns true if the value is a symbol | :x symbol? |
| bool? | Returns true if the value is a bool | true bool? |
| complex? | Returns true if the value is a complex number | 1+2j complex? |
| list? | Returns true if the value is a list (an array is not) | [1 2 3] list? |
| array? | Returns true if the value is an array | [1 2 3] array array? |
| dict? | Returns true if the value is a dictionary | dict dict? |
| code? | Returns true if the value is a code block, without running it (a lambda is not) | {1 2 +} code? |
| null? | Returns true if the value is null (an empty list is not) | null null? |
| empty? | Returns true if a list, an array, a string or a dictionary has no elements | [] empty? |
| number? | Returns true if the value is a number (int, float, or complex) | 42 number? |
| Operator | Description | Example |
|---|---|---|
| sub | Wrap the top value in a one-token code block | :x sub |
| subn | Wrap the top n values in one code block | 1 2 2 subn |
| ans | Push again the result of the last input that left one | ans |
| include | Run another script here, in this scope, every time | "file.stk" include |
| require | Run another script once per interpreter | "file.stk" require |
| eval | Run a code block, a symbol or a lambda (text: parse first) |
{3 5 +} eval, '3 5 +' parse eval |
| echo | Print the specified value to stdout without adding it to the stack | 3 4 + echo |
| echo-err | Print the specified value to standard error, shown as echo shows it |
"warning" echo-err |
| print-err | The same as echo-err |
"warning" print-err |
| printc-err | Print the specified value to standard error without a newline | "." printc-err |
| read | Read one line from the console and push it as a code block | read |
| read-line | Read one line of standard input as a string (null at the end) | read-line |
| read-stdin | Read the rest of standard input as one string | read-stdin |
| args | The command-line arguments after the script name, as strings | args |
| script-path | The absolute path of the running file (null in the REPL) | script-path |
| script-dir | Its directory | script-dir |
| main-script-path | The absolute path of the script the command line ran (null in the REPL) | main-script-path |
| main? | Whether the running code is that script (false inside an included file) | main? |
| read-stdin-bytes | The rest of standard input as a list of bytes (0 to 255) | read-stdin-bytes |
| write-stdout-bytes | Write a list of bytes to standard output | [72 105 10] write-stdout-bytes |
| write-stderr-bytes | Write a list of bytes to standard error | [72 105 10] write-stderr-bytes |
| write-to-file | Write the top element of the stack to a file | 3 "output.txt" write-to-file |
| append-to-file | Append the top element of the stack to a file | 3 "output.txt" append-to-file |
| read-from-file | Read the contents of a file and push it to the stack | "input.txt" read-from-file |
| file-exists | Check if a file exists | "file.txt" file-exists |
| read-bytes | Read a whole file as a list of bytes (0 to 255) | "image.png" read-bytes |
| write-bytes | Write a list of bytes to a file | [72 105] "out.bin" write-bytes |
| path-join | Join two parts of a path (the right one may be a list) | "data" ["in" "x.txt"] path-join |
| list-dir | The names inside a directory, in name order | "." list-dir |
| path-base | The last part of a path | "a/b/c.txt" path-base |
| path-dir | The directory part of a path | "a/b/c.txt" path-dir |
| encode | The bytes of a string in an encoding | "hi" "utf-8" encode |
| decode | The string a list of bytes spells in an encoding | [104 105] "utf-8" decode |
| Constants | Description |
|---|---|
| e | Euler's number |
| pi | Pi |
| phi | Golden ratio |
| tau | Tau |
| nan | Not a number |
| inf | Infinity |
true, false and null are literals rather than constants: they are values, never names, and cannot be redefined.