Skip to content

Instantly share code, notes, and snippets.

@cpldcpu
Last active May 11, 2024 13:17
Show Gist options
  • Save cpldcpu/335193c5a7c05e9ecec12a16be52afe5 to your computer and use it in GitHub Desktop.
Save cpldcpu/335193c5a7c05e9ecec12a16be52afe5 to your computer and use it in GitHub Desktop.
Chronoikos

Chronoikos

You are presented with an infinite number of clocks, each featuring a dial that displays positions from 0 to 59. Each clock is equipped with a button on top that, when pressed, advances the clock by one increment. If the dial transitions from 59 back to 0, a bell within the clock will sound once.

The dials of these clocks are concealed, allowing interaction only through the button and the audible bell. A special instruction allows reading the positions of the dials to read out the result of computations.

Basic Language

An instruction sheet provides sequences in which the clocks should be manipulated. The following operations can be performed:

  • TICK <#clock> Press button on the clock identified by #clock once.
  • GO <label> ON PING If the preceding command triggered a ping sound, navigate to the line labeled <label>.
  • GO <label> ON SILENCE If the preceding command did not trigger a ping sound (i.e., it was silent), navigate to the line labeled <label>.
  • REVEAL Reveal the current positions of all clock dials.

labels are unique strings and are designated by the label followed by a colon in an empty line. For example, label:.

Some language examples

Advance the dial 1 by one increment

TICK 1

Set dial to zero

We can set a dial to zero by advancing it until it pings.

loop1:
    TICK 1
    GO loop1 ON SILENCE

copy inverted dial 1 to dial 2, destroys content of dial 1

loop1:
    TICK 2
    GO loop1 ON SILENCE
loop2:
    TICK 2
    TICK 1
    GO loop1 ON SILENCE

Copy dial 1 to dial 2, using dial 3 as a temporary register

First, we'll set dials 2 and 3 to zero:

loop1:
    TICK 2
    GO loop1 ON SILENCE
loop2:
    TICK 3
    GO loop2 ON SILENCE
loop3:
    TICK 3
    TICK 1
    GO loop3 ON SILENCE
loop4:
    TICK 2
    TICK 3
    GO loop4 ON SILENCE

Self modification

Instead of advancing a dial, the TICK instruction can also be used on another TICK instruction to advanced the clock number it is pointing to. This allows for self-modifying code and can be used to implement a pointer.

Example:

    TICK pointer
pointer:
    TICK 1   ; will be increased to 2
    REVEAL

The first TICK instruction advances the pointer of the next instruction. Therefore, instead of increasing the dial 1, it will increase the dial of clock 1 + 1 = 2

Related

Some relation to Smallfuck. Consider that the pointer is replaced with a direct declaration of cell addresses or a pointer based on self-modifying code.

class Chronoikos:
def __init__(self, num_clocks):
self.clocks = [0] * num_clocks
self.bell = False
self.instruction_pointer = 0
self.labels = {}
self.program = []
def tick(self, clock_num):
if isinstance(clock_num, str) and clock_num in self.labels:
# If the clock number is a label, modify the TICK instruction it points to
instruction = self.program[self.labels[clock_num]]
if instruction[0] == "TICK":
instruction[1] = str(int(instruction[1]) + 1)
else:
# Otherwise, advance the clock as usual
self.clocks[int(clock_num) - 1] = (self.clocks[int(clock_num) - 1] + 1) % 60
self.bell = (self.clocks[int(clock_num) - 1] == 0)
def go_on_ping(self, label):
if self.bell:
self.instruction_pointer = self.labels[label] - 1
def go_on_silence(self, label):
if not self.bell:
self.instruction_pointer = self.labels[label] - 1
def reveal(self):
print(self.clocks)
def load_instructions(self, code):
for index, line in enumerate(code.splitlines()):
line = line.strip()
if not line or line.startswith("#"):
continue
if line.endswith(":"):
self.labels[line[:-1]] = len(self.program)
else:
parts = line.split()
self.program.append(parts)
def execute(self):
while self.instruction_pointer < len(self.program):
instruction = self.program[self.instruction_pointer]
command, *args = instruction
if command == "TICK":
self.tick(args[0])
elif command == "GO":
label, condition = args[0], args[2]
if condition == "PING":
self.go_on_ping(label)
else:
self.go_on_silence(label)
elif command == "REVEAL":
self.reveal()
self.instruction_pointer += 1
# Example usage:
code = """
clear:
TICK 2
GO clear ON SILENCE
TICK pointer
pointer:
TICK 1
REVEAL
"""
chronoikos = Chronoikos(10) # Create an emulator with 10 clocks
chronoikos.load_instructions(code)
chronoikos.execute()
@cpldcpu
Copy link
Author

cpldcpu commented May 10, 2024

A compact language for the same architecture, called 'K'

The compact language is a more concise way to write instructions for the clock dials.

  • <#> Press the button on the clock identified by # once. Numbers are be separated by spaces.
  • ? Display the current positions of all clock dials.
  • [<#> ... !<#> ] Loop. This loop executes until the last clock operated on before the closing bracket pings. The loop will always execute at least once. Multiple loops can be nested.
  • [<#> ... <#> ] Loop. This loop executes until the last clock operated on before the closing bracket does not ping. The loop will always execute at least once. Multiple loops can be nested.
  • <#> {...} Conditional. The code inside the curly braces executes if the last clock operated on before the closing brace pings.
  • !<#> {...} Conditional. The code inside the curly braces executes if the last clock operated on before the closing brace does not ping.

Instead of clock numbers, also variables can be defined. Variables are strings of letters. They are defined by <variable> = <#>. For example, a = 1 defines variable a as clock 1. Variables can be used in place of clock numbers in the compact language.

It is also possible to define macros. Macros are defined by @macro(a,b,c) = { } where a, b, and c are the arguments of the macro. Macros can use labels in place of clock numbers. Example: @macro(a,b,c)

Examples

Advance the dial 1 by one increment

1

Set dial 1 to zero

We can set a dial to zero by advancing it until it pings.

[1]

Copy inverted dial 1 to dial 2, destroys content of dial 1

[2] [2 1]

Copy dial 1 to dial 2, using dial 3 as a temporary register

[2] [3] [1 3] [2 3]

Define a variable and use it

Clear dial 1 declaring it as variable dial1.

dial1 = 1
[dial1]

Define a macro and use it

Define a macro that clears a dial and use it to clear dial 1.

@clear(x) = {[x]}
clear(1)

Define a macro that copies the content of dial a to dial b, using dial tmp as a temporary register. The content of a is retained

@copy(a,b) = { [b] [tmp] [tmp a] [b a tmp] }

Define a macro that adds the content of dial a to dial b, using dial tmp as a temporary register.

@add(a,b) = { [tmp] [tmp a] [b a tmp]}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment