27
\$\begingroup\$

Write a program or function (hereafter "function") that returns or prints the source code of four new functions: Increment, decrement, undo, and peek.

The initial function contains an internal integer value of 0. Each of the returned functions contains a value based on that value, and each behaves differently:

  • Increment contains the value of the function that created it plus 1. When called, it returns or prints the source code of four new functions: Increment, decrement, undo, and peek.

  • Decrement contains the value of the function that created it minus 1. When called, it returns or prints the source code of four new functions: Increment, decrement, undo, and peek.

  • Undo returns increment, decrement, undo, and peek functions equivalent to those returned by the function that created the function that created it, i.e. its grandparent. Calling successive Undo functions will return functions earlier and earlier in the "history." Note: Because the very first generation of Undo has no "grandparent" its behavior is undefined. It may return any value, throw an error, etc.

  • Peek contains the value of the function that created it. When called it returns or prints that value.

Example

Suppose yourFunction is your initial function. It returns four strings that contain the source code of the subsequent functions.

[incr, decr, undo, peek] = yourFunction();
eval(peek)(); // => 0

[incrB, decrB, undoB, peekB] = eval(incr)();
eval(peekB)(); // => 1

[incrC, decrC, undoC, peekC] = eval(incrB)();
eval(peekC)(); // => 2

[incrD, decrD, undoD, peekD] = eval(incrC)();
eval(peekD)(); // => 3

[incrE, decrE, undoE, peekE] = eval(undoD)();
eval(peekE)(); // => 2

[incrF, decrF, undoF, peekF] = eval(undoE)();
eval(peekF)(); // => 1

[incrG, decrG, undoG, peekG] = eval(decrF)();
eval(peekG)(); // => 0
eval(peekF)(); // => 1

Rules

  1. This is a challenge. With the exception of "peek," the initial function and the functions it creates (and the functions they create and so on) must return source code that can be stored (copied/pasted, saved in a file, et al) and executed in a separate session. Returning a closure, for example, is not valid.

  2. The functions generated don't need to be named "increment," "decrement," etc. They're not required to be named at all.

  3. The "depth" of the Undo functions, i.e. how far back you can go in their "history," should be limited only by memory or language constraints.

  4. You're encouraged to include in your answer examples of the four functions generated by your initial function or its successors.

  5. The output formats are flexible. Inclusive of rule (1), standard I/O rules apply; your initial function—and the subsequent functions—could return four programs separated by newlines; an array of four strings; an object with four string properties; four files; etc.

  6. Standard rules apply and standard loopholes are forbidden.

  7. This is . Your score is the number of bytes in your initial function. Lowest number of bytes wins.

\$\endgroup\$
1
  • \$\begingroup\$ Sandbox \$\endgroup\$
    – Jordan
    Oct 27, 2022 at 15:35

12 Answers 12

8
\$\begingroup\$

Wolfram Language (Mathematica), 54 35 bytes

Hold[1~#0~##,#0[-1,##],#0@##2,+##]&

Try it online!

Returns Hold[incr, decr, undo, peek], where the expression body consists of four unevaluated expressions. When evaluated, incr, decr, and undo reduce to similar four-element Hold[...] expressions, and peek to an integer*.

* Upon undoing with no history, peek takes on the value ##2, and subsequent incr/decr operations yield peek=n+##2 for some integer n. undoing that undo restores peek=0.

\$\endgroup\$
6
\$\begingroup\$

JavaScript (ES6), 98 bytes

f=(k=0,p='' )=>[(g=n=>(0+f).replace(/\S*/,`f=(k=${k+n},p="${btoa(f)}"`))(1),g(-1),atob(p),"_=>"+k]

Try it online!

\$\endgroup\$
6
\$\begingroup\$

Rust, 603 bytes

fn f(){let m=("fn ","(){let m=",";let n=vec!",";println!(\"fn a(){{println!(\\\"","\\\")}}\");for i in [(\"b\",",",\"\"),(\"c\",",",\"\"),(\"d\",",",\"o.pop();\")]{let mut o=n.clone();o.push(i.1);","println!(\"{}{}{}{:?}{}{:?}{}{}{}{}{}{}{}{}{}{}{}\",m.0,i.0,m.1,m,m.2,o,m.3,i.1,m.4,i.1+1,m.5,i.1-1,m.6,o[o.len()-2],m.7,i.2,m.8);}}");let n=vec![0,0];println!("fn a(){{println!(\"0\")}}");for i in [("b",1,""),("c",-1,""),("d",0,"o.pop();")]{let mut o=n.clone();o.push(i.1);println!("{}{}{}{:?}{}{:?}{}{}{}{}{}{}{}{}{}{}{}",m.0,i.0,m.1,m,m.2,o,m.3,i.1,m.4,i.1+1,m.5,i.1-1,m.6,o[o.len()-2],m.7,i.2,m.8);}}

Attempt This Online!

Attempt this after a couple iterations

Probably not anywhere close to optimally golfed, but I'm pretty impressed with myself for getting it to work at all.

Expanded version:

fn f() {
    let m=("fn ","(){let m=",";let n=vec!",";println!(\"fn a(){{println!(\\\"","\\\")}}\");for i in [(\"b\",",",\"\"),(\"c\",",",\"\"),(\"d\",",",\"o.pop();\")]{let mut o=n.clone();o.push(i.1);","println!(\"{}{}{}{:?}{}{:?}{}{}{}{}{}{}{}{}{}{}{}\",m.0,i.0,m.1,m,m.2,o,m.3,i.1,m.4,i.1+1,m.5,i.1-1,m.6,o[o.len()-2],m.7,i.2,m.8);}}");
    let n = vec![0, 0];
    println!("fn a(){{println!(\"0\")}}");
    for i in [("b", 1, ""), ("c", -1, ""), ("d", 0, "o.pop();")] {
        let mut o = n.clone();
        o.push(i.1);
        println!(
            "{}{}{}{:?}{}{:?}{}{}{}{}{}{}{}{}{}{}{}",
            m.0,
            i.0,
            m.1,
            m,
            m.2,
            o,
            m.3,
            i.1,
            m.4,
            i.1 + 1,
            m.5,
            i.1 - 1,
            m.6,
            o[o.len() - 2],
            m.7,
            i.2,
            m.8
        );
    }
}
\$\endgroup\$
2
  • 3
    \$\begingroup\$ What is this \$\endgroup\$
    – Ginger
    Oct 28, 2022 at 16:51
  • \$\begingroup\$ @Ginger not a clue \$\endgroup\$
    – mousetail
    Oct 28, 2022 at 16:52
6
\$\begingroup\$

Rust, 279 bytes

||{macro_rules!q{($x:tt)=>{$x(stringify!($x))}}
q!((|s|move|mut v:Vec<_>,n|{let f=|m,u:&_|format!("||{{macro_rules!q{{($x:tt)=>{{$x(stringify!($x))}}}}
q!({})(vec!{:?},{})}}",s,u,m);v.push(n);(f(n+1,&v),f(n-1,&v),f({v.pop();v.pop()}.unwrap(),&v),format!("||{}",n))}))(vec![0],0)}

This is a nullary closure that returns a 4-tuple of strings. It uses a macro to do most of the quine heavy-lifting. The state is inserted right towards the end, using a vector for the undo history. The undo history starts with a single entry so the .unwrap() doesn't panic, but the program outputted using it will panic.

Attempt This Online! (after a few iterations)

Initial output:

// inc
||{macro_rules!q{($x:tt)=>{$x(stringify!($x))}}
q!((| s | move | mut v : Vec < _ >, n |
{
    let f = | m, u : & _ | format!
    ("||{{macro_rules!q{{($x:tt)=>{{$x(stringify!($x))}}}}
q!({})(vec!{:?},{})}}",
    s, u, m) ; v.push(n) ;
    (f(n + 1, & v), f(n - 1, & v), f({ v.pop() ; v.pop() }.unwrap(), & v),
    format! ("||{}", n))
}))(vec![0, 0],1)}

// dec
||{macro_rules!q{($x:tt)=>{$x(stringify!($x))}}
q!((| s | move | mut v : Vec < _ >, n |
{
    let f = | m, u : & _ | format!
    ("||{{macro_rules!q{{($x:tt)=>{{$x(stringify!($x))}}}}
q!({})(vec!{:?},{})}}",
    s, u, m) ; v.push(n) ;
    (f(n + 1, & v), f(n - 1, & v), f({ v.pop() ; v.pop() }.unwrap(), & v),
    format! ("||{}", n))
}))(vec![0, 0],-1)}

// undo (will panic)
||{macro_rules!q{($x:tt)=>{$x(stringify!($x))}}
q!((| s | move | mut v : Vec < _ >, n |
{
    let f = | m, u : & _ | format!
    ("||{{macro_rules!q{{($x:tt)=>{{$x(stringify!($x))}}}}
q!({})(vec!{:?},{})}}",
    s, u, m) ; v.push(n) ;
    (f(n + 1, & v), f(n - 1, & v), f({ v.pop() ; v.pop() }.unwrap(), & v),
    format! ("||{}", n))
}))(vec![],0)}

// peek
||0
\$\endgroup\$
1
  • \$\begingroup\$ Very impressive use of macros, good job \$\endgroup\$
    – mousetail
    Oct 28, 2022 at 18:34
6
\$\begingroup\$

Python REPL, 67 61 59 bytes

eval(s:='0,(n:="eval(s:=%r)")%("-~"+s),n%("~-"+s),n%s[2:]')

Attempt This Online!

Outputs (peek, incr, decr, undo)

Python, 89 99 93 91 86 bytes

lambda:eval(s:='(n:="lambda:eval(s:=%r)")%(s+"\'+1\'"),n%(s+"\'-1\'"),n%s[:-4],n%"0"')

Attempt This Online!

Outputs (incr, decr, undo, peek)

+10 bytes due to an oversight (peek was a lambda and not a string which contained a lambda. Old Answer)

\$\endgroup\$
0
4
\$\begingroup\$

Kotlin 1.7.10, 494 490 bytes

{val q='!'+1;val h=listOf(0,0);fun p(h:List<Int>){val l=listOf("{val q='!'+1;val h=listOf(","h.joinToString()",");fun p(h:List<Int>){val l=listOf(",");print(l[0]+h.joinToString()+l[2]);for(r in l)print(q+r+q+',');println(l[3])};p(h+(h.last()+1));p(h+(h.last()-1));p(h.dropLast(1));print('{');print(h.last());print('}')}");print(l[0]+h.joinToString()+l[2]);for(r in l)print(q+r+q+',');println(l[3])};p(h+(h.last()+1));p(h+(h.last()-1));p(h.dropLast(1));print('{');print(h.last());print('}')}

Try it on Kotlin Playground!

A function/lambda that prints the source code for four new lambdas: increment, decrement, undo and peek.

Calling the first-generation undo function produces a lambda that compiles but produces invalid sources for its own undo.

Explanation

{                         // A lambda of type () -> Unit
    val q = '!' + 1       // The " character to avoid backslashes
    val h = listOf(0, 0)  // The history of numbers, initialised with two numbers
                          // to make initial undo compilable
    fun p(h: List<Int>) { // Define a nested function p(h) that prints
                          // the source code for increment/decrement/undo
        val l = listOf(   // List of source code lines to print
            "{val q='!'+1;val h=listOf(",
            "h.joinToString()",
            ");fun p(h:List<Int>){val l=listOf(",
            ");print(l[0]+h.joinToString()+l[2]);for(r in l)print(q+r+q+',');println(l[3])};p(h+(h.last()+1));p(h+(h.last()-1));p(h.dropLast(1));print('{');print(h.last());print('}')}"
        )
        print(l[0] + h.joinToString() + l[2])
        for (r in l) print(q + r + q + ',') // Print a quoted version of the whole lambda
        println(l[3])
    }
    p(h + (h.last() + 1)) // Increment
    p(h + (h.last() - 1)) // Decrement
    p(h.dropLast(1))      // Undo
    print('{')            // Peek
    print(h.last())
    print('}')
}
  • Increment/decrement/undo are similar lambdas with a different history
  • Peek is a lambda that always returns the last value of h (while the others print)

Example outputs

After one round of incrementing:

  • Increment/decrement/undo print the following program:
    {val q='!'+1;val h=LIST;fun p(h:List<Int>){val l=listOf("{val q='!'+1;val h=listOf(","h.joinToString()",");fun p(h:List<Int>){val l=listOf(",");print(l[0]+h.joinToString()+l[2]);for(r in l)print(q+r+q+',');println(l[3])};p(h+(h.last()+1));p(h+(h.last()-1));p(h.dropLast(1));print('{');print(h.last());print('}')}",);print(l[0]+h.joinToString()+l[2]);for(r in l)print(q+r+q+',');println(l[3])};p(h+(h.last()+1));p(h+(h.last()-1));p(h.dropLast(1));print('{');print(h.last());print('}')}
    
    where LIST is actually:
    • Increment: listOf(0, 0, 1, 2)
    • Decrement: listOf(0, 0, 1, 0)
    • Undo: listOf(0, 0) (the original history)
  • Peek prints {1}, a constant lambda returning the latest value in the history

-4 bytes by replacing the last println with print

\$\endgroup\$
1
  • \$\begingroup\$ Welcome to Code Golf! Great explanation. A little explaining goes a long way. \$\endgroup\$
    – Seggan
    Oct 29, 2022 at 17:39
3
\$\begingroup\$

JavaScript (Node.js), 64 62 bytes

f=_=>`f=${f}+'+1'\nf=${f}+'-1'\nf=${(f+"").slice(0,-5)}\n_=>0`

Try it online!

-2 bytes thanks to @emanresu A

\$\endgroup\$
2
  • 1
    \$\begingroup\$ f=_=>... saves two bytes? Also literal newlines are allowed in backtick strings \$\endgroup\$
    – emanresu A
    Oct 28, 2022 at 7:51
  • 1
    \$\begingroup\$ @emanresuA Have you've looked at the output? It doesn't work anymore with literal newlines, since they're no longer being escaped to \\n. \$\endgroup\$ Oct 28, 2022 at 8:16
3
\$\begingroup\$

Python 3, 105 93 83 bytes

-12 bytes thanks to 97.100.97.109
-10 bytes thanks to Mukundan314 and xnor

print(*map(open(__file__).read().replace,['0']*3,['-~0','~-0','0'[2:]]),'print(0)')

Outputs [incr, decr, undo, peek] separated by spaces

Try it online!

\$\endgroup\$
4
  • \$\begingroup\$ Simple change to 93 bytes. \$\endgroup\$ Oct 27, 2022 at 22:32
  • \$\begingroup\$ 90 bytes using map \$\endgroup\$
    – xnor
    Oct 28, 2022 at 8:29
  • \$\begingroup\$ 88 bytes by not assigning to l and changing [:-2] to [2:] \$\endgroup\$ Oct 28, 2022 at 9:13
  • \$\begingroup\$ 83 bytes by using map like @xnor suggested and inlining all variables \$\endgroup\$ Oct 29, 2022 at 5:26
3
\$\begingroup\$

Bash, 68 51 bytes

echo 'bc<<<0';sed 's/0/0+1/p;s/+/-/p;s/0..../0/' $0

Try it online!

-17 bytes thanks to @Jiří

Outputs [peek, incr, decr, undo] seperated by newlines

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Here is 51B version: Try it online! \$\endgroup\$
    – Jiří
    Oct 29, 2022 at 15:25
2
\$\begingroup\$

C (gcc), 241 bytes

// 0
v;f;l;char*e,b[999];c(n){f=fopen(__FILE__,"r");for(l=1;fgets(b,999,f);l=0)l?v=strtol(b+3,&e,10),printf(n-80?n-85?"// %d%s":"// %s%s":"%s",n-80?n-85?n-73?v-1:v+1:"":b,n-85?b+2:e):puts(b);}main(){c(73);c(68);c(80);c(85);printf("%d\n",v);}

Try it online!

Creates Prints the source code the following programs:

  • I increment
  • D decrement
  • P peek
  • U undo

Full Example

It is oddly satisfying running this on a machine with a spinning hdd with caching disabled.

\$\endgroup\$
0
2
\$\begingroup\$

ARM Thumb machine code, 63 bytes

(Note: offset starts at 2 for alignment)

00000002: 0f a3 01 22 00 21 10 e0 0d a3 1a 78 98 5c 70 47  ...".!.....x.\pG
00000012: 0b a3 1a 78 01 3a 99 5c 07 e0 01 24 00 e0 ff 24  ...x.:.\...$...$
00000022: 07 a3 1a 78 99 5c 21 44 01 32 36 25 5b 1b ae 18  ...x.\!D.26%[...
00000032: 81 55 01 3e 99 5d 81 55 fb d1 42 55 70 47 01     .U.>.].U..BUpG.

Commented assembly

    .syntax unified
    .arch armv7ve
    .thumb
    .globl PEEK, INCR, DECR, UNDO
    .p2align 2,0
    // force 4 byte misalignment. Unscored.
    nop
    .globl func
    .thumb_func

    // Input: r0: pointer to output buffer, bits[1:0] = 0b10
    // Output: written to *r0
    // Clobbers: r0-r6
func:
    // Get address of data (as main code expects)
    adr    r3, data
    // Initial length
    movs   r2, #1
    // Initial value
    movs   r1, #0
    // Output the code
    b      .Lwrite_no_len
begin:
    // Input: none
    // Output: r0
    // Clobbers: r0, r2, r3
peek:
    // Get address of data
    adr    r3, data
    // Load length
    ldrb   r2, [r3, #0]
    // Load data[length]
    ldrb   r0, [r3, r2]
    // Return
    bx     lr
    // Input: r0: pointer to output buffer, bits[1:0] = 0b10
    // Output: written to *r0
    // Clobbers: r0-r6
undo:
    // Get address of data
    adr    r3, data
    // Load length
    ldrb   r2, [r3, #0]
    // Decrement length
    subs   r2, #1
    // Load new value
    ldrb   r1, [r3, r2]
    // Output the code
    b      .Lwrite_no_len

    // Input: r0: pointer to output buffer, bits[1:0] = 0b10
    // Output: written to *r0
    // Clobbers: r0-r6
incr:
    // Value to add
    movs   r4, #1
    // Jump to the common code for incr and decr
    b      .Lnext

    // Input: r0: pointer to output buffer, bits[1:0] = 0b10
    // Output: written to *r0
    // Clobbers: r0-r6
decr:
    // Value to add (I use 8-bit arith so 0xFF == -1)
    movs   r4, #0xFF
.Lnext:
    // Get address of length
    adr    r3, data
    // Load length
    ldrb   r2, [r3]
    // Load data[length]
    ldrb   r1, [r3, r2]
    // Add either 1 or 0xFF to the value
    add    r1, r4
    // Increment length
    adds   r2, #1
    // func and undo jump here to skip length calc
    // expected state:
    //   - r0 = out pointer
    //   - r1 = value
    //   - r2 = length
    //   - r3 = address of length
.Lwrite_no_len:
    // FIXME: awkward
    // Offset from beginning to data
    // Save in a register for later
    // Doesn't work in Clang
    movs.n r5, #data - begin
    // Subtract from pointer to get the beginning
    subs   r3, r3, r5
    // Add to length
    adds   r6, r5, r2
    // Store new value at the end of the stack
    strb   r1, [r0, r6]
    // memcpy loop
.Lloop:
    subs   r6, r6, #1
    ldrb   r1, [r3, r6]
    strb   r1, [r0, r6]
    bne    .Lloop
    // Store length since it is overwritten by memcpy
    strb   r2, [r0, r5]
    // Return
    bx     lr

    .p2align 2,0
data:
    // While this value isn't important, it is still read
    // from in the memcpy loop.
    .byte  1
end:
    // stack of values will be added here


    // helpers for driver, not part of code
    .section .rodata
    .globl INCR, DECR, UNDO, PEEK, SIZE
INCR: .word incr - begin
DECR: .word decr - begin
UNDO: .word undo - begin
PEEK: .word peek - begin
SIZE: .word end - func

Note that this assembly file must be compiled by GAS or GCC. Clang emits a garbage value for the label arithmetic at movs.n.

The code must be placed at a 2 bytes aligned, 4 byte misaligned address, and so must all the output pointers.

This is a doozy that can probably be optimized a lot. The main issue is that calling functions on ARM doesn't automatically make a call stack like x86, the lr register must be saved manually. Additionally, bl is a 4 byte instruction so calling a helper function has a 6 byte overhead.

This generates code by writing to a pointer. Since self modifying code is pretty tricky, here is a driver for Linux:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
typedef volatile unsigned char vu8;
extern void func(vu8*);

// These are helpers in the asm file that mean I don't have to recalculate the lengths.
extern const int PEEK, UNDO, INCR, DECR, SIZE;
#define MAX_SIZE 4096

// Just calling the function pointer wouldn't work
// because the compiler expects me to follow a
// calling convention for some reason.
// Note that you must compile with -march=armv7-a
// (technically v5te+ will work as well) for blx.
static int call(vu8 *output, const volatile void *func)
{
    register int inout asm("r0") = (int)output;
    // ensure Thumb bit
    register int funcptr asm("r1") = 1 | (int)func;
    asm(
        "blx %1"
      : "+r" (inout), "+r" (funcptr)
      :
      : "r2", "r3", "r4", "r5", "r6", "r12", "lr", "cc", "memory"
    );
    return inout;
}

static vu8 *get_page(void)
{
    // Allocate a read/write page
    vu8 *ptr = mmap(
        NULL,
        MAX_SIZE,
        PROT_READ | PROT_WRITE,
        MAP_PRIVATE | MAP_ANONYMOUS,
        -1,
        0
    );
    if (ptr == MAP_FAILED || ptr == NULL) abort();
    // fill with UDF #0xDE to make out of bounds jumps crash
    memset((void *)ptr, 0xDE, MAX_SIZE);
    // Misalign so the pointer is not 4 byte aligned
    return ptr + 2;
}

static void make_exec(vu8 *ptr)
{
    // mark read/exec
    // No rwx here
    mprotect((char *)ptr - 2, MAX_SIZE, PROT_READ | PROT_EXEC);
    // clear instruction cache
    __builtin___clear_cache((char *)ptr - 2, (char *)ptr - 2 + MAX_SIZE);
}

static void peek(vu8 *ptr)
{
    int val = call(NULL, ptr + PEEK);
    printf("%d\n", val);
}

static vu8 *eval(vu8 *ptr, int func_offset)
{
    vu8 *output = get_page();
    call(output, ptr + func_offset);
    make_exec(output);
    peek(output);
    return output;
}

int main(void)
{
    printf("size: %d\n", SIZE);
    printf("offsets: %d %d %d %d\n", INCR, DECR, UNDO, PEEK);
    vu8 *A = get_page();
    call(A, func);
    make_exec(A);
    peek(A);
    vu8 *B = eval(A, INCR);
    vu8 *C = eval(B, INCR);
    vu8 *D = eval(C, INCR);
    vu8 *E = eval(D, UNDO);
    vu8 *F = eval(E, UNDO);
    vu8 *G = eval(F, DECR);
    peek(F);
}

The functions all use the same calling convention except for peek which follows AAPCS.

The input is r0, which is a pointer that is 2 byte aligned and 4 byte misaligned. The function will be written to this pointer.

The function clobbers r0-r6, which is not AAPCS compliant.

The way the code works is it replicates itself by literally reading itself, followed by a length, then a stack of values.

    code
    length
    values....

Each time a new value is added, the new value is appended and the length is incremented. When undo is called, the length is decremented.

The length and the values are all 8-bit integers, so this can only store up to 254 values (the stack is 1-indexed because of the length byte)

Currently, the functions will be stored at the following offsets in the output buffer. When calling these, remember that the lowest bit must be set as these are Thumb functions.

  • peek: 0
  • undo: 8
  • incr: 18
  • decr: 22
\$\endgroup\$
1
\$\begingroup\$

sclin, 64 bytes

0.
,`">S form"map c><"dup "["1+""1-"\pop2*`"end"].".\n"g@"++"4*#

Try it here! Returns [ inc dec undo peek ].

For testing/convenience purposes, the following snippet:

; ."\n"++ n>< n>o

will output each code on separate lines for easy copy-pasting.

For example, running the initial code with the snippet:

; ."\n"++ n>< n>o
0.
,`">S form"map c><"dup "["1+""1-"\pop2*`"end"].".\n"g@"++"4*#

outputs:

"0"dup 1+.
,`">S form"map c><"dup "["1+""1-"\pop2*`"end"].".\n"g@"++"4*#

"0"dup 1-.
,`">S form"map c><"dup "["1+""1-"\pop2*`"end"].".\n"g@"++"4*#

"0"dup pop pop.
,`">S form"map c><"dup "["1+""1-"\pop2*`"end"].".\n"g@"++"4*#

"0"dup end.
,`">S form"map c><"dup "["1+""1-"\pop2*`"end"].".\n"g@"++"4*#

Running inc:

; ."\n"++ n>< n>o
"0"dup 1+.
,`">S form"map c><"dup "["1+""1-"\pop2*`"end"].".\n"g@"++"4*#

outputs:

"0""1"dup 1+.
,`">S form"map c><"dup "["1+""1-"\pop2*`"end"].".\n"g@"++"4*#

"0""1"dup 1-.
,`">S form"map c><"dup "["1+""1-"\pop2*`"end"].".\n"g@"++"4*#

"0""1"dup pop pop.
,`">S form"map c><"dup "["1+""1-"\pop2*`"end"].".\n"g@"++"4*#

"0""1"dup end.
,`">S form"map c><"dup "["1+""1-"\pop2*`"end"].".\n"g@"++"4*#

Explanation

Somewhat prettified code:

0.
,` ( >S form ) map c>< "dup " [ "1+" "1-" \pop 2*` "end" ] .".\n" g@ \++ 4*#

This solution takes advantage of the stack to trivialize the history functionality; for example, undo becomes a simple matter of popping the stack.

  • ,` ( >S form ) map c>< serializes the stack into a series of formatted number strings. This is because sclin constructs negative numbers as 1_ but string-formats numbers as -1; luckily, sclin can perform arithmetic on number strings.
  • "dup " is code for "new item in history" (which, in this case, is simply duplicating the top of the stack).
  • [ "1+" "1-" \pop 2*` "end" ] are the core [ inc dec undo peek ] codes.
  • .".\n" is code for "execute the next line."
  • g@ gets the current line as a string, which allows for code generation in subsequent iterations.
  • \++ 4*# vectorized-concatenates all the code pieces to create complete [ inc dec undo peek ] codes.

A concatenative solution in a concatenative language!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.