Prindeal (pronounced prin-dee-al) is a new esoteric programming language that only has four commands: print, increment, decrement, and alias. Despite its minimalism, complex mathematical operations can be done in Prindeal by cleverly combining the four commands.
Your task in this code golf challenge is to write the shortest program that can run Prindeal code.
The spec is long but I've tried to make it as clear as possible and I believe that if you make the effort to learn Prindeal you'll find it to be quite elegant!
Before a Prindeal program can be interpreted these things need to be removed from it in this order:
- Anything after a
#sign to the end of the line it's on, plus the
#itself. (These are comments.)
- Trailing whitespace on any line.
- Completely empty lines.
For example, the Prindeal program
p cat #The next line has 7 trailing spaces. p dog #p mouse
would be preprocessed into
p cat p dog
From here on we'll assume this preprocessing step has been done.
We quickly need to define variables before showing how they are used.
Variables (and references to variables) are what are passed into the arguments of Prindeal commands. Variables are always global, so modifications to a variable, no matter where they occur, are reflected everywhere.
Each variable holds a non-negative arbitrary-precision integer (0, 1, 2, 3, ...). Variables do not need to be pre-initialized - they always start with the value 0 the first time they are used or called for.
A variable name may be any non-empty string of alphanumerics and underscores that does not start with a digit -
[a-zA-Z_][0-9a-zA-Z_]* in regex. They are case sensitive, so
Spiny_lumpsuck3r are different variables.
Every non-indented line in a Prindeal program is a statement that involves the execution of a single command which may or may not take arguments.
Indented lines only occur after alias commands. Specifically, exactly three lines indented with single spaces occur after every alias command and are considered a part of it. So alias statements are really four lines long. (They could be one line, four is merely more readable.)
With the exception of alias, every statement in a Prindeal program has the form:
[command name] [argument 1] [argument 2] [argument 3] ...
There may be an arbitrary number of arguments (including none at all). Each argument is always a variable or (as we'll see when discussing alias) a reference to a variable.
Once done executing, each statement is flagged as either a failure or success depending on if errors were encountered or not. (This only really matters when we get around to using alias.)
The built-ins print, increment, and decrement are statements with the above form. Here is what they do:
print has command name
pand takes one argument. It prints the name of the variable passed in and its value (in decimal) separated by " = ", then a newline. It is always flagged as a success.
For example, the Prindeal program
p _MyVariable_321 p screaming_hairy_armadillo
_MyVariable_321 = 0 screaming_hairy_armadillo = 0
because all variables start at 0. (The spaces before and after the equals sign are required.)
increment has command name
iand takes one argument. It increments the value of the variable passed in by 1. It is always flagged as a success..
For example, the program
i alpaca p alpaca i alpaca p alpaca
alpaca = 1 alpaca = 2
alpacawas incremented from 0 to 1 even though it had never been accessed before.
decrement has command name
dand takes one argument. If the variable passed in is nonzero its value is decremented by 1 and the statement is flagged as a success. If the variable passed in is 0 then nothing is done and the statement is flagged as a failure.
For example, the program
i malamute p malamute d malamute #success p malamute d malamute #failure p malamute d akita #failure p akita
malamute = 1 malamute = 0 malamute = 0 akita = 0
Notice that decrementing a variable with value 0 is the only way to produce a failure.
The alias Statement and Aliased Commands
The alias command has a special syntax and is the most powerful because it can be used to define new commands. The alias command name is
a and an alias statement has the form:
a [name of new command] [statement A] [statement B] [statement C]
[statement X] represents any non-alias statement, i.e. something with the form
[command name] [argument 1] [argument 2] [argument 3] ....
The aliased command's name
[name of new command] may be any non-empty string of alphanumerics and underscores that does not start with a digit -
[a-zA-Z_][0-9a-zA-Z_]* in regex.
(This is the same set of names as variables but aliased commands and variables are different things used in different places. A variable could be named the same as a command with no ill consequences.)
When an alias statement is executed, a new command is added alongside the original four
a commands. The new command can be used as the
[command name] in statements and called with arguments just like any other non-alias command.
When a statement with an aliased command name is executed, exactly two more statements from its original alias statement are run:
[statement A]is always run
[statement B]is run if
[statement A]was a success
[statement C]is run if
[statement A]was a failure
Statements A, B, and C are always run lazily, i.e. they are evaluated on the fly at the time they are run.
When done executing, the aliased command is flagged with the same success or failure flag as statement B or C, whichever one was executed. (alias statements themselves do not need to be flagged since they cannot occur within themselves.)
Alias Example 1
Say we want a new command that increments the variable
frogtwice. This alias statement achieves it:
a increment_frog_twice i frog i frog d frog
Statement A (
i frog) is always run and always flagged as a success so statement B (
i frog) is also always run and the variable
frogis thus incremented by 2. The
increment_frog_twicecommand is always flagged as a success because statement B is always run and B is always a success. Statement C (
d frog) is never run.
So the output to
a increment_frog_twice i frog i frog d frog p frog increment_frog_twice p frog
frog = 0 frog = 2
We can generalize this example so that any variable can be incremented twice by giving the aliased command an argument.
Within an alias statement the positive integers 1, 2, 3, etc. represent the 1st, 2nd, 3rd, etc. arguments passed into the aliased command. (These arguments might be plain variables or references to variables themselves.) These numbers can only appear within the arguments of statement A, B, and C in an alias statement. It doesn't make sense for them to appear elsewhere.
Alias Example 2
This generalizes the last example - any variable passed into
increment_twicewill be incremented by 2 because
1is a reference to the first argument passed in:
a increment_twice i 1 i 1 d 1 #never reached p toad increment_twice toad p toad
The output of this program would be
toad = 0 toad = 2
We could then alias another command that takes in two arguments and calls
increment_twiceon both of them:
a increment_twice i 1 i 1 d 1 #never reached a increment_both_twice increment_twice 1 increment_twice 2 d 1 #never reached increment_both_twice platypus duck p platypus p duck
The output here would be
platypus = 2 duck = 2
It's important to realize that aliased commands can be recursive, as this is where their true power lies. For example, we can make a command that sets any variable passed in to 0:
Alias Example 3
set_to_zerocommand takes one argument and sets its variable to 0 and is flagged as a success when done:
a set_to_zero d 1 set_to_zero 1 i _dummy_ i oryx i oryx i oryx p oryx set_to_zero oryx p oryx
The output of this program would be
oryx = 3 oryx = 0
What's happening is that when
set_to_zero oryxis run,
d 1successfully decrements
oryxfrom from 3 to 2, then
set_to_zero 1is called, which is the same as calling
set_to_zero oryxagain. So the process repeats until
d 1is a failure, stopping the recursion and increment the
_dummy_variable so a success is produced.
Write a program that can execute Prindeal code exactly as described above. Take the Prindeal code in via stdin, the command line, or as a text file. Print the Prindeal program's output to stdout or your language's closest alternative.
Alternatively, you can write a function that takes in the code as a string and prints or returns the output string.
Additionally, you can assume that:
- The input Prindeal code will only contain newlines and printable ASCII and (optionally) that it ends with an empty line.
- The input code will be valid Prindeal - well formed and syntactically correct.
- Running the code will not produce any infinite loops nor any invalid references to commands that have not been defined or arguments that have not been given.
- The command names
awill never be aliased over. (You may not assume that variables will not have these names.)
Also, it doesn't matter if your variable values aren't truly arbitrary-precision integers as only numbers less than about 1000 will be tested. It's also alright if you language has recursion limits (such as Python) that more complex Prindeal programs might encounter as long as the test program below works.
Here is a large Prindeal program that builds up the operations of addition, multiplication, and exponentiation through the use of dummy variables (starting with
_ by convention) and many helper aliases:
#Command Definitions: a s #flag as a success i _ d _ d _ a f #flag as a failure d _ d _ d _ a z #1 = zero d 1 z 1 s a n #1 = one z 1 i 1 s a move #2 += 1, 1 = zero moveH 1 2 move 1 2 s a moveH #move helper d 1 i 2 f a dupe #2 += 1, 3 += 1, 1 = zero dupeH1 1 2 3 dupe 1 2 3 s a dupeH1 #dupe helper d 1 dupeH2 2 3 f a dupeH2 #dupe helper i 1 i 2 s a copy #2 = 1 z 2 copyH 1 2 s a copyH #copy helper dupe 1 2 _copy move _copy 1 s a addTo #1 += 2 copy 2 _add #testing comments # move _add 1#in weird places # just because # s #it's a g##d idea ### a add #1 = 2 + 3 #its a good idea z 1 addH 1 2 3 s ## # a addH #add helper #this is a comment addTo 1 2 #as is this addTo 1 3 s a mul #1 = 2 * 3 mulH1 1 2 mulH2 1 3 s a mulH1 #mul helper z 1 copy 2 _mul s a mulH2 #mul helper mulH3 1 2 mulH2 1 2 s a mulH3 #mul helper d _mul addTo 1 2 f a mulBy #1 *= 2 mul _mulBy 1 2 copy _mulBy 1 s a pow #1 = 2^3 powH1 1 3 powH2 1 2 s a powH1 #pow helper n 1 copy 2 _pow s a powH2 #pow helper powH3 1 2 powH2 1 2 s a powH3 #pow helper d _pow mulBy 1 2 f #Running Tests: p A p B p C n A #A = 1 n B #B = 1 add C A B #C = A + B = 1 + 1 = 2 p ____ p A p B p C add B A C #B = A + C = 1 + 2 = 3 p ____ p A p B p C mul d B C #d = B * C = 3 * 2 = 6 p ____ p d mulBy d B #d = d * B = 6 * 3 = 18 p ____ p d d A #A = A - 1 = 1 - 1 = 0 mulBy d A #d = d * A = 18 * 0 = 0 p ____ p d pow A C B #A = C ^ B = 2 ^ 3 = 8 p ____ p A p B p C pow A B C #A = B ^ C = 3 ^ 2 = 9 p ____ p A p B p C pow C A B #C = A ^ B = 9 ^ 3 = 729 p ____ p A p B p C
(If you're playing around with this code be aware that many of the commands will fail if the same variable is given multiple times as an argument. This can easily be fixed but the resulting code is longer.)
Your Prindeal interpreter should be able to produce the exact output:
A = 0 B = 0 C = 0 ____ = 0 A = 1 B = 1 C = 2 ____ = 0 A = 1 B = 3 C = 2 ____ = 0 d = 6 ____ = 0 d = 18 ____ = 0 d = 0 ____ = 0 A = 8 B = 3 C = 2 ____ = 0 A = 9 B = 3 C = 2 ____ = 0 A = 9 B = 3 C = 729
The shortest code in bytes wins. Tiebreaker goes to earlier submission.
Brownie Bonus: Write a cool program in Prindeal. I implemented addition and multiplication, can you do subtraction or division?