(min)mod

From Esolang
Jump to navigation Jump to search
(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