(min)mod
Paradigm(s) | Imperative |
---|---|
Designed by | User:Peter |
Appeared in | 2023 |
Memory system | Stack-based |
Computational class | Turing-complete |
Reference implementation | Original interpreter and debugger |
Influenced by | Underload |
File extension(s) | .(m)m |
(min)mod (minimal self-modifying language) is a language designed by me (User:Peter) in 2023. Its main goals are to explore the concept of all atomic data being executable instructions, and to minimize the number of built-in instructions without becoming turing-incomplete or unusable for programming.
Stacks
Stacks are the only data structure in (min)mod, and consist of sub-stacks, instructions, or both. Stacks can either be literal stacks or stack references. Literal stacks are passed by value and are, within the program, opened and closed by square brackets. Stack references are passed by reference and can only be created through the UNWRAP
instruction.
Instructions
Instructions are variables containing stacks. All user-defined instructions are initially uninitialized and therefore unusable. Some instructions - listed below - are built-ins. Those built-ins can be modified in the same ways as user-defined instructions. Instructions can also be used to store data.
Data Stack
The data stack, or DS
, is a special instruction whose data is manipulated by built-in instructions. It's initially defined as an empty stack.
Instruction Stack
The instruction stack, or IS
, is a stack of execution frames, which are stacks of instructions and sub-stacks. Initially, the only execution frame is the program itself, which is therefore stored in the only sub-stack of IS
. When instructions or literal stacks are executed, their contents are pushed onto IS
as a new execution frame. The instructions in the execution frame in the top of IS
are executed and popped from top to bottom. When the uppermost sub-stack is empty, it is popped and the second sub-stack from the top will be executed instead. When there's no sub-stacks left, the program terminates. Since instructions are executed last to first, the stack and sub-stacks containing the program are reversed before being added to the instruction stack, so it doesn't have to be written backwards. When executing an instruction or literal stack, IS
duplicates its value to a new sub-stack that's pushed onto itself.
Set
The SET
instruction pops two elements from the data stack. The first element popped, which must be an instruction, will have its value set to the second element popped. If the second element popped is a stack, it'll be copied by value, so the value of the instruction will be a new stack.
Unwrap
The UNWRAP
instruction can only be used when the top element of the data stack is an instruction. It replaces that instruction by a stack reference referencing its value.
If
The IF
instruction pops a single element from the data stack. If and only if that element was an empty sub-stack, it pops once more.
Indirection
All stack elements have a level of indirection, signified by the number of periods written after the element. Stack elements without trailing periods have an indirection level of 0, and will simply be executed when they're in the top of the current execution frame. Stack elements with an indirection level of 1 or higher will instead push the same element onto the data stack when executed, except that the new element will be one level less indirect. For instance, a
executes instruction a
, a.
pushes a
onto the data stack, and a..
pushes a.
onto the data stack.
Comments
Comments are opened by ;
and last until the end of the line.
File inclusion
Any file path to a .(m)m
file written between double quotes ("
) will have its contents pasted into the code unless it's already been included. The number of dots (.
) before the file name signifies how many directories back the file should be included. A file name with no preceding dots is an absolute file path, one preceding dot signifies that the file to import belongs to the same directory as this one, two means that the file belongs to the parent directory of the current one, and so on.
Example programs
The following programs are examples of (min)mod programs.
Pseudo-hello-world
It's not possible to output anything to the console in (min)mod, so this simply stores World
and Hello
in the data stack. They're stored in reversed order since stacks are logged in reversed order in the original interpreter.
World. Hello. -- Execute uninitialized instruction 'World' with indirection level 1 (push it onto 'DP'), then 'Hello' with indirection level 1 (push onto 'DP').
Pop
The following program defines the pop
instruction, which pops from the data stack by using the IF
instruction on an empty stack.
((). IF). pop. SET -- Set 'pop' to '((). IF)', which pushes '()' onto 'DP' and pops twice since '()' is an empty stack.
Infinite loop
Shortest version:
IS -- Recursively call the instruction stack as an instruction.
More readable version:
(loop). loop. SET loop
Local data stacks
When writing functions, it's often useful to create a temporary data stack to simplify operations on data. More specifically, storing data below the arguments of a function is often useful, in which case one has to create a new data stack containing that data before moving the arguments on top of it. This could be achieved by storing the previous data stack in a variable, which could be called something like old_ds
. However, if a function creating such a temporary data stack calls another function that creates a temporary data stack, old_ds
will be redefined within the second function, destroying the data of the first.
A good solution to this is to create a stack of data stacks, where new data stacks can be created and destroyed as new execution frames are entered and left - quite similar to a call stack. Below, two new instructions are introduced: push_ds
, which creates a new data stack, and pop_ds
, which destroys the current data stack and goes back to the previous one. pop_ds
builds on the pop
function defined earlier, and (). ds_stack. SET push_ds
is required to get the system up and running.
( (). new_ds. SET -- Create a new, empty data stack. ds_stack. DS. SET -- Make instructions operate on the stack of data stacks. new_ds. UNWRAP -- Push the new data stack onto 'ds_stack', as a stack reference rather than an instruction so that it won't change even if 'new_ds' does. new_ds. DS. SET -- Set the data stack to the new stack. uninitialized. new_ds. SET -- Set the new stack to an uninitialized value to make sure errors will be caught if modifications to 'new_ds' are attempted. ). push_ds. SET ( ds_stack. DS. SET -- Make instructions operate on 'ds_stack'. pop -- Pop from that stack. new_ds. SET -- Set the 'new_ds' to the new top data stack (second from the top before popping), popping that data stack in the process. new_ds. UNWRAP -- Add the popped data stack back by pushing it as a stack reference. new_ds. DS. SET -- Set 'DS' to the new data stack. uninitialized. new_ds. SET -- Make 'new_ds' uninitialized again. ). pop_ds. SET
Boolean not
The function assumes that the stack contains at least one element and that the top element is initialized. It replaces the top element with (())
if its value is an empty stack, and ()
if it's not. It relies on the local data stack functions defined previously. See (min)mod#Local data stacks for how to use those.
The function itself, along with definitions of the true
and false
variables:
(()). true. SET -- Can be any non-empty stack. (). false. SET -- Must be an empty stack so the 'IF' instruction pops correctly. ( arg. SET -- Pop the argument and store it in 'arg'. push_ds -- Move control to a new, temporary data stack. true. false. arg. -- Push true, then false, then the argument. IF -- If the argument is an empty stack, the argument itself and 'false' will be popped, making 'true' the top of the stack. Otherwise, only the argument will be popped and the top element will be 'false'. ret. SET -- Set 'ret' to the top value of the stack, which is 'true' if the argument is an empty stack, and 'false' otherwise. pop_ds -- Revert control to the previous data stack. ret. UNWRAP -- Push 'ret' onto that stack and unwrap it so it will stay the same even if 'ret' is redefined in another function. -- Make 'arg' and 'ret' uninitialized to make sure there will be errors if reading from those variables is attempted. uninitialized. arg. SET uninitialized. ret. SET ). not. SET
A full program testing the function for both empty and non-empty stacks (stored in the @not_false
and @not_true
variables, respectively):
( (). IF ). pop. SET ( (). new_ds. SET ds_stack. DS. SET new_ds. UNWRAP new_ds. DS. SET uninitialized. new_ds. SET ). push_ds. SET ( ds_stack. DS. SET pop new_ds. SET new_ds. UNWRAP new_ds. DS. SET uninitialized. new_ds. SET ). pop_ds. SET (()). @true. SET (). @false. SET ( arg. SET push_ds @true. @false. arg. IF ret. SET pop_ds ret. UNWRAP uninitialized. arg. SET uninitialized. ret. SET ). not. SET (). ds_stack. SET push_ds @true. not @not_true. SET @false. not @not_false. SET
Computational class
(min)mod is likely Turing-complete due to the fact that control flow can be simulated through recursion and the IF
instruction, and accessing elements deep down in a stack can be achieved by pushing elements onto another stack, acquiring the desired element, and pushing the elements back.
External resources
- Original interpreter and debugger, downloadable
- Outdated interpreter, online (click
Fork this
in the upper-left corner and write code in theprogram.(m)m
file) - Outdated debugger, online (click
Fork this
in the upper-left corner and write code in theprogram.(m)m
file)