Deviating Percolator
Deviating Percolator or DevPerc is an esoteric interpreted language developed by, and successfully implemented by AJF.
It is completely unrelated to "DEVPAC", whatever that is. It is a bounded storage engine, so is not Turing-complete. It is Multiprogramming, because code can be interpreted in different ways depending on register state.
Fundamentals
DevPerc is an interpreted language. Applications are provided with 26 "registers", each named with a letter of the Latin alphabet, and initialised to the uppercase representation of said letter. DevPerc is an esoteric language because the interpretation (and therefore execution) of the program depends on the values of these registers, which makes it hard to program in, and understand.
For instance, imagine statement "FOO". FOO is composed of 3 ASCII characters, F, O and O. Most programming language interpreters would search for a symbol named by that byte sequence, and perform the relevant actions. However, DevPerc searches for a symbol named by the values of the registers it mentions.
If that's confusing, here's an example:
DEFINE F TO SIXTYFIVE FOO
Assuming "FOO" is a command causing a NOP, then with no register changes, "FOO" is read as "FOO" and nothing happens. However, as F has been changed to 65, it is read as "AOO", as 65 is the ASCII for the Uppercase Latin A. Whilst "FOO" is a valid command, "AOO" isn't, and the program crashes as a result.
This doesn't cause a problem for small programs, as they can simply store data in infrequently used registers, like Z or Q. However, larger programs will need to use more registers to store data. This creates a problem, as these registers might be in the name of the next command in the program. As a result, swapping the values of these registers or resetting them will be necessary. Also, all commands are made up entirely of upper-case Latin letters, even numbers; There are no numerical digits here, numbers are entered using English words, unhyphenated, joined together (e.g. SIXTYFIVE, ONEHUNDREDANDSEVENTYFIVE).
As a result, changes to any register could change the interpretation of a program.
Note: Yes, changing a register does change the register its name refers to:
DEFINE A TO Z/ "A" is now set to "Z" DEFINE A TO TWENTYSEVEN/ This now reads "DEFINE Z TO TWENTY SEVEN" PUT A/ This now reads "PUT Z" PUT Z/ This also reads "PUT Z" DEFINE Z TO A/ This sets "Z" to itself DEFINE M TO SIXTYFIVE/ This sets "M" to 65 (ASCII A) - "M" now means "A" DEFINE M TO SIXTYFIVE/ This sets "A" to 65 (ASCII A) - "A" now means "A" - "M" still means "A" DEFINE A TO M/ This sets "A" to "A" as "M" still means "A" DEFINE Z TO SEVENTYSEVEN/ This sets Z to 77 (ASCII M) - "Z" now means "M" DEFINE Z TO SEVENTYSEVEN/ This sets M to 77 (ASCII M) - "M" now means "M" - "Z" still means "M"
Commands
DevPerc has the following commands: (Yes, just four)
- DEFINE X TO Y - Sets X register to Y value. X is an expression evaluating to the name of a register. Y can be any expression.
- PUT X - Outputs X value (as a byte) to stdout. X can be any expression.
- GET X - Gets a byte of input from stdin and stores it in X. 255 is returned on EOF. X is an expression evaluating to the name of a register.
- IF X PROCEEDTO Y - If X is non-zero then the interpreter jumps to Y. X is an expression. Y is an expression evaluating to a numerical line number (counting from 0).
- / - Line Comment
Expressions
DevPerc supports the following expressions:
- X - Returns the value of register X (This means if you defined X to A, then used "X", it would be read as "A", and hence it would look up the value in register "A".)
- X EQUALS Y - If X is equal to Y this returns 1, else it returns 0.
- X GREATERTHAN Y - If X is greater than Y this returns 1, else it returns 0.
- X LESSTHAN Y - If X is less than Y this returns 1, else it returns 0.
- X PLUS Y - Returns the sum of X and Y.
- X MINUS Y - Returns the difference of X and Y.
- X TIMES Y - Returns the product of X and Y.
- X DIVIDE Y - Returns the integer division of X and Y.
- X MODULO Y - Returns the modulo (integer division remainder) of X and Y.
- RANDOM - Random number from 0 to 255. Munroe algorithm preferred.
If the result of an expression is over 255, it wraps around(256 is 0). Expressions can be nested, but the total length must be three, so "RANDOM EQUALS SEVEN" is valid, but "ONE TIMES TWO DIVIDE SIX" is not.
Interpreting Specifics
A compliant DevPerc interpreter should parse line-by-line. It should read character-by-character, substituting each character for that in the register of the same name (that is, upon encountering an A, look up the A register and substitute for its value) until it finds a newline (\n), at which point it will attempt to execute the line it has just read.
The interpreter should not tolerate trailing or leading whitespace, or additional whitespace between operands in a command, such that "PUT A" is valid but " PUT A " is not. The interpreter should tolerate only uppercase latin letters (the names of the registers), spaces, and newlines. However, if a slash (/) is encountered when reading a line, because it is a comment, the rest of the line should be ignored (invalid letters after a slash should not cause an error).
After the execution of a line, the DevPerc interpreter should go to the next line, unless it is an IF PROCEEDTO statement, in which case it should move to the line specified by that command. The line should be found by counting newlines from 0. If the line cannot be found, then an error should occur. If the last line is reached, program execution should terminate, instead of proceeding to the next line, as it does not exist.
An expression, when encountered, should either be one or three words long. Expressions can be nested, but the total length must not be longer than three. For example, "A DIVIDE RANDOM" and "RANDOM" are valid, but "A TIMES TWO DIVIDE SEVEN" is not. A division by zero should cause an error. If an expression, when evaluated, results in a number under 0 or over 255, it should "wrap around", such that 256 would become 0, -1 would become 255 and 265 would become 9, etc. For this reason, an expression's value may only be in the range 0-255, such that 0 <= n <= 255.
The number names accepted as expressions are in the British English style, uppercase, with no spaces. So, the number 255 would be rendered "TWOHUNDREDANDFIFTYFIVE" not "TWOHUNDREDFIFTYFIVE", as it would be if DevPerc used American English.
The command "GET X" should receive a single byte from stdin and store it in the register named by X. The command "PUT X" should output a single byte to stdout, the value of that byte being the expression X. The character set is assumed to be ASCII-compatible, so if a program only uses bytes in the ASCII range, a program is portable. What bytes outside the ASCII range appear as is implementation-defined.
Example Programs
Hello World!
Obligatory.
PUT H PUT E PUT L PUT L PUT O PUT THIRTYTWO/ Space PUT W PUT O PUT R PUT L PUT D PUT THIRTYTHREE/ Exclamation mark PUT TEN/ New line (UNIX line buffering...)
Output:
HELLO WORLD!
Changing letters to newlines
In this example, Z is defined to 47 (/), and Y to 10 (\n), such that "PUT AZVVVYPUT B" is read as "PUT A/VVV\nPUT B"
DEFINE Z TO FORTYSEVEN DEFINE Y TO TEN PUT AZVVVYPUT B
Output:
AB
cat
Copies characters one-by-one from stdin to stdout.
DEFINE M TO SIXTYFIVE/ Redefines M to A DEFINE Z TO SEVENTYSEVEN/ Redefines Z to M GET Z/ A => <stdin> PUT M/ A IF ONE PROCEEDTO TWO/ Go to line 3 (zero-indexed)
Countdown
Prints "COUNTDOWN!" and counts down from 9 to 1.
PUT C PUT O PUT U PUT N PUT T PUT D PUT O PUT W PUT N PUT THIRTYTHREE/ exclamation mark PUT TEN/ newline DEFINE Q TO B/ Redefines Q to B DEFINE SIXTYSIX TO NINE/ Redefines B to 9 PUT FORTYEIGHT PLUS Q/ 48 + B (48 is ASCII for 0, converts B value to ASCII digit) PUT TEN/ newline DEFINE SIXTYSIX TO Q MINUS ONE/ Redefines B to B - 1 (i.e. B--) IF Q GREATERTHAN ZERO PROCEEDTO THIRTEEN/ If B non-zero, loop
Example output:
COUNTDOWN! 9 8 7 6 5 4 3 2 1
Executing arbitrary code
In this example, up to 23 characters of arbitrary code can be entered, and will be executed:
PUT SIXTYTWO/> GET A/Get string of text GET B GET C GET D GET F GET H GET I GET J GET K GET L GET M GET N GET O GET P GET Q GET R GET S GET U GET V GET W GET X GET Y GET Z ABCDFHIJKLMNOPQRSUVWXYZ/Execute input
Example output:
>PUT THIRTYTHREE/ !
Author reflections
I made this more than 2 and a half years ago, at the time of writing, and somehow managed to miss the Discussion tab. I limited expression length because I didn't know how to write a proper parser or do operator precedence. I still don't know quite how I'd implement it, but I probably could, and these days I'd use a real parser to do the job. - AJF (talk) 01:16, 29 May 2013 (UTC)
Thanks to Bike on freenode's #esoteric, today I discovered it is possible to write semi-useful programs in DevPerc, by using DEFINE with a numerical constant. This means you can, for example, make loops! Hence, I have created two new examples: cat and countdown. AJF (talk) 01:13, 25 July 2013 (BST)
Only now, four years on, have I noticed a rather serious flaw in the syntax/semantics of DevPerc. Specifically, registers are substituted even in comments. I allowed you to replace a register with / or \n to insert a comment, but it wasn't supposed to affect the comment. But if it doesn't affect the comment, then the traditional "PUT AZVVVYPUT B" example doesn't work, unless we only ignore comments that exist with a clean register slate. Oops. So yeah, I guess any and every uppercase Basic Latin letter in the file, regardless of comments, must be substituted. Welp. I guess this simplifies implementation, though! AJF (talk) 02:37, 22 February 2015 (UTC)
Interpreters
- My (AJF's) poor quality Python implementation from 2011: https://github.com/TazeTSchnitzel/DevPerc
- My (AJF's) WIP pure -Wall -pedantic C99 implementation from 2015: https://github.com/TazeTSchnitzel/DevPerc.c