LOCK

From Esolang
Jump to navigation Jump to search

LOCK is an esoteric programming language based loosely on MechaniQue. It was created by MagiMaster. LOCK is a clever acronym if you know what it stands for. (If you do, you might want to add that here.) This specification is a work in progress.

Specification

The main concept of LOCK is the use of locks and keys as the flow control structure. There are no jumps other than over sections of locked code.

Each program is a set of code blocks. Each code block begins with a comma-separated list of keys that are needed to run that block. The program begins with the key 'main' and ends when there are no code blocks that can be run.

A code block consists of a series of lines of code. Each line of code begins with a single character operator followed by a string argument. Leading whitespace is ignored. Any lines of code that don't begin with a recognized operator are instead printed. (\n, \t and \\ are replaced during printing.) Hello, world! would look like:

{main
    Hello, world!
    <main
}

The virtual machine consists of the set of keys, an accumulator and a set of named variables.

The operators are:

  • >key - Add key to the set of keys
  • <key - Remove key
  • ?key - Add key if the accumulator is not 0
  • +var - Add var to the accumulator
  • -var - Subtract var from the accumulator
  • *var - Multiple var with the accumulator
  • /var - Divide the accumulator by var
  • %var - Take the remainder of accumulator/var
  • =var - Set var equal to the accumulator
  • ^var - Set the accumulator equal to var
  • :var - Read an integer into var
  • $var - Print var
  • #num - Set the accumulator to num (a base-10 integer)
  • &... - Comment

In the Hello, world! program above, the second line of code removes the main key, preventing the program from entering an infinite loop.

Execution is carried out as a series of steps. At each step, the first code block that is accessible is executed. This means that, for example, while the first code block is accessible, no other code block will be executed.

Example

This program outputs Fibonacci numbers:

{main
	#0
	=Zero
	=Count
	=Total2
	#1
	=One
	=Total1
	#6
	=Fib
	>test
	>done
	<main
}

{test
	^Count
	-Fib
	<calc
	?calc
	<test
}

{calc
	^Total1
	+Total2
	=Temp
	^Total1
	=Total2
	^Temp
	=Total1
	^Count
	+One
	=Count
	<calc
	>test
}

{done
	The 
	$Fib
	th Fibonacci number is 
	$Total1
	\n
	<calc
	<done
}

Under consideration

These are things that are currently under consideration for being added to the spec:

  • Nested code blocks
  • Negation of keys (for example, ?!main would remove main if the accumulator is not 0, and {!main...} would only run if the program did not have the key main)
  • Restricting key and variable names to contain no whitespace
  • Making any whitespace seperate commands instead of only newline (requires restricting names as above, plus a few other changes)
  • Basic logical operation
  • String operations (currently all input, output and variables are integers)

Potential uses

Like most esoteric languages, its potential for serious use is questionable; however, if locks were the only flow control, it would be easy to parallelize. A robust language like that could be used for writing programs for parallel architectures. (Such a language would probably use higher level syntax and semantics for the code though.)

Computational class

If the accumulator and named variables can only hold bounded integers, then LOCK cannot be more powerful than Finite-state machines. This is because the current state of a LOCK program is given entirely by the current list of keys and current values of the variables and accumulator.

The following reduction from brainfuck should be a formal proof that LOCK with unbounded integers is Turing-complete, though it might contain errors.

brainfuck LOCK
(program start)
{main
  #0
  =tape_left
  =tape_right
  =current
  #255
  =twofivefive
  #256
  =twofivesix
  #1
  =one
+
  #1
  +current
  %twofivesix
  =current
-
  ^current
  -one
  %twofivesix
  =current
>
  ^tape_left
  *twofivesix
  +current
  =tape_left
  ^tape_right
  %twofivesix
  =current
  ^tape_right
  /twofivesix
  =tape_right
<
  ^tape_right
  *twofivesix
  +current
  =tape_right
  ^tape_left
  %twofivesix
  =current
  ^tape_left
  /twofivesix
  =tape_left
.
  $current
,
  :current
[ body of loop ]
  ^current
  >outloop_n
  ?loop_n
  <section_key
}
{loop_n
  body of loop
}
{outloop_n
  <loop_n
  ^current
  ?loop_n
  -one
  %twofivesix
  /twofivefive
  ?after_loop_n
}
{after_loop_n
  <outloop_n
(program end)
  <section_key
}
  • The n in loop_n, outloop_n and after_loop_n must be unique to each loop;
  • section_key is the key to the current section of code (presumably either main or after_loop_something).

Exiting a loop is done using the formula ((x-1)%256)/255 for logical negation, so that ?after_loop_n can be triggered by zero cells.

The tape is stored as two stacks tape_left and tape_right (plus the current cell current); those "stacks" are actually unbounded integers, and tape cells are their base-256 digits (with the less significant digit for the closest cell to the current cell).

Implementation